diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index 6dae40ce07..1b9dccc6d9 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -110,6 +110,7 @@
#define CLICK_CD_RAPID 2
#define CLICK_CD_CLICK_ABILITY 6
#define CLICK_CD_HANDCUFFED 10
+#define CLICK_CD_BREAKOUT 10
#define CLICK_CD_RESIST 20
#define CLICK_CD_GRABBING 10
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 331ef3cca9..246bd60b78 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -335,6 +335,8 @@
#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
#define INTERACTING_WITH(X, Y) (Y in X.do_afters)
+#define DOING_INTERACTION_WITH_TARGET(user, target) (LAZYACCESS(user.do_afters, target))
+
// / Field of vision defines.
#define FOV_90_DEGREES 90
#define FOV_180_DEGREES 180
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index bebad85775..2ae21f4838 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1189,13 +1189,14 @@ GLOBAL_REAL_VAR(list/stack_trace_storage)
str = "0" + str
. = str
-/atom/proc/Shake(pixelshiftx = 15, pixelshifty = 15, duration = 250)
+/// Perform a shake on an atom, resets its position afterwards
+/atom/proc/Shake(pixelshiftx = 2, pixelshifty = 2, duration = 2.5 SECONDS, shake_interval = 0.02 SECONDS)
var/initialpixelx = pixel_x
var/initialpixely = pixel_y
- var/shiftx = rand(-pixelshiftx,pixelshiftx)
- var/shifty = rand(-pixelshifty,pixelshifty)
- animate(src, pixel_x = shiftx, pixel_y = shifty, time = 0.2, loop = duration, flags = ANIMATION_RELATIVE)
- animate(pixel_x = initialpixelx, pixel_y = initialpixely, time = 0.2)
+ animate(src, pixel_x = initialpixelx + rand(-pixelshiftx,pixelshiftx), pixel_y = initialpixelx + rand(-pixelshifty,pixelshifty), time = shake_interval, flags = ANIMATION_PARALLEL)
+ for (var/i in 3 to ((duration / shake_interval))) // Start at 3 because we already applied one, and need another to reset
+ animate(pixel_x = initialpixelx + rand(-pixelshiftx,pixelshiftx), pixel_y = initialpixely + rand(-pixelshifty,pixelshifty), time = shake_interval)
+ animate(pixel_x = initialpixelx, pixel_y = initialpixely, time = shake_interval)
/atom/proc/do_jiggle(targetangle = 25, timer = 20)
var/matrix/OM = matrix(transform)
diff --git a/code/_rendering/parallax/types/space.dm b/code/_rendering/parallax/types/space.dm
index 1a6cf191cb..74e1e218d6 100644
--- a/code/_rendering/parallax/types/space.dm
+++ b/code/_rendering/parallax/types/space.dm
@@ -43,7 +43,7 @@
/atom/movable/screen/parallax_layer/space/random
blend_mode = BLEND_OVERLAY
- speed = 3
+ speed = 2
layer = 3
parallax_intensity = PARALLAX_INSANE
@@ -56,7 +56,7 @@
/atom/movable/screen/parallax_layer/space/planet
icon_state = "planet"
blend_mode = BLEND_OVERLAY
- absolute = TRUE //Status of seperation
+ absolute = FALSE //Status of seperation
speed = 3
layer = 30
dynamic_self_tile = FALSE
diff --git a/code/game/objects/effects/overlays.dm b/code/game/objects/effects/overlays.dm
index 945136466f..28c8226ed8 100644
--- a/code/game/objects/effects/overlays.dm
+++ b/code/game/objects/effects/overlays.dm
@@ -67,6 +67,14 @@
// layer = ATMOS_GROUP_LAYER
// plane = ATMOS_GROUP_PLANE
+/// Door overlay for animating closets
+/obj/effect/overlay/closet_door
+ anchored = TRUE
+ plane = FLOAT_PLANE
+ layer = FLOAT_LAYER
+ vis_flags = VIS_INHERIT_ID
+ appearance_flags = KEEP_TOGETHER | LONG_GLIDE | PIXEL_SCALE
+
// /obj/effect/overlay/light_visible
// name = ""
// icon = 'icons/effects/light_overlays/light_32.dmi'
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index bc5b37655b..90b121b11c 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -8,8 +8,20 @@
integrity_failure = 0.25
armor = list(MELEE = 20, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 10, BIO = 0, RAD = 0, FIRE = 70, ACID = 60)
+ /// 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.12
+ /// The maximum angle the door will be drawn at
+ var/door_anim_angle = 136
+ /// X position of the closet door hinge
+ var/door_hinge_x = -6.5
+ /// Amount of time it takes for the door animation to play
+ var/door_anim_time = 1.5 // set to 0 to make the door not animate at all
+
var/icon_door = null
- var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
var/has_door_icon = TRUE // Set to false to skip trying to draw a door icon.
var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
var/opened = FALSE
@@ -18,6 +30,12 @@
var/large = TRUE
var/wall_mounted = 0 //never solid (You can always pass over it)
var/breakout_time = 1200
+
+ /// 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
+
var/message_cooldown
var/can_weld_shut = TRUE
var/horizontal = FALSE
@@ -58,6 +76,7 @@
/obj/structure/closet/Destroy()
dump_contents(override = FALSE)
+ QDEL_NULL(door_obj)
return ..()
/obj/structure/closet/update_icon()
@@ -73,24 +92,75 @@
/obj/structure/closet/proc/closet_update_overlays(list/new_overlays)
. = new_overlays
- if(opened)
- . += "[icon_door_override ? icon_door : icon_state]_open"
+ if(!is_animating_door)
+ if(opened)
+ . += "[icon_state]_open"
+ return
+
+ if(has_door_icon)
+ . += "[icon_door || icon_state]_door"
+
+ if(welded)
+ . += icon_welded
+
+ if(!secure)
+ return
+ if(broken)
+ . += "off"
+ . += "sparking"
+ //Overlay is similar enough for both that we can use the same mask for both
+ . += emissive_appearance(icon, "locked", alpha = src.alpha)
+ . += locked ? "locked" : "unlocked"
+
+/// 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
- if(has_door_icon)
- . += "[icon_door || icon_state]_door"
- if(welded)
- . += icon_welded
+ 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(!secure)
- return
- if(broken)
- . += "off"
- . += "sparking"
- //Overlay is similar enough for both that we can use the same mask for both
- . += emissive_appearance(icon, "locked", alpha = src.alpha)
- . += locked ? "locked" : "unlocked"
+ 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/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)
. = ..()
@@ -176,6 +246,7 @@
density = FALSE
climb_time *= 0.5 //it's faster to climb onto an open thing
dump_contents()
+ animate_door(FALSE)
update_icon()
after_open(user, force)
return TRUE
@@ -242,6 +313,7 @@
playsound(loc, close_sound, 15, TRUE, -3)
opened = FALSE
density = TRUE
+ animate_door(TRUE)
update_icon()
after_close(user)
return TRUE
@@ -452,34 +524,38 @@
return TRUE
/obj/structure/closet/container_resist(mob/living/user)
+ if(isstructure(loc))
+ relay_container_resist(user, loc)
if(opened)
return
if(ismovable(loc))
- // user.changeNext_move(CLICK_CD_BREAKOUT)
- // user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
var/atom/movable/AM = loc
AM.relay_container_resist(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("[src] begins to shake violently!", \
- "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
- "You hear banging from [src].")
- if(do_after(user, breakout_time, src, IGNORE_TARGET_LOC_CHANGE|IGNORE_HELD_ITEM))
+ 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 || 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("[user] successfully broke out of [src]!",
- "You successfully break out of [src]!")
+ 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, "You fail to break out of [src]!")
+ to_chat(user, span_warning("You fail to break out of [src]!"))
/obj/structure/closet/proc/bust_open()
welded = FALSE //applies to all lockers
@@ -487,6 +563,27 @@
broken = TRUE //applies to secure lockers only
open()
+/// 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/relay_container_resist(mob/living/user, obj/container)
+ container.container_resist()
+
/obj/structure/closet/AltClick(mob/user)
..()
if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc))
diff --git a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
index f6e1e6fe9e..75072405b5 100644
--- a/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
+++ b/code/game/objects/structures/crates_lockers/closets/cardboardbox.dm
@@ -14,6 +14,7 @@
material_drop = /obj/item/stack/sheet/cardboard
delivery_icon = "deliverybox"
anchorable = FALSE
+ door_anim_time = 0 // no animation
var/move_speed_multiplier = 1
var/move_delay = FALSE
var/egged = 0
diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm
index b33962d8de..2b57aada68 100644
--- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm
+++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm
@@ -6,6 +6,7 @@
max_integrity = 70
material_drop = /obj/item/stack/sheet/mineral/wood
cutting_tool = TOOL_SCREWDRIVER
+ door_anim_time = 0 // no animation
/obj/structure/closet/acloset
name = "strange closet"
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm
index 36b770cd25..59a6bdc20e 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/bar.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/bar.dm
@@ -6,6 +6,7 @@
max_integrity = 70
material_drop = /obj/item/stack/sheet/mineral/wood
cutting_tool = TOOL_SCREWDRIVER
+ door_anim_time = 0 // no animation
/obj/structure/closet/secure_closet/bar/PopulateContents()
..()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
index e5a7793ab7..057bbd4377 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm
@@ -1,6 +1,9 @@
/obj/structure/closet/secure_closet/freezer
icon_state = "freezer"
var/jones = FALSE
+ door_anim_squish = 0.22
+ door_anim_angle = 123
+ door_anim_time = 4
/obj/structure/closet/secure_closet/freezer/Destroy()
recursive_organ_check(src)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
index 52abb00cc7..778ae6b68a 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm
@@ -3,6 +3,7 @@
name = "personal closet"
req_access = list(ACCESS_ALL_PERSONAL_LOCKERS)
var/registered_name = null
+ door_anim_time = 0 // no animation
/obj/structure/closet/secure_closet/personal/PopulateContents()
..()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index a2239caa92..50c73684f3 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -163,6 +163,7 @@
max_integrity = 70
material_drop = /obj/item/stack/sheet/mineral/wood
cutting_tool = TOOL_SCREWDRIVER
+ door_anim_time = 0 // no animation
/obj/structure/closet/secure_closet/detective/PopulateContents()
..()
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 650fdf040b..70293d1097 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -12,6 +12,9 @@
climbable = TRUE
climb_time = 10 //real fast, because let's be honest stepping into or onto a crate is easy
climb_stun = 0 //climbing onto crates isn't hard, guys
+ door_anim_time = 0 // no animation
+ x_shake_pixel_shift = 1
+ y_shake_pixel_shift = 2
delivery_icon = "deliverycrate"
material_drop = /obj/item/stack/sheet/plasteel
material_drop_amount = 5
diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
index 7506ae8975..95d8abce4f 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
@@ -825,6 +825,7 @@
icon_state = "abductor"
icon_door = "abductor"
can_weld_shut = FALSE
+ door_anim_time = 0
material_drop = /obj/item/stack/sheet/mineral/abductor
/obj/structure/door_assembly/door_assembly_abductor
diff --git a/icons/effects/parallax.dmi b/icons/effects/parallax.dmi
deleted file mode 100755
index 36dd36ee7a..0000000000
Binary files a/icons/effects/parallax.dmi and /dev/null differ
diff --git a/icons/obj/closet.dmi b/icons/obj/closet.dmi
index 70f5001c26..ec01f4b9a3 100644
Binary files a/icons/obj/closet.dmi and b/icons/obj/closet.dmi differ
diff --git a/icons/screen/parallax.dmi b/icons/screen/parallax.dmi
index 36dd36ee7a..5fe1b16520 100644
Binary files a/icons/screen/parallax.dmi and b/icons/screen/parallax.dmi differ
diff --git a/modular_splurt/code/game/objects/effects/decals/turfdecal/markings.dm b/modular_splurt/code/game/objects/effects/decals/turfdecal/markings.dm
index 7480a9d37e..6f595c8f06 100644
--- a/modular_splurt/code/game/objects/effects/decals/turfdecal/markings.dm
+++ b/modular_splurt/code/game/objects/effects/decals/turfdecal/markings.dm
@@ -133,47 +133,3 @@
/obj/effect/turf_decal/siding/wideplating/dark/end
icon_state = "siding_wideplating_end"
-
-// Replaces the SS13 plaque
-/obj/effect/turf_decal/plaque
- icon = 'modular_splurt/icons/turf/decals.dmi'
- icon_state = "L1"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L2"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L3"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L4"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L5"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L6"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L7"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L8"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L9"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L10"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L11"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L12"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L13"
-
-/obj/effect/turf_decal/plaque
- icon_state = "L14"
diff --git a/modular_splurt/icons/obj/closet.dmi b/modular_splurt/icons/obj/closet.dmi
index 35655f23e4..ece32676c8 100644
Binary files a/modular_splurt/icons/obj/closet.dmi and b/modular_splurt/icons/obj/closet.dmi differ
diff --git a/modular_splurt/icons/turf/decals.dmi b/modular_splurt/icons/turf/decals.dmi
index 715e3fd253..2f434059d6 100644
Binary files a/modular_splurt/icons/turf/decals.dmi and b/modular_splurt/icons/turf/decals.dmi differ