diff --git a/code/__DEFINES/dcs/signals/signals_medical.dm b/code/__DEFINES/dcs/signals/signals_medical.dm index 762203ec736..801eb6b41bb 100644 --- a/code/__DEFINES/dcs/signals/signals_medical.dm +++ b/code/__DEFINES/dcs/signals/signals_medical.dm @@ -4,3 +4,11 @@ /// From /datum/surgery_step/success(): (datum/surgery_step/step, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results) #define COMSIG_MOB_SURGERY_STEP_SUCCESS "mob_surgery_step_success" +/// From /obj/item/shockpaddles/proc/do_success(): (obj/item/shockpaddles/source) +#define COMSIG_DEFIBRILLATOR_SUCCESS "defib_success" + #define COMPONENT_DEFIB_STOP (1<<0) + +/// From /datum/surgery/can_start(): (mob/source, datum/surgery/surgery, mob/living/patient) +#define COMSIG_SURGERY_STARTING "surgery_starting" + #define COMPONENT_CANCEL_SURGERY (1<<0) + #define COMPONENT_FORCE_SURGERY (1<<1) diff --git a/code/__HELPERS/type2type.dm b/code/__HELPERS/type2type.dm index 1a8c9a6886a..e9df60b699e 100644 --- a/code/__HELPERS/type2type.dm +++ b/code/__HELPERS/type2type.dm @@ -156,17 +156,17 @@ GLOBAL_LIST_INIT(modulo_angle_to_dir, list(NORTH,NORTHEAST,EAST,SOUTHEAST,SOUTH, /proc/body_zone2cover_flags(def_zone) switch(def_zone) if(BODY_ZONE_CHEST) - return list(CHEST, GROIN) + return CHEST|GROIN if(BODY_ZONE_HEAD) - return list(HEAD) + return HEAD if(BODY_ZONE_L_ARM) - return list(ARM_LEFT, HAND_LEFT) + return ARM_LEFT|HAND_LEFT if(BODY_ZONE_R_ARM) - return list(ARM_RIGHT, HAND_RIGHT) + return ARM_RIGHT|HAND_RIGHT if(BODY_ZONE_L_LEG) - return list(LEG_LEFT, FOOT_LEFT) + return LEG_LEFT|FOOT_LEFT if(BODY_ZONE_R_LEG) - return list(LEG_RIGHT, FOOT_RIGHT) + return LEG_RIGHT|FOOT_RIGHT //Turns a Body_parts_covered bitfield into a list of organ/limb names. //(I challenge you to find a use for this) -I found a use for it!! | So did I!. diff --git a/code/datums/components/gps.dm b/code/datums/components/gps.dm index 7164bf28072..18cc08ccd97 100644 --- a/code/datums/components/gps.dm +++ b/code/datums/components/gps.dm @@ -167,8 +167,8 @@ GLOBAL_LIST_EMPTY(GPS_list) gpstag = a . = TRUE - usr.log_message("renamed [parentasatom] to \"global positioning system ([gpstag])\".", LOG_GAME) - parentasatom.name = "global positioning system ([gpstag])" + usr.log_message("renamed [parentasatom] to \"[initial(parentasatom.name)] ([gpstag])\".", LOG_GAME) + parentasatom.name = "[initial(parentasatom.name)] ([gpstag])" if("power") toggletracking(usr) diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index 71745ea1e56..4a702294d4b 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -44,13 +44,12 @@ . = ..() paddles = new paddle_type(src) update_power() - return + RegisterSignal(paddles, COMSIG_DEFIBRILLATOR_SUCCESS, .proc/on_defib_success) /obj/item/defibrillator/loaded/Initialize(mapload) //starts with hicap . = ..() cell = new(src) update_power() - return /obj/item/defibrillator/examine(mob/user) . = ..() @@ -259,6 +258,12 @@ paddles.update_appearance() update_power() +/obj/item/defibrillator/proc/on_defib_success(obj/item/shockpaddles/source) + deductcharge(source.revivecost) + source.cooldown = TRUE + cooldowncheck() + return COMPONENT_DEFIB_STOP + /obj/item/defibrillator/compact name = "compact defibrillator" desc = "A belt-equipped defibrillator that can be rapidly deployed." @@ -377,14 +382,16 @@ visible_message(span_notice("[src] snap back into [defib].")) snap_back() -/obj/item/shockpaddles/proc/recharge(time) - if(req_defib || !time) +/obj/item/shockpaddles/proc/recharge(time = 0) + if(req_defib) return cooldown = TRUE update_appearance() - sleep(time) - var/turf/T = get_turf(src) - T.audible_message(span_notice("[src] beeps: Unit is recharged.")) + addtimer(CALLBACK(src, .proc/finish_recharge), time) + +/obj/item/shockpaddles/proc/finish_recharge() + var/turf/current_turf = get_turf(src) + current_turf.audible_message(span_notice("[src] beeps: Unit is recharged.")) playsound(src, 'sound/machines/defib_ready.ogg', 50, FALSE) cooldown = FALSE update_appearance() @@ -484,12 +491,9 @@ busy = FALSE update_appearance() - if(req_defib) - defib.deductcharge(revivecost) - cooldown = TRUE - defib.cooldowncheck() - else - recharge(recharge_time) + if(SEND_SIGNAL(src, COMSIG_DEFIBRILLATOR_SUCCESS) & COMPONENT_DEFIB_STOP) + return + recharge(recharge_time) /// Called whenever the paddles fail to shock something after a do_x proc /obj/item/shockpaddles/proc/do_cancel() @@ -528,7 +532,7 @@ span_warning("You overcharge the paddles and begin to place them onto [H]'s chest...")) busy = TRUE update_appearance() - if(do_after(user, 1.5 SECONDS, H)) + if(do_after(user, 1.5 SECONDS, H, extra_checks = CALLBACK(src, .proc/is_wielded))) user.visible_message(span_notice("[user] places [src] on [H]'s chest."), span_warning("You place [src] on [H]'s chest and begin to charge them.")) var/turf/T = get_turf(defib) @@ -537,7 +541,7 @@ T.audible_message(span_warning("\The [defib] lets out an urgent beep and lets out a steadily rising hum...")) else user.audible_message(span_warning("[src] let out an urgent beep.")) - if(do_after(user, 1.5 SECONDS, H)) //Takes longer due to overcharging + if(do_after(user, 1.5 SECONDS, H, extra_checks = CALLBACK(src, .proc/is_wielded))) //Takes longer due to overcharging if(!H) do_cancel() return @@ -568,11 +572,11 @@ user.visible_message(span_warning("[user] begins to place [src] on [H]'s chest."), span_warning("You begin to place [src] on [H]'s chest...")) busy = TRUE update_appearance() - if(do_after(user, 3 SECONDS, H)) //beginning to place the paddles on patient's chest to allow some time for people to move away to stop the process + if(do_after(user, 3 SECONDS, H, extra_checks = CALLBACK(src, .proc/is_wielded))) //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(span_notice("[user] places [src] on [H]'s chest."), span_warning("You place [src] on [H]'s chest.")) playsound(src, 'sound/machines/defib_charge.ogg', 75, FALSE) var/obj/item/organ/internal/heart = H.getorgan(/obj/item/organ/internal/heart) - if(do_after(user, 2 SECONDS, H)) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total + if(do_after(user, 2 SECONDS, H, extra_checks = CALLBACK(src, .proc/is_wielded))) //placed on chest and short delay to shock for dramatic effect, revive time is 5sec total if((!combat && !req_defib) || (req_defib && !defib.combat)) for(var/obj/item/clothing/C in H.get_equipped_items()) if((C.body_parts_covered & CHEST) && (C.clothing_flags & THICKMATERIAL)) //check to see if something is obscuring their chest. @@ -656,6 +660,9 @@ playsound(src, 'sound/machines/defib_failed.ogg', 50, FALSE) do_cancel() +/obj/item/shockpaddles/proc/is_wielded() + return HAS_TRAIT(src, TRAIT_WIELDED) + /obj/item/shockpaddles/cyborg name = "cyborg defibrillator paddles" icon = 'icons/obj/medical/defib.dmi' diff --git a/code/game/objects/structures/crates_lockers/closets/bodybag.dm b/code/game/objects/structures/crates_lockers/closets/bodybag.dm index ea47acb1ac5..cacc7dbc5a9 100644 --- a/code/game/objects/structures/crates_lockers/closets/bodybag.dm +++ b/code/game/objects/structures/crates_lockers/closets/bodybag.dm @@ -70,7 +70,7 @@ if(tagged) . += "bodybag_label" -/obj/structure/closet/body_bag/close() +/obj/structure/closet/body_bag/close(mob/living/user) . = ..() if(.) set_density(FALSE) @@ -182,12 +182,47 @@ contents_pressure_protection = 0.8 contents_thermal_insulation = 0.5 foldedbag_path = /obj/item/bodybag/environmental + /// The list of weathers we protect from. var/list/weather_protection = list(TRAIT_ASHSTORM_IMMUNE, TRAIT_RADSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE, TRAIT_VOIDSTORM_IMMUNE) // Does not protect against lava or the The Floor Is Lava spell. + /// The contents of the gas to be distributed to an occupant. Set in Initialize() + var/datum/gas_mixture/air_contents = null /obj/structure/closet/body_bag/environmental/Initialize(mapload) . = ..() for(var/trait in weather_protection) ADD_TRAIT(src, trait, ROUNDSTART_TRAIT) + refresh_air() + +/obj/structure/closet/body_bag/environmental/Destroy() + if(air_contents) + QDEL_NULL(air_contents) + return ..() + +/obj/structure/closet/body_bag/environmental/return_air() + refresh_air() + return air_contents + +/obj/structure/closet/body_bag/environmental/remove_air(amount) + refresh_air() + return air_contents.remove(amount) + +/obj/structure/closet/body_bag/environmental/return_analyzable_air() + refresh_air() + return air_contents + +/obj/structure/closet/body_bag/environmental/togglelock(mob/living/user, silent) + . = ..() + for(var/mob/living/target in contents) + to_chat(target, span_warning("You hear a faint hiss, and a white mist fills your vision...")) + +/obj/structure/closet/body_bag/environmental/proc/refresh_air() + air_contents = null + air_contents = new(50) //liters + air_contents.temperature = T20C + + air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen) + air_contents.gases[/datum/gas/oxygen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD + air_contents.gases[/datum/gas/nitrogen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD /obj/structure/closet/body_bag/environmental/nanotrasen name = "elite environmental protection bag" @@ -227,19 +262,12 @@ else icon_state = initial(icon_state) -/obj/structure/closet/body_bag/environmental/prisoner/can_open(mob/living/user, force = FALSE) - if(force) - return TRUE - if(sinched) - to_chat(user, span_danger("The buckles on [src] are sinched down, preventing it from opening.")) - return FALSE - . = ..() - /obj/structure/closet/body_bag/environmental/prisoner/open(mob/living/user, force = FALSE) - if(!can_open(user, force)) - return + if(sinched && !force) + to_chat(user, span_danger("The buckles on [src] are sinched down, preventing it from opening.")) + return TRUE if(opened) - return + return FALSE sinched = FALSE playsound(loc, open_sound, open_sound_volume, TRUE, -3) opened = TRUE @@ -316,55 +344,7 @@ user.log_message("[sinched ? "sinched":"unsinched"] secure environmental bag [src]", LOG_GAME) update_appearance() -/obj/structure/closet/body_bag/environmental/prisoner/pressurized - name = "pressurized prisoner transport bag" - foldedbag_path = /obj/item/bodybag/environmental/prisoner/pressurized - /// The contents of the gas to be distributed to an occupant once sinched down. Set in Initialize() - var/datum/gas_mixture/air_contents = null - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/Initialize(mapload) - . = ..() - refresh_air() - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/proc/refresh_air() - air_contents = null - air_contents = new(50) //liters - air_contents.temperature = T20C - - air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen) - air_contents.gases[/datum/gas/oxygen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD - air_contents.gases[/datum/gas/nitrogen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/Destroy() - if(air_contents) - QDEL_NULL(air_contents) - return ..() - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/return_air() - if(sinched) - refresh_air() - return air_contents - return ..() - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/remove_air(amount) - if(sinched) - refresh_air() - return air_contents.remove(amount) // The internals for this bag are bottomless. - return ..(amount) - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/return_analyzable_air() - if(sinched) - refresh_air() - return air_contents - return ..() - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/togglelock(mob/living/user, silent) - . = ..() - if(sinched) - for(var/mob/living/target in contents) - to_chat(target, span_warning("You hear a faint hiss, and a white mist fills your vision...")) - -/obj/structure/closet/body_bag/environmental/prisoner/pressurized/syndicate +/obj/structure/closet/body_bag/environmental/prisoner/syndicate name = "syndicate prisoner transport bag" desc = "An alteration of Nanotrasen's environmental protection bag which has been used in several high-profile kidnappings. Designed to keep a victim unconscious, alive, and secured during transport." icon = 'icons/obj/bodybag.dmi' @@ -384,3 +364,27 @@ air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrous_oxide) air_contents.gases[/datum/gas/oxygen][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD air_contents.gases[/datum/gas/nitrous_oxide][MOLES] = (ONE_ATMOSPHERE*50)/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD + +/obj/structure/closet/body_bag/environmental/hardlight + name = "hardlight bodybag" + desc = "A hardlight bag for storing bodies. Resistant to space." + icon_state = "holobag_med" + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + foldedbag_path = null + weather_protection = list(TRAIT_VOIDSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE) + +/obj/structure/closet/body_bag/environmental/hardlight/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type in list(BRUTE, BURN)) + playsound(src, 'sound/weapons/egloves.ogg', 80, TRUE) + +/obj/structure/closet/body_bag/environmental/prisoner/hardlight + name = "hardlight prisoner bodybag" + desc = "A hardlight bag for storing bodies. Resistant to space, can be sinched to prevent escape." + icon_state = "holobag_sec" + resistance_flags = LAVA_PROOF | FIRE_PROOF | ACID_PROOF + foldedbag_path = null + weather_protection = list(TRAIT_VOIDSTORM_IMMUNE, TRAIT_SNOWSTORM_IMMUNE) + +/obj/structure/closet/body_bag/environmental/prisoner/hardlight/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + if(damage_type in list(BRUTE, BURN)) + playsound(src, 'sound/weapons/egloves.ogg', 80, TRUE) diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 5cf98e72910..4ef4664c0ec 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -206,8 +206,7 @@ RegisterSignal(C, COMSIG_MOVABLE_MOVED, .proc/bristle, override = TRUE) zones_disabled++ - for(var/i in body_zone2cover_flags(def_zone)) - body_parts_covered &= ~i + body_parts_covered &= ~body_zone2cover_flags(def_zone) if(body_parts_covered == NONE) // if there are no more parts to break then the whole thing is kaput atom_destruction((damage_type == BRUTE ? MELEE : LASER)) // melee/laser is good enough since this only procs from direct attacks anyway and not from fire/bombs diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index 6d467cace78..ab707347272 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -321,8 +321,7 @@ return adjusted for(var/zone in list(BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) // ugly check to make sure we don't reenable protection on a disabled part if(damage_by_parts[zone] > limb_integrity) - for(var/part in body_zone2cover_flags(zone)) - body_parts_covered &= part + body_parts_covered &= body_zone2cover_flags(zone) return adjusted /obj/item/clothing/under/rank diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index d8c45589d71..b177dc956b9 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -85,6 +85,8 @@ suit_store = /obj/item/tank/internals/oxygen back = /obj/item/mod/control/pre_equipped/rescue suit = null + head = null + uniform = /obj/item/clothing/under/rank/medical/chief_medical_officer mask = /obj/item/clothing/mask/breath/medical r_pocket = /obj/item/flashlight/pen/paramedic internals_slot = ITEM_SLOT_SUITSTORE diff --git a/code/modules/jobs/job_types/medical_doctor.dm b/code/modules/jobs/job_types/medical_doctor.dm index cc3993f6d27..b15f7d1aaf6 100644 --- a/code/modules/jobs/job_types/medical_doctor.dm +++ b/code/modules/jobs/job_types/medical_doctor.dm @@ -67,6 +67,8 @@ suit_store = /obj/item/tank/internals/oxygen back = /obj/item/mod/control/pre_equipped/medical suit = null + head = null + uniform = /obj/item/clothing/under/rank/medical/doctor mask = /obj/item/clothing/mask/breath/medical r_pocket = /obj/item/flashlight/pen internals_slot = ITEM_SLOT_SUITSTORE diff --git a/code/modules/mod/modules/module_kinesis.dm b/code/modules/mod/modules/module_kinesis.dm index e9373fbd3c2..75f734d9f36 100644 --- a/code/modules/mod/modules/module_kinesis.dm +++ b/code/modules/mod/modules/module_kinesis.dm @@ -9,7 +9,7 @@ icon_state = "kinesis" module_type = MODULE_ACTIVE complexity = 3 - use_power_cost = DEFAULT_CHARGE_DRAIN*3 + use_power_cost = DEFAULT_CHARGE_DRAIN * 3 incompatible_modules = list(/obj/item/mod/module/anomaly_locked/kinesis) cooldown_time = 0.5 SECONDS overlay_state_inactive = "module_kinesis" diff --git a/code/modules/mod/modules/modules_antag.dm b/code/modules/mod/modules/modules_antag.dm index c064e8b9ad3..4fcee017449 100644 --- a/code/modules/mod/modules/modules_antag.dm +++ b/code/modules/mod/modules/modules_antag.dm @@ -297,7 +297,7 @@ icon_state = "power_kick" module_type = MODULE_ACTIVE removable = FALSE - use_power_cost = DEFAULT_CHARGE_DRAIN*5 + use_power_cost = DEFAULT_CHARGE_DRAIN * 5 incompatible_modules = list(/obj/item/mod/module/power_kick) cooldown_time = 5 SECONDS /// Damage on kick. diff --git a/code/modules/mod/modules/modules_medical.dm b/code/modules/mod/modules/modules_medical.dm index 82194baad76..2f3d0ec9260 100644 --- a/code/modules/mod/modules/modules_medical.dm +++ b/code/modules/mod/modules/modules_medical.dm @@ -71,10 +71,10 @@ incompatible_modules = list(/obj/item/mod/module/quick_carry, /obj/item/mod/module/constructor) /obj/item/mod/module/quick_carry/on_suit_activation() - ADD_TRAIT(mod.wearer, TRAIT_QUICK_CARRY, MOD_TRAIT) + ADD_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT) /obj/item/mod/module/quick_carry/on_suit_deactivation(deleting = FALSE) - REMOVE_TRAIT(mod.wearer, TRAIT_QUICK_CARRY, MOD_TRAIT) + REMOVE_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT) /obj/item/mod/module/quick_carry/advanced name = "MOD advanced quick carry module" @@ -82,11 +82,11 @@ complexity = 0 /obj/item/mod/module/quick_carry/on_suit_activation() - ADD_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT) + . = ..() ADD_TRAIT(mod.wearer, TRAIT_FASTMED, MOD_TRAIT) /obj/item/mod/module/quick_carry/on_suit_deactivation(deleting = FALSE) - REMOVE_TRAIT(mod.wearer, TRAIT_QUICKER_CARRY, MOD_TRAIT) + . = ..() REMOVE_TRAIT(mod.wearer, TRAIT_FASTMED, MOD_TRAIT) ///Injector - Gives the suit an extendable large-capacity piercing syringe. @@ -212,3 +212,143 @@ else organ.forceMove(drop_location()) organ = null + +///Patrient Transport - Generates hardlight bags you can put people in. +/obj/item/mod/module/criminalcapture/patienttransport + name = "MOD patient transport module" + desc = "A module built into the forearm of the suit. Countless waves of mostly-lost mining teams being sent to \ + Indecipheries and other hazardous locations have taught the DeForest Medical Company many lessons. \ + Physical bodybags are difficult to store, hard to deploy, and even worse to keep intact in tough scenarios. \ + Enter the hardlight transport bag. Summonable with merely a gesture, weightless, and immunized against \ + any extreme scenario the wearer could think of, this bag is perfectly designed for \ + transport of any body in any environment, any time." + icon_state = "patient_transport" + bodybag_type = /obj/structure/closet/body_bag/environmental/hardlight + capture_time = 1.5 SECONDS + packup_time = 0.5 SECONDS + +///Defibrillator - Gives the suit an extendable pair of shock paddles. +/obj/item/mod/module/defibrillator + name = "MOD defibrillator module" + desc = "A module built into the gauntlets of the suit; commonly known as the 'Healing Hands' by medical professionals. \ + The user places their palms above the patient. Onboard computers in the suit calculate the necessary voltage, \ + and a modded targeting computer determines the best position for the user to push. \ + Twenty five pounds of force are applied to the patient's skin. Shocks travel from the suit's gloves \ + and counter-shock the heart, and the wearer returns to Medical a hero. Don't you even think about using it as a weapon; \ + regulations on manufacture and software locks expressly forbid it." + icon_state = "defibrillator" + module_type = MODULE_ACTIVE + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN * 25 + device = /obj/item/shockpaddles/mod + incompatible_modules = list(/obj/item/mod/module/defibrillator) + cooldown_time = 0.5 SECONDS + var/defib_cooldown = 5 SECONDS + +/obj/item/mod/module/defibrillator/Initialize(mapload) + . = ..() + RegisterSignal(device, COMSIG_DEFIBRILLATOR_SUCCESS, .proc/on_defib_success) + +/obj/item/mod/module/defibrillator/proc/on_defib_success(obj/item/shockpaddles/source) + drain_power(use_power_cost) + source.recharge(defib_cooldown) + return COMPONENT_DEFIB_STOP + +/obj/item/shockpaddles/mod + name = "MOD defibrillator paddles" + req_defib = FALSE + +///Thread Ripper - Temporarily rips apart clothing to make it not cover the body. +/obj/item/mod/module/thread_ripper + name = "MOD thread ripper module" + desc = "A custom-built module integrated with the suit's wrist. The thread ripper is built from \ + recent technology dating back to the start of 2562, after an attempt by a well-known Nanotrasen researcher to \ + expand on the rapid-tailoring technology found in Autodrobes. Rather than being capable of creating \ + any fabric pattern under the suns, the thread ripper is capable of rapid disassembly of them. \ + Anything from kevlar-weave, to leather, to durathread can be quickly pulled open to the wearer's specification \ + and sewn back together, a development commonly utilized by Medical workers to obtain easy access for \ + surgery, defibrillation, or injection of chemicals to ease patients into not worrying about their \ + brand-name fashion being marred." + icon_state = "thread_ripper" + module_type = MODULE_ACTIVE + complexity = 2 + use_power_cost = DEFAULT_CHARGE_DRAIN + incompatible_modules = list(/obj/item/mod/module/thread_ripper) + cooldown_time = 1.5 SECONDS + overlay_state_inactive = "module_threadripper" + /// An associated list of ripped clothing and the body part covering slots they covered before + var/list/ripped_clothing = list() + +/obj/item/mod/module/thread_ripper/on_select_use(atom/target) + . = ..() + if(!.) + return + if(!mod.wearer.Adjacent(target) || !iscarbon(target) || target == mod.wearer) + balloon_alert(mod.wearer, "invalid target!") + return + var/mob/living/carbon/carbon_target = target + if(length(ripped_clothing)) + balloon_alert(mod.wearer, "already ripped!") + return + balloon_alert(mod.wearer, "ripping clothing...") + playsound(src, 'sound/items/zip.ogg', 25, TRUE, frequency = -1) + if(!do_after(mod.wearer, 1.5 SECONDS, target = carbon_target)) + balloon_alert(mod.wearer, "interrupted!") + return + var/target_zones = body_zone2cover_flags(mod.wearer.zone_selected) + for(var/obj/item/clothing as anything in carbon_target.get_all_worn_items()) + if(!clothing) + continue + var/shared_flags = target_zones & clothing.body_parts_covered + if(shared_flags) + ripped_clothing[clothing] = shared_flags + clothing.body_parts_covered &= ~shared_flags + +/obj/item/mod/module/thread_ripper/on_process(delta_time) + . = ..() + if(!.) + return + if(!length(ripped_clothing)) + return + var/zipped = FALSE + for(var/obj/item/clothing as anything in ripped_clothing) + if(QDELETED(clothing)) + ripped_clothing -= clothing + continue + var/mob/living/carbon/clothing_wearer = clothing.loc + if(istype(clothing_wearer) && mod.wearer.Adjacent(clothing_wearer) && !clothing_wearer.is_holding(clothing)) + continue + zipped = TRUE + clothing.body_parts_covered |= ripped_clothing[clothing] + ripped_clothing -= clothing + if(zipped) + playsound(src, 'sound/items/zip.ogg', 25, TRUE) + balloon_alert(mod.wearer, "clothing mended") + +/obj/item/mod/module/thread_ripper/on_suit_deactivation(deleting) + if(!length(ripped_clothing)) + return + for(var/obj/item/clothing as anything in ripped_clothing) + if(QDELETED(clothing)) + ripped_clothing -= clothing + continue + clothing.body_parts_covered |= ripped_clothing[clothing] + ripped_clothing = list() + if(!deleting) + playsound(src, 'sound/items/zip.ogg', 25, TRUE) + +///Surgical Processor - Lets you do advanced surgeries portably. +/obj/item/mod/module/surgical_processor + name = "MOD surgical processor module" + desc = "A module using an onboard surgical computer which can be connected to other computers to download and \ + perform advanced surgeries on the go." + icon_state = "surgical_processor" + module_type = MODULE_ACTIVE + complexity = 2 + active_power_cost = DEFAULT_CHARGE_DRAIN + device = /obj/item/surgical_processor/mod + incompatible_modules = list(/obj/item/mod/module/surgical_processor) + cooldown_time = 0.5 SECONDS + +/obj/item/surgical_processor/mod + name = "MOD surgical processor" diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index 4f987c1b18d..f411f8579b0 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -176,34 +176,40 @@ speech_args[SPEECH_SPANS] |= voicespan drain_power(use_power_cost) -///Criminal Capture - Lets you put people in transport bags. +///Criminal Capture - Generates hardlight bags you can put people in and sinch. /obj/item/mod/module/criminalcapture name = "MOD criminal capture module" desc = "The private security that had orders to take in people dead were quite \ happy with their space-proofed suit, but for those who wanted to bring back \ whomever their targets were still breathing needed a way to \"share\" the \ - space-proofing. And thus: criminal capture! Creates a prisoner transport bag \ - around the apprehended that has breathable atmos and even stabilizes critical \ - conditions." - icon_state = "criminalcapture" + space-proofing. And thus: criminal capture! Creates a hardlight prisoner transport bag \ + around the apprehended that has breathable atmospheric conditions." + icon_state = "criminal_capture" module_type = MODULE_ACTIVE complexity = 2 use_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 incompatible_modules = list(/obj/item/mod/module/criminalcapture) cooldown_time = 0.5 SECONDS - /// Max bag capacity. - var/max_capacity = 3 /// Time to capture a prisoner. - var/capture_time = 1 SECONDS - /// Time to pack a bodybag up. - var/packup_time = 0.5 SECONDS - /// List of our capture bags. - var/list/criminal_capture_bags = list() + var/capture_time = 2.5 SECONDS + /// Time to dematerialize a bodybag. + var/packup_time = 1 SECONDS + /// Typepath of our bodybag + var/bodybag_type = /obj/structure/closet/body_bag/environmental/prisoner/hardlight + /// Our linked bodybag. + var/obj/structure/closet/body_bag/linked_bodybag -/obj/item/mod/module/criminalcapture/Initialize(mapload) +/obj/item/mod/module/criminalcapture/on_process(delta_time) + idle_power_cost = linked_bodybag ? (DEFAULT_CHARGE_DRAIN * 3) : 0 + return ..() + +/obj/item/mod/module/criminalcapture/on_deactivation(display_message, deleting) . = ..() - for(var/i in 1 to max_capacity) - criminal_capture_bags += new /obj/structure/closet/body_bag/environmental/prisoner/pressurized(src) + if(!.) + return + if(!linked_bodybag) + return + packup() /obj/item/mod/module/criminalcapture/on_select_use(atom/target) . = ..() @@ -211,42 +217,51 @@ return if(!mod.wearer.Adjacent(target)) return - if(isliving(target)) - var/mob/living/living_target = target - var/turf/target_turf = get_turf(living_target) - playsound(src, 'sound/items/zip.ogg', 25, TRUE) - if(!do_after(mod.wearer, capture_time, target = living_target)) + if(target == linked_bodybag) + playsound(src, 'sound/machines/ding.ogg', 25, TRUE) + if(!do_after(mod.wearer, packup_time, target = target)) balloon_alert(mod.wearer, "interrupted!") - return - var/obj/structure/closet/body_bag/environmental/prisoner/dropped_bag = pop(criminal_capture_bags) - dropped_bag.forceMove(target_turf) - dropped_bag.close() - living_target.forceMove(dropped_bag) - else if(istype(target, /obj/structure/closet/body_bag/environmental/prisoner) || istype(target, /obj/item/bodybag/environmental/prisoner)) - var/obj/item/bodybag/environmental/prisoner/bag = target - if(criminal_capture_bags.len >= max_capacity) - balloon_alert(mod.wearer, "bag limit reached!") - return - playsound(src, 'sound/items/zip.ogg', 25, TRUE) - if(!do_after(mod.wearer, packup_time, target = bag)) - balloon_alert(mod.wearer, "interrupted!") - return - if(criminal_capture_bags.len >= max_capacity) - balloon_alert(mod.wearer, "bag limit reached!") - return - if(locate(/mob/living) in bag) - balloon_alert(mod.wearer, "living creatures inside!") - return - if(istype(bag, /obj/item/bodybag/environmental/prisoner)) - bag = bag.deploy_bodybag(mod.wearer, get_turf(bag)) - var/obj/structure/closet/body_bag/environmental/prisoner/structure_bag = bag - if(!structure_bag.opened) - structure_bag.open(mod.wearer, force = TRUE) - bag.forceMove(src) - criminal_capture_bags += bag - balloon_alert(mod.wearer, "bag stored") - else - balloon_alert(mod.wearer, "invalid target!") + packup() + return + if(linked_bodybag) + return + var/turf/target_turf = get_turf(target) + if(target_turf.is_blocked_turf(exclude_mobs = TRUE)) + return + playsound(src, 'sound/machines/ding.ogg', 25, TRUE) + if(!do_after(mod.wearer, capture_time, target = target)) + balloon_alert(mod.wearer, "interrupted!") + return + if(linked_bodybag) + return + linked_bodybag = new bodybag_type(target_turf) + linked_bodybag.take_contents() + playsound(linked_bodybag, 'sound/weapons/egloves.ogg', 80, TRUE) + RegisterSignal(linked_bodybag, COMSIG_MOVABLE_MOVED, .proc/check_range) + RegisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, .proc/check_range) + +/obj/item/mod/module/criminalcapture/proc/packup() + if(!linked_bodybag) + return + playsound(linked_bodybag, 'sound/weapons/egloves.ogg', 80, TRUE) + apply_wibbly_filters(linked_bodybag) + animate(linked_bodybag, 0.5 SECONDS, alpha = 50, flags = ANIMATION_PARALLEL) + addtimer(CALLBACK(src, .proc/delete_bag, linked_bodybag), 0.5 SECONDS) + linked_bodybag = null + +/obj/item/mod/module/criminalcapture/proc/check_range() + SIGNAL_HANDLER + + if(get_dist(mod.wearer, linked_bodybag) <= 9) + return + packup() + +/obj/item/mod/module/criminalcapture/proc/delete_bag(obj/structure/closet/body_bag/bag) + if(mod?.wearer) + UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED, .proc/check_range) + balloon_alert(mod.wearer, "bag dissipated") + bag.open(force = TRUE) + qdel(bag) ///Mirage grenade dispenser - Dispenses grenades that copy the user's appearance. /obj/item/mod/module/dispenser/mirage @@ -342,7 +357,7 @@ /obj/item/mod/module/active_sonar name = "MOD active sonar" desc = "Ancient tech from the 20th century, this module uses sonic waves to detect living creatures within the user's radius. \ - Its loud ping is much harder to hide in an indoor station than in the outdoor operations it was designed for." + Its loud ping is much harder to hide in an indoor station than in the outdoor operations it was designed for." icon_state = "active_sonar" module_type = MODULE_USABLE use_power_cost = DEFAULT_CHARGE_DRAIN * 5 @@ -356,7 +371,7 @@ return balloon_alert(mod.wearer, "readying sonar...") playsound(mod.wearer, 'sound/mecha/skyfall_power_up.ogg', vol = 20, vary = TRUE, extrarange = SHORT_RANGE_SOUND_EXTRARANGE) - if(!do_after(mod.wearer, 1.1 SECONDS)) + if(!do_after(mod.wearer, 1.1 SECONDS, target = mod)) return var/creatures_detected = 0 for(var/mob/living/creature in range(9, mod.wearer)) diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index 1e87c527208..d26489035be 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -305,7 +305,7 @@ icon_state = "magnet_loader" module_type = MODULE_ACTIVE removable = FALSE - use_power_cost = DEFAULT_CHARGE_DRAIN*3 + use_power_cost = DEFAULT_CHARGE_DRAIN * 3 incompatible_modules = list(/obj/item/mod/module/magnet) cooldown_time = 1.5 SECONDS overlay_state_active = "module_magnet" @@ -472,8 +472,8 @@ icon_state = "sphere" module_type = MODULE_ACTIVE removable = FALSE - active_power_cost = DEFAULT_CHARGE_DRAIN*0.5 - use_power_cost = DEFAULT_CHARGE_DRAIN*3 + active_power_cost = DEFAULT_CHARGE_DRAIN * 0.5 + use_power_cost = DEFAULT_CHARGE_DRAIN * 3 incompatible_modules = list(/obj/item/mod/module/sphere_transform) cooldown_time = 1.25 SECONDS /// Time it takes us to complete the animation. diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm index 3e27754a4fe..68142aec257 100644 --- a/code/modules/research/designs/mechfabricator_designs.dm +++ b/code/modules/research/designs/mechfabricator_designs.dm @@ -1357,6 +1357,55 @@ build_path = /obj/item/mod/module/holster department_type = MODULE_SECURITY +/datum/design/module/mod_sonar + name = "MOD Module: Active Sonar" + id = "mod_sonar" + materials = list(/datum/material/titanium = 250, /datum/material/glass = 1000, /datum/material/gold = 500, /datum/material/uranium = 250) + build_path = /obj/item/mod/module/active_sonar + department_type = MODULE_SECURITY + +/datum/design/module/projectile_dampener + name = "MOD Module: Projectile Dampener" + id = "mod_projectile_dampener" + materials = list(/datum/material/iron = 1000, /datum/material/bluespace = 500) + build_path = /obj/item/mod/module/projectile_dampener + department_type = MODULE_SECURITY + +/datum/design/module/surgicalprocessor + name = "MOD Module: Surgical Processor" + id = "mod_surgicalprocessor" + materials = list(/datum/material/titanium = 250, /datum/material/glass = 1000, /datum/material/silver = 1500) + build_path = /obj/item/mod/module/surgical_processor + department_type = MODULE_MEDICAL + +/datum/design/module/threadripper + name = "MOD Module: Thread Ripper" + id = "mod_threadripper" + materials = list(/datum/material/titanium = 250, /datum/material/plastic = 1000, /datum/material/silver = 1500) + build_path = /obj/item/mod/module/thread_ripper + department_type = MODULE_MEDICAL + +/datum/design/module/defibrillator + name = "MOD Module: Defibrillator" + id = "mod_defib" + materials = list(/datum/material/titanium = 250, /datum/material/diamond = 1000, /datum/material/silver = 1500) + build_path = /obj/item/mod/module/defibrillator + department_type = MODULE_MEDICAL + +/datum/design/module/patienttransport + name = "MOD Module: Patient Transport" + id = "mod_patienttransport" + materials = list(/datum/material/iron = 1000, /datum/material/bluespace = 1000) + build_path = /obj/item/mod/module/criminalcapture/patienttransport + department_type = MODULE_MEDICAL + +/datum/design/module/criminalcapture + name = "MOD Module: Criminal Capture" + id = "mod_criminalcapture" + materials = list(/datum/material/iron = 1000, /datum/material/bluespace = 1000) + build_path = /obj/item/mod/module/criminalcapture + department_type = MODULE_SECURITY + //MODsuit bepis modules /datum/design/module/disposal @@ -1388,17 +1437,3 @@ materials = list(/datum/material/iron = 2500, /datum/material/glass = 2000, /datum/material/uranium = 1000, /datum/material/bluespace = 1000) build_path = /obj/item/mod/module/anomaly_locked/kinesis department_type = MODULE_ENGINEERING - -/datum/design/module/mod_sonar - name = "MOD Module: Active Sonar" - id = "mod_sonar" - materials = list(/datum/material/titanium = 250, /datum/material/glass = 1000, /datum/material/gold = 500, /datum/material/uranium = 250) - build_path = /obj/item/mod/module/active_sonar - department_type = MODULE_SECURITY - -/datum/design/module/projectile_dampener - name = "MOD Module: Projectile Dampener" - id = "mod_projectile_dampener" - materials = list(/datum/material/iron = 1000, /datum/material/bluespace = 500) - build_path = /obj/item/mod/module/projectile_dampener - department_type = MODULE_SECURITY diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 0a3b52c2f13..2775043cea3 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -1562,9 +1562,22 @@ "mod_injector", "mod_organ_thrower", "mod_dna_lock", + "mod_patienttransport", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/mod_advanced_medical + id = "mod_advanced_medical" + display_name = "Advanced Medical Modular Suits" + description = "Advanced medical suits for quicker rescue purposes." + prereq_ids = list("mod_medical", "adv_biotech") + design_ids = list( + "mod_defib", + "mod_threadripper", + "mod_surgicalprocessor", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 3500) + /datum/techweb_node/mod_security id = "mod_security" display_name = "Security Modular Suits" @@ -1579,6 +1592,7 @@ "mod_holster", "mod_sonar", "mod_projectile_dampener", + "mod_criminalcapture", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) diff --git a/code/modules/surgery/surgery.dm b/code/modules/surgery/surgery.dm index a3828d06182..afdf4268555 100644 --- a/code/modules/surgery/surgery.dm +++ b/code/modules/surgery/surgery.dm @@ -70,14 +70,11 @@ if(requires_tech) . = FALSE - if(iscyborg(user)) - var/mob/living/silicon/robot/robo_surgeon = user - var/obj/item/surgical_processor/surgical_processor = locate() in robo_surgeon.model.modules - if(surgical_processor) //no early return for !surgical_processor since we want to check optable should this not exist. - if(replaced_by in surgical_processor.advanced_surgeries) - return FALSE - if(type in surgical_processor.advanced_surgeries) - return TRUE + var/surgery_signal = SEND_SIGNAL(user, COMSIG_SURGERY_STARTING, src, patient) + if(surgery_signal & COMPONENT_FORCE_SURGERY) + return TRUE + if(surgery_signal & COMPONENT_CANCEL_SURGERY) + return FALSE var/turf/patient_turf = get_turf(patient) diff --git a/code/modules/surgery/tools.dm b/code/modules/surgery/tools.dm index 5d2ff19dbfd..2f94bcfdcee 100644 --- a/code/modules/surgery/tools.dm +++ b/code/modules/surgery/tools.dm @@ -245,30 +245,48 @@ /obj/item/surgical_processor //allows medical cyborgs to scan and initiate advanced surgeries - name = "\improper Surgical Processor" + name = "surgical processor" desc = "A device for scanning and initiating surgeries from a disk or operating computer." icon = 'icons/obj/device.dmi' icon_state = "spectrometer" item_flags = NOBLUDGEON - var/list/advanced_surgeries = list() + var/list/loaded_surgeries = list() -/obj/item/surgical_processor/afterattack(obj/item/design_holder, mob/user, proximity) +/obj/item/surgical_processor/equipped(mob/user, slot, initial) . = ..() - if(!proximity) + if(slot != ITEM_SLOT_HANDS) + UnregisterSignal(user, COMSIG_SURGERY_STARTING) return - if(istype(design_holder, /obj/item/disk/surgery)) - to_chat(user, span_notice("You load the surgery protocol from [design_holder] into [src].")) - var/obj/item/disk/surgery/surgery_disk = design_holder - if(do_after(user, 10, target = design_holder)) - advanced_surgeries |= surgery_disk.surgeries - return TRUE - if(istype(design_holder, /obj/machinery/computer/operating)) - to_chat(user, span_notice("You copy surgery protocols from [design_holder] into [src].")) - var/obj/machinery/computer/operating/OC = design_holder - if(do_after(user, 10, target = design_holder)) - advanced_surgeries |= OC.advanced_surgeries - return TRUE - return + RegisterSignal(user, COMSIG_SURGERY_STARTING, .proc/check_surgery) + +/obj/item/surgical_processor/dropped(mob/user, silent) + . = ..() + UnregisterSignal(user, COMSIG_SURGERY_STARTING) + +/obj/item/surgical_processor/afterattack(atom/design_holder, mob/user, proximity) + if(!proximity) + return ..() + if(!istype(design_holder, /obj/item/disk/surgery) && !istype(design_holder, /obj/machinery/computer/operating)) + return ..() + balloon_alert(user, "copying designs...") + playsound(src, 'sound/machines/terminal_processing.ogg', 25, TRUE) + if(do_after(user, 1 SECONDS, target = design_holder)) + if(istype(design_holder, /obj/item/disk/surgery)) + var/obj/item/disk/surgery/surgery_disk = design_holder + loaded_surgeries |= surgery_disk.surgeries + else + var/obj/machinery/computer/operating/surgery_computer = design_holder + loaded_surgeries |= surgery_computer.advanced_surgeries + playsound(src, 'sound/machines/terminal_success.ogg', 25, TRUE) + return TRUE + +/obj/item/surgical_processor/proc/check_surgery(mob/user, datum/surgery/surgery, mob/patient) + SIGNAL_HANDLER + + if(surgery.replaced_by in loaded_surgeries) + return COMPONENT_CANCEL_SURGERY + if(surgery.type in loaded_surgeries) + return COMPONENT_FORCE_SURGERY /obj/item/scalpel/advanced name = "laser scalpel" diff --git a/icons/mob/clothing/modsuit/mod_clothing.dmi b/icons/mob/clothing/modsuit/mod_clothing.dmi index 39db9061b06..cb9e8603b76 100644 Binary files a/icons/mob/clothing/modsuit/mod_clothing.dmi and b/icons/mob/clothing/modsuit/mod_clothing.dmi differ diff --git a/icons/mob/clothing/modsuit/mod_modules.dmi b/icons/mob/clothing/modsuit/mod_modules.dmi index c61251221af..b195aea49a1 100644 Binary files a/icons/mob/clothing/modsuit/mod_modules.dmi and b/icons/mob/clothing/modsuit/mod_modules.dmi differ diff --git a/icons/obj/bodybag.dmi b/icons/obj/bodybag.dmi index f3af89a5af8..7b7200efc10 100644 Binary files a/icons/obj/bodybag.dmi and b/icons/obj/bodybag.dmi differ diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi index 69affa3fa49..39189b1805a 100644 Binary files a/icons/obj/clothing/modsuit/mod_modules.dmi and b/icons/obj/clothing/modsuit/mod_modules.dmi differ