Files
Bubberstation/code/modules/library/skill_learning/skill_station.dm
Ghom 778ed9f1ab The death or internal/external organ pathing (ft. fixed fox ears and recoloring bodypart overlays with dye sprays) (#87434)
## About The Pull Request
This PR kills the abstract internal and external typepaths for organs,
now replaced by an EXTERNAL_ORGAN flag to distinguish the two kinds.

This PR also fixes fox ears (from #87162, no tail is added) and
mushpeople's caps (they should be red, the screenshot is a tad
outdated).

And yes, you can now use a hair dye spray to recolor body parts like
most tails, podpeople hair, mushpeople caps and cat ears. The process
can be reversed by using the spray again.

## Why It's Good For The Game
Time-Green put some effort during the last few months to untie functions
and mechanics from external/internal organ pathing. Now, all that this
pathing is good for are a few typechecks, easily replaceable with
bitflags.

Also podpeople and mushpeople need a way to recolor their "hair". This
kind of applies to fish tails from the fish infusion, which colors can't
be selected right now. The rest is just there if you ever want to
recolor your lizard tail for some reason.

Proof of testing btw (screenshot taken before mushpeople cap fix, right
side has dyed body parts, moth can't be dyed, they're already fabolous):

![immagine](https://github.com/user-attachments/assets/2bb625c9-9233-42eb-b9b8-e0bd6909ce89)

## Changelog

🆑
code: Removed internal/external pathing from organs in favor of a bit
flag. Hopefully this shouldn't break anything about organs.
fix: Fixed invisible fox ears.
fix: Fixed mushpeople caps not being colored red by default.
add: You can now dye most tails, podpeople hair, mushpeople caps etc.
with a hair dye spray.
/🆑
2024-10-30 08:03:02 +01:00

311 lines
10 KiB
Plaintext

#define SKILLCHIP_IMPLANT_TIME (15 SECONDS)
#define SKILLCHIP_REMOVAL_TIME (15 SECONDS)
/obj/machinery/skill_station
name = "\improper Skillsoft station"
desc = "Learn skills with only minimal chance for brain damage."
icon = 'icons/obj/machines/implant_chair.dmi'
icon_state = "implantchair"
occupant_typecache = list(/mob/living/carbon) //todo make occupant_typecache per type
state_open = TRUE
// Only opens UI when inside; also, you can use the machine while lying down (for paraplegics and the like)
interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_IGNORE_MOBILITY
circuit = /obj/item/circuitboard/machine/skill_station
/// Currently implanting/removing
var/working = FALSE
/// Timer until implanting/removing finishes.
var/work_timer
/// What we're implanting
var/obj/item/skillchip/inserted_skillchip
/obj/machinery/skill_station/Initialize(mapload)
. = ..()
update_appearance()
//Only usable by the person inside
/obj/machinery/skill_station/ui_state(mob/user)
return GLOB.contained_state
/obj/machinery/skill_station/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "SkillStation", name)
ui.open()
/obj/machinery/skill_station/update_icon_state()
icon_state = initial(icon_state)
if(state_open)
icon_state += "_open"
if(occupant)
icon_state += "_occupied"
return ..()
/obj/machinery/skill_station/update_overlays()
. = ..()
if(working)
. += "working"
/obj/machinery/skill_station/relaymove(mob/living/user, direction)
open_machine()
/obj/machinery/skill_station/open_machine(drop = TRUE, density_to_set = FALSE)
. = ..()
interrupt_operation()
/obj/machinery/skill_station/Exited(atom/movable/gone, direction)
. = ..()
if(gone == inserted_skillchip)
inserted_skillchip = null
interrupt_operation()
/obj/machinery/skill_station/power_change()
. = ..()
if(working)
interrupt_operation()
/obj/machinery/skill_station/close_machine(atom/movable/target, density_to_set = TRUE)
. = ..()
if(occupant)
ui_interact(occupant)
/obj/machinery/skill_station/proc/interrupt_operation()
working = FALSE
if(work_timer)
deltimer(work_timer)
work_timer = null
update_appearance()
/obj/machinery/skill_station/interact(mob/user)
. = ..()
if(user == occupant)
ui_interact(user)
else
toggle_open()
/obj/machinery/skill_station/attackby(obj/item/I, mob/living/user, params)
if(istype(I,/obj/item/skillchip))
if(inserted_skillchip)
to_chat(user,span_notice("There's already a skillchip inside."))
return
if(!user.transferItemToLoc(I, src))
return
inserted_skillchip = I
return
return ..()
/obj/machinery/skill_station/dump_contents()
. = ..()
inserted_skillchip = null
/obj/machinery/skill_station/dump_inventory_contents(list/subset = null)
// Don't drop the skillchip, it's directly inserted into the machine.
// dump_contents() will drop everything including the skillchip as an alternative to this.
return ..(contents - inserted_skillchip)
/obj/machinery/skill_station/proc/toggle_open(mob/user)
state_open ? close_machine() : open_machine()
// Functions below do not validate occupant exists - should be handled outer wrappers.
/// Start implanting.
/obj/machinery/skill_station/proc/start_implanting()
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_REF(implant)),SKILLCHIP_IMPLANT_TIME,TIMER_STOPPABLE)
update_appearance()
/// Finish implanting.
/obj/machinery/skill_station/proc/implant()
working = FALSE
work_timer = null
var/mob/living/carbon/carbon_occupant = occupant
var/implant_msg = carbon_occupant.implant_skillchip(inserted_skillchip, FALSE)
if(implant_msg)
to_chat(carbon_occupant,span_notice("Operation failed! [implant_msg]"))
else
to_chat(carbon_occupant,span_notice("Operation complete!"))
inserted_skillchip = null
update_appearance()
/// 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_notice("DANGER! Operation cannot be completed, removal is unsafe."))
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_REF(remove_skillchip),to_be_removed),SKILLCHIP_REMOVAL_TIME,TIMER_STOPPABLE)
update_appearance()
/// Finish removal.
/obj/machinery/skill_station/proc/remove_skillchip(obj/item/skillchip/to_be_removed)
working = FALSE
work_timer = null
update_appearance()
var/mob/living/carbon/carbon_occupant = occupant
if(to_be_removed.is_on_cooldown())
to_chat(carbon_occupant,span_notice("Safety mechanisms activated! Skillchip cannot be safely removed."))
return
if(!istype(carbon_occupant))
to_chat(carbon_occupant,span_notice("Occupant does not appear to be a carbon-based lifeform!"))
return
if(!carbon_occupant.remove_skillchip(to_be_removed))
to_chat(carbon_occupant,span_notice("Failed to remove skillchip!"))
return
if(to_be_removed.removable)
carbon_occupant.put_in_hands(to_be_removed)
else
qdel(to_be_removed)
to_chat(carbon_occupant, span_notice("Operation complete!"))
/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_notice("Safety mechanisms activated! Skillchip cannot be safely modified."))
return
if(!istype(carbon_occupant))
to_chat(carbon_occupant,span_notice("Occupant does not appear to be a carbon-based lifeform!"))
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_notice("Failed to deactivate skillchip! [active_msg]"))
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_notice("Failed to activate skillchip! [active_msg]"))
/obj/machinery/skill_station/ui_data(mob/user)
. = ..()
.["working"] = working
.["timeleft"] = work_timer ? timeleft(work_timer) : null
var/mob/living/carbon/carbon_occupant = occupant
.["skillchip_ready"] = inserted_skillchip ? TRUE : FALSE
if(inserted_skillchip)
// 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.get_organ_slot(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, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
if(usr != occupant)
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.get_organ_slot(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
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_notice("You eject the skillchip."))
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.get_organ_slot(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
#undef SKILLCHIP_IMPLANT_TIME
#undef SKILLCHIP_REMOVAL_TIME