Files
Bubberstation/code/modules/library/skill_learning/skill_station.dm
MrMelbert 5261efb67f Re-refactors batons / Refactors attack chain force modifiers (#90809)
## About The Pull Request

Melee attack chain now has a list passed along with it,
`attack_modifiers`, which you can stick force modifiers to change the
resulting attack

This is basically a soft implementation of damage packets until a more
definitive pr, but one that only applies to item attack chain, and not
unarmed attacks.

This change was done to facilitate a baton refactor - batons no longer
hack together their own attack chain, and are now integrated straight
into the real attack chain. This refactor itself was done because batons
don't send any attack signals, which has been annoying in the past (for
swing combat).

## Changelog

🆑 Melbert
refactor: Batons have been refactored again. Baton stuns now properly
count as an attack, when before it was a nothing. Report any oddities,
particularly in regards to harmbatonning vs normal batonning.
refactor: The method of adjusting item damage mid-attack has been
refactored - some affected items include the Nullblade and knives.
Report any strange happenings with damage numbers.
refactor: A few objects have been moved to the new interaction chain -
records consoles, mawed crucible, alien weeds and space vines, hedges,
restaurant portals, and some mobs - to name a few.
fix: Spears only deal bonus damage against secure lockers, not all
closet types (including crates)
/🆑
2025-05-19 13:32:12 +10: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, list/modifiers, list/attack_modifiers)
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