diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm index 4fb880fb4d..4a17fef999 100644 --- a/_maps/map_files/generic/CentCom.dmm +++ b/_maps/map_files/generic/CentCom.dmm @@ -1120,9 +1120,7 @@ /obj/structure/window{ dir = 8 }, -/obj/machinery/sleeper{ - dir = 1 - }, +/obj/machinery/stasis, /turf/open/floor/holofloor{ icon_state = "white" }, @@ -1134,9 +1132,7 @@ }, /area/holodeck/rec_center/medical) "dd" = ( -/obj/machinery/sleeper{ - dir = 1 - }, +/obj/machinery/stasis, /turf/open/floor/holofloor{ icon_state = "white" }, diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index e3c928a9a9..b29daa85f6 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -153,3 +153,6 @@ ///////////// #define STASIS_ASCENSION_EFFECT "heretic_ascension" + +/// If the incapacitated status effect will ignore a mob in stasis (stasis beds) +#define IGNORE_STASIS (1<<1) \ No newline at end of file diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index e1154b305f..daec1ce4c2 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -70,6 +70,10 @@ #define HAS_TRAIT_NOT_FROM(target, trait, source) (target.status_traits ? (target.status_traits[trait] ? (length(target.status_traits[trait] - source) > 0) : FALSE) : FALSE) //mob traits +/// Prevents voluntary movement. +#define TRAIT_IMMOBILIZED "immobilized" +/// Prevents usage of manipulation appendages (picking, holding or using items, manipulating storage). +#define TRAIT_HANDS_BLOCKED "handsblocked" #define TRAIT_BLIND "blind" #define TRAIT_MUTE "mute" #define TRAIT_EMOTEMUTE "emotemute" diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index d113b8b789..4cd82f9e66 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -484,3 +484,5 @@ GLOBAL_LIST_EMPTY(species_datums) //check if the person is dead, not sure where to put this #define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat) + +#define IS_IN_STASIS(mob) (mob.has_status_effect(/datum/status_effect/grouped/stasis)) diff --git a/code/__HELPERS/time.dm b/code/__HELPERS/time.dm index 2e27588ae5..519a54b38d 100644 --- a/code/__HELPERS/time.dm +++ b/code/__HELPERS/time.dm @@ -75,10 +75,8 @@ GLOBAL_VAR_INIT(rollovercheck_last_timeofday, 0) /proc/daysSince(realtimev) return round((world.realtime - realtimev) / (24 HOURS)) -/proc/worldtime2text() - return gameTimestamp("hh:mm:ss", world.time) +/proc/worldtime2text(wtime = world.timeofday) + return gameTimestamp("hh:mm:ss", wtime) -/proc/gameTimestamp(format = "hh:mm:ss", wtime=null) - if(!wtime) - wtime = world.time +/proc/gameTimestamp(format = "hh:mm:ss", wtime=world.time) return time2text(wtime - GLOB.timezoneOffset, format) diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm index 9a0665e91f..56865f8567 100644 --- a/code/controllers/subsystem/traumas.dm +++ b/code/controllers/subsystem/traumas.dm @@ -107,7 +107,7 @@ SUBSYSTEM_DEF(traumas) /obj/item/clothing/under/rank/medical/doctor/nurse, /obj/item/clothing/under/rank/medical/chief_medical_officer, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/pill/, /obj/item/reagent_containers/hypospray, /obj/item/storage/firstaid, /obj/item/storage/pill_bottle, /obj/item/healthanalyzer, - /obj/structure/sign/departments/medbay, /obj/machinery/door/airlock/medical, /obj/machinery/sleeper, + /obj/structure/sign/departments/medbay, /obj/machinery/door/airlock/medical, /obj/machinery/sleeper, /obj/machinery/stasis, /obj/machinery/dna_scannernew, /obj/machinery/atmospherics/components/unary/cryo_cell, /obj/item/surgical_drapes, /obj/item/retractor, /obj/item/hemostat, /obj/item/cautery, /obj/item/surgicaldrill, /obj/item/scalpel, /obj/item/circular_saw, /obj/item/clothing/suit/bio_suit/plaguedoctorsuit, /obj/item/clothing/head/plaguedoctorhat, /obj/item/clothing/mask/gas/plaguedoctor)), diff --git a/code/controllers/subsystem/vis_overlays.dm b/code/controllers/subsystem/vis_overlays.dm index b0e5d6c689..b672217480 100644 --- a/code/controllers/subsystem/vis_overlays.dm +++ b/code/controllers/subsystem/vis_overlays.dm @@ -7,10 +7,12 @@ SUBSYSTEM_DEF(vis_overlays) var/list/vis_overlay_cache var/list/unique_vis_overlays var/list/currentrun + var/datum/callback/rotate_cb /datum/controller/subsystem/vis_overlays/Initialize() vis_overlay_cache = list() unique_vis_overlays = list() + rotate_cb = CALLBACK(src, .proc/rotate_vis_overlay) return ..() /datum/controller/subsystem/vis_overlays/fire(resumed = FALSE) @@ -55,6 +57,7 @@ SUBSYSTEM_DEF(vis_overlays) if(!thing.managed_vis_overlays) thing.managed_vis_overlays = list(overlay) + RegisterSignal(thing, COMSIG_ATOM_DIR_CHANGE, rotate_cb) else thing.managed_vis_overlays += overlay return overlay @@ -78,3 +81,18 @@ SUBSYSTEM_DEF(vis_overlays) thing.managed_vis_overlays -= overlays if(!length(thing.managed_vis_overlays)) thing.managed_vis_overlays = null + UnregisterSignal(thing, COMSIG_ATOM_DIR_CHANGE) + +/datum/controller/subsystem/vis_overlays/proc/rotate_vis_overlay(atom/thing, old_dir, new_dir) + if(old_dir == new_dir) + return + var/rotation = dir2angle(old_dir) - dir2angle(new_dir) + var/list/overlays_to_remove = list() + for(var/i in thing.managed_vis_overlays - unique_vis_overlays) + var/obj/effect/overlay/vis/overlay = i + add_vis_overlay(thing, overlay.icon, overlay.icon_state, overlay.layer, overlay.plane, turn(overlay.dir, rotation), overlay.alpha, overlay.appearance_flags) + overlays_to_remove += overlay + for(var/i in thing.managed_vis_overlays & unique_vis_overlays) + var/obj/effect/overlay/vis/overlay = i + overlay.dir = turn(overlay.dir, rotation) + remove_vis_overlay(thing, overlays_to_remove) diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm index b5a7a38915..b7680c0d80 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs.dm @@ -127,13 +127,61 @@ desc = "You've fallen asleep. Wait a bit and you should wake up. Unless you don't, considering how helpless you are." icon_state = "asleep" - /datum/status_effect/grouped/stasis id = "stasis" duration = -1 tick_interval = 10 + alert_type = /atom/movable/screen/alert/status_effect/stasis var/last_dead_time +/datum/status_effect/grouped/stasis/proc/update_time_of_death() + if(last_dead_time) + var/delta = world.time - last_dead_time + var/new_timeofdeath = owner.timeofdeath + delta + owner.timeofdeath = new_timeofdeath + owner.tod = gameTimestamp(wtime=new_timeofdeath) + last_dead_time = null + if(owner.stat == DEAD) + last_dead_time = world.time + +/datum/status_effect/grouped/stasis/on_creation(mob/living/new_owner, set_duration) + . = ..() + if(.) + update_time_of_death() + owner.reagents?.end_metabolization(owner, FALSE) + +/datum/status_effect/grouped/stasis/on_apply() + . = ..() + if(!.) + return + owner.mobility_flags &= ~MOBILITY_USE + owner.mobility_flags &= ~MOBILITY_PICKUP + owner.mobility_flags &= ~MOBILITY_PULL + owner.mobility_flags &= ~MOBILITY_HOLD + owner.update_mobility() + owner.add_filter("stasis_status_ripple", 2, list("type" = "ripple", "flags" = WAVE_BOUNDED, "radius" = 0, "size" = 2)) + var/filter = owner.get_filter("stasis_status_ripple") + animate(filter, radius = 32, time = 15, size = 0, loop = -1) + + +/datum/status_effect/grouped/stasis/tick() + update_time_of_death() + +/datum/status_effect/grouped/stasis/on_remove() + owner.mobility_flags |= MOBILITY_USE + owner.mobility_flags |= MOBILITY_PICKUP + owner.mobility_flags |= MOBILITY_PULL + owner.mobility_flags |= MOBILITY_HOLD + owner.update_mobility() + owner.remove_filter("stasis_status_ripple") + update_time_of_death() + return ..() + +/atom/movable/screen/alert/status_effect/stasis + name = "Stasis" + desc = "Your biological functions have halted. You could live forever this way, but it's pretty boring." + icon_state = "stasis" + /datum/status_effect/robotic_emp id = "emp_no_combat_mode" diff --git a/code/game/machinery/stasis.dm b/code/game/machinery/stasis.dm new file mode 100644 index 0000000000..5f7f0263db --- /dev/null +++ b/code/game/machinery/stasis.dm @@ -0,0 +1,141 @@ +#define STASIS_TOGGLE_COOLDOWN 50 +/obj/machinery/stasis + name = "Lifeform Stasis Unit" + desc = "A not so comfortable looking bed with some nozzles at the top and bottom. It will keep someone in stasis." + icon = 'icons/obj/machines/stasis.dmi' + icon_state = "stasis" + density = FALSE + can_buckle = TRUE + buckle_lying = 90 + circuit = /obj/item/circuitboard/machine/stasis + idle_power_usage = 40 + active_power_usage = 340 + fair_market_price = 10 + payment_department = ACCOUNT_MED + var/stasis_enabled = TRUE + var/last_stasis_sound = FALSE + var/stasis_can_toggle = 0 + var/mattress_state = "stasis_on" + var/obj/effect/overlay/vis/mattress_on + +/obj/machinery/stasis/examine(mob/user) + ..() + var/turn_on_or_off = stasis_enabled ? "turn off" : "turn on" + to_chat(user, "Alt-click to [turn_on_or_off] the machine.") + +/obj/machinery/stasis/proc/play_power_sound() + var/_running = stasis_running() + if(last_stasis_sound != _running) + var/sound_freq = rand(5120, 8800) + if(_running) + playsound(src, 'sound/machines/synth_yes.ogg', 50, TRUE, frequency = sound_freq) + else + playsound(src, 'sound/machines/synth_no.ogg', 50, TRUE, frequency = sound_freq) + last_stasis_sound = _running + +/obj/machinery/stasis/AltClick(mob/user) + if(world.time >= stasis_can_toggle && user.canUseTopic(src)) + stasis_enabled = !stasis_enabled + stasis_can_toggle = world.time + STASIS_TOGGLE_COOLDOWN + playsound(src, 'sound/machines/click.ogg', 60, TRUE) + play_power_sound() + update_icon() + +/obj/machinery/stasis/Exited(atom/movable/AM, atom/newloc) + if(AM == occupant) + var/mob/living/L = AM + if(L.IsInStasis()) + thaw_them(L) + . = ..() + +/obj/machinery/stasis/proc/stasis_running() + return stasis_enabled && is_operational() + +/obj/machinery/stasis/update_icon() + . = ..() + var/_running = stasis_running() + var/list/overlays_to_remove = managed_vis_overlays + + if(mattress_state) + if(!mattress_on || !managed_vis_overlays) + mattress_on = SSvis_overlays.add_vis_overlay(src, icon, mattress_state, layer, plane, dir, alpha = 0, unique = TRUE) + + if(mattress_on.alpha ? !_running : _running) //check the inverse of _running compared to truthy alpha, to see if they differ + var/new_alpha = _running ? 255 : 0 + var/easing_direction = _running ? EASE_OUT : EASE_IN + animate(mattress_on, alpha = new_alpha, time = 50, easing = CUBIC_EASING|easing_direction) + + overlays_to_remove = managed_vis_overlays - mattress_on + + SSvis_overlays.remove_vis_overlay(src, overlays_to_remove) + + if(occupant) + SSvis_overlays.add_vis_overlay(src, 'icons/obj/machines/stasis.dmi', "tubes", LYING_MOB_LAYER + 0.1, plane, dir) //using vis_overlays instead of normal overlays for mouse_opacity here + + if(stat & BROKEN) + icon_state = "stasis_broken" + return + if(panel_open || stat & MAINT) + icon_state = "stasis_maintenance" + return + icon_state = "stasis" + +/obj/machinery/stasis/obj_break(damage_flag) + . = ..() + play_power_sound() + update_icon() + +/obj/machinery/stasis/power_change() + . = ..() + play_power_sound() + update_icon() + +/obj/machinery/stasis/proc/chill_out(mob/living/target) + if(target != occupant) + return + var/freq = rand(24750, 26550) + playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 2, frequency = freq) + target.SetStasis(TRUE) + target.ExtinguishMob() + use_power = ACTIVE_POWER_USE + +/obj/machinery/stasis/proc/thaw_them(mob/living/target) + target.SetStasis(FALSE) + if(target == occupant) + use_power = IDLE_POWER_USE + +/obj/machinery/stasis/post_buckle_mob(mob/living/L) + if(!can_be_occupant(L)) + return + occupant = L + if(stasis_running() && check_nap_violations()) + chill_out(L) + update_icon() + +/obj/machinery/stasis/post_unbuckle_mob(mob/living/L) + thaw_them(L) + if(L == occupant) + occupant = null + update_icon() + +/obj/machinery/stasis/process() + if( !( occupant && isliving(occupant) && check_nap_violations() ) ) + use_power = IDLE_POWER_USE + return + var/mob/living/L_occupant = occupant + if(stasis_running()) + if(!L_occupant.IsInStasis()) + chill_out(L_occupant) + else if(L_occupant.IsInStasis()) + thaw_them(L_occupant) + +/obj/machinery/stasis/screwdriver_act(mob/living/user, obj/item/I) + . = default_deconstruction_screwdriver(user, "stasis_maintenance", "stasis", I) + update_icon() + +/obj/machinery/stasis/crowbar_act(mob/living/user, obj/item/I) + return default_deconstruction_crowbar(I) + +/obj/machinery/stasis/nap_violation(mob/violator) + unbuckle_mob(violator, TRUE) +#undef STASIS_TOGGLE_COOLDOWN diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index cb1d4aae88..c42d40db83 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -1482,3 +1482,11 @@ icon_state = "engineering" build_path = /obj/machinery/research/explosive_compressor req_components = list(/obj/item/stock_parts/matter_bin = 3) + +/obj/item/circuitboard/machine/stasis + name = "Lifeform Stasis Unit (Machine Board)" + build_path = /obj/machinery/stasis + req_components = list( + /obj/item/stack/cable_coil = 3, + /obj/item/stock_parts/manipulator = 1, + /obj/item/stock_parts/capacitor = 1) \ No newline at end of file diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm index ba4b02fcf4..36e9d0a9a9 100644 --- a/code/modules/mining/equipment/survival_pod.dm +++ b/code/modules/mining/equipment/survival_pod.dm @@ -162,6 +162,26 @@ if(!state_open) . += "sleeper_cover" +//Lifeform Stasis Unit +/obj/machinery/stasis/survival_pod + icon = 'icons/obj/lavaland/survival_pod.dmi' + icon_state = "sleeper" + mattress_state = null + buckle_lying = 270 + +/obj/machinery/stasis/survival_pod/play_power_sound() + return + +/obj/machinery/stasis/survival_pod/update_icon() + return + +//NanoMed +/obj/machinery/vending/wallmed/survival_pod + name = "survival pod medical supply" + desc = "Wall-mounted Medical Equipment dispenser. This one seems just a tiny bit smaller." + refill_canister = null + onstation = FALSE + //Computer /obj/item/gps/computer name = "pod computer" diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index 0c73c623d1..18a500f6f4 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -6,11 +6,12 @@ SHOULD_NOT_SLEEP(TRUE) if(mob_transforming) return - + handle_traits() // eye, ear, brain damages + handle_status_effects() //all special effects, stun, knockdown, jitteryness, hallucination, sleeping, etc . = SEND_SIGNAL(src, COMSIG_LIVING_LIFE, seconds, times_fired) if(!(. & COMPONENT_INTERRUPT_LIFE_PHYSICAL)) PhysicalLife(seconds, times_fired) - if(!(. & COMPONENT_INTERRUPT_LIFE_BIOLOGICAL)) + if(!(. & COMPONENT_INTERRUPT_LIFE_BIOLOGICAL) && !IS_IN_STASIS()) BiologicalLife(seconds, times_fired) // CODE BELOW SHOULD ONLY BE THINGS THAT SHOULD HAPPEN NO MATTER WHAT AND CAN NOT BE SUSPENDED! @@ -69,9 +70,6 @@ handle_block_parry(seconds) - // These two MIGHT need to be moved to base Life() if we get any in the future that's a "physical" effect that needs to fire even while in stasis. - handle_traits() // eye, ear, brain damages - handle_status_effects() //all special effects, stun, knockdown, jitteryness, hallucination, sleeping, etc return TRUE /** diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index bf3094cb08..e4ffb94493 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -433,8 +433,8 @@ to_chat(src, "You have given up life and succumbed to death.") death() -/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE) - if(stat || IsUnconscious() || IsStun() || IsParalyzed() || (combat_flags & COMBAT_FLAG_HARD_STAMCRIT) || (check_immobilized && IsImmobilized()) || (!ignore_restraints && restrained(ignore_grab))) +/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE, ignore_stasis = FALSE) + if(stat || IsUnconscious() || IsStun() || IsParalyzed() || (combat_flags & COMBAT_FLAG_HARD_STAMCRIT) || (check_immobilized && IsImmobilized()) || (!ignore_restraints && restrained(ignore_grab)) || (!ignore_stasis && IS_IN_STASIS())) return TRUE /mob/living/canUseStorage() diff --git a/icons/obj/machines/stasis.dmi b/icons/obj/machines/stasis.dmi new file mode 100644 index 0000000000..21844b0f8d Binary files /dev/null and b/icons/obj/machines/stasis.dmi differ diff --git a/tgstation.dme b/tgstation.dme index cb2fa07f50..b019ea10b1 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -912,6 +912,7 @@ #include "code\game\machinery\Sleeper.dm" #include "code\game\machinery\slotmachine.dm" #include "code\game\machinery\spaceheater.dm" +#include "code\game\machinery\stasis.dm" #include "code\game\machinery\status_display.dm" #include "code\game\machinery\suit_storage_unit.dm" #include "code\game\machinery\syndicatebeacon.dm"