Files
Bubberstation/code/game/objects/structures/morgue.dm
SkyratBot fd3b832bb8 [MIRROR] Makes decals only caught on mapload & removes turf_loc_check [MDB IGNORE] (#22051)
* Makes decals only caught on mapload & removes turf_loc_check (#76130)

Theres one player-facing change in this PR and it's that I removed human
gibs from being valid in space turfs, making it more consistent with the
other gib decals.

I've cleaned up many instances of decals spawning in bad turfs on
mapload in https://github.com/tgstation/tgstation/pull/75189, but making
it error anytime in-game a decal is put over an invalid turf is bad as
it punishes contributors for not optimizing their decals properly.
This changes it so it only errors if it's on mapload, unit testing or
not.

I've also removed ``turf_loc_check`` and replaced instances of it with
overwriting ``NeverShouldHaveComeHere``, which gives us greater control
of where decals can be placed.
This let me remove 2 subtypes that were made to have decals in specific
places (which then got placed elsewhere, ruining it all).

Mappers are still able to set decals to be placed anywhere, they just
need to add it as a valid turf for that decal.

* Makes decals only caught on mapload & removes turf_loc_check

---------

Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
2023-06-24 16:06:58 -07:00

466 lines
14 KiB
Plaintext

///The cooldown between messages when attempting to break out of a morgue tray.
#define BREAKOUT_COOLDOWN (5 SECONDS)
///The amount of time it takes to break out of a morgue tray.
#define BREAKDOWN_TIME (60 SECONDS)
/obj/item/paper/guides/jobs/medical/morgue
name = "morgue memo"
default_raw_text = "<font size='2'>Since this station's medbay never seems to fail to be staffed by the mindless monkeys \
meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure \
to leave.</font><BR><BR><font size='4'><font color=red>Red lights mean there's a plain ol' dead body inside.</font><BR><BR>\
<font color=orange>Yellow lights mean there's non-body objects inside.</font><BR><font size='2'>Probably stuff pried off a \
corpse someone grabbed, or if you're lucky it's stashed booze.</font><BR><BR><font color=green>Green lights mean the morgue \
system detects the body may be able to be brought back to life.</font></font><BR><font size='2'>I don't know how that works, \
but keep it away from the kitchen and go yell at the coroner.</font><BR><BR>- CentCom medical inspector"
/* Morgue stuff
* Contains:
* Morgue
* Morgue tray
* Crematorium
* Creamatorium
* Crematorium tray
* Crematorium button
*/
/*
* Bodycontainer
* Parent class for morgue and crematorium
* For overriding only
*/
GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties.
/obj/structure/bodycontainer
icon = 'icons/obj/stationobjs.dmi'//ICON OVERRIDEN IN SKYRAT AESTHETICS - SEE MODULE
icon_state = "morgue1"
density = TRUE
anchored = TRUE
max_integrity = 400
pass_flags_self = LETPASSTHROW | PASSSTRUCTURE
dir = SOUTH
///The morgue tray this container will open/close to put/take things in/out.
var/obj/structure/tray/connected
///Boolean on whether we're locked and will not allow the tray to be opened.
var/locked = FALSE
///Cooldown between breakout msesages.
COOLDOWN_DECLARE(breakout_message_cooldown)
/obj/structure/bodycontainer/Initialize(mapload)
. = ..()
if(connected)
connected = new connected(src)
connected.connected = src
GLOB.bodycontainers += src
toggle_organ_decay(src)
register_context()
/obj/structure/bodycontainer/add_context(atom/source, list/context, obj/item/held_item, mob/user)
if(!locked)
context[SCREENTIP_CONTEXT_LMB] = "Open/Close"
return CONTEXTUAL_SCREENTIP_SET
/obj/structure/bodycontainer/Destroy()
GLOB.bodycontainers -= src
open()
if(connected)
QDEL_NULL(connected)
return ..()
/obj/structure/bodycontainer/on_log(login)
..()
update_appearance(UPDATE_ICON)
/obj/structure/bodycontainer/relaymove(mob/living/user, direction)
if(user.stat || !isturf(loc))
return
if(locked)
if(COOLDOWN_FINISHED(src, breakout_message_cooldown))
COOLDOWN_START(src, breakout_message_cooldown, BREAKOUT_COOLDOWN)
to_chat(user, span_warning("[src]'s door won't budge!"))
return
open()
/obj/structure/bodycontainer/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
if(locked)
to_chat(user, span_danger("It's locked."))
return
if(!connected)
to_chat(user, "That doesn't appear to have a tray.")
return
if(connected.loc == src)
open()
else
close()
add_fingerprint(user)
/obj/structure/bodycontainer/attack_paw(mob/user, list/modifiers)
return attack_hand(user, modifiers)
/obj/structure/bodycontainer/attack_robot(mob/user)
if(!user.Adjacent(src))
return
return attack_hand(user)
/obj/structure/bodycontainer/deconstruct(disassembled = TRUE)
if (!(flags_1 & NODECONSTRUCT_1))
new /obj/item/stack/sheet/iron(loc, 5)
toggle_organ_decay(src)
qdel(src)
/obj/structure/bodycontainer/container_resist_act(mob/living/user)
if(!locked)
open()
return
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message(null, \
span_notice("You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(BREAKDOWN_TIME)].)"), \
span_hear("You hear a metallic creaking from [src]."))
if(!do_after(user, BREAKDOWN_TIME, target = src))
return
if(!user || user.stat != CONSCIOUS || user.loc != src)
return
user.visible_message(
span_warning("[user] successfully broke out of [src]!"),
span_notice("You successfully break out of [src]!"),
)
open()
/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 2)
/obj/structure/bodycontainer/proc/open()
toggle_organ_decay(src)
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
playsound(src, 'sound/effects/roll.ogg', 5, TRUE)
var/turf/T = get_step(src, dir)
if (connected)
connected.setDir(dir)
for(var/atom/movable/AM in src)
AM.forceMove(T)
update_appearance()
/obj/structure/bodycontainer/proc/close()
playsound(src, 'sound/effects/roll.ogg', 5, TRUE)
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
for(var/atom/movable/AM in connected.loc)
if(!AM.anchored || AM == connected)
if(isliving(AM))
var/mob/living/living_mob = AM
if(living_mob.incorporeal_move)
continue
else if(istype(AM, /obj/effect/dummy/phased_mob))
continue
AM.forceMove(src)
toggle_organ_decay(src)
update_appearance()
/*
* Morgue
*/
/obj/structure/bodycontainer/morgue
name = "morgue"
desc = "Used to keep bodies in until someone fetches them. Includes a high-tech alert system."
icon_state = "morgue1"
base_icon_state = "morgue"
dir = EAST
connected = /obj/structure/tray/m_tray
/// Whether or not this morgue beeps to alert parameds of revivable corpses.
var/beeper = TRUE
/// The minimum time between beeps.
var/beep_cooldown = 5 SECONDS
/// The cooldown to prevent this from spamming beeps.
COOLDOWN_DECLARE(next_beep)
/obj/structure/bodycontainer/morgue/beeper_off
name = "secure morgue"
desc = "Used to keep bodies in until someone fetches them. Starts with their beeper off."
beeper = FALSE
/obj/structure/bodycontainer/morgue/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
context[SCREENTIP_CONTEXT_ALT_LMB] = "[beeper ? "disable beeper" : "enable beeper"]"
return CONTEXTUAL_SCREENTIP_SET
/obj/structure/bodycontainer/morgue/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs)
. = ..()
if(!istype(arrived, /obj/structure/closet/body_bag))
return
var/obj/structure/closet/body_bag/arrived_bag = arrived
if(!arrived_bag.tag_name)
return
name = "[initial(name)] - ([arrived_bag.tag_name])"
update_appearance(UPDATE_ICON)
/obj/structure/bodycontainer/morgue/Exited(atom/movable/gone, direction)
. = ..()
if(istype(gone, /obj/structure/closet/body_bag))
name = initial(name)
update_appearance(UPDATE_ICON)
/obj/structure/bodycontainer/morgue/examine(mob/user)
. = ..()
. += span_notice("The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it.")
/obj/structure/bodycontainer/morgue/AltClick(mob/user)
..()
if(!user.can_perform_action(src, (ALLOW_SILICON_REACH|ALLOW_RESTING)))
return
beeper = !beeper
to_chat(user, span_notice("You turn the speaker function [beeper ? "on" : "off"]."))
/obj/structure/bodycontainer/morgue/emag_act(mob/user)
if(obj_flags & EMAGGED)
return
to_chat(user, span_warning("You overload [src]'s alert system."))
obj_flags |= EMAGGED
update_appearance(UPDATE_ICON)
/obj/structure/bodycontainer/morgue/update_icon_state()
if(!connected || connected.loc != src) // Open or tray is gone.
icon_state = "morgue0"
return ..()
if(contents.len == 1) // Empty
icon_state = "morgue1"
return ..()
var/list/compiled = get_all_contents_type(/mob/living) // Search for mobs in all contents.
if(!length(compiled)) // No mobs?
icon_state = "morgue3"
return ..()
if(!(obj_flags & EMAGGED))
for(var/mob/living/occupant as anything in compiled)
var/mob/living/mob_occupant = get_mob_or_brainmob(occupant)
if(!mob_occupant.client || HAS_TRAIT(mob_occupant, TRAIT_SUICIDED) || HAS_TRAIT(mob_occupant, TRAIT_BADDNA))
continue
icon_state = "morgue4" // Revivable
if(mob_occupant.stat == DEAD && beeper && COOLDOWN_FINISHED(src, next_beep))
playsound(src, 'sound/weapons/gun/general/empty_alarm.ogg', 50, FALSE) //Revive them you blind fucks
COOLDOWN_START(src, next_beep, beep_cooldown)
return ..()
icon_state = "morgue2" // Dead, brainded mob.
return ..()
/obj/structure/bodycontainer/morgue/update_overlays()
. = ..()
underlays.Cut()
if(name != initial(name))
. += "[base_icon_state]_label"
/*
* Crematorium
*/
GLOBAL_LIST_EMPTY(crematoriums)
/obj/structure/bodycontainer/crematorium
name = "crematorium"
desc = "A human incinerator. Works well on barbecue nights."
icon_state = "crema1"
base_icon_state = "crema"
dir = SOUTH
connected = /obj/structure/tray/c_tray
var/id = 1
/obj/structure/bodycontainer/crematorium/Initialize(mapload)
. = ..()
GLOB.crematoriums += src
/obj/structure/bodycontainer/crematorium/Destroy()
GLOB.crematoriums -= src
return ..()
/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help
to_chat(user, span_warning("[src] is locked against you."))
return
/obj/structure/bodycontainer/crematorium/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
id = "[port.shuttle_id]_[id]"
/obj/structure/bodycontainer/crematorium/update_icon_state()
if(!connected || connected.loc != src)
icon_state = "[base_icon_state]0"
return ..()
if(locked)
icon_state = "[base_icon_state]_active"
return ..()
icon_state = "[base_icon_state][(contents.len > 1) ? 2 : 1]"
return ..()
/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user)
if(locked)
return //don't let you cremate something twice or w/e
// Make sure we don't delete the actual morgue and its tray
var/list/conts = get_all_contents() - src - connected
if(!conts.len)
audible_message(span_hear("You hear a hollow crackle."))
return
else
audible_message(span_hear("You hear a roar as the crematorium activates."))
locked = TRUE
update_appearance()
for(var/mob/living/M in conts)
if(M.incorporeal_move) //can't cook revenants!
continue
if (M.stat != DEAD)
M.emote("scream")
if(user)
log_combat(user, M, "cremated")
else
M.log_message("was cremated", LOG_ATTACK)
if(user.stat != DEAD)
user.investigate_log("has died from being cremated.", INVESTIGATE_DEATHS)
M.death(TRUE)
if(M) //some animals get automatically deleted on death.
M.ghostize()
qdel(M)
for(var/obj/O in conts) //conts defined above, ignores crematorium and tray
// SKYRAT EDIT ADDITION
if(istype(O, /obj/item/goldeneye_key))
continue
// SKYRAT EDIT END
if(istype(O, /obj/effect/dummy/phased_mob)) //they're not physical, don't burn em.
continue
qdel(O)
if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up
new/obj/effect/decal/cleanable/ash(src)
sleep(3 SECONDS)
if(!QDELETED(src))
locked = FALSE
update_appearance()
playsound(src.loc, 'sound/machines/ding.ogg', 50, TRUE) //you horrible people
/obj/structure/bodycontainer/crematorium/creamatorium
name = "creamatorium"
desc = "A human incinerator. Works well during ice cream socials."
/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user)
var/list/icecreams = new()
for(var/mob/living/i_scream as anything in get_all_contents_type(/mob/living))
var/obj/item/food/icecream/IC = new(null, list(ICE_CREAM_MOB = list(null, i_scream.name)))
icecreams += IC
. = ..()
for(var/obj/IC in icecreams)
IC.forceMove(src)
/*
* Generic Tray
* Parent class for morguetray and crematoriumtray
* For overriding only
*/
/obj/structure/tray
icon = 'icons/obj/stationobjs.dmi'
density = TRUE
anchored = TRUE
pass_flags_self = PASSTABLE | LETPASSTHROW
max_integrity = 350
///The bodycontainer we are a tray to.
var/obj/structure/bodycontainer/connected
/obj/structure/tray/Destroy()
if(connected)
connected.connected = null
connected.update_appearance()
connected = null
return ..()
/obj/structure/tray/deconstruct(disassembled = TRUE)
new /obj/item/stack/sheet/iron (loc, 2)
qdel(src)
/obj/structure/tray/attack_paw(mob/user, list/modifiers)
return attack_hand(user, modifiers)
/obj/structure/tray/attack_robot(mob/user, list/modifiers)
return attack_hand(user, modifiers)
/obj/structure/tray/attack_hand(mob/user, list/modifiers)
. = ..()
if(.)
return
if (connected)
connected.close()
else
to_chat(user, span_warning("That's not connected to anything!"))
add_fingerprint(user)
/obj/structure/tray/attackby(obj/P, mob/user, params)
if(!istype(P, /obj/item/riding_offhand))
return ..()
var/obj/item/riding_offhand/riding_item = P
var/mob/living/carried_mob = riding_item.rider
if(carried_mob == user) //Piggyback user.
return
user.unbuckle_mob(carried_mob)
MouseDrop_T(carried_mob, user)
/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user)
if(!ismovable(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user)
return
if(!ismob(O))
if(!istype(O, /obj/structure/closet/body_bag))
return
else
var/mob/M = O
if(M.buckled)
return
if(!ismob(user) || user.incapacitated())
return
if(isliving(user))
var/mob/living/L = user
if(L.body_position == LYING_DOWN)
return
O.forceMove(src.loc)
if (user != O)
visible_message(span_warning("[user] stuffs [O] into [src]."))
return
/*
* Crematorium tray
*/
/obj/structure/tray/c_tray
name = "crematorium tray"
desc = "Apply body before burning."
icon_state = "cremat"
/*
* Morgue tray
*/
/obj/structure/tray/m_tray
name = "morgue tray"
desc = "Apply corpse before closing."
icon_state = "morguet"
pass_flags_self = PASSTABLE | LETPASSTHROW
/obj/structure/tray/m_tray/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(.)
return
if(locate(/obj/structure/table) in get_turf(mover))
return TRUE
#undef BREAKOUT_COOLDOWN
#undef BREAKDOWN_TIME