diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 03cb46d9c44..17f4d2389c8 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -554,6 +554,25 @@ ///from [/obj/effect/mine/proc/triggermine]: #define COMSIG_MINE_TRIGGERED "minegoboom" +/// Defib-specific signals + +/// Called when a defibrillator is first applied to someone. (mob/living/user, mob/living/target, harmful) +#define COMSIG_DEFIB_PADDLES_APPLIED "defib_paddles_applied" + /// Defib is out of power. + #define COMPONENT_BLOCK_DEFIB_DEAD (1<<0) + /// Something else: we won't have a custom message for this and should let the defib handle it. + #define COMPONENT_BLOCK_DEFIB_MISC (1<<1) + /// If our safeties are on, turn them off for this shock. + #define COMPONENT_DEFIB_BECOME_THE_DANGER (1<<2) + /// If our safeties are off, turn them on for this shock. + #define COMPONENT_DEFIB_BECOME_SAFE (1<<3) +/// Called when a defib has been successfully used, and a shock has been applied. (mob/living/user, mob/living/target, harmful, successful) +#define COMSIG_DEFIB_SHOCK_APPLIED "defib_zap" +/// Called when a defib is aborted and no shock was applied. (mob/living/user, mob/living/target, harmful) +#define COMSIG_DEFIB_ABORTED "defib_aborted" +/// Called when a defib's cooldown has run its course and it is once again ready. () +#define COMSIG_DEFIB_READY "defib_ready" + // /obj/item signals for economy ///called when an item is sold by the exports subsystem #define COMSIG_ITEM_SOLD "item_sold" diff --git a/code/datums/components/defibrillator.dm b/code/datums/components/defibrillator.dm new file mode 100644 index 00000000000..32144f9279e --- /dev/null +++ b/code/datums/components/defibrillator.dm @@ -0,0 +1,381 @@ +/** + * A component for an item that attempts to defibrillate a mob when activated. + */ +/datum/component/defib + /// If this is being used by a borg or not, with necessary safeties applied if so. + var/robotic + /// If it should penetrate space suits + var/combat + /// If combat is true, this determines whether or not it should always cause a heart attack. + var/heart_attack_chance + /// Whether the safeties are enabled or not + var/safety + /// If the defib is actively performing a defib cycle + var/busy = FALSE + /// Cooldown length for this defib in deciseconds + var/cooldown + /// Whether or not we're currently on cooldown + var/on_cooldown = FALSE + /// How fast the defib should work. + var/speed_multiplier + /// If true, EMPs will have no effect. + var/emp_proof + /// If true, this cannot be emagged. + var/emag_proof + /// uid to an item that should be making noise and handling things that our direct parent shouldn't be concerned with. + var/actual_unit_uid + +/** + * Create a new defibrillation component. + * + * Arguments: + * * robotic - whether this should be treated like a borg module. + * * cooldown - Minimum time possible between shocks. + * * speed_multiplier - Speed multiplier for defib do-afters. + * * combat - If true, the defib can zap through hardsuits. + * * heart_attack_chance - If combat and safeties are off, the % chance for this to cause a heart attack on harm intent. + * * safe_by_default - If true, safety will be enabled by default. + * * emp_proof - If true, safety won't be switched by emp. Note that the device itself can still have behavior from it, it's just that the component will not. + * * emag_proof - If true, safety won't be switched by emag. Note that the device itself can still have behavior from it, it's just that the component will not. + * * actual_unit - Unit which the component's parent is based from, such as a large defib unit or a borg. The actual_unit will make the sounds and be the "origin" of visible messages, among other things. + */ +/datum/component/defib/Initialize(robotic, cooldown = 5 SECONDS, speed_multiplier = 1, combat = FALSE, heart_attack_chance = 100, safe_by_default = TRUE, emp_proof = FALSE, emag_proof = FALSE, obj/item/actual_unit = null) + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + + src.robotic = robotic + src.speed_multiplier = speed_multiplier + src.cooldown = cooldown + src.combat = combat + src.heart_attack_chance = heart_attack_chance + safety = safe_by_default + src.emp_proof = emp_proof + src.emag_proof = emag_proof + + if(actual_unit) + actual_unit_uid = actual_unit.UID() + + var/effect_target = isnull(actual_unit) ? parent : actual_unit + + RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(trigger_defib)) + RegisterSignal(effect_target, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag)) + RegisterSignal(effect_target, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp)) + +/** + * Get the "parent" that effects (emags, EMPs) should be applied onto. + */ +/datum/component/defib/proc/get_effect_target() + var/actual_unit = locateUID(actual_unit_uid) + if(!actual_unit) + return parent + return actual_unit + +/datum/component/defib/proc/on_emp(obj/item/unit) + SIGNAL_HANDLER // COMSIG_ATOM_EMP_ACT + if(emp_proof) + return + + if(safety) + safety = FALSE + unit.visible_message("[unit] beeps: Safety protocols disabled!") + playsound(get_turf(unit), 'sound/machines/defib_saftyoff.ogg', 50, 0) + else + safety = TRUE + unit.visible_message("[unit] beeps: Safety protocols enabled!") + playsound(get_turf(unit), 'sound/machines/defib_saftyon.ogg', 50, 0) + +/datum/component/defib/proc/on_emag(obj/item/unit, mob/user) + SIGNAL_HANDLER // COMSIG_ATOM_EMAG_ACT + if(emag_proof) + return + safety = !safety + if(user && !robotic) + to_chat(user, "You silently [safety ? "disable" : "enable"] [unit]'s safety protocols with the card.") + +/datum/component/defib/proc/set_cooldown() + on_cooldown = TRUE + addtimer(CALLBACK(src, PROC_REF(end_cooldown)), cooldown) + +/datum/component/defib/proc/end_cooldown() + on_cooldown = FALSE + SEND_SIGNAL(parent, COMSIG_DEFIB_READY) + +/** + * Start the defibrillation process when triggered by a signal. + */ +/datum/component/defib/proc/trigger_defib(obj/item/paddles, mob/living/carbon/human/target, mob/living/user) + SIGNAL_HANDLER // COMSIG_ITEM_ATTACK + // This includes some do-afters, so we have to pass it off asynchronously + INVOKE_ASYNC(src, PROC_REF(defibrillate), user, target) + return TRUE + +/** + * Perform a defibrillation. + */ +/datum/component/defib/proc/defibrillate(mob/living/user, mob/living/carbon/human/target) + var/tobehealed + var/threshold = -HEALTH_THRESHOLD_DEAD + var/parent_unit = locateUID(actual_unit_uid) + var/should_cause_harm = user.a_intent == INTENT_HARM && !safety + + // find what the defib should be referring to itself as + var/atom/defib_ref + if(parent_unit) + defib_ref = parent_unit + if(!defib_ref && robotic) + defib_ref = user + if(!defib_ref) + defib_ref = parent // contingency + + // before we do all the hard work, make sure we aren't already defibbing someone + if(busy) + return + + // check what the unit itself has to say about how the defib went + var/application_result = SEND_SIGNAL(parent, COMSIG_DEFIB_PADDLES_APPLIED, user, target, should_cause_harm) + + if(!should_cause_harm && (application_result & COMPONENT_DEFIB_BECOME_THE_DANGER)) + should_cause_harm = TRUE + if(should_cause_harm && (application_result & COMPONENT_DEFIB_BECOME_SAFE)) + should_cause_harm = FALSE + + if(application_result & COMPONENT_BLOCK_DEFIB_DEAD) + user.visible_message("[defib_ref] beeps: Unit is unpowered.") + playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) + return + + if(on_cooldown) + to_chat(user, "[defib_ref] is recharging.") + return + + if(application_result & COMPONENT_BLOCK_DEFIB_MISC) + return // the unit should handle this + + if(!istype(target)) + if(robotic) + to_chat(user, "This unit is only designed to work on humanoid lifeforms.") + else + to_chat(user, "The instructions on [defib_ref] don't mention how to defibrillate that...") + return + + if(should_cause_harm) + fibrillate(user, target) + return + + user.visible_message( + "[user] begins to place [parent] on [target]'s chest.", + "You begin to place [parent] on [target.name]'s chest." + ) + + busy = TRUE + var/mob/dead/observer/ghost = target.get_ghost(TRUE) + if(ghost?.can_reenter_corpse) + to_chat(ghost, "Your heart is being defibrillated. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)") + window_flash(ghost.client) + notify_ghosts() + ghost << sound('sound/effects/genetics.ogg') + + if(!do_after(user, 3 SECONDS * speed_multiplier, target = target)) //beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process + busy = FALSE + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + return + + user.visible_message("[user] places [parent] on [target]'s chest.", "You place [parent] on [target]'s chest.") + playsound(get_turf(defib_ref), 'sound/machines/defib_charge.ogg', 50, 0) + + if(ghost && !ghost.client && !QDELETED(ghost)) + log_debug("Ghost of name [ghost.name] is bound to [target.real_name], but lacks a client. Deleting ghost.") + QDEL_NULL(ghost) + + var/tplus = world.time - target.timeofdeath + var/tlimit = DEFIB_TIME_LIMIT + var/tloss = DEFIB_TIME_LOSS + var/total_burn = 0 + var/total_brute = 0 + + var/signal_result = SEND_SIGNAL(target, COMSIG_LIVING_PRE_DEFIB, user, parent, ghost) + + if(!do_after(user, 2 SECONDS * speed_multiplier, target = target)) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total + busy = FALSE + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + return + + signal_result |= SEND_SIGNAL(target, COMSIG_LIVING_DEFIBBED, user, parent, ghost) + if(istype(target.wear_suit, /obj/item/clothing/suit/space) && !combat) + user.visible_message("[defib_ref] buzzes: Patient's chest is obscured. Operation aborted.") + playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) + busy = FALSE + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + return + + if(signal_result & COMPONENT_DEFIB_OVERRIDE) + // let our signal handle it + busy = FALSE + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + return + + if(target.undergoing_cardiac_arrest()) + if(!target.get_int_organ(/obj/item/organ/internal/heart) && !target.get_int_organ(/obj/item/organ/internal/brain/slime)) //prevents defibing someone still alive suffering from a heart attack attack if they lack a heart + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Failed to pick up any heart electrical activity.") + playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + busy = FALSE + return + + var/obj/item/organ/internal/heart/heart = target.get_int_organ(/obj/item/organ/internal/heart) + if(heart.status & ORGAN_DEAD) + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Heart necrosis detected.") + playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + busy = FALSE + return + + target.set_heartattack(FALSE) + SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100) + SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, TRUE) + set_cooldown() + user.visible_message("[defib_ref] pings: Cardiac arrhythmia corrected.") + target.visible_message("[target]'s body convulses a bit.", "You feel a jolt, and your heartbeat seems to steady.") + playsound(get_turf(defib_ref), 'sound/machines/defib_zap.ogg', 50, 1, -1) + playsound(get_turf(defib_ref), "bodyfall", 50, 1) + playsound(get_turf(defib_ref), 'sound/machines/defib_success.ogg', 50, 0) + busy = FALSE + return + + if(target.stat != DEAD) + user.visible_message("[defib_ref] buzzes: Patient is not in a valid state. Operation aborted.") + playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) + SEND_SIGNAL(parent, COMSIG_DEFIB_ABORTED, user, target, should_cause_harm) + busy = FALSE + return + + var/health = target.health + target.visible_message("[target]'s body convulses a bit.") + playsound(get_turf(defib_ref), "bodyfall", 50, 1) + playsound(get_turf(defib_ref), 'sound/machines/defib_zap.ogg', 50, 1, -1) + for(var/obj/item/organ/external/O in target.bodyparts) + total_brute += O.brute_dam + total_burn += O.burn_dam + ghost = target.get_ghost(TRUE) // We have to double check whether the dead guy has entered their body during the above + + var/defib_success = TRUE + + // run through some quick failure states after shocking. + + if(tplus > tlimit || (!target.get_int_organ(/obj/item/organ/internal/heart || !target.get_int_organ(/obj/item/organ/internal/brain/slime)))) + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation.") + defib_success = FALSE + else if(total_burn >= 180 || total_brute >= 180) + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Severe tissue damage detected.") + defib_success = FALSE + else if(HAS_TRAIT(target, TRAIT_HUSK)) + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Subject is husked.") + defib_success = FALSE + else if (target.blood_volume < BLOOD_VOLUME_SURVIVE) + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Patient blood volume critically low.") + defib_success = FALSE + else if(ghost) + if(!ghost.can_reenter_corpse) // DNR or AntagHUD + user.visible_message("[defib_ref] buzzes: Resuscitation failed - No electrical brain activity detected.") + else + user.visible_message("[defib_ref] buzzes: Resuscitation failed - Patient's brain is unresponsive. Further attempts may succeed.") + defib_success = FALSE + else if(HAS_TRAIT(target, TRAIT_BADDNA) || (signal_result & COMPONENT_BLOCK_DEFIB)) // these are a bit more arbitrary + user.visible_message("[defib_ref] buzzes: Resuscitation failed.") + defib_success = FALSE + + if(!defib_success) + playsound(get_turf(defib_ref), 'sound/machines/defib_failed.ogg', 50, 0) + else + tobehealed = min(health + threshold, 0) // It's HILARIOUS without this min statqement, let me tell you + tobehealed -= 5 //They get 5 of each type of damage healed so excessive combined damage will not immediately kill them after they get revived + target.adjustOxyLoss(tobehealed) + target.adjustToxLoss(tobehealed) + target.adjustFireLoss(tobehealed) + target.adjustBruteLoss(tobehealed) + if(target.get_int_organ(/obj/item/organ/internal/brain) && target.getBrainLoss() >= 100) + // If you want to treat this with mannitol, it'll have to metabolize while the patient is alive, so it's alright to bring them back up for a minute + playsound(get_turf(defib_ref), 'sound/machines/defib_saftyoff.ogg', 50, 0) + user.visible_message("[defib_ref] chimes: Minimal brain activity detected, brain treatment recommended for full resuscitation.") + else + playsound(get_turf(defib_ref), 'sound/machines/defib_success.ogg', 50, 0) + user.visible_message("[defib_ref] pings: Resuscitation successful.") + target.update_revive() + target.KnockOut() + target.Paralyse(10 SECONDS) + target.emote("gasp") + if(tplus > tloss) + target.setBrainLoss(max(0, min(99, ((tlimit - tplus) / tlimit * 100)))) + + SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100) + if(ishuman(target.pulledby)) // for some reason, pulledby isnt a list despite it being possible to be pulled by multiple people + excess_shock(user, target, target.pulledby, defib_ref) + for(var/obj/item/grab/G in target.grabbed_by) + if(ishuman(G.assailant)) + excess_shock(user, target, G.assailant, defib_ref) + + target.med_hud_set_health() + target.med_hud_set_status() + SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, TRUE) + add_attack_logs(user, target, "Revived with [defib_ref]") + SSblackbox.record_feedback("tally", "players_revived", 1, "defibrillator") + SEND_SIGNAL(parent, COMSIG_DEFIB_SHOCK_APPLIED, user, target, should_cause_harm, defib_success) + set_cooldown() + busy = FALSE + +/** + * Inflict stamina loss (and possibly inflict cardiac arrest) on someone. + * + * Arguments: + * * user - wielder of the defib + * * target - person getting shocked + */ +/datum/component/defib/proc/fibrillate(mob/user, mob/living/carbon/human/target) + if(!istype(target)) + return + busy = TRUE + target.visible_message("[user] has touched [target] with [parent]!", \ + "[user] touches you with [parent], and you feel a strong jolt!") + target.adjustStaminaLoss(60) + target.KnockDown(10 SECONDS) + playsound(get_turf(parent), 'sound/machines/defib_zap.ogg', 50, 1, -1) + target.emote("gasp") + if(combat && prob(heart_attack_chance)) // If the victim is not having a heart attack, and a 10% chance passes, or the defib has heart attack variable to TRUE while being a combat defib, or if another 10% chance passes with combat being TRUE + target.set_heartattack(TRUE) + SEND_SIGNAL(target, COMSIG_LIVING_MINOR_SHOCK, 100) + add_attack_logs(user, target, "Stunned with [parent]") + set_cooldown() + busy = FALSE + return + +/* + * Pass excess shock from a defibrillation into someone else. + * + * Arguments: + * * user - The person using the defib + * * origin - The person the shock was originally applied to, the person being defibrillated + * * affecting - The person the shock is spreading to and negatively affecting. + * * cell_location - item holding the power source. +*/ +/datum/component/defib/proc/excess_shock(mob/user, mob/living/origin, mob/living/carbon/human/affecting, obj/item/cell_location) + if(user == affecting) + return + var/power_source + if(robotic) + power_source = user + else + if(cell_location) + power_source = locate(/obj/item/stock_parts/cell) in cell_location + + if(!power_source) + return + + if(electrocute_mob(affecting, power_source, origin)) // shock anyone touching them >:) + var/obj/item/organ/internal/heart/HE = affecting.get_organ_slot("heart") + if(HE.parent_organ == "chest" && affecting.has_both_hands()) // making sure the shock will go through their heart (drask hearts are in their head), and that they have both arms so the shock can cross their heart inside their chest + affecting.visible_message("[affecting]'s entire body shakes as a shock travels up [affecting.p_their()] arm!", \ + "You feel a powerful shock travel up your [affecting.hand ? affecting.get_organ("l_arm") : affecting.get_organ("r_arm")] and back down your [affecting.hand ? affecting.get_organ("r_arm") : affecting.get_organ("l_arm")]!") + affecting.set_heartattack(TRUE) + + + diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 228a6af3614..bb329df6b24 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -306,7 +306,7 @@ * severity - The severity of the EMP. Either EMP_HEAVY or EMP_LIGHT */ /atom/proc/emp_act(severity) - return + SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) /atom/proc/water_act(volume, temperature, source, method = REAGENT_TOUCH) //amount of water acting : temperature of water in kelvin : object that called it (for shennagins) return TRUE @@ -513,8 +513,8 @@ /atom/proc/welder_act(mob/living/user, obj/item/I) return -/atom/proc/emag_act() - return +/atom/proc/emag_act(mob/user) + SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT, user) /atom/proc/unemag() return diff --git a/code/game/objects/items/weapons/defib.dm b/code/game/objects/items/weapons/defib.dm index 82bc3e192d4..aa44584eae5 100644 --- a/code/game/objects/items/weapons/defib.dm +++ b/code/game/objects/items/weapons/defib.dm @@ -17,25 +17,38 @@ "Vox" = 'icons/mob/clothing/species/vox/back.dmi' ) - var/paddles_on_defib = TRUE //if the paddles are on the defib (TRUE) - var/powered = FALSE //if there's a cell in the defib with enough power for a revive, blocks paddles from reviving otherwise + /// If the paddles are currently attached to the unit. + var/paddles_on_defib = TRUE + /// if there's a cell in the defib with enough power for a revive; blocks paddles from reviving otherwise + var/powered = FALSE + /// Ref to attached paddles var/obj/item/twohanded/shockpaddles/paddles + /// Ref to internal power cell. var/obj/item/stock_parts/cell/high/cell = null - var/safety = TRUE //if this is FALSE you can zap people with the defibs on harm mode - var/combat = FALSE //can we revive through space suits? - var/heart_attack = FALSE //if TRUE it always cause heart attacks if it is a combat defib. + /// If false, using harm intent will let you zap people. Note that any updates to this after init will only impact icons. + var/safety = TRUE + /// If true, this can be used through hardsuits, and can cause heart attacks in harm intent. + var/combat = FALSE + // If safety is false and combat is true, the chance that this will cause a heart attack. + var/heart_attack_probability = 30 + /// If this is vulnerable to EMPs. + var/hardened = FALSE + /// If this can be emagged. + var/emag_proof = FALSE + base_icon_state = "defibpaddles" + /// Type of paddles that should be attached to this defib. var/obj/item/twohanded/shockpaddles/paddle_type = /obj/item/twohanded/shockpaddles /obj/item/defibrillator/get_cell() return cell -/obj/item/defibrillator/Initialize(mapload) //starts without a cell for rnd +/obj/item/defibrillator/Initialize(mapload) // Base version starts without a cell for rnd . = ..() paddles = new paddle_type(src) update_icon(UPDATE_OVERLAYS) -/obj/item/defibrillator/loaded/Initialize(mapload) //starts with hicap +/obj/item/defibrillator/loaded/Initialize(mapload) // Loaded version starts with high-capacity cell. . = ..() cell = new(src) update_icon(UPDATE_OVERLAYS) @@ -113,28 +126,18 @@ update_icon(UPDATE_OVERLAYS) return TRUE -/obj/item/defibrillator/emag_act(user as mob) - if(safety) - safety = FALSE - to_chat(user, "You silently disable [src]'s safety protocols with the card.") - else - safety = TRUE - to_chat(user, "You silently enable [src]'s safety protocols with the card.") - /obj/item/defibrillator/emp_act(severity) if(cell) deductcharge(1000 / severity) - if(safety) - safety = FALSE - visible_message("[src] beeps: Safety protocols disabled!") - playsound(get_turf(src), 'sound/machines/defib_saftyoff.ogg', 50, 0) - else - safety = TRUE - visible_message("[src] beeps: Safety protocols enabled!") - playsound(get_turf(src), 'sound/machines/defib_saftyon.ogg', 50, 0) + safety = !safety update_icon(UPDATE_OVERLAYS) ..() +/obj/item/defibrillator/emag_act(mob/user) + safety = !safety + ..() + update_icon(UPDATE_OVERLAYS) + /obj/item/defibrillator/verb/toggle_paddles() set name = "Toggle Paddles" set category = "Object" @@ -198,19 +201,6 @@ update_icon(UPDATE_OVERLAYS) return FALSE -/obj/item/defibrillator/proc/cooldowncheck(mob/user) - spawn(50) - if(cell) - if(cell.charge >= paddles.revivecost) - user.visible_message("[src] beeps: Unit ready.") - playsound(get_turf(src), 'sound/machines/defib_ready.ogg', 50, 0) - else - user.visible_message("[src] beeps: Charge depleted.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - paddles.cooldown = FALSE - paddles.update_icon(UPDATE_ICON_STATE) - update_icon(UPDATE_OVERLAYS) - /obj/item/defibrillator/compact name = "compact defibrillator" desc = "A belt-mounted defibrillator that can be rapidly deployed." @@ -238,7 +228,7 @@ paddle_type = /obj/item/twohanded/shockpaddles/syndicate combat = TRUE safety = FALSE - heart_attack = TRUE + heart_attack_probability = 100 /obj/item/defibrillator/compact/combat/loaded/Initialize(mapload) . = ..() @@ -253,7 +243,9 @@ paddle_type = /obj/item/twohanded/shockpaddles/advanced combat = TRUE safety = TRUE + hardened = TRUE // emp-proof (on the component), but not emag-proof. resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //Objective item, better not have it destroyed. + heart_attack_probability = 10 var/next_emp_message //to prevent spam from the emagging message on the advanced defibrillator @@ -274,13 +266,6 @@ playsound(src, 'sound/machines/defib_saftyon.ogg', 50) next_emp_message = world.time + 5 SECONDS -/obj/item/defibrillator/compact/advanced/attackby(obj/item/W, mob/user, params) - if(W == paddles) - paddles.unwield() - toggle_paddles() - update_icon(UPDATE_OVERLAYS) - return - //paddles /obj/item/twohanded/shockpaddles @@ -294,26 +279,67 @@ w_class = WEIGHT_CLASS_BULKY resistance_flags = INDESTRUCTIBLE toolspeed = 1 - - var/revivecost = 1000 - var/cooldown = FALSE - var/busy = FALSE base_icon_state = "defibpaddles" + /// Amount of power used on a shock. + var/revivecost = 1000 + /// Active defib this is connected to. var/obj/item/defibrillator/defib + /// Whether or not the paddles are on cooldown. Used for tracking icon states. + var/on_cooldown = FALSE /obj/item/twohanded/shockpaddles/New(mainunit) - ..() + . = ..() if(check_defib_exists(mainunit, src)) defib = mainunit loc = defib - busy = FALSE update_icon(UPDATE_ICON_STATE) + AddComponent(/datum/component/defib, actual_unit = defib, combat = defib.combat, safe_by_default = defib.safety, heart_attack_chance = defib.heart_attack_probability, emp_proof = defib.hardened, emag_proof = defib.emag_proof) + else + AddComponent(/datum/component/defib) + RegisterSignal(src, COMSIG_DEFIB_READY, PROC_REF(on_cooldown_expire)) + RegisterSignal(src, COMSIG_DEFIB_SHOCK_APPLIED, PROC_REF(after_shock)) + RegisterSignal(src, COMSIG_DEFIB_PADDLES_APPLIED, PROC_REF(on_application)) + +/obj/item/twohanded/shockpaddles/Destroy() + defib = null + return ..() + +/// Check to see if we should abort this before we've even gotten started +/obj/item/twohanded/shockpaddles/proc/on_application(obj/item/paddles, mob/living/user, mob/living/carbon/human/target, should_cause_harm) + SIGNAL_HANDLER // COMSIG_DEFIB_PADDLES_APPLIED + + if(!wielded) + to_chat(user, "You need to wield the paddles in both hands before you can use them on someone!") + return COMPONENT_BLOCK_DEFIB_MISC + + if(!defib.powered) + return COMPONENT_BLOCK_DEFIB_DEAD + return +/obj/item/twohanded/shockpaddles/proc/on_cooldown_expire(obj/item/paddles) + SIGNAL_HANDLER // COMSIG_DEFIB_READY + on_cooldown = FALSE + if(defib.cell) + if(defib.cell.charge >= revivecost) + defib.visible_message("[defib] beeps: Unit ready.") + playsound(get_turf(src), 'sound/machines/defib_ready.ogg', 50, 0) + else + defib.visible_message("[defib] beeps: Charge depleted.") + playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) + update_icon(UPDATE_ICON_STATE) + defib.update_icon(UPDATE_ICON_STATE) + +/obj/item/twohanded/shockpaddles/proc/after_shock() + SIGNAL_HANDLER // COMSIG_DEFIB_SHOCK_APPLIED + on_cooldown = TRUE + defib.deductcharge(revivecost) + update_icon(UPDATE_ICON_STATE) + /obj/item/twohanded/shockpaddles/update_icon_state() icon_state = "[base_icon_state][wielded]" item_state = "[base_icon_state][wielded]" - if(cooldown) + if(on_cooldown) icon_state = "[base_icon_state][wielded]_cooldown" /obj/item/twohanded/shockpaddles/suicide_act(mob/user) @@ -349,194 +375,6 @@ else return TRUE -/obj/item/twohanded/shockpaddles/attack(mob/M, mob/user) - var/tobehealed - var/threshold = -HEALTH_THRESHOLD_DEAD - var/mob/living/carbon/human/H = M - - if(busy) - return - if(!defib.powered) - user.visible_message("[defib] beeps: Unit is unpowered.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - return - if(!wielded) - to_chat(user, "You need to wield the paddles in both hands before you can use them on someone!") - return - if(cooldown) - to_chat(user, "[defib] is recharging.") - return - if(!ishuman(M)) - to_chat(user, "The instructions on [defib] don't mention how to defibrillate that...") - return - else - if(user.a_intent == INTENT_HARM && !defib.safety) - busy = TRUE - H.visible_message("[user] has touched [H.name] with [src]!", \ - "[user] has touched [H.name] with [src]!") - H.adjustStaminaLoss(60) - H.KnockDown(10 SECONDS) - playsound(get_turf(src), 'sound/machines/defib_zap.ogg', 50, 1, -1) - H.emote("gasp") - if((defib.combat && defib.heart_attack) || prob(10) && (defib.combat)) // If the victim is not having a heart attack, and a 10% chance passes, or the defib has heart attack variable to TRUE while being a combat defib, or if another 10% chance passes with combat being TRUE - H.set_heartattack(TRUE) - SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK, 100) - add_attack_logs(user, M, "Stunned with [src]") - defib.deductcharge(revivecost) - cooldown = TRUE - busy = FALSE - update_icon(UPDATE_ICON_STATE) - defib.cooldowncheck(user) - return - user.visible_message("[user] begins to place [src] on [M.name]'s chest.", "You begin to place [src] on [M.name]'s chest.") - busy = TRUE - update_icon(UPDATE_ICON_STATE) - var/mob/dead/observer/ghost = H.get_ghost(TRUE) - if(ghost && ghost.can_reenter_corpse) - to_chat(ghost, "Your heart is being defibrillated. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)") - window_flash(ghost.client) - notify_ghosts() - ghost << sound('sound/effects/genetics.ogg') - if(do_after(user, 30 * toolspeed, target = M)) //beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process - user.visible_message("[user] places [src] on [M.name]'s chest.", "You place [src] on [M.name]'s chest.") - playsound(get_turf(src), 'sound/machines/defib_charge.ogg', 50, 0) - - if(ghost && !ghost.client && !QDELETED(ghost)) - QDEL_NULL(ghost) - var/tplus = world.time - H.timeofdeath - var/tlimit = DEFIB_TIME_LIMIT - var/tloss = DEFIB_TIME_LOSS - var/total_burn = 0 - var/total_brute = 0 - - var/signal_result = SEND_SIGNAL(M, COMSIG_LIVING_PRE_DEFIB, user, defib, ghost) - - if(do_after(user, 20 * toolspeed, target = M)) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total - signal_result |= SEND_SIGNAL(M, COMSIG_LIVING_DEFIBBED, user, defib, ghost) - for(var/obj/item/carried_item in H.contents) - if(istype(carried_item, /obj/item/clothing/suit/space)) - if(!defib.combat) - user.visible_message("[defib] buzzes: Patient's chest is obscured. Operation aborted.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - busy = FALSE - update_icon(UPDATE_ICON_STATE) - return - if(signal_result & COMPONENT_DEFIB_OVERRIDE) - // let our signal handle it - return - if(H.undergoing_cardiac_arrest()) - if(!H.get_int_organ(/obj/item/organ/internal/heart) && !H.get_int_organ(/obj/item/organ/internal/brain/slime)) //prevents defibing someone still alive suffering from a heart attack attack if they lack a heart - user.visible_message("[defib] buzzes: Resuscitation failed - Failed to pick up any heart electrical activity.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - busy = FALSE - update_icon(UPDATE_ICON_STATE) - return - else - var/obj/item/organ/internal/heart/heart = H.get_int_organ(/obj/item/organ/internal/heart) - if(heart.status & ORGAN_DEAD) - user.visible_message("[defib] buzzes: Resuscitation failed - Heart necrosis detected.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - busy = FALSE - update_icon(UPDATE_ICON_STATE) - return - H.set_heartattack(FALSE) - SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK, 100) - user.visible_message("[defib] pings: Cardiac arrhythmia corrected.") - M.visible_message("[M]'s body convulses a bit.") - playsound(get_turf(src), 'sound/machines/defib_zap.ogg', 50, 1, -1) - playsound(get_turf(src), "bodyfall", 50, 1) - playsound(get_turf(src), 'sound/machines/defib_success.ogg', 50, 0) - defib.deductcharge(revivecost) - busy = FALSE - cooldown = TRUE - update_icon(UPDATE_ICON_STATE) - defib.cooldowncheck(user) - return - if(H.stat == DEAD) - var/health = H.health - M.visible_message("[M]'s body convulses a bit.") - playsound(get_turf(src), "bodyfall", 50, 1) - playsound(get_turf(src), 'sound/machines/defib_zap.ogg', 50, 1, -1) - for(var/obj/item/organ/external/O in H.bodyparts) - total_brute += O.brute_dam - total_burn += O.burn_dam - ghost = H.get_ghost(TRUE) // We have to double check whether the dead guy has entered their body during the above - if(total_burn <= 180 && total_brute <= 180 && !H.suiciding && !ghost && tplus < tlimit && !HAS_TRAIT(H, TRAIT_HUSK) && !HAS_TRAIT(H, TRAIT_BADDNA) && H.blood_volume > BLOOD_VOLUME_SURVIVE && (H.get_int_organ(/obj/item/organ/internal/heart) || H.get_int_organ(/obj/item/organ/internal/brain/slime)) && !(signal_result & COMPONENT_BLOCK_DEFIB)) - tobehealed = min(health + threshold, 0) // It's HILARIOUS without this min statement, let me tell you - tobehealed -= 5 //They get 5 of each type of damage healed so excessive combined damage will not immediately kill them after they get revived - H.adjustOxyLoss(tobehealed) - H.adjustToxLoss(tobehealed) - H.adjustFireLoss(tobehealed) - H.adjustBruteLoss(tobehealed) - if(H.get_int_organ(/obj/item/organ/internal/brain) && H.getBrainLoss() >= 100) - // If you want to treat this with mannitol, it'll have to metabolize while the patient is alive, so it's alright to bring them back up for a minute - playsound(get_turf(src), 'sound/machines/defib_saftyoff.ogg', 50, 0) - user.visible_message("[defib] chimes: Minimal brain activity detected, brain treatment recommended for full resuscitation.") - else - playsound(get_turf(src), 'sound/machines/defib_success.ogg', 50, 0) - user.visible_message("[defib] pings: Resuscitation successful.") - H.update_revive() - H.KnockOut() - H.Paralyse(10 SECONDS) - H.emote("gasp") - if(tplus > tloss) - H.setBrainLoss( max(0, min(99, ((tlimit - tplus) / tlimit * 100)))) - - SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK, 100) - if(ishuman(H.pulledby)) // for some reason, pulledby isnt a list despite it being possible to be pulled by multiple people - excess_shock(user, H, H.pulledby) - for(var/obj/item/grab/G in H.grabbed_by) - if(ishuman(G.assailant)) - excess_shock(user, H, G.assailant) - - H.med_hud_set_health() - H.med_hud_set_status() - defib.deductcharge(revivecost) - add_attack_logs(user, M, "Revived with [src]") - SSblackbox.record_feedback("tally", "players_revived", 1, "defibrillator") - else - if(tplus > tlimit|| !H.get_int_organ(/obj/item/organ/internal/heart)) - user.visible_message("[defib] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation.") - else if(total_burn >= 180 || total_brute >= 180) - user.visible_message("[defib] buzzes: Resuscitation failed - Severe tissue damage detected.") - else if(HAS_TRAIT(H, TRAIT_HUSK)) - user.visible_message("[defib] buzzes: Resuscitation failed: Subject is husked.") - else if (H.blood_volume < BLOOD_VOLUME_SURVIVE) - user.visible_message("[defib] buzzes: Resuscitation failed: Patient blood volume critically low.") - else if(ghost) - if(!ghost.can_reenter_corpse) // DNR or AntagHUD - user.visible_message("[defib] buzzes: Resuscitation failed: No electrical brain activity detected.") - else - user.visible_message("[defib] buzzes: Resuscitation failed: Patient's brain is unresponsive. Further attempts may succeed.") - else - user.visible_message("[defib] buzzes: Resuscitation failed.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - defib.deductcharge(revivecost) - cooldown = TRUE - update_icon(UPDATE_ICON_STATE) - defib.cooldowncheck(user) - else - user.visible_message("[defib] buzzes: Patient is not in a valid state. Operation aborted.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - busy = FALSE - update_icon(UPDATE_ICON_STATE) - -/* - * user = the person using the defib - * origin = person being revived - * affecting = person being shocked with excess energy from the defib -*/ -/obj/item/twohanded/shockpaddles/proc/excess_shock(mob/user, mob/living/carbon/human/origin, mob/living/carbon/human/affecting) - if(user == affecting) - return - - if(electrocute_mob(affecting, defib.cell, origin)) // shock anyone touching them >:) - var/obj/item/organ/internal/heart/HE = affecting.get_organ_slot("heart") - if(HE.parent_organ == "chest" && affecting.has_both_hands()) // making sure the shock will go through their heart (drask hearts are in their head), and that they have both arms so the shock can cross their heart inside their chest - affecting.visible_message("[affecting]'s entire body shakes as a shock travels up their arm!", \ - "You feel a powerful shock travel up your [affecting.hand ? affecting.get_organ("l_arm") : affecting.get_organ("r_arm")] and back down your [affecting.hand ? affecting.get_organ("r_arm") : affecting.get_organ("l_arm")]!") - affecting.set_heartattack(TRUE) - /obj/item/borg_defib name = "defibrillator paddles" desc = "A pair of paddles with flat metal surfaces that are used to deliver powerful electric shocks." @@ -546,125 +384,29 @@ force = 0 w_class = WEIGHT_CLASS_BULKY var/revivecost = 1000 - var/cooldown = FALSE - var/busy = FALSE var/safety = TRUE flags = NODROP toolspeed = 1 -/obj/item/borg_defib/attack(mob/M, mob/user) - var/tobehealed - var/threshold = -HEALTH_THRESHOLD_DEAD - var/mob/living/carbon/human/H = M +/obj/item/borg_defib/Initialize(mapload) + . = ..() + AddComponent(/datum/component/defib, robotic = TRUE, safe_by_default = safety, emp_proof = TRUE) - if(busy) - return - if(cooldown) - to_chat(user, "[src] is recharging.") - if(!ishuman(M)) - to_chat(user, "This unit is only designed to work on humanoid lifeforms.") - return - else - if(user.a_intent == INTENT_HARM && !safety) - busy = TRUE - H.visible_message("[user] has touched [H.name] with [src]!", \ - "[user] has touched [H.name] with [src]!") - H.adjustStaminaLoss(50) - H.Weaken(10 SECONDS) - if(!H.undergoing_cardiac_arrest() && prob(10)) // Your heart explodes. - H.set_heartattack(TRUE) - SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK, 100) - playsound(get_turf(src), 'sound/machines/defib_zap.ogg', 50, 1, -1) - H.emote("gasp") - add_attack_logs(user, M, "Stunned with [src]") - if(isrobot(user)) - var/mob/living/silicon/robot/R = user - R.cell.use(revivecost) - cooldown = TRUE - busy = FALSE - update_icon(UPDATE_ICON_STATE) - spawn(50) - cooldown = FALSE - update_icon(UPDATE_ICON_STATE) - return - user.visible_message("[user] begins to place [src] on [M.name]'s chest.", "You begin to place [src] on [M.name]'s chest.") - busy = TRUE - update_icon() - if(do_after(user, 30 * toolspeed, target = M)) //beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process - user.visible_message("[user] places [src] on [M.name]'s chest.", "You place [src] on [M.name]'s chest.") - playsound(get_turf(src), 'sound/machines/defib_charge.ogg', 50, 0) - var/mob/dead/observer/ghost = H.get_ghost() - if(ghost && !ghost.client) - // In case the ghost's not getting deleted for some reason - H.key = ghost.key - stack_trace("Ghost of name [ghost.name] is bound to [H.real_name], but lacks a client. Deleting ghost.") + RegisterSignal(src, COMSIG_DEFIB_READY, PROC_REF(on_cooldown_expire)) + RegisterSignal(src, COMSIG_DEFIB_SHOCK_APPLIED, PROC_REF(after_shock)) - QDEL_NULL(ghost) - var/tplus = world.time - H.timeofdeath - var/tlimit = 3000 //past this much time the patient is unrecoverable (in deciseconds) - var/tloss = 600 //brain damage starts setting in on the patient after some time left rotting - var/total_burn = 0 - var/total_brute = 0 +/obj/item/borg_defib/proc/after_shock(obj/item/defib, mob/user) + SIGNAL_HANDLER // COMSIG_DEFIB_SHOCK_APPLIED + if(isrobot(user)) + var/mob/living/silicon/robot/R = user + R.cell.use(revivecost) + update_icon(UPDATE_ICON_STATE) - var/signal_result = SEND_SIGNAL(M, COMSIG_LIVING_PRE_DEFIB, user, src, ghost) - if(do_after(user, 20 * toolspeed, target = M)) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total - signal_result |= SEND_SIGNAL(M, COMSIG_LIVING_DEFIBBED, user, src, ghost) - if(H.stat == DEAD) - var/health = H.health - M.visible_message("[M]'s body convulses a bit.") - playsound(get_turf(src), "bodyfall", 50, 1) - playsound(get_turf(src), 'sound/machines/defib_zap.ogg', 50, 1, -1) - for(var/obj/item/organ/external/O in H.bodyparts) - total_brute += O.brute_dam - total_burn += O.burn_dam - if(total_burn <= 180 && total_brute <= 180 && !H.suiciding && !ghost && tplus < tlimit && !HAS_TRAIT(H, TRAIT_HUSK) && !(signal_result & COMPONENT_BLOCK_DEFIB)) - tobehealed = min(health + threshold, 0) // It's HILARIOUS without this min statement, let me tell you - tobehealed -= 5 //They get 5 of each type of damage healed so excessive combined damage will not immediately kill them after they get revived - H.adjustOxyLoss(tobehealed) - H.adjustToxLoss(tobehealed) - H.adjustFireLoss(tobehealed) - H.adjustBruteLoss(tobehealed) - user.visible_message("[user] pings: Resuscitation successful.") - playsound(get_turf(src), 'sound/machines/defib_success.ogg', 50, 0) - H.update_revive(FALSE) - H.KnockOut(FALSE) - H.Paralyse(10 SECONDS) - H.emote("gasp") - if(tplus > tloss) - H.setBrainLoss( max(0, min(99, ((tlimit - tplus) / tlimit * 100)))) - SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK, 100) - SEND_SIGNAL(H, COMSIG_LIVING_DEFIBBED, user, src) - if(isrobot(user)) - var/mob/living/silicon/robot/R = user - R.cell.use(revivecost) - add_attack_logs(user, M, "Revived with [src]") - SSblackbox.record_feedback("tally", "players_revived", 1, "defibrillator") - else - if(tplus > tlimit) - user.visible_message("[user] buzzes: Resuscitation failed - Heart tissue damage beyond point of no return for defibrillation.") - else if(total_burn >= 180 || total_brute >= 180) - user.visible_message("[user] buzzes: Resuscitation failed - Severe tissue damage detected.") - else if(ghost) - user.visible_message("[user] buzzes: Resuscitation failed: Patient's brain is unresponsive. Further attempts may succeed.") - to_chat(ghost, "Your heart is being defibrillated. Return to your body if you want to be revived! (Verbs -> Ghost -> Re-enter corpse)") - window_flash(ghost.client) - ghost << sound('sound/effects/genetics.ogg') - else - user.visible_message("[user] buzzes: Resuscitation failed.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - if(isrobot(user)) - var/mob/living/silicon/robot/R = user - R.cell.use(revivecost) - cooldown = TRUE - update_icon(UPDATE_ICON_STATE) - spawn(50) - cooldown = FALSE - update_icon(UPDATE_ICON_STATE) - else - user.visible_message("[user] buzzes: Patient is not in a valid state. Operation aborted.") - playsound(get_turf(src), 'sound/machines/defib_failed.ogg', 50, 0) - busy = FALSE - update_icon(UPDATE_ICON_STATE) +/obj/item/borg_defib/proc/on_cooldown_expire(obj/item/defib) + SIGNAL_HANDLER // COMSIG_DEFIB_READY + visible_message("[src] beeps: Defibrillation unit ready.") + playsound(get_turf(src), 'sound/machines/defib_ready.ogg', 50, 0) + update_icon(UPDATE_ICON_STATE) /obj/item/twohanded/shockpaddles/syndicate name = "combat defibrillator paddles" diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 1d7be7c8539..47f8047b850 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1020,7 +1020,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( SetLockdown(0) if(module) - module.emag_act() + module.emag_act(user) module.module_type = "Malf" // For the cool factor update_module_icon() module.rebuild_modules() // This will add the emagged items to the borgs inventory. diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index d4a6dd3b755..f5054e8ca2f 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -331,15 +331,15 @@ special_rechargables = list(/obj/item/reagent_containers/spray/cyborg_facid, /obj/item/extinguisher/mini) // Disable safeties on the borg's defib. -/obj/item/robot_module/medical/emag_act() +/obj/item/robot_module/medical/emag_act(mob/user) . = ..() for(var/obj/item/borg_defib/F in modules) - F.safety = FALSE + F.emag_act() // Enable safeties on the borg's defib. /obj/item/robot_module/medical/unemag() for(var/obj/item/borg_defib/F in modules) - F.safety = TRUE + F.emag_act() return ..() // Fluorosulphuric acid spray bottle. diff --git a/paradise.dme b/paradise.dme index c1f4c4223f5..003ecbc84d6 100644 --- a/paradise.dme +++ b/paradise.dme @@ -354,6 +354,7 @@ #include "code\datums\components\_component.dm" #include "code\datums\components\caltrop.dm" #include "code\datums\components\decal.dm" +#include "code\datums\components\defibrillator.dm" #include "code\datums\components\ducttape.dm" #include "code\datums\components\edit_complainer.dm" #include "code\datums\components\emissive_blocker.dm"