[MIRROR] medical/security modsuit updates and modules [MDB IGNORE] (#15953)

* medical/security modsuit updates and modules (#69194)

tweaks the colors on the security modsuit to be lighter, i think i did that on the original pr but it got reverted due to merge conflicts
makes the standard quick carry module have nitrile level carrying instead of latex level
environmental bodybags are now always pressurized
adds medical and security hardlight bags, which are environmental bags you can use to move people through space, security ones can be sinched to prevent escape
adds a medical version of the prisoner capture module, for patient transport, prisoner capture and patient transport now use the medical/security holobags, medical one deploying them faster. when you move too far out of range the holobag dissipates
adds a defibrillator module, extends shock paddles
adds a thread ripper module, this module temporarily rips away the matter of clothing, to allow stuff like injections, surgery, defibrillation etc through it
adds a surgery processor module, essentially a portable surgery computer like borgs have
fixes a bug where you can unwield defibrillator paddles just after starting the do_after to defib onehanded
fixes a bug where the modsuit gps would have a broken name when renamed

* medical/security modsuit updates and modules

Co-authored-by: Fikou <23585223+Fikou@users.noreply.github.com>
This commit is contained in:
SkyratBot
2022-09-04 13:57:06 +02:00
committed by GitHub
parent ef32d46d2b
commit 77c20e53bd
22 changed files with 430 additions and 190 deletions
@@ -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)
+6 -6
View File
@@ -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!.
+2 -2
View File
@@ -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)
+24 -17
View File
@@ -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.
@@ -661,6 +665,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'
@@ -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)
+1 -2
View File
@@ -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
+1 -2
View File
@@ -323,8 +323,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
@@ -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
@@ -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
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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.
+144 -4
View File
@@ -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"
+67 -52
View File
@@ -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))
+3 -3
View File
@@ -311,7 +311,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"
@@ -478,8 +478,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.
@@ -1360,6 +1360,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
@@ -1391,17 +1440,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
@@ -1653,9 +1653,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"
@@ -1670,6 +1683,7 @@
"mod_holster",
"mod_sonar",
"mod_projectile_dampener",
"mod_criminalcapture",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+5 -8
View File
@@ -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)
+35 -17
View File
@@ -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"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB