[MIRROR] Implements job skillchip framework as per hackmd.io design document (#212)

* Implements job skillchip framework as per hackmd.io design document (#52630)

* Shift wire revealing logic to dedicated proc

* Bit of code modification. Framework for roundstart job skillchips.

* Implement roboticist skillchip trait functionality.

* Feex moth feet messup

* Add skill chips to robodrobe inventory

* Code refactor. Add missing skill_stations to Pubby and Delta.

* Implement special flags, changelings inherit skillchip skills

* Additional code refactor. Traitor chameleon skillchip framework.

* Implement traitor skillchip, fix up more code, move job chips to outfit datums

* Modify autosurgeon, create skillchip variant, add to uplink

* Implement chip cooldowns. Add new skillchip for station engineers. Cleanup some code. Add job chips to lockers.

* Feex

* Feex

* Code review implementation

* More feexes, improved chameleon chip code and more.

* Code review and minor refactor

* Additional review fixes, rebuild tgui

* Minor logic fixes

* Final Rohesie review

* Robust code changes, improved slot/complexity system.

* Rebuild tgui

* Code review

* Brain regeneration failsafe

* Lazylist cut fix

* Implements job skillchip framework as per hackmd.io design document

Co-authored-by: Timberpoes <silent_insomnia_pp@hotmail.co.uk>
This commit is contained in:
SkyratBot
2020-08-06 01:03:04 +01:00
committed by GitHub
co-authored by Timberpoes
parent 0e318204dd
commit f9790d97a9
37 changed files with 1768 additions and 300 deletions
@@ -0,0 +1,6 @@
/obj/item/skillchip/job
skillchip_flags = SKILLCHIP_RESTRICTED_CATEGORIES
chip_category = "job"
incompatibility_list = list("job")
abstract_parent_type = /obj/item/skillchip/job
slot_use = 2
@@ -0,0 +1,9 @@
/obj/item/skillchip/job/roboticist
name = "Cyborg C1-RCU-1T skillchip"
desc = "A roboticist's second best friend."
auto_trait = TRAIT_KNOW_CYBORG_WIRES
skill_name = "Cyborg Circuitry"
skill_description = "Recognise cyborg wire layouts and understand their functionality at a glance."
skill_icon = "sitemap"
activate_message = "<span class='notice'>You suddenly comprehend the secrets behind cyborg circuitry.</span>"
deactivate_message = "<span class='notice'>Cyborg circuitry stops making sense as images of coloured wires fade from your mind.</span>"
@@ -0,0 +1,9 @@
/obj/item/skillchip/job/engineer
name = "Engineering C1-RCU-1T skillchip"
desc = "Endorsed by Poly."
auto_trait = TRAIT_KNOW_ENGI_WIRES
skill_name = "Engineering Circuitry"
skill_description = "Recognise airlock and APC wire layouts and understand their functionality at a glance."
skill_icon = "sitemap"
activate_message = "<span class='notice'>You suddenly comprehend the secrets behind airlock and APC circuitry.</span>"
deactivate_message = "<span class='notice'>Airlock and APC circuitry stops making sense as images of coloured wires fade from your mind.</span>"
@@ -0,0 +1,173 @@
/obj/item/skillchip/chameleon
name = "Chameleon skillchip"
desc = "A highly advanced Syndicate skillchip that does nothing on its own. It is loaded with the data of every skillchip."
skill_name = "Imitate Skillchip"
skill_description = "Reacts to the user's thoughts, selecting a skill from a wide database of choices."
activate_message = "<span class='notice'>You feel at one with the skillchip.</span>"
deactivate_message = "<span class='notice'>The infinite mysteries of the skillchip escape your mind.</span>"
skill_icon = "microchip"
// Chip does nothing on its own, so it has 0 complexity.
complexity = 0
// Chamelelon chips cannot mimic chips with a greater slot cost. Increasing this will potentially increase
// the pool of mimic'd chips. Decreasing it will decrease the pool of mimic'd chips. See initialize_disguises()
// in the chameleon_action#'s type for the logic block.
slot_use = 2
removable = FALSE
skillchip_flags = SKILLCHIP_CHAMELEON_INCOMPATIBLE
/// Action for the skillchip selection.
var/datum/action/item_action/chameleon/change/skillchip/chameleon_action
/obj/item/skillchip/chameleon/Initialize(mapload, is_removable = FALSE)
. = ..()
// This chameleon_action uses snowflake code. Do not set the chameleon_blacklist as that is ignored.
// Instead, set the SKILLCHIP_CHAMELEON_INCOMPATIBLE flag on skillchips that should not be copyable.
// Set abstract_parent_type on skillchips that are abstract (see /obj/item/skillchip definition)
chameleon_action = new(src)
chameleon_action.chameleon_type = /obj/item/skillchip
chameleon_action.chameleon_name = "Skillchip"
chameleon_action.initialize_disguises()
/obj/item/skillchip/chameleon/Destroy()
QDEL_NULL(chameleon_action)
return ..()
/// We don't want this to grant the item_action automatically.
/obj/item/skillchip/chameleon/item_action_slot_check(slot, mob/user)
return FALSE
/obj/item/skillchip/chameleon/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
chameleon_action.emp_randomise()
/obj/item/skillchip/chameleon/on_implant(obj/item/organ/brain/owner_brain)
// If there's already a mimic'd skillchip available, run its implant code alongside this.
if(chameleon_action.skillchip_mimic)
chameleon_action.skillchip_mimic.on_implant(owner_brain)
return ..()
/// DANGEROUS - Doesn't check that the mimic'd chip can be activated. Assumes this check has been done already.
/obj/item/skillchip/chameleon/on_activate(mob/living/carbon/user, silent=TRUE)
. = ..()
// If there's already a mimic'd skillchip available, activate it. Generally only happens with changelings. Rare interaction.
if(chameleon_action.skillchip_mimic && chameleon_action.skillchip_mimic.is_active())
chameleon_action.skillchip_mimic.on_activate(user, silent)
chameleon_action.Grant(user);
/obj/item/skillchip/chameleon/on_removal(silent = FALSE)
. = ..()
// Also call the on_removal of the mimic'd skillchip.
if(chameleon_action.skillchip_mimic)
chameleon_action.skillchip_mimic.on_removal(silent)
/obj/item/skillchip/chameleon/on_deactivate(mob/living/carbon/user, silent = FALSE)
chameleon_action.clear_mimic_chip()
chameleon_action.Remove(user)
return ..()
/obj/item/skillchip/chameleon/has_skillchip_incompatibility(obj/item/skillchip/skillchip)
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.has_skillchip_incompatibility(skillchip)
return ..()
/obj/item/skillchip/chameleon/has_activate_incompatibility(obj/item/organ/brain/brain)
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.has_activate_incompatibility(brain)
return ..()
/obj/item/skillchip/chameleon/has_mob_incompatibility(mob/living/carbon/target)
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.has_mob_incompatibility(target)
return ..()
/obj/item/skillchip/chameleon/has_brain_incompatibility(obj/item/organ/brain/brain)
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.has_brain_incompatibility(brain)
return ..()
/obj/item/skillchip/chameleon/get_chip_data()
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
var/list/mimic_data = chameleon_action.skillchip_mimic.get_chip_data()
// Overwrite the ref with this chip's, as we'll want any operations to be performed through this chip and
// not through the mimic chip.
mimic_data["ref"] = REF(src)
return mimic_data
return ..()
/obj/item/skillchip/chameleon/try_activate_skillchip(silent = FALSE, force = FALSE)
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.try_activate_skillchip(silent, force)
return ..()
/obj/item/skillchip/chameleon/try_deactivate_skillchip(silent = FALSE, force = FALSE)
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.try_deactivate_skillchip(silent, force)
return ..()
/obj/item/skillchip/chameleon/is_on_cooldown()
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.is_on_cooldown()
return ..()
/obj/item/skillchip/chameleon/is_active()
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.is_active()
return ..()
/obj/item/skillchip/chameleon/get_complexity()
// If we've selected a skillchip to mimic, we'll want to intercept this proc and forward it to the mimic chip.
if(chameleon_action.skillchip_mimic)
return chameleon_action.skillchip_mimic.get_complexity()
return ..()
/obj/item/skillchip/chameleon/get_metadata()
var/list/metadata = ..()
// If we have a skillchip to mimic, create a new set of metadata for that chip.
if(chameleon_action.skillchip_mimic)
metadata["mimic_chip"] = chameleon_action.skillchip_mimic.get_metadata()
return metadata
/obj/item/skillchip/chameleon/set_metadata(list/metadata)
// Set base metadata first.
. = ..()
// Get rid of the old mimic chip regardless, we're setting metadata here. If the metadata doesn't
// contain new mimic chip info, then assume we want to delete it.
chameleon_action.clear_mimic_chip()
// If this is metadata for another chamelelon chip variant, it may have a mimic_chip set.
var/list/mimic_chip = metadata["mimic_chip"]
// Call the mimic chip's proc to set the relevant metadata. Mimic chip activation should be handled
// through this chip's activation procs being called externally.
if(mimic_chip)
var/type = mimic_chip["type"]
chameleon_action.skillchip_mimic = new type(src)
chameleon_action.skillchip_mimic.set_metadata(mimic_chip)
@@ -30,7 +30,6 @@
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SkillStation", name)
ui.set_autoupdate(FALSE)
ui.open()
/obj/machinery/skill_station/update_icon_state()
@@ -90,7 +89,6 @@
if(!user.transferItemToLoc(I, src))
return
inserted_skillchip = I
SStgui.update_uis(src)
return
return ..()
@@ -104,8 +102,11 @@
// Functions below do not validate occupant exists - should be handled outer wrappers.
/// Start implanting.
/obj/machinery/skill_station/proc/start_implanting()
if(!inserted_skillchip?.can_be_implanted(occupant))
return
var/mob/living/carbon/carbon_occupant = occupant
if(inserted_skillchip.has_mob_incompatibility(carbon_occupant))
CRASH("Unusual error - [usr] attempted to start implanting of [inserted_skillchip] when the interface state should not have allowed it.")
working = TRUE
work_timer = addtimer(CALLBACK(src,.proc/implant),SKILLCHIP_IMPLANT_TIME,TIMER_STOPPABLE)
update_icon()
@@ -114,16 +115,25 @@
/obj/machinery/skill_station/proc/implant()
working = FALSE
work_timer = null
if(inserted_skillchip?.can_be_implanted(occupant))
implant_skillchip(occupant,inserted_skillchip)
var/mob/living/carbon/carbon_occupant = occupant
var/implant_msg = carbon_occupant.implant_skillchip(inserted_skillchip, FALSE)
if(implant_msg)
to_chat(occupant,"<span class='notice'>Operation failed! [implant_msg]</span>")
else
to_chat(occupant,"<span class='notice'>Operation complete!</span>")
inserted_skillchip = null
update_icon()
SStgui.update_uis(src)
to_chat(occupant,"<span class='notice'>Operation complete!</span>")
/// Start removal.
/obj/machinery/skill_station/proc/start_removal(obj/item/skillchip/to_be_removed)
if(!to_be_removed)
return
if(to_be_removed.is_on_cooldown())
to_chat(occupant, "<span class='notice'>DANGER! Operation cannot be completed, removal is unsafe.</span>")
CRASH("Unusual error - [usr] attempted to start removal of [to_be_removed] when the interface state should not have allowed it.")
working = TRUE
work_timer = addtimer(CALLBACK(src,.proc/remove_skillchip,to_be_removed),SKILLCHIP_REMOVAL_TIME,TIMER_STOPPABLE)
update_icon()
@@ -132,50 +142,108 @@
/obj/machinery/skill_station/proc/remove_skillchip(obj/item/skillchip/to_be_removed)
working = FALSE
work_timer = null
update_icon()
var/mob/living/carbon/carbon_occupant = occupant
var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(carbon_occupant) || QDELETED(occupant_brain) || !(to_be_removed in occupant_brain.skillchips))
if(to_be_removed.is_on_cooldown())
to_chat(carbon_occupant,"<span class='notice'>Safety mechanisms activated! Skillchip cannot be safely removed.</span>")
return
to_be_removed.on_removal(carbon_occupant, silent=FALSE)
LAZYREMOVE(occupant_brain.skillchips, to_be_removed)
if(!istype(carbon_occupant))
to_chat(carbon_occupant,"<span class='notice'>Occupant does not appear to be a carbon-based lifeform!</span>")
return
if(!carbon_occupant.remove_skillchip(to_be_removed))
to_chat(carbon_occupant,"<span class='notice'>Failed to remove skillchip!</span>")
return
if(to_be_removed.removable)
carbon_occupant.put_in_hands(to_be_removed)
else
qdel(to_be_removed)
update_icon()
SStgui.update_uis(src)
to_chat(carbon_occupant,"<span class='notice'>Operation complete!</span>")
/obj/machinery/skill_station/proc/implant_skillchip(mob/living/carbon/target,obj/item/skillchip/chip)
var/obj/item/organ/brain/target_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
chip.on_apply(target, silent = FALSE)
chip.forceMove(target_brain)
LAZYADD(target_brain.skillchips, chip)
to_chat(carbon_occupant, "<span class='notice'>Operation complete!</span>")
/obj/machinery/skill_station/proc/toggle_chip_active(obj/item/skillchip/to_be_toggled)
var/mob/living/carbon/carbon_occupant = occupant
if(to_be_toggled.is_on_cooldown())
to_chat(carbon_occupant,"<span class='notice'>Safety mechanisms activated! Skillchip cannot be safely modified.</span>")
return
if(!istype(carbon_occupant))
to_chat(carbon_occupant,"<span class='notice'>Occupant does not appear to be a carbon-based lifeform!</span>")
return
if(to_be_toggled.is_active())
var/active_msg = to_be_toggled.try_deactivate_skillchip(FALSE, FALSE)
if(active_msg)
to_chat(carbon_occupant,"<span class='notice'>Failed to deactivate skillchip! [active_msg]</span>")
return
// This code will fire when to_be_toggled.active is FALSE
var/active_msg = to_be_toggled.try_activate_skillchip(FALSE, FALSE)
if(active_msg)
to_chat(carbon_occupant,"<span class='notice'>Failed to activate skillchip! [active_msg]</span>")
/obj/machinery/skill_station/ui_data(mob/user)
. = ..()
.["working"] = working
.["timeleft"] = work_timer ? timeleft(work_timer) : null
var/mob/living/carbon/carbon_occupant = occupant
var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(carbon_occupant) || QDELETED(occupant_brain))
.["error"] = "Brain not detected. Please consult nearest medical practitioner."
else
var/list/current_skills = list()
for(var/obj/item/skillchip/skill_chip in occupant_brain)
current_skills += list(list("name"=skill_chip.skill_name,"icon"=skill_chip.skill_icon,"cost"=skill_chip.slot_cost,"ref"=REF(skill_chip),"active"=(skill_chip in occupant_brain.skillchips)))
.["current"] = current_skills
.["slots_used"] = carbon_occupant.used_skillchip_slots
.["slots_max"] = carbon_occupant.max_skillchip_slots
.["skillchip_ready"] = inserted_skillchip ? TRUE : FALSE
if(inserted_skillchip)
.["implantable"] = inserted_skillchip.can_be_implanted(occupant)
.["implantable_reason"] = inserted_skillchip.can_be_implanted_message(occupant)
.["skill_name"] = inserted_skillchip.skill_name
.["skill_desc"] = inserted_skillchip.skill_description
.["skill_icon"] = inserted_skillchip.skill_icon
.["skill_cost"] = inserted_skillchip.slot_cost
// This is safe, incompatibility check can accept a null or invalid mob.
var/incompatibility_check = inserted_skillchip.has_mob_incompatibility(carbon_occupant)
// Grab chip data. We do this because of special chips like Chameleon that may want to
// spoof their information.
var/list/inserted_chip_data = inserted_skillchip.get_chip_data()
if(incompatibility_check)
.["implantable"] = FALSE
.["implantable_reason"] = incompatibility_check
else
.["implantable"] = TRUE
.["implantable_reason"] = null
.["skill_name"] = inserted_chip_data["name"]
.["skill_desc"] = inserted_chip_data["desc"]
.["skill_icon"] = inserted_chip_data["icon"]
.["complexity"] = inserted_chip_data["complexity"]
.["slot_use"] = inserted_chip_data["slot_use"]
// If there's no occupant, we don't need to worry about what skillchips are in their brain.
if(!carbon_occupant)
.["error"] = "No valid occupant detected. Please consult nearest medical practitioner."
.["current"] = null
.["complexity_used"] = null
.["complexity_max"] = null
.["slots_used"] = null
.["slots_max"] = null
return
var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
// If there's no brain, we don't need to worry either.
if(QDELETED(occupant_brain))
.["error"] = "Brain not detected. Please consult nearest medical practitioner."
.["current"] = null
.["complexity_used"] = null
.["complexity_max"] = null
.["slots_used"] = null
.["slots_max"] = null
return
.["complexity_used"] = occupant_brain.get_used_skillchip_complexity()
.["complexity_max"] = occupant_brain.get_max_skillchip_complexity()
.["slots_used"] = occupant_brain.get_used_skillchip_slots()
.["slots_max"] = occupant_brain.get_max_skillchip_slots()
// If we got here, we have both an occupant and it has a brain, so we can check for skillchips.
var/list/current_skills = list()
for(var/obj/item/skillchip/skill_chip in occupant_brain.skillchips)
current_skills += list(skill_chip.get_chip_data())
.["current"] = current_skills
/obj/machinery/skill_station/ui_act(action, list/params)
. = ..()
@@ -185,24 +253,49 @@
return
switch(action)
if("implant")
if(working)
stack_trace("[usr] tried to start skillchip implanting when [src] was in an invalid state.")
return TRUE
if(occupant && inserted_skillchip)
start_implanting()
return TRUE
if("remove")
if(working)
stack_trace("[usr] tried to start skillchip removal when [src] was in an invalid state.")
return TRUE
var/chipref = params["ref"]
var/mob/living/carbon/carbon_occupant = occupant
var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(carbon_occupant) || QDELETED(occupant_brain))
return TRUE
var/obj/item/skillchip/to_be_removed = locate(chipref) in occupant_brain
var/obj/item/skillchip/to_be_removed = locate(chipref) in occupant_brain.skillchips
if(!to_be_removed)
return TRUE
start_removal(to_be_removed)
return TRUE
if("eject")
if(working)
stack_trace("[usr] tried to toggle skillchip activation when [src] was in an invalid state.")
return TRUE
if(inserted_skillchip)
to_chat(occupant,"<span class='notice'>You eject the skillchip.</span>")
var/mob/living/carbon/human/H = occupant
H.put_in_hands(inserted_skillchip)
inserted_skillchip = null
return TRUE
if("toggle_activate")
var/chipref = params["ref"]
// Check if the machine is already working. If it is, this act should not have sent.
if(working)
stack_trace("[usr] tried to toggle skillchip activation when [src] was in an invalid state.")
return TRUE
var/mob/living/carbon/carbon_occupant = occupant
var/obj/item/organ/brain/occupant_brain = carbon_occupant.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(carbon_occupant) || QDELETED(occupant_brain))
return TRUE
var/obj/item/skillchip/to_be_removed = locate(chipref) in occupant_brain.skillchips
if(!to_be_removed)
return TRUE
toggle_chip_active(to_be_removed)
return TRUE
+355 -67
View File
@@ -13,62 +13,358 @@
var/skill_name
/// Skill description shown on UI
var/skill_description
/// Category string. Used alongside SKILLCHIP_RESTRICTED_CATEGORIES flag to make a chip incompatible with chips from another category.
var/chip_category = "general"
/// List of any incompatible categories.
var/list/incompatibility_list
/// Fontawesome icon show on UI, list of possible icons https://fontawesome.com/icons?d=gallery&m=free
var/skill_icon = "brain"
/// Message shown when implanting the chip
var/implanting_message
/// Message shown when extracting the chip
var/removal_message
//If set to TRUE, trying to extract the chip will destroy it instead
var/removable = TRUE
/// How many skillslots this one takes
var/slot_cost = 1
/// Variable for flags
/// Message shown when activating the chip
var/activate_message
/// Message shown when deactivating the chip
var/deactivate_message
//If set to FALSE, trying to extract the chip will destroy it instead
var/removable
/// How complex the skillchip is. Brains can only handle so much complexity at once and skillchips will start to deactivate when the brain's complexity limit is exceeded.
var/complexity = 1
/// How many slots taken up in the brain by this chip. Max brain slots are hard set and should not be changed at all.
var/slot_use = 1
/// Variable for flags. DANGEROUS - Child types overwrite flags instead of adding to them. If you change this, make sure all child types have the appropriate flags set too.
var/skillchip_flags = NONE
/// Cooldown before the skillchip can be extracted after it has been implanted.
var/cooldown = 5 MINUTES
/// Cooldown for chip actions.
COOLDOWN_DECLARE(chip_cooldown)
/// Used to determine if this is an abstract type or not. If this is meant to be an abstract type, set it to the type's path. Will be overridden by subsequent abstract parents. See /datum/action/item_action/chameleon/change/skillchip/initialize_disguises()
var/abstract_parent_type = /obj/item/skillchip
/// Set to TRUE when the skill chip's effects are applied. Set to FALSE when they're not.
var/active = FALSE
/// Brain that holds this skillchip.
var/obj/item/organ/brain/holding_brain
/obj/item/skillchip/Initialize(mapload, is_removable = TRUE)
. = ..()
removable = is_removable
/**
* Activates the skillchip, if possible.
*
* Returns a message containing the reason if activation is not possible.
* Arguments:
* * silent - Boolean. Whether or not an activation message should be shown to the user.
* * force - Boolean. Whether or not to just force de-activation if it would be prevented for any reason.
*/
/obj/item/skillchip/proc/try_activate_skillchip(silent = FALSE, force = FALSE)
// Should not happen. Holding brain is destroyed and the chip hasn't had its state set appropriately.
if(QDELETED(holding_brain))
stack_trace("Skillchip's owner is null or qdeleted brain.")
return "Skillchip cannot detect viable brain."
// Also should not happen. We're somehow activating skillchips in a bodyless brain.
if(QDELETED(holding_brain.owner))
stack_trace("Skillchip's brain has no owner, owner is null or owner qdeleted.")
return "Skillchip cannot detect viable body."
// We have a holding brain, the holding brain has an owner. If we're forcing this, do it hard and fast.
if(force)
on_activate(holding_brain.owner, silent)
return
// Is the chip still experiencing a cooldown period?
if(!COOLDOWN_FINISHED(src, chip_cooldown))
return "Skillchip is still recharging for [COOLDOWN_TIMELEFT(src, chip_cooldown) * 0.1]s"
// So, we have a brain and that brain has a body. Let's start checking for incompatibility.
var/activate_msg = has_activate_incompatibility(holding_brain)
// If there's an activate_msg it means we can't activate for some reason. Return the feedback message.
if(activate_msg)
return activate_msg
// Either there's no incompatibility or we're forcing the activation. We're good to go!
on_activate(holding_brain.owner, silent)
/**
* Deactivates the skillchip, if possible.
*
* Returns a message containing the reason if deactivation is not possible.
* Arguments:
* * silent - Boolean. Whether or not an activation message should be shown to the user.
* * force - Boolean. Whether or not to just force de-activation if it would be prevented for any reason.
*/
/obj/item/skillchip/proc/try_deactivate_skillchip(silent = FALSE, force = FALSE)
if(!active)
return "Skillchip is not active."
// Should not happen. Holding brain is destroyed and the chip hasn't had its state set appropriately.
if(QDELETED(holding_brain))
stack_trace("Skillchip's owner is null or qdeleted brain.")
return "Skillchip cannot detect viable brain."
// Also should not happen. We're somehow deactivating skillchips in a bodyless brain.
if(QDELETED(holding_brain.owner))
active = FALSE
stack_trace("Skillchip's brain has no owner, owner is null or owner qdeleted.")
return "Skillchip cannot detect viable body."
// We have a holding brain, the holding brain has an owner. If we're forcing this, do it hard and fast.
if(force)
on_deactivate(holding_brain.owner, silent)
return
// Is the chip still experiencing a cooldown period?
if(!COOLDOWN_FINISHED(src, chip_cooldown))
return "Skillchip is still recharging for [COOLDOWN_TIMELEFT(src, chip_cooldown) * 0.1]s"
// We're good to go. Deactive this chip.
on_deactivate(holding_brain.owner, silent)
/**
* Called when a skillchip is inserted in a user's brain.
*
* Arguments:
* * owner_brain - The brain that this skillchip was implanted in to.
*/
/obj/item/skillchip/proc/on_implant(obj/item/organ/brain/owner_brain)
if(holding_brain)
CRASH("Skillchip is trying to be implanted into [owner_brain], but it's already implanted in [holding_brain]")
holding_brain = owner_brain
/**
* Called when a skillchip is activated.
*
* Arguments:
* * user - The user to apply skillchip effects to.
* * silent - Boolean. Whether or not an activation message should be shown to the user.
*/
/obj/item/skillchip/proc/on_activate(mob/living/carbon/user, silent=FALSE)
if(!silent && activate_message)
to_chat(user, activate_message)
/// Called after implantation and/or brain entering new body
/obj/item/skillchip/proc/on_apply(mob/living/carbon/user,silent=TRUE)
if(!silent && implanting_message)
to_chat(user,implanting_message)
if(auto_trait)
ADD_TRAIT(user,auto_trait,SKILLCHIP_TRAIT)
user.used_skillchip_slots += slot_cost
ADD_TRAIT(user, auto_trait, SKILLCHIP_TRAIT)
active = TRUE
COOLDOWN_START(src, chip_cooldown, cooldown)
/**
* Called when a skillchip is removed from the user's brain.
*
* Always deactivates the skillchip.
* Arguments:
* * user - The user to remove skillchip effects from.
* * silent - Boolean. Whether or not a deactivation message should be shown to the user.
*/
/obj/item/skillchip/proc/on_removal(silent=FALSE)
if(active)
try_deactivate_skillchip(silent, TRUE)
COOLDOWN_RESET(src, chip_cooldown)
holding_brain = null
/**
* Called when a skillchip is deactivated.
*
* Arguments:
* * user - The user to remove skillchip effects from.
* * silent - Boolean. Whether or not a deactivation message should be shown to the user.
*/
/obj/item/skillchip/proc/on_deactivate(mob/living/carbon/user, silent=FALSE)
if(!silent && deactivate_message)
to_chat(user, deactivate_message)
/// Called after removal and/or brain exiting the body
/obj/item/skillchip/proc/on_removal(mob/living/carbon/user,silent=TRUE)
if(!silent && removal_message)
to_chat(user,removal_message)
if(auto_trait)
REMOVE_TRAIT(user,auto_trait,SKILLCHIP_TRAIT)
user.used_skillchip_slots -= slot_cost
REMOVE_TRAIT(user, auto_trait, SKILLCHIP_TRAIT)
/// Checks if this implant is valid to implant in a given mob.
/obj/item/skillchip/proc/can_be_implanted(mob/living/carbon/target)
//No brain
var/obj/item/organ/brain/target_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(target_brain))
return FALSE
//No skill slots left
if(target.used_skillchip_slots + slot_cost > target.max_skillchip_slots)
return FALSE
//Only one multiple copies of a type if SKILLCHIP_ALLOWS_MULTIPLE flag is set
if(!(skillchip_flags & SKILLCHIP_ALLOWS_MULTIPLE) && (locate(type) in target_brain.skillchips))
return FALSE
return TRUE
active = FALSE
/// Returns readable reason why implanting cannot succeed --todo switch to flag retval in can_be_implanted to cut down copypaste
/obj/item/skillchip/proc/can_be_implanted_message(mob/living/carbon/target)
//No brain
var/obj/item/organ/brain/target_brain = target.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(target_brain))
COOLDOWN_START(src, chip_cooldown, cooldown)
/**
* Checks whether a given skillchip has an incompatibility with a brain that should render it impossible
* to activate.
*
* Returns a string with an explanation if the chip is not activatable. FALSE otherwise.
* Arguments:
* * skillchip - The skillchip you're intending to activate. Does not activate the chip.
*/
/obj/item/skillchip/proc/has_activate_incompatibility(obj/item/organ/brain/brain)
if(QDELETED(brain))
return "No brain detected."
//No skill slots left
if(target.used_skillchip_slots + slot_cost > target.max_skillchip_slots)
return "Complexity limit exceeded."
//Only one multiple copies of a type if SKILLCHIP_ALLOWS_MULTIPLE flag is set
if(!(skillchip_flags & SKILLCHIP_ALLOWS_MULTIPLE) && (locate(type) in target_brain.skillchips))
return "Duplicate chip detected."
return "Chip ready for implantation."
// Check if there's enough complexity usage left to activate the skillchip.
var/max_complexity = brain.get_max_skillchip_complexity()
var/new_complexity = brain.get_used_skillchip_complexity() + get_complexity()
if(new_complexity > max_complexity)
return "Skillchip is too complex to activate: [new_complexity] total out of [max_complexity] max complexity."
return FALSE
/**
* Checks for skillchip incompatibility with another chip.
*
* Does *this* skillchip have incompatibility with the skillchip in the args?
* Override this with any snowflake chip-vs-chip incompatibility checks.
* Returns a string with an incompatibility explanation if the chip is not compatible, returns FALSE
* if it is compatible.
* Arguments:
* * skillchip - The skillchip to test for incompatability.
*/
/obj/item/skillchip/proc/has_skillchip_incompatibility(obj/item/skillchip/skillchip)
// Only allow multiple copies of a type if SKILLCHIP_ALLOWS_MULTIPLE flag is set
if(!(skillchip_flags & SKILLCHIP_ALLOWS_MULTIPLE) && (skillchip.type == type))
return "Duplicate chip detected: [skillchip.name]"
// Prevent implanting multiple chips of the same category.
if((skillchip_flags & SKILLCHIP_RESTRICTED_CATEGORIES) && (skillchip.chip_category in incompatibility_list))
return "Incompatible with implanted [skillchip.chip_category] chip [skillchip.name]."
return FALSE
/**
* Performs a full sweep of checks that dictate if this chip can be implanted in a given target.
*
* Override this with any snowflake chip checks. An example of which would be checking if a target is
* mindshielded if you've got a special security skillchip.
* Returns a string with an incompatibility explanation if the chip is not compatible, returns FALSE
* if it is compatible.
* Arguments:
* * target - The mob to check for implantability with.
*/
/obj/item/skillchip/proc/has_mob_incompatibility(mob/living/carbon/target)
// No carbon/carbon of incorrect type
if(!istype(target))
return "Incompatible lifeform detected."
// No brain
var/obj/item/organ/brain/brain = target.getorganslot(ORGAN_SLOT_BRAIN)
if(QDELETED(brain))
return "No brain detected."
// Check brain incompatibility. This also performs skillchip-to-skillchip incompatibility checks.
var/brain_message = has_brain_incompatibility(brain)
if(brain_message)
return brain_message
return FALSE
/**
* Performs a full sweep of checks that dictate if this chip can be implanted in a given brain.
*
* Override this with any snowflake chip checks.
* Returns TRUE if the chip is fully compatible, FALSE otherwise.
* Arguments:
* * brain - The brain to check for implantability with.
*/
/obj/item/skillchip/proc/has_brain_incompatibility(obj/item/organ/brain/brain)
if(!istype(brain))
stack_trace("Attempted to check incompatibility with invalid brain object [brain].")
return "Incompatible brain."
var/chip_message
// Slot capacity check!
var/max_slots = brain.get_max_skillchip_slots()
var/used_slots = brain.get_used_skillchip_slots()
if(used_slots + slot_use > max_slots)
return "Not enough free slots. You have [max_slots - used_slots] free and need [slot_use]."
// Check if this chip is incompatible with any other chips in the brain.
for(var/skillchip in brain.skillchips)
chip_message = has_skillchip_incompatibility(skillchip)
if(chip_message)
return chip_message
return FALSE
/**
* Returns whether the chip is on cooldown. Chips ordinarily go on cooldown when activated.
*
* This does not mean the chip should be impossible to do anything with.
* It's up to each individual piece of code to decide what it does with the result of this proc.
*
* Returns TRUE if the chip's extraction cooldown hasn't yet passed.
*/
/obj/item/skillchip/proc/is_on_cooldown()
return !COOLDOWN_FINISHED(src, chip_cooldown)
/**
* Returns whether the chip is active.
*
* Intended to be overriden.
* Returns TRUE if the chip is active.
*/
/obj/item/skillchip/proc/is_active()
return active
/**
* Returns the chip's complexity.
*
* Intended to be overriden.
*/
/obj/item/skillchip/proc/get_complexity()
return complexity
/**
* Returns a list of basic chip info. Used by the skill station.
*/
/obj/item/skillchip/proc/get_chip_data()
return list(
"name" = skill_name,
"icon" = skill_icon,
"desc" = skill_description,
"complexity" = get_complexity(),
"slot_use" = slot_use,
"removable" = removable,
"ref" = REF(src),
"active" = is_active(),
"active_error" = has_activate_incompatibility(holding_brain),
"cooldown" = COOLDOWN_TIMELEFT(src, chip_cooldown),
"actionable" = is_on_cooldown())
/**
* Gets key metadata from this skillchip in an assoc list.
*
* If you override this proc, don't forget to also override set_metadata, which takes the output of
* this proc and uses it to set the metadata.
* Does not copy over any owner or brain status. Handle that externally.
*/
/obj/item/skillchip/proc/get_metadata()
var/list/metadata = list()
metadata["type"] = type
metadata["chip_cooldown"] = chip_cooldown
metadata["active"] = active
metadata["removable"] = removable
return metadata
/**
* Sets key metadata for this skillchip from an assoc list.
*
* Best used with the output from get_metadata() of another chip.
* If you override this proc, don't forget to also override get_metadata, which is where you should
* usually get the assoc list that feeds into this proc.
* Does not set any owner or brain status. Handle that externally.
* Arguments:
* metadata - Ideally the output of another chip's get_metadata proc. Assoc list of metadata.
*/
/obj/item/skillchip/proc/set_metadata(list/metadata)
var/active_msg
// Start by trying to activate.
active = metadata["active"]
if(active)
active_msg = try_activate_skillchip(FALSE, TRUE)
// Whether it worked or not, set the rest of the metadata and then return any activate message.
chip_cooldown = metadata["chip_cooldown"]
removable = metadata["removable"]
return active_msg
/obj/item/skillchip/basketweaving
name = "Basketsoft 3000 skillchip"
@@ -77,8 +373,8 @@
skill_name = "Underwater Basketweaving"
skill_description = "Master intricate art of using twine to create perfect baskets while submerged."
skill_icon = "shopping-basket"
implanting_message = "<span class='notice'>You're one with the twine and the sea.</span>"
removal_message = "<span class='notice'>Higher mysteries of underwater basketweaving leave your mind.</span>"
activate_message = "<span class='notice'>You're one with the twine and the sea.</span>"
deactivate_message = "<span class='notice'>Higher mysteries of underwater basketweaving leave your mind.</span>"
/obj/item/skillchip/wine_taster
name = "WINE skillchip"
@@ -87,8 +383,8 @@
skill_name = "Wine Tasting"
skill_description = "Recognize wine vintage from taste alone. Never again lack an opinion when presented with an unknown drink."
skill_icon = "wine-bottle"
implanting_message = "<span class='notice'>You recall wine taste.</span>"
removal_message = "<span class='notice'>Your memories of wine evaporate.</span>"
activate_message = "<span class='notice'>You recall wine taste.</span>"
deactivate_message = "<span class='notice'>Your memories of wine evaporate.</span>"
/obj/item/skillchip/bonsai
name = "Hedge 3 skillchip"
@@ -96,25 +392,17 @@
skill_name = "Hedgetrimming"
skill_description = "Trim hedges and potted plants into marvelous new shapes with any old knife. Not applicable to plastic plants."
skill_icon = "spa"
implanting_message = "<span class='notice'>Your mind is filled with plant arrangments.</span>"
removal_message = "<span class='notice'>Your can't remember how a hedge looks like anymore.</span>"
activate_message = "<span class='notice'>Your mind is filled with plant arrangments.</span>"
deactivate_message = "<span class='notice'>Your can't remember how a hedge looks like anymore.</span>"
/obj/item/skillchip/useless_adapter
name = "Skillchip adapter"
skill_name = "Useless adapter"
skill_description = "Allows you to insert another identical skillchip into this adapter, but the adapter also takes a slot ..."
skill_description = "Allows you to insert another skillchip into this adapter after it has been inserted into your brain..."
skill_icon = "plug"
implanting_message = "<span class='notice'>You can now implant another chip into this adapter, but the adapter also took up an existing slot ...</span>"
removal_message = "<span class='notice'>You no longer have the useless skillchip adapter.</span>"
skillchip_flags = SKILLCHIP_ALLOWS_MULTIPLE
slot_cost = 0
/obj/item/skillchip/useless_adapter/on_apply(mob/living/carbon/user, silent)
. = ..()
user.max_skillchip_slots++
user.used_skillchip_slots++
/obj/item/skillchip/useless_adapter/on_removal(mob/living/carbon/user, silent)
user.max_skillchip_slots--
user.used_skillchip_slots--
return ..()
activate_message = "<span class='notice'>You can now activate another chip through this adapter, but you're not sure why you did this...</span>"
deactivate_message = "<span class='notice'>You no longer have the useless skillchip adapter.</span>"
skillchip_flags = SKILLCHIP_ALLOWS_MULTIPLE | SKILLCHIP_CHAMELEON_INCOMPATIBLE
// Literally does nothing.
complexity = 0
slot_use = 0