diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index 81e52c3511..3fd5d2277e 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -1,3 +1,7 @@
+//CITADEL EDIT
+GLOBAL_LIST_EMPTY(objectives)
+//END EDIT
+
/datum/objective
var/datum/mind/owner //The primary owner of the objective. !!SOMEWHAT DEPRECATED!! Prefer using 'team' for new code.
var/datum/team/team //An alternative to 'owner': a team. Use this when writing new code.
@@ -9,6 +13,7 @@
var/martyr_compatible = 0 //If the objective is compatible with martyr objective, i.e. if you can still do it while dead.
/datum/objective/New(var/text)
+ GLOB.objectives += src // CITADEL EDIT FOR CRYOPODS
if(text)
explanation_text = text
@@ -72,6 +77,8 @@
possible_targets = all_possible_targets
if(possible_targets.len > 0)
target = pick(possible_targets)
+ else
+ target = null//we'd rather have no target than an invalid one // CITADEL EDIT
update_explanation_text()
return target
@@ -818,6 +825,3 @@ GLOBAL_LIST_EMPTY(possible_items_special)
/datum/objective/changeling_team_objective/impersonate_department/impersonate_heads
explanation_text = "Have X or more heads of staff escape on the shuttle disguised as heads, while the real heads are dead"
command_staff_only = TRUE
-
-
-
diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm
index 5206ac957b..29b1476c6b 100644
--- a/code/game/machinery/announcement_system.dm
+++ b/code/game/machinery/announcement_system.dm
@@ -89,6 +89,10 @@ GLOBAL_LIST_EMPTY(announcement_systems)
message = CompileText(arrival, user, rank)
else if(message_type == "NEWHEAD" && newheadToggle)
message = CompileText(newhead, user, rank)
+ //CITADEL EDIT for cryopods
+ else if(message_type == "CRYOSTORAGE")
+ message = CompileText("%PERSON, %RANK has been moved to cryo storage.", user, rank)
+ //END EDIT
else if(message_type == "ARRIVALS_BROKEN")
message = "The arrivals shuttle has been damaged. Docking for repairs..."
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 96128a7f6f..07e7fe826c 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -270,6 +270,15 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
set name = "Ghost"
set desc = "Relinquish your life and enter the land of the dead."
+// CITADEL EDIT
+ if(istype(loc, /obj/machinery/cryopod))
+ var/response = alert(src, "Are you -sure- you want to ghost?\n(You are alive. If you ghost whilst still alive you may not play again this round! You can't change your mind so choose wisely!!)","Are you sure you want to ghost?","Ghost","Stay in body")
+ if(response != "Ghost")//darn copypaste
+ return
+ var/obj/machinery/cryopod/C = loc
+ C.despawn_occupant()
+ return
+// END EDIT
if(stat != DEAD)
succumb()
if(stat == DEAD)
diff --git a/icons/obj/Cryogenic2.dmi b/icons/obj/Cryogenic2.dmi
index 9d7b7510c5..764e67ebf0 100644
Binary files a/icons/obj/Cryogenic2.dmi and b/icons/obj/Cryogenic2.dmi differ
diff --git a/modular_citadel/code/controllers/subsystem/job.dm b/modular_citadel/code/controllers/subsystem/job.dm
index 234c606e00..846c17344c 100644
--- a/modular_citadel/code/controllers/subsystem/job.dm
+++ b/modular_citadel/code/controllers/subsystem/job.dm
@@ -25,3 +25,12 @@
if(!M.equip_to_slot_if_possible(I, G.category, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // If the job's dresscode compliant, try to put it in its slot, first
if(!M.equip_to_slot_if_possible(I, slot_in_backpack, disable_warning = TRUE, bypass_equip_delay_self = TRUE)) // Otherwise, try to put it in the backpack
I.forceMove(get_turf(M)) // If everything fails, just put it on the floor under the mob.
+
+/datum/controller/subsystem/job/proc/FreeRole(rank)
+ if(!rank)
+ return
+ Debug("Freeing role: [rank]")
+ var/datum/job/job = GetJob(rank)
+ if(!job)
+ return FALSE
+ job.current_positions = max(0, job.current_positions - 1)
diff --git a/modular_citadel/code/game/machinery/cryopod.dm b/modular_citadel/code/game/machinery/cryopod.dm
new file mode 100644
index 0000000000..81d336bc98
--- /dev/null
+++ b/modular_citadel/code/game/machinery/cryopod.dm
@@ -0,0 +1,441 @@
+/*
+ * Cryogenic refrigeration unit. Basically a despawner.
+ * Stealing a lot of concepts/code from sleepers due to massive laziness.
+ * The despawn tick will only fire if it's been more than time_till_despawned ticks
+ * since time_entered, which is world.time when the occupant moves in.
+ * ~ Zuhayr
+ */
+
+
+//Main cryopod console.
+
+/obj/machinery/computer/cryopod
+ name = "cryogenic oversight console"
+ desc = "An interface between crew and the cryogenic storage oversight systems."
+ icon = 'icons/obj/Cryogenic2.dmi'
+ icon_state = "cellconsole_1"
+ circuit = /obj/item/circuitboard/cryopodcontrol
+ density = FALSE
+ interact_offline = TRUE
+ req_one_access = list(ACCESS_HEADS, ACCESS_ARMORY) //Heads of staff or the warden can go here to claim recover items from their department that people went were cryodormed with.
+ var/mode = null
+
+ //Used for logging people entering cryosleep and important items they are carrying.
+ var/list/frozen_crew = list()
+ var/list/frozen_items = list()
+
+ var/storage_type = "crewmembers"
+ var/storage_name = "Cryogenic Oversight Control"
+ var/allow_items = TRUE
+
+/obj/machinery/computer/cryopod/attack_ai()
+ attack_hand()
+
+/obj/machinery/computer/cryopod/attack_hand(mob/user = usr)
+ if(stat & (NOPOWER|BROKEN))
+ return
+
+ user.set_machine(src)
+ add_fingerprint(user)
+
+ var/dat
+
+ dat += "
[storage_name]
"
+ dat += "Welcome, [user.real_name].
"
+ dat += "View storage log.
"
+ if(allow_items)
+ dat += "View objects.
"
+ dat += "Recover object.
"
+ dat += "Recover all objects.
"
+
+ user << browse(dat, "window=cryopod_console")
+ onclose(user, "cryopod_console")
+
+/obj/machinery/computer/cryopod/Topic(href, href_list)
+ if(..())
+ return 1
+
+ var/mob/user = usr
+
+ add_fingerprint(user)
+
+ if(href_list["log"])
+
+ var/dat = "Recently stored [storage_type]
"
+ for(var/person in frozen_crew)
+ dat += "[person]
"
+ dat += "
"
+
+ user << browse(dat, "window=cryolog")
+
+ if(href_list["view"])
+ if(!allow_items) return
+
+ var/dat = "Recently stored objects
"
+ for(var/obj/item/I in frozen_items)
+ dat += "[I.name]
"
+ dat += "
"
+
+ user << browse(dat, "window=cryoitems")
+
+ else if(href_list["item"])
+ if(!allowed(user))
+ to_chat(user, "Access Denied.")
+ return
+ if(!allow_items) return
+
+ if(frozen_items.len == 0)
+ to_chat(user, "There is nothing to recover from storage.")
+ return
+
+ var/obj/item/I = input(user, "Please choose which object to retrieve.","Object recovery",null) as null|anything in frozen_items
+ if(!I)
+ return
+
+ if(!(I in frozen_items))
+ to_chat(user, "\The [I] is no longer in storage.")
+ return
+
+ visible_message("The console beeps happily as it disgorges \the [I].")
+
+ I.forceMove(get_turf(src))
+ frozen_items -= I
+
+ else if(href_list["allitems"])
+ if(!allowed(user))
+ to_chat(user, "Access Denied.")
+ return
+ if(!allow_items) return
+
+ if(frozen_items.len == 0)
+ to_chat(user, "There is nothing to recover from storage.")
+ return
+
+ visible_message("The console beeps happily as it disgorges the desired objects.")
+
+ for(var/obj/item/I in frozen_items)
+ I.forceMove(get_turf(src))
+ frozen_items -= I
+
+ updateUsrDialog()
+ return
+
+/obj/item/circuitboard/cryopodcontrol
+ name = "Circuit board (Cryogenic Oversight Console)"
+ build_path = "/obj/machinery/computer/cryopod"
+
+
+//Cryopods themselves.
+/obj/machinery/cryopod
+ name = "cryogenic freezer"
+ desc = "Suited for Cyborgs and Humanoids, the pod is a safe place for personnel affected by the Space Sleep Disorder to get some rest."
+ icon = 'icons/obj/cryogenic2.dmi'
+ icon_state = "cryopod-open"
+ density = TRUE
+ anchored = TRUE
+ state_open = TRUE
+
+ var/on_store_message = "has entered long-term storage."
+ var/on_store_name = "Cryogenic Oversight"
+
+ // 15 minutes-ish safe period before being despawned.
+ var/time_till_despawn = 15 * 600 // This is reduced by 90% if a player manually enters cryo
+ var/despawn_world_time = null // Used to keep track of the safe period.
+
+ var/obj/machinery/computer/cryopod/control_computer
+ var/last_no_computer_message = 0
+
+ // These items are preserved when the process() despawn proc occurs.
+ var/list/preserve_items = list(
+ /obj/item/hand_tele,
+ /obj/item/card/id/captains_spare,
+ /obj/item/device/aicard,
+ /obj/item/device/mmi,
+ /obj/item/device/paicard,
+ /obj/item/gun,
+ /obj/item/pinpointer,
+ /obj/item/clothing/shoes/magboots,
+ /obj/item/areaeditor/blueprints,
+ /obj/item/clothing/head/helmet/space,
+ /obj/item/clothing/suit/space,
+ /obj/item/clothing/suit/armor,
+ /obj/item/defibrillator/compact,
+ /obj/item/reagent_containers/hypospray/CMO,
+ /obj/item/clothing/accessory/medal/gold/captain,
+ /obj/item/clothing/gloves/krav_maga,
+ /obj/item/nullrod,
+ /obj/item/tank/jetpack,
+ /obj/item/documents,
+ /obj/item/nuke_core_container
+ )
+ // These items will NOT be preserved
+ var/list/do_not_preserve_items = list (
+ /obj/item/device/mmi/posibrain
+ )
+
+/obj/machinery/cryopod/Initialize()
+ update_icon()
+ find_control_computer()
+ return ..()
+
+/obj/machinery/cryopod/proc/find_control_computer(urgent = 0)
+ for(var/obj/machinery/computer/cryopod/C in area_contents(get_area(src)))
+ control_computer = C
+ break
+
+ // Don't send messages unless we *need* the computer, and less than five minutes have passed since last time we messaged
+ if(!control_computer && urgent && last_no_computer_message + 5*60*10 < world.time)
+ log_admin("Cryopod in [get_area(src)] could not find control computer!")
+ message_admins("Cryopod in [get_area(src)] could not find control computer!")
+ last_no_computer_message = world.time
+
+ return control_computer != null
+
+/obj/machinery/cryopod/close_machine(mob/user)
+ if(!control_computer)
+ find_control_computer(TRUE)
+ if((isnull(user) || istype(user)) && state_open && !panel_open)
+ ..(user)
+ var/mob/living/mob_occupant = occupant
+ if(mob_occupant && mob_occupant.stat != DEAD)
+ to_chat(occupant, "You feel cool air surround you. You go numb as your senses turn inward.")
+ if(mob_occupant.client)//if they're logged in
+ despawn_world_time = world.time + (time_till_despawn * 0.1)
+ else
+ despawn_world_time = world.time + time_till_despawn
+ icon_state = "cryopod"
+
+/obj/machinery/cryopod/open_machine()
+ ..()
+ icon_state = "cryopod-open"
+ density = TRUE
+ name = initial(name)
+
+/obj/machinery/cryopod/container_resist(mob/living/user)
+ visible_message("[occupant] emerges from [src]!",
+ "You climb out of [src]!")
+ open_machine()
+
+/obj/machinery/cryopod/relaymove(mob/user)
+ container_resist(user)
+
+/obj/machinery/cryopod/process()
+ if(!occupant)
+ return
+
+ var/mob/living/mob_occupant = occupant
+ if(mob_occupant)
+ // Eject dead people
+ if(mob_occupant.stat == DEAD)
+ open_machine()
+
+ if(!(world.time > despawn_world_time + 100))//+ 10 seconds
+ return
+
+ if(!mob_occupant.client && mob_occupant.stat < 2) //Occupant is living and has no client.
+ if(!control_computer)
+ find_control_computer(urgent = TRUE)//better hope you found it this time
+
+ despawn_occupant()
+
+// This function can not be undone; do not call this unless you are sure
+/obj/machinery/cryopod/proc/despawn_occupant()
+ var/mob/living/mob_occupant = occupant
+ var/list/target_candidates = list()
+
+ if(istype(SSticker.mode, /datum/antagonist/cult))//thank
+ if("sacrifice" in SSticker.mode.cult)
+ for(var/mob/living/carbon/human/player in GLOB.player_list)
+ if(player.mind && !player.mind.has_antag_datum(/datum/antagonist/cult) && !is_convertable_to_cult(player) && player.stat != DEAD)
+ target_candidates += player.mind
+
+ target_candidates -= mob_occupant.mind
+
+ if(target_candidates.len == 0)
+ message_admins("Cult Sacrifice: Could not find unconvertable target, checking for convertable target.")
+ for(var/mob/living/carbon/human/player in GLOB.player_list)
+ if(player.mind && !player.mind.has_antag_datum(/datum/antagonist/cult) && player.stat != DEAD)
+ target_candidates += player.mind
+
+ listclearnulls(target_candidates)
+ if(LAZYLEN(target_candidates))
+ var/datum/objective/sacrifice/sac_objective = locate() in GLOB.objectives
+ sac_objective.target = pick(target_candidates)
+ sac_objective.update_explanation_text()
+
+ var/datum/job/sacjob = SSjob.GetJob(sac_objective.target.assigned_role)
+ var/datum/preferences/sacface = sac_objective.target.current.client.prefs
+ var/icon/reshape = get_flat_human_icon(null, sacjob, sacface)
+ reshape.Shift(SOUTH, 4)
+ reshape.Shift(EAST, 1)
+ reshape.Crop(7,4,26,31)
+ reshape.Crop(-5,-3,26,30)
+ sac_objective.sac_image = reshape
+
+ for(var/datum/mind/H in SSticker.mode.cult)
+ if(H.current)
+ to_chat(H.current, "Nar'Sie murmurs, [occupant] is beyond your reach. Sacrifice [sac_objective.target.current] instead...")
+
+ else
+ message_admins("Cult Sacrifice: Could not find unconvertable or convertable target after cryopod. WELP!")
+
+ //Update any existing objectives involving this mob.
+ for(var/datum/objective/O in GLOB.objectives)
+ // We don't want revs to get objectives that aren't for heads of staff. Letting
+ // them win or lose based on cryo is silly so we remove the objective.
+ if(istype(O,/datum/objective/mutiny) && O.target == mob_occupant.mind)
+ qdel(O)
+ else if(O.target && istype(O.target, /datum/mind))
+ if(O.target == mob_occupant.mind)
+ if(O.owner && O.owner.current)
+ to_chat(O.owner.current, "
You get the feeling your target is no longer within reach. Time for Plan [pick("A","B","C","D","X","Y","Z")]. Objectives updated!")
+ O.target = null
+ spawn(10) //This should ideally fire after the occupant is deleted.
+ if(!O)
+ return
+ O.find_target()
+ O.update_explanation_text()
+ if(!(O.target))
+ O.owner.objectives -= O
+ qdel(O)
+
+ if(mob_occupant.mind && mob_occupant.mind.assigned_role)
+ //Handle job slot/tater cleanup.
+ var/job = mob_occupant.mind.assigned_role
+ SSjob.FreeRole(job)
+ if(mob_occupant.mind.objectives.len)
+ mob_occupant.mind.objectives.Cut()
+ mob_occupant.mind.special_role = null
+
+ // Delete them from datacore.
+
+ var/announce_rank = null
+ for(var/datum/data/record/R in GLOB.data_core.medical)
+ if((R.fields["name"] == mob_occupant.real_name))
+ qdel(R)
+ for(var/datum/data/record/T in GLOB.data_core.security)
+ if((T.fields["name"] == mob_occupant.real_name))
+ qdel(T)
+ for(var/datum/data/record/G in GLOB.data_core.general)
+ if((G.fields["name"] == mob_occupant.real_name))
+ announce_rank = G.fields["rank"]
+ qdel(G)
+
+ for(var/obj/machinery/computer/cloning/cloner in world)
+ for(var/datum/data/record/R in cloner.records)
+ if(R.fields["name"] == mob_occupant.real_name)
+ cloner.records.Remove(R)
+
+ //Make an announcement and log the person entering storage.
+ if(control_computer)
+ control_computer.frozen_crew += "[mob_occupant.real_name]"
+
+ if(GLOB.announcement_systems.len)
+ var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems)
+ announcer.announce("CRYOSTORAGE", mob_occupant.real_name, announce_rank, list())
+ visible_message("\The [src] hums and hisses as it moves [mob_occupant.real_name] into storage.")
+
+
+ for(var/obj/item/W in mob_occupant.GetAllContents())
+ if(W.loc.loc && (( W.loc.loc == loc ) || (W.loc.loc == control_computer)))
+ continue//means we already moved whatever this thing was in
+ //I'm a professional, okay
+ for(var/T in preserve_items)
+ if(istype(W, T))
+ if(control_computer && control_computer.allow_items)
+ control_computer.frozen_items += W
+ mob_occupant.transferItemToLoc(W, control_computer, TRUE)
+ else
+ mob_occupant.transferItemToLoc(W, loc, TRUE)
+
+ for(var/obj/item/W in mob_occupant.GetAllContents())
+ qdel(W)//because we moved all items to preserve away
+ //and yes, this totally deletes their bodyparts one by one, I just couldn't bother
+
+ if(iscyborg(mob_occupant))
+ var/mob/living/silicon/robot/R = occupant
+ if(!istype(R)) return ..()
+
+ R.contents -= R.mmi
+ qdel(R.mmi)
+
+ // Ghost and delete the mob.
+ if(!mob_occupant.get_ghost(1))
+ if(world.time < 30 * 600)//before the 30 minute mark
+ mob_occupant.ghostize(0) // Players despawned too early may not re-enter the game
+ else
+ mob_occupant.ghostize(1)
+ QDEL_NULL(occupant)
+ open_machine()
+ name = initial(name)
+
+/obj/machinery/cryopod/MouseDrop_T(mob/living/target, mob/user)
+ if(!istype(target) || user.incapacitated() || !target.Adjacent(user) || !Adjacent(user) || !ismob(target) || (!ishuman(user) && !iscyborg(user)) || !istype(user.loc, /turf) || target.buckled)
+ return
+
+ if(occupant)
+ to_chat(user, "The cryo pod is already occupied!")
+ return
+
+ if(target.stat == DEAD)
+ to_chat(user, "Dead people can not be put into cryo.")
+ return
+
+ if(target.client && user != target)
+ if(iscyborg(target))
+ to_chat(user, "You can't put [target] into [src]. They're online.")
+ else
+ to_chat(user, "You can't put [target] into [src]. They're conscious.")
+ return
+ else if(target.client)
+ if(alert(target,"Would you like to enter cryosleep?",,"Yes","No") == "No")
+ return
+
+ var/generic_plsnoleave_message = " Please adminhelp before leaving the round, even if there are no administrators online!"
+
+ if(target == user && world.time - target.client.cryo_warned > 5 * 600)//if we haven't warned them in the last 5 minutes
+ var/caught = FALSE
+ if(target.mind.assigned_role in GLOB.command_positions)
+ alert("You're a Head of Staff![generic_plsnoleave_message]")
+ caught = TRUE
+ if(iscultist(target) || is_servant_of_ratvar(target))
+ to_chat(target, "You're a Cultist![generic_plsnoleave_message]")
+ caught = TRUE
+ if(istype(SSticker.mode, /datum/antagonist/blob))
+ if(target.mind in GLOB.overminds)
+ alert("You're a Blob![generic_plsnoleave_message]")
+ caught = TRUE
+ if(is_devil(target))
+ alert("You're a Devil![generic_plsnoleave_message]")
+ caught = TRUE
+ if(istype(SSticker.mode, /datum/antagonist/rev))
+ if(target.mind.has_antag_datum(/datum/antagonist/rev/head))
+ alert("You're a Head Revolutionary![generic_plsnoleave_message]")
+ caught = TRUE
+ else if(target.mind.has_antag_datum(/datum/antagonist/rev))
+ alert("You're a Revolutionary![generic_plsnoleave_message]")
+ caught = TRUE
+
+ if(caught)
+ target.client.cryo_warned = world.time
+ return
+
+ if(target == user)
+ visible_message("[user] starts climbing into the cryo pod.")
+ else
+ visible_message("[user] starts putting [target] into the cryo pod.")
+
+ if(occupant)
+ to_chat(user, "\The [src] is in use.")
+ return
+ close_machine(target)
+
+ to_chat(target, "If you ghost, log out or close your client now, your character will shortly be permanently removed from the round.")
+ name = "[name] ([occupant.name])"
+ log_admin("[key_name(target)] entered a stasis pod.")
+ message_admins("[key_name_admin(target)] entered a stasis pod. (JMP)")
+ add_fingerprint(target)
+
+//Attacks/effects.
+/obj/machinery/cryopod/blob_act()
+ return //Sorta gamey, but we don't really want these to be destroyed.
diff --git a/modular_citadel/code/modules/client/client_defines.dm b/modular_citadel/code/modules/client/client_defines.dm
new file mode 100644
index 0000000000..d816b15d1d
--- /dev/null
+++ b/modular_citadel/code/modules/client/client_defines.dm
@@ -0,0 +1,2 @@
+/client
+ var/cryo_warned = -3000//when was the last time we warned them about not cryoing without an ahelp, set to -5 minutes so that rounstart cryo still warns
diff --git a/tgstation.dme b/tgstation.dme
index 638ebb6275..4b87b29463 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2521,6 +2521,7 @@
#include "modular_citadel\code\datums\uplink_items_cit.dm"
#include "modular_citadel\code\datums\mutations\hulk.dm"
#include "modular_citadel\code\game\gamemodes\miniantags\bot_swarm\swarmer_event.dm"
+#include "modular_citadel\code\game\machinery\cryopod.dm"
#include "modular_citadel\code\game\machinery\Sleeper.dm"
#include "modular_citadel\code\game\objects\ids.dm"
#include "modular_citadel\code\game\objects\items\handcuffs.dm"
@@ -2540,6 +2541,7 @@
#include "modular_citadel\code\modules\admin\topic.dm"
#include "modular_citadel\code\modules\cargo\console.dm"
#include "modular_citadel\code\modules\cargo\packs.dm"
+#include "modular_citadel\code\modules\client\client_defines.dm"
#include "modular_citadel\code\modules\client\client_procs.dm"
#include "modular_citadel\code\modules\client\preferences.dm"
#include "modular_citadel\code\modules\client\preferences_savefile.dm"