Files
VMSolidus 0625b841c3 Surgery Speed Modifiers (#22472)
This PR "gently refactors" surgery doafter timers to be deterministic
and variable. Instead of being simply a random number of seconds between
an upper and lower bounds, surgeries now have a base amount of time they
take to perform (in fractional seconds). This doafter time can then be
modified via signal interactions, such as coming from components.

The Morale component, Surgery skill, Anatomy skill, and Xenobiology
skill all have had signal hooks for surgery speed added, while the
latter two are uniquely conditional signal modifiers. Anatomy only
provides its modifier if the target's species matches your own, while
Xenobiology only applies its modifier if the target is an alien. Morale
can situationally either give a bonus or a penalty depending on your
current amount of morale points.

So to offset that this is now significantly more variable and that 4
different sources of bonus surgical speed exist, I've actually made the
base surgery times always on the upper end of what their previous
doafter duration range was, so surgeries are in general slower if you
aren't taking advantage of specializing in it.

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
2026-06-19 16:47:29 +00:00

218 lines
9.7 KiB
Plaintext

//Procedures in this file: Putting items in body cavity. Implant removal. Items removal.
//////////////////////////////////////////////////////////////////
// ITEM PLACEMENT SURGERY //
//////////////////////////////////////////////////////////////////
/singleton/surgery_step/cavity
priority = 1
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
return FALSE
var/obj/item/organ/external/affected = target.get_organ(target_zone)
return affected && IS_ORGAN_FULLY_OPEN && !(affected.status & ORGAN_BLEEDING)
/singleton/surgery_step/cavity/proc/get_max_wclass(var/obj/item/organ/external/affected)
switch(affected.name)
if(BP_HEAD)
return WEIGHT_CLASS_TINY
if("upper body")
return WEIGHT_CLASS_NORMAL
if("lower body")
return WEIGHT_CLASS_SMALL
return 0
/singleton/surgery_step/cavity/proc/get_cavity(var/obj/item/organ/external/affected)
switch(affected.name)
if(BP_HEAD)
return "cranial"
if("upper body")
return "thoracic"
if("lower body")
return "abdominal"
return ""
/singleton/surgery_step/cavity/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
user.visible_message(SPAN_WARNING("[user]'s hand slips, scraping around inside [target]'s [affected.name] with \the [tool]!"), \
SPAN_WARNING("Your hand slips, scraping around inside [target]'s [affected.name] with \the [tool]!"))
target.apply_damage(20, DAMAGE_BRUTE, target_zone, 0, tool, damage_flags = tool.damage_flags())
/singleton/surgery_step/cavity/make_space
name = "Hollow Out Cavity"
allowed_tools = list(
TOOL_DRILL = 100, \
/obj/item/pen = 75, \
/obj/item/stack/rods = 50
)
base_surgery_time = 7 SECONDS
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/make_space/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
return FALSE
var/obj/item/organ/external/affected = target.get_organ(target_zone)
return affected && !affected.cavity
/singleton/surgery_step/cavity/make_space/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("[user] starts making some space inside [target]'s [get_cavity(affected)] cavity with \the [tool].", \
"You start making some space inside [target]'s [get_cavity(affected)] cavity with \the [tool]." )
target.custom_pain("The pain in your chest is living hell!",1)
affected.cavity = CAVITY_OPEN
..()
/singleton/surgery_step/cavity/make_space/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
user.visible_message("<b>[user]</b> pushes apart some organs inside [target]'s [get_cavity(affected)] cavity with \the [tool].", \
SPAN_NOTICE("You make some space inside [target]'s [get_cavity(affected)] cavity with \the [tool].") )
/singleton/surgery_step/cavity/close_space
name = "Close Cavity"
priority = 2
allowed_tools = list(
TOOL_CAUTERY = 100, \
/obj/item/clothing/mask/smokable/cigarette = 75, \
/obj/item/flame/lighter = 50, \
/obj/item/weldingtool = 25
)
base_surgery_time = 7 SECONDS
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/close_space/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
return FALSE
var/obj/item/organ/external/affected = target.get_organ(target_zone)
return affected && affected.cavity
/singleton/surgery_step/cavity/close_space/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<b>[user]</b> starts mending [target]'s [get_cavity(affected)] cavity wall with \the [tool].", \
"You start mending [target]'s [get_cavity(affected)] cavity wall with \the [tool]." )
target.custom_pain("The pain in your chest is living hell!", 75)
affected.cavity = CAVITY_CLOSED
..()
/singleton/surgery_step/cavity/close_space/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
user.visible_message("<b>[user]</b> mends [target]'s [get_cavity(affected)] cavity walls with \the [tool].", \
SPAN_NOTICE("You mend [target]'s [get_cavity(affected)] cavity walls with \the [tool].") )
/singleton/surgery_step/cavity/place_item
name = "Place Item in Cavity"
priority = 0
allowed_tools = list(/obj/item = 100)
base_surgery_time = 8 SECONDS
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/place_item/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
return FALSE
var/obj/item/organ/external/affected = target.get_organ(target_zone)
if(istype(user,/mob/living/silicon/robot))
return
if(affected && affected.cavity)
var/total_volume = tool.w_class
for(var/obj/item/I in affected.implants)
if(istype(I,/obj/item/implant))
continue
total_volume += I.w_class
return total_volume <= get_max_wclass(affected)
/singleton/surgery_step/cavity/place_item/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("<b>[user]</b> starts putting \the [tool] inside [target]'s [get_cavity(affected)] cavity.", \
SPAN_NOTICE("You start putting \the [tool] inside [target]'s [get_cavity(affected)] cavity." ))
target.custom_pain("The pain in your chest is living hell!", 75)
playsound(target.loc, 'sound/effects/squelch1.ogg', 50, 1)
..()
/singleton/surgery_step/cavity/place_item/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
user.visible_message("<b>[user]</b> puts \the [tool] inside [target]'s [get_cavity(affected)] cavity.", \
SPAN_NOTICE("You put \the [tool] inside [target]'s [get_cavity(affected)] cavity.") )
if(tool.w_class > get_max_wclass(affected)/2 && prob(50) && !BP_IS_ROBOTIC(affected))
to_chat(user, SPAN_WARNING("You tear \the [affected.artery_name] trying to fit an object so big!"))
affected.sever_artery()
affected.owner.custom_pain("You feel something rip in your [affected.name]!", 1)
user.drop_item()
affected.implants += tool
tool.forceMove(affected)
affected.cavity = CAVITY_CLOSED
//////////////////////////////////////////////////////////////////
// IMPLANT/ITEM REMOVAL SURGERY //
//////////////////////////////////////////////////////////////////
/singleton/surgery_step/cavity/implant_removal
name = "Remove Foreign Body"
allowed_tools = list(
TOOL_HEMOSTAT = 100, \
TOOL_WIRECUTTER = 75, \
/obj/item/material/kitchen/utensil/fork = 20
)
base_surgery_time = 8 SECONDS
skill_requirements = alist(SURGERY_SKILL_COMPONENT = SKILL_LEVEL_TRAINED)
/singleton/surgery_step/cavity/implant_removal/can_use(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
if(!..())
return FALSE
var/obj/item/organ/external/affected = target.get_organ(target_zone)
return affected
/singleton/surgery_step/cavity/implant_removal/begin_step(mob/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/affected = target.get_organ(target_zone)
user.visible_message("[user] starts poking around inside [target]'s [affected.name] with \the [tool].", \
"You start poking around inside [target]'s [affected.name] with \the [tool]." )
target.custom_pain("The pain in your [affected.name] is living hell!", 50)
..()
/singleton/surgery_step/cavity/implant_removal/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
if(length(affected.implants))
var/list/implants = list()
var/shrapnel_present = FALSE
for(var/I in affected.implants)
implants += I
if(!istype(I, /obj/item/implant))
shrapnel_present = TRUE
for(var/I in implants)
/// Prioritize shrapnel instead of stuff like loyalty implants.
if(shrapnel_present && istype(I, /obj/item/implant))
continue
if(do_mob(user, target, 0.5 SECONDS))
user.visible_message("<b>[user]</b> takes [I] out of incision on [target]'s [affected.name] with \the [tool].", \
SPAN_NOTICE("You take [I] out of incision on [target]'s [affected.name]s with \the [tool].") )
target.remove_implant(I, TRUE, affected)
BITSET(target.hud_updateflag, IMPLOYAL_HUD)
//Handle possessive brain borers.
if(istype(I, /mob/living/simple_animal/borer))
var/mob/living/simple_animal/borer/worm = I
if(worm.controlling)
target.release_control()
worm.detach()
worm.leave_host()
playsound(target.loc, 'sound/effects/squelch1.ogg', 50, 1)
else
user.visible_message("<b>[user]</b> could not find anything inside [target]'s [affected.name], and pulls \the [tool] out.", \
SPAN_NOTICE("You could not find anything inside [target]'s [affected.name].") )
/singleton/surgery_step/cavity/implant_removal/fail_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool)
..()
var/obj/item/organ/external/chest/affected = target.get_organ(target_zone)
user.visible_message(SPAN_WARNING("[user] loses their grip and stabs [target] with \the [tool]!"), SPAN_WARNING("You lose your grip on \the [tool] and stab [target]!"))
affected.sever_artery()
target.apply_damage(25, DAMAGE_BRUTE, target_zone)