Files
WaterpigandMajkl-J bb70889f6e TG Upstream Part 1
3591 individual conflicts

Update build.js

Update install_node.sh

Update byond.js

oh my fucking god

hat

slow

huh

holy shit

we all fall down

2 more I missed

2900 individual conflicts

2700 Individual conflicts

replaces yarn file with tg version, bumping us down to 2200-ish

Down to 2000 individual conflicts

140 down

mmm

aaaaaaaaaaaaaaaaaaa

not yt

575

soon

900 individual conflicts

600 individual conflicts, 121 file conflicts

im not okay

160 across 19 files

29 in 4 files

0 conflicts, compiletime fix time

some minor incap stuff

missed ticks

weird dupe definition stuff

missed ticks 2

incap fixes

undefs and pie fix

Radio update and some extra minor stuff

returns a single override

no more dupe definitions, 175 compiletime errors

Unticked file fix

sound and emote stuff

honk and more radio stuff
2024-10-19 08:04:33 -07:00

1226 lines
40 KiB
Plaintext

#define LOCKER_FULL -1
///A comprehensive list of all closets (NOT CRATES) in the game world
GLOBAL_LIST_EMPTY(roundstart_station_closets)
/obj/structure/closet
name = "closet"
desc = "It's a basic storage unit."
icon = 'icons/obj/storage/closet.dmi'
icon_state = "generic"
density = TRUE
drag_slowdown = 1.5 // Same as a prone mob
max_integrity = 200
integrity_failure = 0.25
armor_type = /datum/armor/structure_closet
blocks_emissive = EMISSIVE_BLOCK_GENERIC
/// How close being inside of the thing provides complete pressure safety. Must be between 0 and 1!
contents_pressure_protection = 0
/// How insulated the thing is, for the purposes of calculating body temperature. Must be between 0 and 1!
contents_thermal_insulation = 0
pass_flags_self = PASSSTRUCTURE | LETPASSCLICKS
/// The overlay for the closet's door
var/obj/effect/overlay/closet_door/door_obj
/// Whether or not this door is being animated
var/is_animating_door = FALSE
/// Vertical squish of the door
var/door_anim_squish = 0.2
/// The maximum angle the door will be drawn at
var/door_anim_angle = 140
/// X position of the closet door hinge, relative to the center of the sprite
var/door_hinge_x = -6.5
/// Amount of time it takes for the door animation to play
var/door_anim_time = 2 // set to 0 to make the door not animate at all
/// Paint jobs for this closet, crates are a subtype of closet so they override these values
var/list/paint_jobs = TRUE
/// Controls whether a door overlay should be applied using the icon_door value as the icon state
var/enable_door_overlay = TRUE
var/has_opened_overlay = TRUE
var/has_closed_overlay = TRUE
var/icon_door = null
var/opened = FALSE
var/welded = FALSE
var/locked = FALSE
var/large = TRUE
var/wall_mounted = 0 //never solid (You can always pass over it)
var/breakout_time = 1200
var/message_cooldown
var/can_weld_shut = TRUE
var/horizontal = FALSE
var/allow_objects = FALSE
var/allow_dense = FALSE
var/dense_when_open = FALSE //if it's dense when open or not
var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
var/cutting_tool = /obj/item/weldingtool
var/open_sound = 'sound/machines/closet_open.ogg'
var/close_sound = 'sound/machines/closet_close.ogg'
var/open_sound_volume = 35
var/close_sound_volume = 50
var/material_drop = /obj/item/stack/sheet/iron
var/material_drop_amount = 2
var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
var/anchorable = TRUE
var/icon_welded = "welded"
var/icon_broken = "sparking"
/// Whether a skittish person can dive inside this closet. Disable if opening the closet causes "bad things" to happen or that it leads to a logical inconsistency.
var/divable = TRUE
/// true whenever someone with the strong pull component (or magnet modsuit module) is dragging this, preventing opening
var/strong_grab = FALSE
/// secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
var/secure = FALSE
var/can_install_electronics = TRUE
var/is_maploaded = FALSE
var/contents_initialized = FALSE
/// is this closet locked by an exclusive id, i.e. your own personal locker
var/datum/weakref/id_card = null
/// should we prevent further access change
var/access_locked = FALSE
/// is the card reader installed in this machine
var/card_reader_installed = FALSE
/// access types for card reader
var/list/access_choices = TRUE
/// Whether this closet is sealed or not. If sealed, it'll have its own internal air
var/sealed = FALSE
/// Internal gas for this closet.
var/datum/gas_mixture/internal_air
/// Volume of the internal air
var/air_volume = TANK_STANDARD_VOLUME * 3
/// How many pixels the closet can shift on the x axis when shaking
var/x_shake_pixel_shift = 2
/// how many pixels the closet can shift on the y axes when shaking
var/y_shake_pixel_shift = 1
/datum/armor/structure_closet
melee = 20
bullet = 10
laser = 10
bomb = 10
fire = 70
acid = 60
/obj/structure/closet/Initialize(mapload)
. = ..()
var/static/list/closet_paint_jobs
if(isnull(closet_paint_jobs))
closet_paint_jobs = list(
"Cargo" = list("icon_state" = "qm"),
"Engineering" = list("icon_state" = "ce"),
"Engineering Secure" = list("icon_state" = "eng_secure"),
"Radiation" = list("icon_state" = "eng", "icon_door" = "eng_rad"),
"Tool Storage" = list("icon_state" = "eng", "icon_door" = "eng_tool"),
"Fire Equipment" = list("icon_state" = "fire"),
"Emergency" = list("icon_state" = "emergency"),
"Hydroponics" = list("icon_state" = "hydro"),
"Medical" = list("icon_state" = "med"),
"Science" = list("icon_state" = "rd"),
"Security" = list("icon_state" = "cap"),
"Mining" = list("icon_state" = "mining"),
"Virology" = list("icon_state" = "bio_viro"),
)
if(paint_jobs)
paint_jobs = closet_paint_jobs
var/static/list/card_reader_choices
if(isnull(card_reader_choices))
card_reader_choices = list(
"Personal",
"Departmental",
"None"
)
if(access_choices)
access_choices = card_reader_choices
if(is_station_level(z) && mapload)
add_to_roundstart_list()
// if closed, any item at the crate's loc is put in the contents
if (mapload)
is_maploaded = TRUE
. = INITIALIZE_HINT_LATELOAD
populate_contents_immediate()
var/static/list/loc_connections = list(
COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(locker_living),
COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock),
)
AddElement(/datum/element/connect_loc, loc_connections)
register_context()
if(opened)
opened = FALSE //nessassary because open() proc will early return if its true
if(open(special_effects = FALSE)) //closets which are meant to be open by default dont need to be animated open
return
update_appearance()
/obj/structure/closet/LateInitialize()
if(!opened && is_maploaded)
take_contents()
if(sealed)
var/datum/gas_mixture/external_air = loc.return_air()
if(external_air && is_maploaded)
internal_air = external_air.copy()
else
internal_air = new()
START_PROCESSING(SSobj, src)
/obj/structure/closet/return_air()
if(sealed)
return internal_air
else
return ..()
//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
/obj/structure/closet/proc/PopulateContents()
return
/// Populate the closet with stuff that needs to be added before it is opened.
/// This is useful for things like traitor objectives.
/obj/structure/closet/proc/populate_contents_immediate()
return
/obj/structure/closet/Destroy()
id_card = null
QDEL_NULL(internal_air)
QDEL_NULL(door_obj)
GLOB.roundstart_station_closets -= src
return ..()
/obj/structure/closet/process(seconds_per_tick)
if(!sealed)
return PROCESS_KILL
process_internal_air(seconds_per_tick)
/obj/structure/closet/proc/process_internal_air(seconds_per_tick)
if(opened)
var/datum/gas_mixture/current_exposed_air = loc.return_air()
if(!current_exposed_air)
return
if(current_exposed_air.equalize(internal_air))
var/turf/location = get_turf(src)
location.air_update_turf()
/obj/structure/closet/update_appearance(updates=ALL)
. = ..()
if(opened || broken || !secure)
luminosity = 0
return
luminosity = 1
/obj/structure/closet/update_icon()
. = ..()
if(issupplypod(src))
return
layer = opened ? BELOW_OBJ_LAYER : OBJ_LAYER
/obj/structure/closet/update_overlays()
. = ..()
closet_update_overlays(.)
/obj/structure/closet/proc/closet_update_overlays(list/new_overlays)
. = new_overlays
if(enable_door_overlay && !is_animating_door)
var/overlay_state = isnull(base_icon_state) ? initial(icon_state) : base_icon_state
if(opened && has_opened_overlay)
var/mutable_appearance/door_overlay = mutable_appearance(icon, "[overlay_state]_open", alpha = src.alpha)
. += door_overlay
door_overlay.overlays += emissive_blocker(door_overlay.icon, door_overlay.icon_state, src, alpha = door_overlay.alpha) // If we don't do this the door doesn't block emissives and it looks weird.
else if(has_closed_overlay)
. += "[icon_door || overlay_state]_door"
if(opened)
return
if(welded)
. += icon_welded
if(broken && secure)
. += mutable_appearance(icon, icon_broken, alpha = alpha)
. += emissive_appearance(icon, icon_broken, src, alpha = alpha)
return
if(broken || !secure)
return
//Overlay is similar enough for both that we can use the same mask for both
. += emissive_appearance(icon, "locked", src, alpha = src.alpha)
. += locked ? "locked" : "unlocked"
/obj/structure/closet/vv_edit_var(vname, vval)
if(vname == NAMEOF(src, opened))
if(vval == opened)
return FALSE
if(vval && !opened && open(force = TRUE))
datum_flags |= DF_VAR_EDITED
return TRUE
else if(!vval && opened && close())
datum_flags |= DF_VAR_EDITED
return TRUE
return FALSE
. = ..()
if(vname == NAMEOF(src, welded) && welded && !can_weld_shut)
can_weld_shut = TRUE
else if(vname == NAMEOF(src, can_weld_shut) && !can_weld_shut && welded)
welded = FALSE
update_appearance()
if(vname in list(NAMEOF(src, locked), NAMEOF(src, welded), NAMEOF(src, secure), NAMEOF(src, icon_welded), NAMEOF(src, delivery_icon)))
update_appearance()
/// Animates the closet door opening and closing
/obj/structure/closet/proc/animate_door(closing = FALSE)
if(!door_anim_time)
return
if(!door_obj)
door_obj = new
var/default_door_icon = "[icon_door || icon_state]_door"
vis_contents += door_obj
door_obj.icon = icon
door_obj.icon_state = default_door_icon
is_animating_door = TRUE
var/num_steps = door_anim_time / world.tick_lag
for(var/step in 0 to num_steps)
var/angle = door_anim_angle * (closing ? 1 - (step/num_steps) : (step/num_steps))
var/matrix/door_transform = get_door_transform(angle)
var/door_state
var/door_layer
if (angle >= 90)
door_state = "[icon_state]_back"
door_layer = FLOAT_LAYER
else
door_state = default_door_icon
door_layer = ABOVE_MOB_LAYER
if(step == 0)
door_obj.transform = door_transform
door_obj.icon_state = door_state
door_obj.layer = door_layer
else if(step == 1)
animate(door_obj, transform = door_transform, icon_state = door_state, layer = door_layer, time = world.tick_lag, flags = ANIMATION_END_NOW)
else
animate(transform = door_transform, icon_state = door_state, layer = door_layer, time = world.tick_lag)
addtimer(CALLBACK(src, PROC_REF(end_door_animation)), door_anim_time, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_CLIENT_TIME)
/// Ends the door animation and removes the animated overlay
/obj/structure/closet/proc/end_door_animation()
is_animating_door = FALSE
vis_contents -= door_obj
update_icon()
/// Calculates the matrix to be applied to the animated door overlay
/obj/structure/closet/proc/get_door_transform(angle)
var/matrix/door_matrix = matrix()
door_matrix.Translate(-door_hinge_x, 0)
door_matrix.Multiply(matrix(cos(angle), 0, 0, -sin(angle) * door_anim_squish, 1, 0))
door_matrix.Translate(door_hinge_x, 0)
return door_matrix
/obj/structure/closet/examine(mob/user)
. = ..()
if(id_card)
. += span_notice("It can be [EXAMINE_HINT("marked")] with a pen.")
if(can_weld_shut && !welded)
. += span_notice("Its can be [EXAMINE_HINT("welded")] shut.")
if(welded)
. += span_notice("Its [EXAMINE_HINT("welded")] shut.")
if(anchorable && !anchored)
. += span_notice("It can be [EXAMINE_HINT("bolted")] to the ground.")
if(anchored)
. += span_notice("It's [anchorable ? EXAMINE_HINT("bolted") : "attached firmly"] to the ground.")
if(length(paint_jobs))
. += span_notice("It can be [EXAMINE_HINT("painted")] another texture.")
if(HAS_TRAIT(user, TRAIT_SKITTISH) && divable)
. += span_notice("If you bump into [p_them()] while running, you will jump inside.")
if(can_install_electronics)
if(!secure)
. += span_notice("You can install airlock electronics for access control.")
else
. += span_notice("Its airlock electronics are [EXAMINE_HINT("screwed")] in place.")
if(!card_reader_installed && length(access_choices))
. += span_notice("You can install a card reader for further access control.")
else if(card_reader_installed)
. += span_notice("The card reader could be [EXAMINE_HINT("pried")] out.")
. += span_notice("Swipe your PDA with an ID card/Just ID to change access levels.")
. += span_notice("Use multitool to [access_locked ? "unlock" : "lock"] the access panel.")
/obj/structure/closet/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
var/screentip_change = FALSE
if(isnull(held_item))
if(secure && !broken)
context[SCREENTIP_CONTEXT_RMB] = opened ? "Lock" : "Unlock"
if(!welded)
context[SCREENTIP_CONTEXT_LMB] = opened ? "Close" : "Open"
screentip_change = TRUE
if(istype(held_item, cutting_tool))
if(opened)
context[SCREENTIP_CONTEXT_LMB] = "Deconstruct"
screentip_change = TRUE
else
if(!welded && can_weld_shut)
context[SCREENTIP_CONTEXT_LMB] = "Weld"
screentip_change = TRUE
else if(welded)
context[SCREENTIP_CONTEXT_LMB] = "Unweld"
screentip_change = TRUE
if(istype(held_item) && held_item.tool_behaviour == TOOL_WRENCH && anchorable)
context[SCREENTIP_CONTEXT_RMB] = anchored ? "Unanchor" : "Anchor"
screentip_change = TRUE
if(!locked && !opened && (welded || !can_weld_shut))
if(!secure)
if(!broken && can_install_electronics && istype(held_item, /obj/item/electronics/airlock))
context[SCREENTIP_CONTEXT_LMB] = "Install Electronics"
screentip_change = TRUE
else
if(istype(held_item) && held_item.tool_behaviour == TOOL_SCREWDRIVER)
context[SCREENTIP_CONTEXT_LMB] = "Remove Electronics"
screentip_change = TRUE
if(!card_reader_installed && length(access_choices) && !broken && can_install_electronics && istype(held_item, /obj/item/stock_parts/card_reader))
context[SCREENTIP_CONTEXT_LMB] = "Install Reader"
screentip_change = TRUE
if(card_reader_installed && istype(held_item) && held_item.tool_behaviour == TOOL_CROWBAR)
context[SCREENTIP_CONTEXT_LMB] = "Remove Reader"
screentip_change = TRUE
if(!locked && !opened)
if(id_card && IS_WRITING_UTENSIL(held_item))
context[SCREENTIP_CONTEXT_LMB] = "Rename"
screentip_change = TRUE
if(secure && card_reader_installed && !broken)
if(!access_locked && istype(held_item) && !isnull(held_item.GetID()))
context[SCREENTIP_CONTEXT_LMB] = "Change Access"
screentip_change = TRUE
if(istype(held_item) && istype(held_item) && held_item.tool_behaviour == TOOL_MULTITOOL)
context[SCREENTIP_CONTEXT_LMB] = "[access_locked ? "Unlock" : "Lock"] Access Panel"
screentip_change = TRUE
return screentip_change ? CONTEXTUAL_SCREENTIP_SET : NONE
/obj/structure/closet/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(wall_mounted)
return TRUE
/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE)
if(force)
return TRUE
if(welded || locked)
return FALSE
if(strong_grab)
if(user)
to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening."))
return FALSE
var/turf/T = get_turf(src)
for(var/mob/living/L in T)
if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
if(user)
to_chat(user, span_danger("There's something large on top of [src], preventing it from opening."))
return FALSE
return TRUE
/obj/structure/closet/proc/can_close(mob/living/user)
var/turf/T = get_turf(src)
for(var/obj/structure/closet/closet in T)
if(closet != src && !closet.wall_mounted)
if(user)
balloon_alert(user, "[closet.name] is in the way!")
return FALSE
for(var/mob/living/L in T)
if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
if(user)
to_chat(user, span_danger("There's something too large in [src], preventing it from closing."))
return FALSE
return TRUE
/obj/structure/closet/dump_contents()
if (!contents_initialized)
contents_initialized = TRUE
PopulateContents()
SEND_SIGNAL(src, COMSIG_CLOSET_CONTENTS_INITIALIZED)
var/atom/L = drop_location()
for(var/atom/movable/AM in src)
AM.forceMove(L)
if(throwing) // you keep some momentum when getting out of a thrown closet
step(AM, dir)
if(throwing)
throwing.finalize(FALSE)
/obj/structure/closet/proc/take_contents(mapload = FALSE)
var/atom/location = drop_location()
if(!location)
return
for(var/atom/movable/AM in location)
if(AM != src && insert(AM, mapload) == LOCKER_FULL) // limit reached
if(mapload) // Yea, it's a mapping issue. Blame mappers.
log_mapping("Closet storage capacity of [type] exceeded on mapload at [AREACOORD(src)]")
break
for(var/i in reverse_range(location.get_all_contents()))
var/atom/movable/thing = i
thing.atom_storage?.close_all()
///Proc to write checks before opening a door
/obj/structure/closet/proc/before_open(mob/living/user, force)
return TRUE
/obj/structure/closet/proc/open(mob/living/user, force = FALSE, special_effects = TRUE)
if(opened || !can_open(user, force))
return FALSE
if(!before_open(user, force) || (SEND_SIGNAL(src, COMSIG_CLOSET_PRE_OPEN, user, force) & BLOCK_OPEN))
return FALSE
welded = FALSE
locked = FALSE
if(special_effects)
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
opened = TRUE
if(!dense_when_open)
set_density(FALSE)
dump_contents()
if(special_effects)
animate_door(FALSE)
update_appearance()
after_open(user, force)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_OPEN, user, force)
return TRUE
///Proc to override for effects after opening a door
/obj/structure/closet/proc/after_open(mob/living/user, force = FALSE)
return
/obj/structure/closet/proc/insert(atom/movable/inserted, mapload = FALSE)
if(length(contents) >= storage_capacity)
if(!mapload)
return LOCKER_FULL
//For maploading, we only return LOCKER_FULL if the movable was otherwise insertable. This way we can avoid logging false flags.
return insertion_allowed(inserted) ? LOCKER_FULL : FALSE
if(!insertion_allowed(inserted))
return FALSE
if(SEND_SIGNAL(src, COMSIG_CLOSET_INSERT, inserted) & COMPONENT_CLOSET_INSERT_INTERRUPT)
return TRUE
inserted.forceMove(src)
return TRUE
/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
if(ismob(AM))
if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets...
return FALSE
var/mob/living/L = AM
if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
return FALSE
if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
if(horizontal && L.density)
return FALSE
if(L.mob_size > max_mob_size)
return FALSE
var/mobs_stored = 0
for(var/mob/living/M in contents)
mobs_stored++
if(mobs_stored >= mob_storage_capacity)
return FALSE
L.stop_pulling()
else if(istype(AM, /obj/structure/closet))
return FALSE
else if(isobj(AM))
if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs() || ismecha(AM))
return FALSE
else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
return TRUE
else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
return FALSE
else
return FALSE
return TRUE
///Proc to write checks before closing a door
/obj/structure/closet/proc/before_close(mob/living/user)
return TRUE
/obj/structure/closet/proc/close(mob/living/user)
if(!opened || !can_close(user))
return FALSE
if(!before_close(user) || (SEND_SIGNAL(src, COMSIG_CLOSET_PRE_CLOSE, user) & BLOCK_CLOSE))
return FALSE
take_contents()
playsound(loc, close_sound, close_sound_volume, TRUE, -3)
opened = FALSE
set_density(TRUE)
animate_door(TRUE)
update_appearance()
after_close(user)
SEND_SIGNAL(src, COMSIG_CLOSET_POST_CLOSE, user)
return TRUE
///Proc to do effects after closet has closed
/obj/structure/closet/proc/after_close(mob/living/user)
return
/**
* Toggles a closet open or closed, to the opposite state. Does not respect locked or welded states, however.
*/
/obj/structure/closet/proc/toggle(mob/living/user)
if(opened)
return close(user)
else
return open(user)
/obj/structure/closet/handle_deconstruct(disassembled)
dump_contents()
if(obj_flags & NO_DEBRIS_AFTER_DECONSTRUCTION)
return
if(ispath(material_drop) && material_drop_amount)
new material_drop(loc, material_drop_amount)
if (secure)
var/obj/item/electronics/airlock/electronics = new(drop_location())
if(length(req_one_access))
electronics.one_access = TRUE
electronics.accesses = req_one_access
else
electronics.accesses = req_access
if(card_reader_installed)
new /obj/item/stock_parts/card_reader(drop_location())
/obj/structure/closet/atom_break(damage_flag)
. = ..()
if(!broken)
bust_open()
/obj/structure/closet/CheckParts(list/parts_list)
var/obj/item/electronics/airlock/access_control = locate() in parts_list
if(QDELETED(access_control))
return
if (access_control.one_access)
req_one_access = access_control.accesses
req_access = null
else
req_access = access_control.accesses
req_one_access = null
access_control.moveToNullspace()
parts_list -= access_control
qdel(access_control)
/obj/structure/closet/multitool_act(mob/living/user, obj/item/tool)
if(!secure || !card_reader_installed || broken || locked || opened)
return
access_locked = !access_locked
balloon_alert(user, "access panel [access_locked ? "locked" : "unlocked"]")
return TRUE
/// sets the access for the closets from the swiped ID card
/obj/structure/closet/proc/set_access(list/accesses)
if(length(req_one_access))
req_one_access = accesses
req_access = null
else
req_access = accesses
req_one_access = null
/obj/structure/closet/attackby(obj/item/W, mob/user, params)
if(user in src)
return
if(src.tool_interact(W,user))
return 1 // No afterattack
else
return ..()
/// check if we can install airlock electronics in this closet
/obj/structure/closet/proc/can_install_airlock_electronics(mob/user)
if(secure || !can_install_electronics || opened)
return FALSE
if(broken)
balloon_alert(user, "its broken!")
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// check if we can unscrew airlock electronics from this closet
/obj/structure/closet/proc/can_unscrew_airlock_electronics(mob/user)
if(!secure || opened)
return FALSE
if(card_reader_installed)
balloon_alert(user, "attached to reader!")
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// check if we can install card reader in this closet
/obj/structure/closet/proc/can_install_card_reader(mob/user)
if(card_reader_installed || !can_install_electronics || !length(access_choices) || opened)
return FALSE
if(broken)
balloon_alert(user, "its broken!")
return FALSE
if(!secure)
balloon_alert(user, "no electronics inside!")
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// check if we can pry out the card reader from this closet
/obj/structure/closet/proc/can_pryout_card_reader(mob/user)
if(!card_reader_installed || opened)
return FALSE
if(locked)
balloon_alert(user, "unlock first!")
return FALSE
return TRUE
/// returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise
/obj/structure/closet/proc/tool_interact(obj/item/weapon, mob/living/user)
. = TRUE
var/obj/item/card/id/id = null
if(!opened && istype(weapon, /obj/item/airlock_painter))
if(!length(paint_jobs))
return
var/choice = tgui_input_list(user, "Set Closet Paintjob", "Paintjob", paint_jobs)
if(isnull(choice))
return
var/obj/item/airlock_painter/painter = weapon
if(!painter.use_paint(user))
return
var/list/paint_job = paint_jobs[choice]
icon_state = paint_job["icon_state"]
base_icon_state = icon_state
icon_door = paint_job["icon_door"]
update_appearance()
else if(istype(weapon, /obj/item/electronics/airlock) && can_install_airlock_electronics(user))
user.visible_message(span_notice("[user] installs the electronics into the [src]."),\
span_notice("You start to install electronics into the [src]..."))
if(!do_after(user, 4 SECONDS, target = src, extra_checks = CALLBACK(src, PROC_REF(can_install_airlock_electronics), user)))
return
if(!user.transferItemToLoc(weapon, src))
return
CheckParts(list(weapon))
secure = TRUE
balloon_alert(user, "electronics installed")
update_appearance()
else if(weapon.tool_behaviour == TOOL_SCREWDRIVER && can_unscrew_airlock_electronics(user))
user.visible_message(span_notice("[user] begins to remove the electronics from the [src]."),\
span_notice("You begin to remove the electronics from the [src]..."))
if (!weapon.use_tool(src, user, 40, volume = 50, extra_checks = CALLBACK(src, PROC_REF(can_unscrew_airlock_electronics), user)))
return
var/obj/item/electronics/airlock/airlock_electronics = new(drop_location())
if(length(req_one_access))
airlock_electronics.one_access = TRUE
airlock_electronics.accesses = req_one_access
else
airlock_electronics.accesses = req_access
req_access = list()
req_one_access = null
id_card = null
secure = FALSE
balloon_alert(user, "electronics removed")
update_appearance()
else if(istype(weapon, /obj/item/stock_parts/card_reader) && can_install_card_reader(user))
user.visible_message(span_notice("[user] is installing a card reader."),
span_notice("You begin installing the card reader."))
if(!do_after(user, 4 SECONDS, target = src, extra_checks = CALLBACK(src, PROC_REF(can_install_card_reader), user)))
return
qdel(weapon)
card_reader_installed = TRUE
balloon_alert(user, "card reader installed")
else if(weapon.tool_behaviour == TOOL_CROWBAR && can_pryout_card_reader(user))
user.visible_message(span_notice("[user] begins to pry the card reader out from [src]."),\
span_notice("You begin to pry the card reader out from [src]..."))
if(!weapon.use_tool(src, user, 4 SECONDS, extra_checks = CALLBACK(src, PROC_REF(can_pryout_card_reader), user)))
return
new /obj/item/stock_parts/card_reader(drop_location())
card_reader_installed = FALSE
balloon_alert(user, "card reader removed")
else if(secure && !broken && card_reader_installed && !locked && !opened && !access_locked && !isnull((id = weapon.GetID())))
var/num_choices = length(access_choices)
if(!num_choices)
return
var/choice
if(num_choices == 1)
choice = access_choices[1]
else
choice = tgui_input_list(user, "Set Access Type", "Access Type", access_choices)
if(isnull(choice))
return
id_card = null
switch(choice)
if("Personal") //only the player who swiped their id has access.
id_card = WEAKREF(id)
name = "[id.registered_name] locker"
desc = "now owned by [id.registered_name]. [initial(desc)]"
if("Departmental") //anyone who has the same access permissions as this id has access
name = "[id.assignment] closet"
desc = "Its a [id.assignment] closet. [initial(desc)]"
set_access(id.GetAccess())
if("None") //free for all
name = initial(name)
desc = initial(desc)
req_access = list()
req_one_access = null
set_access(list())
if(!isnull(id_card))
balloon_alert(user, "now owned by [id.registered_name]")
else
balloon_alert(user, "set to [choice]")
else if(!opened && IS_WRITING_UTENSIL(weapon))
if(locked)
balloon_alert(user, "unlock first!")
return
if(isnull(id_card))
balloon_alert(user, "not yours to rename!")
return
var/name_set = FALSE
var/desc_set = FALSE
var/str = tgui_input_text(user, "Personal Locker Name", "Locker Name")
if(!isnull(str))
name = str
name_set = TRUE
str = tgui_input_text(user, "Personal Locker Description", "Locker Description")
if(!isnull(str))
desc = str
desc_set = TRUE
var/bit_flag = NONE
if(name_set)
bit_flag |= UPDATE_NAME
if(desc_set)
bit_flag |= UPDATE_DESC
if(bit_flag)
update_appearance(bit_flag)
else if(opened)
if(istype(weapon, cutting_tool))
if(weapon.tool_behaviour == TOOL_WELDER)
if(!weapon.tool_start_check(user, amount=1))
return
to_chat(user, span_notice("You begin cutting \the [src] apart..."))
if(weapon.use_tool(src, user, 40, volume=50))
if(!opened)
return
user.visible_message(span_notice("[user] slices apart \the [src]."),
span_notice("You cut \the [src] apart with \the [weapon]."),
span_hear("You hear welding."))
deconstruct(TRUE)
return
else // for example cardboard box is cut with wirecutters
user.visible_message(span_notice("[user] cut apart \the [src]."), \
span_notice("You cut \the [src] apart with \the [weapon]."))
deconstruct(TRUE)
return
if (user.combat_mode)
return
if(user.transferItemToLoc(weapon, drop_location())) // so we put in unlit welder too
return
else if(weapon.tool_behaviour == TOOL_WELDER && can_weld_shut)
if(!weapon.tool_start_check(user, amount=1))
return
if(weapon.use_tool(src, user, 40, volume=50))
if(opened)
return
welded = !welded
after_weld(welded)
user.visible_message(span_notice("[user] [welded ? "welds shut" : "unwelded"] \the [src]."),
span_notice("You [welded ? "weld" : "unwelded"] \the [src] with \the [weapon]."),
span_hear("You hear welding."))
user.log_message("[welded ? "welded":"unwelded"] closet [src] with [weapon]", LOG_GAME)
update_appearance()
else if(!user.combat_mode)
var/item_is_id = weapon.GetID()
if(!item_is_id)
return FALSE
if((item_is_id || !toggle(user)) && !opened)
togglelock(user)
else
return FALSE
/obj/structure/closet/wrench_act_secondary(mob/living/user, obj/item/tool)
if(!anchorable)
balloon_alert(user, "no anchor bolts!")
return TRUE
if(isinspace() && !anchored) // We want to prevent anchoring a locker in space, but we should still be able to unanchor it there
balloon_alert(user, "nothing to anchor to!")
return TRUE
set_anchored(!anchored)
tool.play_tool_sound(src, 75)
user.balloon_alert_to_viewers("[anchored ? "anchored" : "unanchored"]")
return TRUE
/obj/structure/closet/proc/after_weld(weld_state)
return
/obj/structure/closet/mouse_drop_receive(atom/movable/O, mob/living/user, params)
if(!istype(O) || O.anchored || istype(O, /atom/movable/screen))
return
if(!istype(user) || user.incapacitated || user.body_position == LYING_DOWN)
return
if(user == O) //try to climb onto it
return ..()
if(!opened)
return
if(!isturf(O.loc))
return
var/actuallyismob = 0
if(isliving(O))
actuallyismob = 1
else if(!isitem(O))
return
var/turf/T = get_turf(src)
add_fingerprint(user)
user.visible_message(span_warning("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src]."), \
span_warning("You [actuallyismob ? "try to ":""]stuff [O] into [src]."), \
span_hear("You hear clanging."))
if(actuallyismob)
if(do_after(user, 4 SECONDS, O))
user.visible_message(span_notice("[user] stuffs [O] into [src]."), \
span_notice("You stuff [O] into [src]."), \
span_hear("You hear a loud metal bang."))
var/mob/living/L = O
if(!issilicon(L))
L.Paralyze(40)
if(istype(src, /obj/structure/closet/supplypod/extractionpod))
O.forceMove(src)
else
O.forceMove(T)
close()
log_combat(user, O, "stuffed", addition = "inside of [src]")
else
O.forceMove(T)
/obj/structure/closet/relaymove(mob/living/user, direction)
if(user.stat || !isturf(loc))
return
if(locked)
if(message_cooldown <= world.time)
message_cooldown = world.time + 50
to_chat(user, span_warning("[src]'s door won't budge!"))
return
container_resist_act(user)
/obj/structure/closet/attack_hand(mob/living/user, list/modifiers)
. = ..()
if(.)
return
if(user.body_position == LYING_DOWN && get_dist(src, user) > 0)
return
if(toggle(user))
return
if(!opened)
togglelock(user)
/obj/structure/closet/attack_paw(mob/user, list/modifiers)
return attack_hand(user, modifiers)
/obj/structure/closet/attack_robot(mob/user)
if(user.Adjacent(src))
return attack_hand(user)
/obj/structure/closet/attack_robot_secondary(mob/user, list/modifiers)
if(!user.Adjacent(src))
return SECONDARY_ATTACK_CONTINUE_CHAIN
togglelock(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
// tk grab then use on self
/obj/structure/closet/attack_self_tk(mob/user)
if(attack_hand(user))
return ITEM_INTERACT_BLOCKING
/obj/structure/closet/verb/verb_toggleopen()
set src in view(1)
set category = "Object"
set name = "Toggle Open"
if(!usr.can_perform_action(src) || !isturf(loc))
return
if(iscarbon(usr) || issilicon(usr) || isdrone(usr))
return toggle(usr)
else
to_chat(usr, span_warning("This mob type can't use this verb."))
// Objects that try to exit a locker by stepping were doing so successfully,
// and due to an oversight in turf/Enter() were going through walls. That
// should be independently resolved, but this is also an interesting twist.
/obj/structure/closet/Exit(atom/movable/leaving, direction)
open()
if(leaving.loc == src)
return FALSE
return TRUE
/obj/structure/closet/container_resist_act(mob/living/user, loc_required = TRUE)
if(isstructure(loc))
relay_container_resist_act(user, loc)
if(opened)
return
if(ismovable(loc))
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
var/atom/movable/AM = loc
AM.relay_container_resist_act(user, src)
return
if(!welded && !locked)
open()
return
//okay, so the closet is either welded or locked... resist!!!
user.changeNext_move(CLICK_CD_BREAKOUT)
user.last_special = world.time + CLICK_CD_BREAKOUT
user.visible_message(span_warning("[src] begins to shake violently!"), \
span_notice("You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)"), \
span_hear("You hear banging from [src]."))
addtimer(CALLBACK(src, PROC_REF(check_if_shake)), 1 SECONDS)
if(do_after(user,(breakout_time), target = src))
if(!user || user.stat != CONSCIOUS || (loc_required && (user.loc != src)) || opened || (!locked && !welded) )
return
//we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting
user.visible_message(span_danger("[user] successfully broke out of [src]!"),
span_notice("You successfully break out of [src]!"))
bust_open()
else
if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
to_chat(user, span_warning("You fail to break out of [src]!"))
/obj/structure/closet/relay_container_resist_act(mob/living/user, obj/container)
container.container_resist_act()
/// Check if someone is still resisting inside, and choose to either keep shaking or stop shaking the closet
/obj/structure/closet/proc/check_if_shake()
// Assuming we decide to shake again, how long until we check to shake again
var/next_check_time = 1 SECONDS
// How long we shake between different calls of Shake(), so that it starts shaking and stops, instead of a steady shake
var/shake_duration = 0.3 SECONDS
for(var/mob/living/mob in contents)
if(DOING_INTERACTION_WITH_TARGET(mob, src))
// Shake and queue another check_if_shake
Shake(x_shake_pixel_shift, y_shake_pixel_shift, shake_duration, shake_interval = 0.1 SECONDS)
addtimer(CALLBACK(src, PROC_REF(check_if_shake)), next_check_time)
return TRUE
// If we reach here, nobody is resisting, so dont shake
return FALSE
/obj/structure/closet/proc/bust_open()
SIGNAL_HANDLER
welded = FALSE //applies to all lockers
locked = FALSE //applies to critter crates and secure lockers only
broken = TRUE //applies to secure lockers only
open(force = TRUE, special_effects = FALSE)
/obj/structure/closet/attack_hand_secondary(mob/user, modifiers)
. = ..()
if(!user.can_perform_action(src) || !isturf(loc))
return
if(!opened && secure)
togglelock(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/**
* returns TRUE if the closet is allowed to unlock
* * user: the player trying to unlock this closet
* * player_id: the id of the player trying to unlock this closet
* * registered_id: the id registered to this closet, null if no one registered
*/
/obj/structure/closet/proc/can_unlock(mob/living/user, obj/item/card/id/player_id, obj/item/card/id/registered_id)
if(isnull(registered_id))
return allowed(user)
return player_id == registered_id
/obj/structure/closet/proc/togglelock(mob/living/user, silent)
if(!secure || broken)
return
if(locked) //only apply checks while unlocking else allow anyone to lock it
var/error_msg = ""
if(!isnull(id_card))
var/obj/item/card/id/registered_id = id_card.resolve()
if(!registered_id) //id was deleted at some point. make this closet public access again
name = initial(name)
desc = initial(desc)
id_card = null
req_access = list()
req_one_access = null
togglelock(user, silent)
return
if(!can_unlock(user, user.get_idcard(), registered_id))
error_msg = "not your locker!"
else if(!can_unlock(user, user.get_idcard()))
error_msg = "access denied!"
if(error_msg)
if(!silent)
balloon_alert(user, error_msg)
return
if(iscarbon(user))
add_fingerprint(user)
locked = !locked
user.visible_message(
span_notice("[user] [locked ? "locks" : "unlocks"] [src]."),
span_notice("You [locked ? "locked" : "unlocked"] [src]."),
)
update_appearance()
/obj/structure/closet/emag_act(mob/user, obj/item/card/emag/emag_card)
if(secure && !broken)
visible_message(span_warning("Sparks fly from [src]!"), blind_message = span_hear("You hear a faint electrical spark."))
balloon_alert(user, "lock broken open")
playsound(src, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
broken = TRUE
locked = FALSE
update_appearance()
return TRUE
return FALSE
/obj/structure/closet/get_remote_view_fullscreens(mob/user)
if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
user.overlay_fullscreen("remote_view", /atom/movable/screen/fullscreen/impaired, 1)
/obj/structure/closet/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
if (!(. & EMP_PROTECT_CONTENTS))
for(var/obj/O in src)
O.emp_act(severity)
if(secure && !broken && !(. & EMP_PROTECT_SELF))
if(prob(50 / severity))
locked = !locked
update_appearance()
if(prob(20 / severity) && !opened)
if(!locked)
open()
else
req_access = list()
req_access += pick(SSid_access.get_region_access_list(list(REGION_ALL_STATION)))
/obj/structure/closet/contents_explosion(severity, target)
switch(severity)
if(EXPLODE_DEVASTATE)
SSexplosions.high_mov_atom += contents
if(EXPLODE_HEAVY)
SSexplosions.med_mov_atom += contents
if(EXPLODE_LIGHT)
SSexplosions.low_mov_atom += contents
/obj/structure/closet/singularity_act()
dump_contents()
..()
/obj/structure/closet/AllowDrop()
return TRUE
/obj/structure/closet/return_temperature()
return
/obj/structure/closet/proc/locker_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon)
SIGNAL_HANDLER
if(!opened && (locked || welded)) //Yes this could be less code, no I don't care
return
if(!opened && ((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED)))
return
var/was_opened = opened
if(!toggle())
return
if(was_opened)
if (!target.Move(get_turf(src), get_dir(target, src)))
return
target.forceMove(src)
else
target.Knockdown(SHOVE_KNOCKDOWN_SOLID)
update_icon()
target.visible_message(span_danger("[shover.name] shoves [target.name] into [src]!"),
span_userdanger("You're shoved into [src] by [shover.name]!"),
span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover)
to_chat(src, span_danger("You shove [target.name] into [src]!"))
log_combat(shover, target, "shoved", "into [src] (locker/crate)[weapon ? " with [weapon]" : ""]")
return COMSIG_LIVING_SHOVE_HANDLED
/// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted.
/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, atom/caster)
SIGNAL_HANDLER
locked = FALSE
INVOKE_ASYNC(src, PROC_REF(open))
/obj/structure/closet/preopen
opened = TRUE
///Adds the closet to a global list. Placed in its own proc so that crates may be excluded.
/obj/structure/closet/proc/add_to_roundstart_list()
GLOB.roundstart_station_closets += src
#undef LOCKER_FULL