diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index afcc9686cff5..6643cd6b805c 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -1,4 +1,8 @@
#define HEAT_CAPACITY_HUMAN 100 //249840 J/K, for a 72 kg person.
+#define DEATH_STAGE_NONE 0
+#define DEATH_STAGE_EARLY 1
+#define DEATH_STAGE_WARNING 2
+#define DEATH_STAGE_CRITICAL 3
/obj/structure/machinery/cryo_cell
name = "cryo cell"
@@ -19,6 +23,7 @@
var/mob/living/carbon/occupant = null
var/obj/item/reagent_container/glass/beaker = null
+ var/occupant_death_stage = DEATH_STAGE_NONE
/obj/structure/machinery/cryo_cell/Initialize()
. = ..()
@@ -28,19 +33,18 @@
QDEL_NULL(beaker)
. = ..()
-
/obj/structure/machinery/cryo_cell/process()
if(!on)
updateUsrDialog()
return
if(occupant)
- if(occupant.stat != DEAD)
- process_occupant()
- else
+ var/mob/living/carbon/human/human_occupant = occupant
+ if(occupant.stat == DEAD && (!istype(human_occupant) || human_occupant.undefibbable))
go_out(TRUE, TRUE) //Whether auto-eject is on or not, we don't permit literal deadbeats to hang around.
- playsound(src.loc, 'sound/machines/ping.ogg', 25, 1)
- visible_message("[icon2html(src, viewers(src))] [SPAN_WARNING("\The [src] pings: Patient is dead!")]")
+ display_message("Patient is dead!", warning = TRUE)
+ else
+ process_occupant()
updateUsrDialog()
return TRUE
@@ -116,13 +120,14 @@
data["beakerContents"] = beakerContents
return data
-/obj/structure/machinery/cryo_cell/ui_act(action, list/params)
+/obj/structure/machinery/cryo_cell/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("power")
on = !on
+ update_use_power(on ? USE_POWER_ACTIVE : USE_POWER_IDLE)
update_icon()
. = TRUE
if("eject")
@@ -143,7 +148,8 @@
if("notice")
release_notice = !release_notice
. = TRUE
- updateUsrDialog()
+
+ updateUsrDialog(ui.user)
/obj/structure/machinery/cryo_cell/attackby(obj/item/W, mob/living/user)
if(istype(W, /obj/item/reagent_container/glass))
@@ -158,34 +164,59 @@
beaker = W
var/reagentnames = ""
- for(var/datum/reagent/R in beaker.reagents.reagent_list)
- reagentnames += ";[R.name]"
+ for(var/datum/reagent/cur_reagent in beaker.reagents.reagent_list)
+ reagentnames += ";[cur_reagent.name]"
- msg_admin_niche("[key_name(user)] put \a [beaker] into \the [src], containing [reagentnames] at ([src.loc.x],[src.loc.y],[src.loc.z]) [ADMIN_JMP(src.loc)].", 1)
+ msg_admin_niche("[key_name(user)] put \a [beaker] into [src], containing [reagentnames] at ([src.loc.x],[src.loc.y],[src.loc.z]) [ADMIN_JMP(src.loc)].", 1)
if(user.drop_inv_item_to_loc(W, src))
- user.visible_message("[user] adds \a [W] to \the [src]!", "You add \a [W] to \the [src]!")
+ user.visible_message("[user] adds \a [W] to [src]!", "You add \a [W] to [src]!")
else if(istype(W, /obj/item/grab))
- if(isxeno(user)) return
- var/obj/item/grab/G = W
- if(!ismob(G.grabbed_thing))
+ if(isxeno(user))
+ return
+ var/obj/item/grab/grabber = W
+ if(!ismob(grabber.grabbed_thing))
return
- var/mob/M = G.grabbed_thing
- put_mob(M)
+ var/mob/grabbed_mob = grabber.grabbed_thing
+ put_mob(grabbed_mob)
- updateUsrDialog()
+ updateUsrDialog(user)
+/obj/structure/machinery/cryo_cell/power_change(area/master_area)
+ . = ..()
+ if((occupant || on) && operable())
+ update_use_power(USE_POWER_ACTIVE)
+ update_icon()
/obj/structure/machinery/cryo_cell/update_icon()
icon_state = initial(icon_state)
- icon_state = "[icon_state]-[on ? "on" : "off"]-[occupant ? "occupied" : "empty"]"
+ var/is_on = on && operable()
+ icon_state = "[icon_state]-[is_on ? "on" : "off"]-[occupant ? "occupied" : "empty"]"
/obj/structure/machinery/cryo_cell/proc/process_occupant()
- if(occupant)
- if(occupant.stat == DEAD)
- return
- occupant.bodytemperature += 2*(temperature - occupant.bodytemperature)
- occupant.bodytemperature = max(occupant.bodytemperature, temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise
+ if(!occupant)
+ return
+ if(!operable())
+ return
+
+ occupant.bodytemperature += 2*(temperature - occupant.bodytemperature)
+ occupant.bodytemperature = max(occupant.bodytemperature, temperature) // this is so ugly i'm sorry for doing it i'll fix it later i promise
+
+ // Warnings if dead
+ if(occupant.stat == DEAD && ishuman(occupant))
+ var/mob/living/carbon/human/human_occupant = occupant
+ var/old_state = occupant_death_stage
+ if(world.time > occupant.timeofdeath + human_occupant.revive_grace_period - 1 MINUTES)
+ occupant_death_stage = DEATH_STAGE_CRITICAL
+ else if(world.time > occupant.timeofdeath + human_occupant.revive_grace_period - 2.5 MINUTES)
+ occupant_death_stage = DEATH_STAGE_WARNING
+ else
+ occupant_death_stage = DEATH_STAGE_EARLY
+ if(old_state != occupant_death_stage)
+ display_message("Patient is critical!", warning = TRUE)
+
+ // Passive healing if alive and cold enough
+ if(occupant.stat != DEAD)
occupant.recalculate_move_delay = TRUE
occupant.set_stat(UNCONSCIOUS)
if(occupant.bodytemperature < T0C)
@@ -202,22 +233,39 @@
var/heal_brute = occupant.getBruteLoss() ? min(1, 20/occupant.getBruteLoss()) : 0
var/heal_fire = occupant.getFireLoss() ? min(1, 20/occupant.getFireLoss()) : 0
occupant.heal_limb_damage(heal_brute,heal_fire)
- var/has_cryo = occupant.reagents.get_reagent_amount("cryoxadone") >= 1
- var/has_clonexa = occupant.reagents.get_reagent_amount("clonexadone") >= 1
- var/has_cryo_medicine = has_cryo || has_clonexa
- if(beaker && !has_cryo_medicine)
- beaker.reagents.trans_to(occupant, 1, 10)
+
+ // Chemical healing if cryo meds are involved
+ if(beaker && occupant.reagents && beaker.reagents)
+ var/occupant_has_cryo_meds = occupant.reagents.get_reagent_amount("cryoxadone") >= 1 || occupant.reagents.get_reagent_amount("clonexadone") >= 1
+ var/beaker_has_cryo_meds = beaker.reagents.get_reagent_amount("cryoxadone") >= 1 || beaker.reagents.get_reagent_amount("clonexadone") >= 1
+
+ // To administer, either the occupant has cryo meds and the beaker doesn't or vice versa (not both)
+ var/can_administer = (occupant_has_cryo_meds ^ beaker_has_cryo_meds) && length(beaker.reagents.reagent_list)
+ if(can_administer && occupant_has_cryo_meds)
+ // If its the case of the occupant has cryo meds and not the beaker, we need to pace out the dosage
+ // So lets make sure they don't already have some of the beaker drugs
+ for(var/datum/reagent/cur_beaker_reagent in beaker.reagents.reagent_list)
+ for(var/datum/reagent/cur_occupant_reagent in occupant.reagents.reagent_list)
+ if(cur_beaker_reagent.id == cur_occupant_reagent.id)
+ can_administer = FALSE
+ break
+
+ if(can_administer)
+ beaker.reagents.trans_to(occupant, 5)
beaker.reagents.reaction(occupant)
- if(!occupant.getBruteLoss(TRUE) && !occupant.getFireLoss(TRUE) && !occupant.getCloneLoss() && autoeject) //release the patient automatically when brute and burn are handled on non-robotic limbs
- display_message("external wounds are")
+
+ if(autoeject)
+ //release the patient automatically when brute and burn are handled on non-robotic limbs
+ if(!occupant.getBruteLoss(TRUE) && !occupant.getFireLoss(TRUE) && !occupant.getCloneLoss())
+ display_message("Patient's external wounds are healed.")
go_out(TRUE)
return
- if(occupant.health >= 100 && autoeject)
- display_message("external wounds are")
+ if(occupant.health >= occupant.maxHealth)
+ display_message("Patient's external wounds are healed.")
go_out(TRUE)
return
-/obj/structure/machinery/cryo_cell/proc/go_out(auto_eject = null, dead = null)
+/obj/structure/machinery/cryo_cell/proc/go_out(auto_eject = FALSE, dead = FALSE)
if(!(occupant))
return
if(occupant.client)
@@ -235,66 +283,72 @@
if(occupant.bodytemperature < 261 && occupant.bodytemperature >= 70)
occupant.bodytemperature = 261
occupant.recalculate_move_delay = TRUE
- occupant = null
if(auto_eject) //Turn off and announce if auto-ejected because patient is recovered or dead.
on = FALSE
if(release_notice) //If auto-release notices are on as it should be, let the doctors know what's up
- playsound(src.loc, 'sound/machines/ping.ogg', 100, 14)
- var/reason = "Reason for release: Patient recovery."
+ var/reason = "Reason for release: Patient recovery."
if(dead)
- reason = "Reason for release: Patient death."
- ai_silent_announcement("Patient [occupant] has been automatically released from \the [src] at: [get_area(occupant)]. [reason]", MED_FREQ)
+ reason = "Reason for release: Patient death."
+ ai_silent_announcement("Patient [occupant] has been automatically released from [src] at: [sanitize_area((get_area(occupant))?.name)]. [reason]", ":m")
+ occupant = null
update_use_power(USE_POWER_IDLE)
update_icon()
return
-/obj/structure/machinery/cryo_cell/proc/put_mob(mob/living/carbon/M as mob)
+/obj/structure/machinery/cryo_cell/proc/put_mob(mob/living/carbon/cur_mob)
if(inoperable())
to_chat(usr, SPAN_DANGER("The cryo cell is not functioning."))
return
- if(!istype(M) || isxeno(M))
- to_chat(usr, SPAN_DANGER("The cryo cell cannot handle such a lifeform!"))
+ if(!istype(cur_mob) || isxeno(cur_mob))
+ to_chat(usr, SPAN_DANGER("The cryo cell cannot handle such a lifeform!"))
return
if(occupant)
- to_chat(usr, SPAN_DANGER("The cryo cell is already occupied!"))
+ to_chat(usr, SPAN_DANGER("The cryo cell is already occupied!"))
return
- if(M.abiotic())
+ if(cur_mob.abiotic())
to_chat(usr, SPAN_DANGER("Subject may not have abiotic items on."))
return
- if(do_after(usr, 20, INTERRUPT_NO_NEEDHAND, BUSY_ICON_GENERIC))
- to_chat(usr, SPAN_NOTICE("You move [M.name] inside the cryo cell."))
- M.forceMove(src)
- if(M.health >= -100 && (M.health <= 0 || M.sleeping))
- to_chat(M, SPAN_NOTICE("You feel cold liquid surround you. Your skin starts to freeze up."))
- occupant = M
+ if(do_after(usr, 2 SECONDS, INTERRUPT_NO_NEEDHAND, BUSY_ICON_GENERIC))
+ visible_message(SPAN_NOTICE("[usr] moves [usr == cur_mob ? "" : "[cur_mob] "]inside the cryo cell."))
+ cur_mob.forceMove(src)
+ if(cur_mob.health >= HEALTH_THRESHOLD_DEAD && (cur_mob.health <= 0 || cur_mob.sleeping))
+ to_chat(cur_mob, SPAN_NOTICE("You feel cold liquid surround you. Your skin starts to freeze up."))
+ occupant = cur_mob
+ occupant_death_stage = DEATH_STAGE_NONE
update_use_power(USE_POWER_ACTIVE)
update_icon()
return TRUE
-/obj/structure/machinery/cryo_cell/proc/display_message(msg)
- playsound(src.loc, 'sound/machines/ping.ogg', 25, 1)
- visible_message("[icon2html(src, viewers(src))] [SPAN_NOTICE("\The [src] pings: Patient's " + msg + " healed.")]")
+/obj/structure/machinery/cryo_cell/proc/display_message(msg, silent = FALSE, warning = FALSE)
+ if(!silent)
+ if(warning)
+ playsound(loc, 'sound/machines/twobeep.ogg', 40)
+ else
+ playsound(loc, 'sound/machines/ping.ogg', 25, 1)
+ visible_message("[icon2html(src, viewers(src))] [SPAN_NOTICE("[src] [warning ? "beeps" : "pings"]: [msg]")]")
/obj/structure/machinery/cryo_cell/verb/move_eject()
set name = "Eject occupant"
set category = "Object"
set src in oview(1)
if(usr == occupant)//If the user is inside the tube...
- if(usr.stat == 2)//and he's not dead....
+ if(usr.stat == DEAD)//and he's not dead....
return
- if(alert(usr, "Would you like to activate the ejection sequence of the cryo cell? Healing may be in progress.", "Confirm", "Yes", "No") == "Yes")
+ if(tgui_alert(usr, "Would you like to activate the ejection sequence of the cryo cell? Healing may be in progress.", "Confirm", list("Yes", "No")) == "Yes")
to_chat(usr, SPAN_NOTICE("Cryo cell release sequence activated. This will take thirty seconds."))
- visible_message(SPAN_WARNING ("The cryo cell's tank starts draining as its ejection lights blare!"))
- sleep(300)
- if(!src || !usr || !occupant || (occupant != usr)) //Check if someone's released/replaced/bombed him already
- return
- go_out()//and release him from the eternal prison.
- else
- if(usr.stat != 0)
- return
- go_out()
- return
+ visible_message(SPAN_WARNING("The cryo cell's tank starts draining as its ejection lights blare!"))
+ addtimer(CALLBACK(src, PROC_REF(finish_eject), usr), 30 SECONDS, TIMER_UNIQUE|TIMER_NO_HASH_WAIT)
+ else
+ if(usr.stat != CONSCIOUS)
+ return
+ go_out()
+
+/obj/structure/machinery/cryo_cell/proc/finish_eject(mob/original)
+ //Check if someone's released/replaced/bombed him already
+ if(QDELETED(src) || QDELETED(original) || !occupant || occupant != original)
+ return
+ go_out()//and release him from the eternal prison.
/obj/structure/machinery/cryo_cell/verb/move_inside()
set name = "Move Inside"
@@ -309,8 +363,8 @@
//clickdrag code - "resist to get out" code is in living_verbs.dm
/obj/structure/machinery/cryo_cell/MouseDrop_T(mob/target, mob/user)
. = ..()
- var/mob/living/H = user
- if(!istype(H) || target != user) //cant make others get in. grab-click for this
+ var/mob/living/living_mob = user
+ if(!istype(living_mob) || target != user) //cant make others get in. grab-click for this
return
put_mob(target)
@@ -324,3 +378,8 @@
/datum/data/function/proc/display()
return
+
+#undef DEATH_STAGE_NONE
+#undef DEATH_STAGE_EARLY
+#undef DEATH_STAGE_WARNING
+#undef DEATH_STAGE_CRITICAL
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 8a37eef3ee73..13d1bbaefcf7 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -142,31 +142,39 @@
return "on [t_his] feet"
return "...somewhere?"
-/obj/proc/updateUsrDialog()
- if(in_use)
- var/is_in_use = 0
- var/list/nearby = viewers(1, src)
- for(var/mob/M in nearby)
- if ((M.client && M.interactee == src))
- is_in_use = 1
- attack_hand(M)
- if (isSilicon(usr))
- if (!(usr in nearby))
- if (usr.client && usr.interactee==src) // && M.interactee == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
- is_in_use = 1
- attack_remote(usr)
- in_use = is_in_use
+/obj/proc/updateUsrDialog(mob/user)
+ if(!user)
+ user = usr
+ if(!in_use || !user)
+ return
+
+ var/is_in_use = FALSE
+ var/list/nearby = viewers(1, src)
+ for(var/mob/cur_mob in nearby)
+ if(cur_mob.client && cur_mob.interactee == src)
+ is_in_use = TRUE
+ attack_hand(cur_mob)
+ if(isSilicon(user))
+ if(!(user in nearby))
+ if(user.client && user.interactee == src) // && M.interactee == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh.
+ is_in_use = TRUE
+ attack_remote(user)
+
+ in_use = is_in_use
/obj/proc/updateDialog()
// Check that people are actually using the machine. If not, don't update anymore.
- if(in_use)
- var/list/nearby = viewers(1, src)
- var/is_in_use = 0
- for(var/mob/M in nearby)
- if ((M.client && M.interactee == src))
- is_in_use = 1
- src.interact(M)
- in_use = is_in_use
+ if(!in_use)
+ return
+
+ var/is_in_use = FALSE
+ var/list/nearby = viewers(1, src)
+ for(var/mob/cur_mob in nearby)
+ if(cur_mob.client && cur_mob.interactee == src)
+ is_in_use = TRUE
+ interact(cur_mob)
+
+ in_use = is_in_use
/obj/proc/interact(mob/user)
return
diff --git a/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm b/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm
index eafac03fd51f..9bf275a5448a 100644
--- a/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm
+++ b/code/modules/mob/living/carbon/human/life/handle_chemicals_in_body.dm
@@ -39,25 +39,35 @@
SHOULD_NOT_SLEEP(TRUE)
if(!reagents || undefibbable)
return // Double checking due to Life() funny background=1
- for(var/datum/reagent/generated/R in reagents.reagent_list)
+
+ var/has_cryo_medicine = reagents.get_reagent_amount("cryoxadone") >= 1 || reagents.get_reagent_amount("clonexadone") >= 1
+ if(has_cryo_medicine)
+ var/obj/structure/machinery/cryo_cell/cryo = loc
+ if(!istype(cryo) || !cryo.on || cryo.inoperable())
+ has_cryo_medicine = FALSE
+
+ for(var/datum/reagent/cur_reagent in reagents.reagent_list)
+ if(!has_cryo_medicine && !istype(cur_reagent, /datum/reagent/generated))
+ continue
+
var/list/mods = list( REAGENT_EFFECT = TRUE,
REAGENT_BOOST = FALSE,
REAGENT_PURGE = FALSE,
- REAGENT_FORCE = FALSE,
+ REAGENT_FORCE = has_cryo_medicine,
REAGENT_CANCEL = FALSE)
- for(var/datum/chem_property/P in R.properties)
- var/list/A = P.pre_process(src)
- if(!A)
+ for(var/datum/chem_property/cur_prop in cur_reagent.properties)
+ var/list/results = cur_prop.pre_process(src)
+ if(!results)
continue
- for(var/mod in A)
- mods[mod] |= A[mod]
+ for(var/mod in results)
+ mods[mod] |= results[mod]
if(mods[REAGENT_CANCEL])
return
if(mods[REAGENT_FORCE])
- R.handle_processing(src, mods, delta_time)
- R.holder.remove_reagent(R.id, R.custom_metabolism * delta_time)
+ cur_reagent.handle_processing(src, mods, delta_time)
+ cur_reagent.holder.remove_reagent(cur_reagent.id, cur_reagent.custom_metabolism * delta_time)
- R.handle_dead_processing(src, mods, delta_time)
+ cur_reagent.handle_dead_processing(src, mods, delta_time)
diff --git a/tgui/packages/tgui/interfaces/Cryo.jsx b/tgui/packages/tgui/interfaces/Cryo.jsx
index 338717f2d0ca..be1dce801ada 100644
--- a/tgui/packages/tgui/interfaces/Cryo.jsx
+++ b/tgui/packages/tgui/interfaces/Cryo.jsx
@@ -34,6 +34,11 @@ export const Cryo = () => {
const CryoContent = (props) => {
const { act, data } = useBackend();
+
+ let soundicon = 'volume-high';
+ if (!data.notify) {
+ soundicon = 'volume-xmark';
+ }
return (
<>
@@ -89,12 +94,30 @@ const CryoContent = (props) => {
icon="eject"
disabled={!data.hasOccupant}
onClick={() => act('eject')}
- content="eject patient"
+ content="Eject Patient"
/>