diff --git a/_maps/map_files/debug/multiz.dmm b/_maps/map_files/debug/multiz.dmm
index 9167ee4ad96..22c88a5afd0 100644
--- a/_maps/map_files/debug/multiz.dmm
+++ b/_maps/map_files/debug/multiz.dmm
@@ -14,6 +14,20 @@
"ah" = (
/turf/closed/wall/r_wall,
/area/station/engineering/atmos)
+"ai" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/machinery/door/poddoor/preopen{
+ id = "test_vator"
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/turf/open/floor/iron/edge{
+ dir = 4
+ },
+/area/station/commons/storage/primary)
"aj" = (
/turf/closed/wall/r_wall,
/area/station/engineering/main)
@@ -156,11 +170,11 @@
/turf/open/floor/iron,
/area/station/engineering/gravity_generator)
"aW" = (
-/obj/effect/turf_decal/stripes/line{
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
dir = 1
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"aY" = (
/obj/machinery/light/directional/east,
@@ -398,13 +412,7 @@
/turf/open/floor/iron,
/area/station/hallway/primary/central)
"bU" = (
-/obj/machinery/airalarm/directional/north{
- locked = 0;
- pixel_y = 23
- },
/obj/structure/closet/firecloset/full,
-/obj/structure/cable,
-/obj/machinery/power/apc/auto_name/directional/west,
/turf/open/floor/iron{
dir = 9
},
@@ -418,6 +426,10 @@
/area/station/hallway/secondary/entry)
"bW" = (
/obj/structure/closet/secure_closet/hos,
+/obj/machinery/airalarm/directional/north{
+ locked = 0;
+ pixel_y = 23
+ },
/turf/open/floor/iron{
dir = 1
},
@@ -490,6 +502,9 @@
"cx" = (
/obj/machinery/door/airlock/glass,
/obj/structure/cable,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{
+ dir = 4
+ },
/turf/open/floor/iron,
/area/station/hallway/secondary/entry)
"cy" = (
@@ -544,41 +559,14 @@
/obj/structure/cable,
/turf/open/floor/iron,
/area/station/hallway/primary/central)
-"cG" = (
-/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{
- dir = 4
- },
-/turf/closed/wall/r_wall,
-/area/station/hallway/secondary/entry)
"cH" = (
-/obj/machinery/atmospherics/components/unary/vent_pump/on{
- dir = 8
- },
-/turf/open/floor/iron{
- dir = 8
- },
-/area/station/hallway/secondary/entry)
-"cI" = (
/obj/structure/table,
/obj/item/storage/fancy/donut_box,
+/obj/item/storage/fancy/donut_box,
/turf/open/floor/iron{
dir = 8
},
/area/station/hallway/secondary/entry)
-"cJ" = (
-/obj/structure/table,
-/obj/item/storage/fancy/donut_box,
-/turf/open/floor/iron{
- dir = 10
- },
-/area/station/hallway/secondary/entry)
-"cK" = (
-/obj/structure/table,
-/obj/item/stack/sheet/glass/fifty,
-/obj/item/stack/rods/fifty,
-/obj/machinery/light/directional/south,
-/turf/open/floor/iron,
-/area/station/hallway/secondary/entry)
"cL" = (
/obj/structure/table,
/obj/item/paper_bin{
@@ -597,6 +585,13 @@
"cS" = (
/turf/closed/wall/r_wall,
/area/station/commons/storage/primary)
+"cT" = (
+/obj/machinery/elevator_control_panel/directional/north{
+ linked_elevator_id = "test_vator"
+ },
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/hallway/secondary/service)
"cU" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden,
/turf/closed/wall/r_wall,
@@ -632,6 +627,9 @@
/area/station/commons/storage/primary)
"de" = (
/obj/machinery/light/directional/north,
+/obj/machinery/button/elevator/directional/north{
+ id = "test_vator"
+ },
/turf/open/floor/iron,
/area/station/commons/storage/primary)
"di" = (
@@ -774,6 +772,7 @@
dir = 4
},
/obj/machinery/light/directional/west,
+/obj/structure/railing/corner,
/turf/open/floor/iron,
/area/station/engineering/storage)
"em" = (
@@ -782,11 +781,6 @@
dir = 1
},
/area/station/hallway/secondary/service)
-"en" = (
-/turf/open/floor/iron{
- dir = 10
- },
-/area/station/hallway/secondary/service)
"eo" = (
/turf/open/floor/iron,
/area/station/hallway/secondary/entry)
@@ -911,6 +905,14 @@
/obj/machinery/suit_storage_unit/ce,
/turf/open/floor/iron,
/area/station/construction)
+"gz" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/edge{
+ dir = 1
+ },
+/area/station/engineering/storage)
"gA" = (
/obj/effect/spawner/structure/window/reinforced,
/obj/machinery/atmospherics/pipe/smart/simple/green/visible/layer2{
@@ -961,6 +963,12 @@
/obj/structure/lattice,
/turf/open/space/basic,
/area/space/nearstation)
+"iA" = (
+/obj/structure/sign/warning/directional/north,
+/obj/machinery/light/directional/north,
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/commons/storage/primary)
"iH" = (
/obj/effect/turf_decal/stripes/white/line,
/obj/structure/railing/corner{
@@ -977,6 +985,13 @@
/obj/machinery/atmospherics/pipe/multiz,
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"ji" = (
+/obj/structure/lattice,
+/obj/structure/railing/corner{
+ dir = 4
+ },
+/turf/open/openspace,
+/area/station/engineering/storage)
"jz" = (
/obj/machinery/airalarm/directional/north,
/turf/open/floor/plating,
@@ -998,12 +1013,26 @@
},
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"jM" = (
+/obj/machinery/elevator_control_panel/directional/north{
+ linked_elevator_id = "test_vator"
+ },
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/engineering/storage)
"jV" = (
/obj/effect/turf_decal/stripes/white/line{
dir = 6
},
/turf/open/floor/plating,
/area/station/engineering/storage)
+"jY" = (
+/obj/item/stack/sheet/glass/fifty,
+/obj/item/stack/rods/fifty,
+/obj/machinery/light/directional/south,
+/obj/structure/table,
+/turf/open/floor/iron,
+/area/station/hallway/secondary/entry)
"kg" = (
/obj/effect/turf_decal/stripes/asteroid/line{
dir = 4
@@ -1040,11 +1069,11 @@
/turf/open/floor/iron,
/area/station/hallway/secondary/service)
"lT" = (
-/obj/effect/turf_decal/stripes/line{
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
dir = 6
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"mb" = (
/obj/structure/cable,
@@ -1054,12 +1083,19 @@
/obj/structure/disposalpipe/trunk/multiz,
/turf/open/floor/plating,
/area/station/construction)
-"mG" = (
-/obj/effect/turf_decal/stripes/line{
+"my" = (
+/obj/structure/cable,
+/obj/machinery/power/apc/auto_name/directional/west,
+/turf/open/floor/iron{
dir = 8
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/area/station/hallway/secondary/entry)
+"mG" = (
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
+ dir = 8
+ },
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"mZ" = (
/turf/open/floor/iron{
@@ -1084,9 +1120,9 @@
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
"nS" = (
-/obj/effect/turf_decal/stripes/line,
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"nW" = (
/obj/structure/grille,
@@ -1122,6 +1158,27 @@
"qo" = (
/turf/open/openspace,
/area/station/engineering/storage)
+"qu" = (
+/obj/machinery/door/poddoor/preopen{
+ id = "test_vator"
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/turf/open/floor/iron/edge{
+ dir = 4
+ },
+/area/station/commons/storage/primary)
+"qI" = (
+/obj/structure/lattice/catwalk,
+/obj/structure/railing/corner{
+ dir = 8
+ },
+/turf/open/openspace,
+/area/station/engineering/storage)
"qR" = (
/obj/effect/turf_decal/stripes/white/line{
dir = 9
@@ -1142,6 +1199,12 @@
/obj/structure/cable,
/turf/open/floor/iron,
/area/station/hallway/primary/central)
+"rT" = (
+/obj/machinery/light/directional/north,
+/obj/structure/sign/warning/directional/north,
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/hallway/secondary/service)
"sh" = (
/turf/open/floor/iron{
dir = 1
@@ -1176,6 +1239,20 @@
},
/turf/open/floor/plating,
/area/station/engineering/storage)
+"tw" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 4
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/obj/machinery/door/poddoor{
+ id = "test_vator"
+ },
+/turf/open/floor/iron/edge{
+ dir = 4
+ },
+/area/station/engineering/storage)
"uv" = (
/obj/structure/cable,
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{
@@ -1214,6 +1291,13 @@
"vX" = (
/turf/closed/wall/r_wall,
/area/station/construction)
+"wb" = (
+/obj/machinery/elevator_control_panel/directional/north{
+ linked_elevator_id = "test_vator"
+ },
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/commons/storage/primary)
"wr" = (
/obj/effect/turf_decal/stripes/line,
/obj/structure/cable,
@@ -1225,6 +1309,12 @@
},
/turf/open/floor/plating,
/area/station/construction)
+"xb" = (
+/obj/machinery/light/directional/north,
+/obj/structure/sign/warning/directional/north,
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/engineering/storage)
"xw" = (
/obj/effect/turf_decal/stripes/asteroid/line{
dir = 8
@@ -1232,6 +1322,14 @@
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"xx" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/edge{
+ dir = 1
+ },
+/area/station/commons/storage/primary)
"xB" = (
/obj/effect/turf_decal/stripes/white/line{
dir = 1
@@ -1243,11 +1341,11 @@
/turf/open/floor/iron,
/area/station/hallway/secondary/service)
"xK" = (
-/obj/effect/turf_decal/stripes/line{
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
dir = 10
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"xO" = (
/obj/structure/lattice,
@@ -1264,15 +1362,23 @@
/turf/open/floor/iron,
/area/station/engineering/main)
"yR" = (
-/obj/effect/turf_decal/stripes/line{
+/obj/machinery/elevator_control_panel/directional/east{
+ preset_destination_names = list("2"="Bottom Floor","3"="Middle Floor","4"="Top Floor");
+ linked_elevator_id = "test_vator"
+ },
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
dir = 5
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"yX" = (
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/obj/effect/landmark/lift_id{
+ specific_lift_id = "test_vator"
+ },
+/obj/machinery/light/floor,
+/obj/structure/industrial_lift/public,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"zd" = (
/obj/structure/railing{
@@ -1310,13 +1416,19 @@
"AG" = (
/turf/closed/wall/r_wall,
/area/station/engineering/storage)
+"Bl" = (
+/obj/machinery/atmospherics/components/unary/vent_pump/on{
+ dir = 8
+ },
+/turf/open/floor/iron,
+/area/station/hallway/secondary/entry)
"Bm" = (
-/obj/machinery/light/directional/east,
-/obj/effect/turf_decal/stripes/line{
+/obj/structure/sign/warning/directional/east,
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
dir = 4
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"Bw" = (
/obj/effect/turf_decal/stripes/line{
@@ -1342,6 +1454,13 @@
/obj/machinery/disposal/bin,
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"Dl" = (
+/obj/structure/lattice/catwalk,
+/obj/structure/railing{
+ dir = 1
+ },
+/turf/open/openspace,
+/area/station/engineering/storage)
"Dm" = (
/obj/machinery/light/directional/north,
/turf/open/floor/plating,
@@ -1395,14 +1514,19 @@
"Gb" = (
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"GG" = (
+/obj/structure/lattice/catwalk,
+/obj/structure/railing,
+/turf/open/openspace,
+/area/station/engineering/storage)
+"Ho" = (
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/commons/storage/primary)
"Hp" = (
/obj/structure/cable,
/turf/open/floor/iron,
/area/station/commons/storage/primary)
-"Ht" = (
-/obj/machinery/light/directional/east,
-/turf/open/openspace,
-/area/station/hallway/secondary/service)
"Hu" = (
/obj/structure/railing/corner,
/turf/open/floor/plating,
@@ -1420,6 +1544,15 @@
/obj/machinery/atmospherics/components/binary/valve,
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"IK" = (
+/obj/structure/cable,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{
+ dir = 4
+ },
+/turf/open/floor/iron{
+ dir = 8
+ },
+/area/station/hallway/secondary/entry)
"IL" = (
/obj/structure/railing/corner,
/obj/structure/railing/corner{
@@ -1472,6 +1605,15 @@
},
/turf/open/floor/plating,
/area/station/hallway/secondary/entry)
+"Km" = (
+/obj/effect/turf_decal/stripes/asteroid/line{
+ dir = 4
+ },
+/obj/structure/railing/corner{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/station/engineering/storage)
"Kw" = (
/obj/effect/turf_decal/stripes/asteroid/line{
dir = 1
@@ -1509,6 +1651,16 @@
},
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"LL" = (
+/obj/structure/lattice/catwalk,
+/obj/structure/railing/corner{
+ dir = 1
+ },
+/obj/machinery/button/elevator/directional/north{
+ id = "test_vator"
+ },
+/turf/open/openspace,
+/area/station/engineering/storage)
"LM" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden,
/obj/machinery/meter,
@@ -1612,6 +1764,10 @@
/obj/machinery/door/airlock/glass,
/turf/open/floor/iron,
/area/station/construction)
+"Rm" = (
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/engineering/storage)
"RQ" = (
/obj/machinery/door/airlock/glass,
/obj/structure/cable,
@@ -1624,7 +1780,15 @@
/obj/effect/turf_decal/stripes/line{
dir = 4
},
-/turf/open/floor/iron,
+/obj/machinery/door/poddoor{
+ id = "test_vator"
+ },
+/obj/effect/turf_decal/stripes/line{
+ dir = 8
+ },
+/turf/open/floor/iron/edge{
+ dir = 4
+ },
/area/station/hallway/secondary/service)
"TH" = (
/turf/open/floor/plating,
@@ -1643,6 +1807,10 @@
/obj/effect/landmark/blobstart,
/turf/open/floor/iron,
/area/station/hallway/primary/central)
+"Ui" = (
+/obj/machinery/door/airlock,
+/turf/open/floor/plating,
+/area/station/hallway/secondary/service)
"Um" = (
/obj/effect/turf_decal/stripes/asteroid/line{
dir = 8
@@ -1670,6 +1838,10 @@
},
/turf/open/floor/plating,
/area/station/construction)
+"Vb" = (
+/obj/effect/turf_decal/stripes/line,
+/turf/open/floor/iron/edge,
+/area/station/hallway/secondary/service)
"Vm" = (
/obj/structure/cable/multilayer/multiz,
/turf/open/floor/plating,
@@ -1679,8 +1851,15 @@
/obj/effect/turf_decal/stripes/asteroid/line{
dir = 4
},
+/obj/structure/railing/corner,
/turf/open/floor/iron,
/area/station/engineering/storage)
+"VF" = (
+/obj/machinery/button/elevator/directional/north{
+ id = "test_vator"
+ },
+/turf/open/floor/iron,
+/area/station/hallway/secondary/service)
"VG" = (
/obj/structure/railing/corner{
dir = 1
@@ -1705,6 +1884,13 @@
/obj/effect/spawner/random/entertainment/arcade,
/turf/open/floor/iron,
/area/station/hallway/primary/central)
+"WX" = (
+/obj/structure/cable,
+/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden{
+ dir = 4
+ },
+/turf/open/floor/iron,
+/area/station/hallway/primary/central)
"XK" = (
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden,
/turf/closed/wall/r_wall,
@@ -1725,6 +1911,14 @@
/obj/effect/spawner/structure/window/reinforced,
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
+"YV" = (
+/obj/effect/turf_decal/stripes/line{
+ dir = 1
+ },
+/turf/open/floor/iron/edge{
+ dir = 1
+ },
+/area/station/hallway/secondary/service)
"Zc" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on{
dir = 8
@@ -1732,11 +1926,11 @@
/turf/open/floor/plating,
/area/station/hallway/secondary/service)
"Zk" = (
-/obj/effect/turf_decal/stripes/line{
+/obj/structure/industrial_lift/public,
+/obj/effect/turf_decal/siding/white{
dir = 9
},
-/obj/structure/industrial_lift,
-/turf/open/floor/plating,
+/turf/open/floor/plating/elevatorshaft,
/area/station/commons/storage/primary)
"ZQ" = (
/obj/effect/turf_decal/stripes/white/line{
@@ -3551,7 +3745,7 @@ ak
WR
bE
co
-bO
+WX
cA
bE
bE
@@ -3606,18 +3800,18 @@ by
by
by
cx
-cG
-by
by
by
+dJ
+cS
de
dJ
dB
-dl
-dE
-dH
-dI
-dB
+cS
+dF
+dk
+dk
+dC
dl
dE
dJ
@@ -3657,21 +3851,21 @@ aL
dV
by
bU
+my
cl
-cl
-cl
+IK
cH
-cI
-cJ
by
-dk
-dk
-dC
+cS
+cS
+qu
+ai
+qu
+cS
+cS
+dl
+dl
dl
-dE
-dH
-dI
-dB
dl
dE
dJ
@@ -3713,19 +3907,19 @@ by
bV
eo
eo
-eo
-eo
-eo
-cK
-by
+Bl
+cM
+jY
+cS
+wb
Zk
mG
xK
+xx
+cS
+dl
+dl
dl
-dF
-dk
-dk
-dC
dl
dF
dk
@@ -3769,14 +3963,14 @@ eo
eo
eo
eo
-eo
cL
-by
+cS
+iA
aW
yX
nS
-dl
-dl
+xx
+cS
dl
dl
dl
@@ -3824,13 +4018,13 @@ eo
cy
eo
eo
-cM
-by
+cS
+Ho
yR
Bm
lT
-dl
-dl
+xx
+cS
dl
dl
dl
@@ -3878,8 +4072,8 @@ by
oh
by
cn
-by
-by
+cS
+cS
cS
cS
cS
@@ -6416,13 +6610,13 @@ TY
TY
dY
TY
-TY
-TY
-TY
dY
dY
+TY
+VF
dY
-eF
+dY
+TY
iu
iu
iu
@@ -6469,15 +6663,15 @@ ee
iK
ez
iK
-iK
-iK
-en
+TY
+TY
+TY
TY
SN
SN
SN
-eF
-iu
+TY
+TY
iu
iu
iu
@@ -6525,13 +6719,13 @@ dY
dY
dY
dY
-dY
TY
+cT
iu
iu
iu
-eF
-iu
+YV
+TY
iu
iu
iu
@@ -6579,11 +6773,12 @@ dY
dY
dY
dY
-dY
TY
+rT
iu
iu
iu
+YV
eF
iu
iu
@@ -6593,7 +6788,6 @@ iu
iu
iu
iu
-iu
eC
ad
SF
@@ -6633,13 +6827,13 @@ dY
iK
dY
dY
-dY
TY
+Vb
iu
-Ht
iu
-eF
iu
+YV
+TY
iu
iu
iu
@@ -6682,18 +6876,18 @@ TY
TY
TY
TY
-Gb
-Gb
-Gb
-Gb
-Gb
+TY
+TY
+Ui
+TY
+TY
+TY
TY
TY
TY
TY
TY
TY
-iu
iu
iu
iu
@@ -8796,10 +8990,10 @@ Qw
Qw
Qw
ej
-Qw
+Km
Qw
Vn
-xO
+ji
xO
xO
xO
@@ -8850,9 +9044,9 @@ qo
xO
qo
qo
+Dl
fp
-fp
-fp
+GG
qo
qo
xO
@@ -8904,9 +9098,9 @@ qo
xO
qo
qo
+Dl
fp
-fp
-fp
+GG
qo
qo
xO
@@ -8958,9 +9152,9 @@ qo
xO
qo
qo
+Dl
fp
-fp
-fp
+GG
qo
qo
xO
@@ -9012,10 +9206,10 @@ qo
xO
qo
qo
+Dl
fp
-fp
-fp
-qo
+qI
+AG
qo
xO
qo
@@ -9066,10 +9260,10 @@ qo
xO
qo
qo
+Dl
fp
fp
-fp
-qo
+AG
qo
xO
qo
@@ -9120,10 +9314,10 @@ qo
xO
qo
qo
+Dl
fp
fp
-fp
-qo
+AG
qo
xO
qo
@@ -9174,10 +9368,10 @@ qo
xO
qo
qo
+Dl
fp
fp
-fp
-qo
+AG
qo
xO
qo
@@ -9227,11 +9421,11 @@ qo
qo
xO
qo
-qo
+AG
+LL
fp
fp
-fp
-qo
+AG
qo
xO
qo
@@ -9280,13 +9474,13 @@ qo
qo
qo
xO
-qo
-fp
-fp
-fp
-fp
-fp
-qo
+AG
+AG
+tw
+tw
+tw
+AG
+AG
xO
qo
qo
@@ -9334,13 +9528,13 @@ qo
qo
qo
xO
-qo
-fp
+AG
+jM
qo
qo
qo
-fp
-qo
+gz
+AG
xO
qo
qo
@@ -9388,13 +9582,13 @@ xO
xO
xO
xO
-xO
-fp
+AG
+xb
qo
qo
qo
-fp
-xO
+gz
+DK
xO
xO
xO
@@ -9442,13 +9636,13 @@ qo
qo
qo
xO
-qo
-fp
+AG
+Rm
qo
qo
qo
-fp
-qo
+gz
+AG
xO
qo
qo
@@ -9496,13 +9690,13 @@ qo
qo
qo
xO
-qo
-fp
-fp
-fp
-fp
-fp
-qo
+AG
+AG
+AG
+AG
+AG
+AG
+AG
xO
qo
qo
diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm
index ce0c4994006..3651139b127 100644
--- a/code/__DEFINES/dcs/signals/signals_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_object.dm
@@ -405,7 +405,7 @@
///from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params)
#define COMSIG_ITEM_ATTACK_QDELETED "item_attack_qdeleted"
-///from /obj/item/assembly/proc/pulsed()
+///from /obj/item/assembly/proc/pulsed(mob/pulser)
#define COMSIG_ASSEMBLY_PULSED "assembly_pulsed"
///from base of /obj/item/mmi/set_brainmob(): (mob/living/brain/new_brainmob)
diff --git a/code/__DEFINES/industrial_lift.dm b/code/__DEFINES/industrial_lift.dm
index 9d3052c01f0..ba9efa223d5 100644
--- a/code/__DEFINES/industrial_lift.dm
+++ b/code/__DEFINES/industrial_lift.dm
@@ -18,3 +18,7 @@
#define MAIN_STATION_TRAM "main station tram"
///the specific_lift_id of the tram on the hilbert research station
#define HILBERT_TRAM "tram_hilbert"
+
+// Defines for update_lift_doors
+#define OPEN_DOORS "open"
+#define CLOSE_DOORS "close"
diff --git a/code/datums/ai/telegraph_effects.dm b/code/datums/ai/telegraph_effects.dm
index 7508de29b40..62bbe9a0397 100644
--- a/code/datums/ai/telegraph_effects.dm
+++ b/code/datums/ai/telegraph_effects.dm
@@ -9,6 +9,12 @@
/obj/effect/temp_visual/telegraphing/vending_machine_tilt
duration = 1 SECONDS
+/obj/effect/temp_visual/telegraphing/lift_travel
+
+/obj/effect/temp_visual/telegraphing/lift_travel/Initialize(mapload, duration)
+ src.duration = duration
+ return ..()
+
/obj/effect/temp_visual/telegraphing/thunderbolt
icon = 'icons/mob/telegraphing/telegraph.dmi'
icon_state = "target_circle"
diff --git a/code/datums/components/trapdoor.dm b/code/datums/components/trapdoor.dm
index 689ea3b4696..c2cb1adb1a8 100644
--- a/code/datums/components/trapdoor.dm
+++ b/code/datums/components/trapdoor.dm
@@ -230,12 +230,13 @@
var/list/stored_decals = list()
-/obj/item/assembly/trapdoor/pulsed(radio)
+/obj/item/assembly/trapdoor/pulsed(radio, mob/pulser)
. = ..()
if(linked)
return
if(!COOLDOWN_FINISHED(src, search_cooldown))
- visible_message(span_warning("[src] cannot attempt another trapdoor linkup so soon!"))
+ if(loc && pulser)
+ loc.balloon_alert(pulser, "linking on cooldown!")
return
attempt_link_up()
COOLDOWN_START(src, search_cooldown, search_cooldown_time)
@@ -305,23 +306,33 @@
/obj/item/trapdoor_remote/attack_self(mob/user, modifiers)
. = ..()
if(.)
- return
+ return TRUE
+
if(!internals)
- to_chat(user, span_warning("[src] has no internals!"))
- return
+ user.balloon_alert(user, "no device!")
+ return TRUE
+
if(!internals.linked)
- to_chat(user, span_notice("You activate [src]."))
- internals.pulsed()
- return
+ internals.pulsed(pulser = user)
+ // The pulse linked successfully
+ if(internals.linked)
+ user.balloon_alert(user, "linked")
+ // The pulse failed to link
+ else
+ user.balloon_alert(user, "link failed!")
+ return TRUE
+
if(!COOLDOWN_FINISHED(src, trapdoor_cooldown))
- to_chat(user, span_warning("[src] is on a short cooldown."))
- return
- to_chat(user, span_notice("You activate [src]."))
+ user.balloon_alert(user, "on cooldown!")
+ return TRUE
+
+ user.balloon_alert(user, "trapdoor triggered")
playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE)
icon_state = "trapdoor_pressed"
addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), trapdoor_cooldown_time)
COOLDOWN_START(src, trapdoor_cooldown, trapdoor_cooldown_time)
- internals.pulsed()
+ internals.pulsed(pulser = user)
+ return TRUE
#undef TRAPDOOR_LINKING_SEARCH_RANGE
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index cef47083dc7..fded1a87b0e 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -107,6 +107,7 @@
return ..()
/obj/machinery/button/emag_act(mob/user)
+ . = ..()
if(obj_flags & EMAGGED)
return
req_access = list()
@@ -114,6 +115,12 @@
playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
obj_flags |= EMAGGED
+ // The device inside can be emagged by swiping the button
+ // returning TRUE will prevent feedback (so we can do our own)
+ if(device?.emag_act(user))
+ return
+ balloon_alert(user, "access overridden")
+
/obj/machinery/button/attack_ai(mob/user)
if(!silicon_access_disabled && !panel_open)
return attack_hand(user)
@@ -175,7 +182,7 @@
icon_state = "[skin]1"
if(device)
- device.pulsed()
+ device.pulsed(pulser = user)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_BUTTON_PRESSED,src)
addtimer(CALLBACK(src, /atom/.proc/update_appearance), 15)
@@ -309,20 +316,6 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/button/door, 24)
custom_materials = list(/datum/material/iron=MINERAL_MATERIAL_AMOUNT)
pixel_shift = 24
-/obj/machinery/button/elevator
- name = "elevator button"
- desc = "Go back. Go back. Go back. Can you operate the elevator."
- icon_state = "launcher"
- skin = "launcher"
- device_type = /obj/item/assembly/control/elevator
- req_access = list()
- id = 1
-
-/obj/machinery/button/elevator/examine(mob/user)
- . = ..()
- . += span_notice("There's a small inscription on the button...")
- . += span_notice("THIS CALLS THE ELEVATOR! IT DOES NOT OPERATE IT! Interact with the elevator itself to use it!")
-
/obj/machinery/button/tram
name = "tram caller"
desc = "A button for calling the tram. It has a speakerbox in it with some internals."
diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm
index 089f1e95a82..c8a529a8d51 100644
--- a/code/modules/assembly/assembly.dm
+++ b/code/modules/assembly/assembly.dm
@@ -63,11 +63,11 @@
return TRUE
///Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
-/obj/item/assembly/proc/pulsed(radio = FALSE)
+/obj/item/assembly/proc/pulsed(radio = FALSE, mob/pulser)
if(wire_type & WIRE_RECEIVE)
- INVOKE_ASYNC(src, .proc/activate)
+ INVOKE_ASYNC(src, .proc/activate, pulser)
if(radio && (wire_type & WIRE_RADIO_RECEIVE))
- INVOKE_ASYNC(src, .proc/activate)
+ INVOKE_ASYNC(src, .proc/activate, pulser)
SEND_SIGNAL(src, COMSIG_ASSEMBLY_PULSED)
return TRUE
@@ -83,7 +83,7 @@
return TRUE
/// What the device does when turned on
-/obj/item/assembly/proc/activate()
+/obj/item/assembly/proc/activate(mob/activator)
if(QDELETED(src) || !secured || (next_activate > world.time))
return FALSE
next_activate = world.time + 30
diff --git a/code/modules/assembly/doorcontrol.dm b/code/modules/assembly/doorcontrol.dm
index 7b254d9e5a2..b956851a223 100644
--- a/code/modules/assembly/doorcontrol.dm
+++ b/code/modules/assembly/doorcontrol.dm
@@ -175,59 +175,6 @@
addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 50)
-//how long it spends on each floor when moving somewhere, so it'd take 4 seconds to reach you if it had to travel up 2 floors
-#define FLOOR_TRAVEL_TIME 2 SECONDS
-/obj/item/assembly/control/elevator
- name = "elevator controller"
- desc = "A small device used to call elevators to the current floor."
-
-/obj/item/assembly/control/elevator/activate()
- if(cooldown)
- return
- cooldown = TRUE
- var/datum/lift_master/lift
- for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
- if(possible_match.specific_lift_id != id || !check_z(possible_match) || possible_match.controls_locked)
- continue
-
- lift = possible_match
- break
-
- if(!lift)
- addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
- return
-
- var/obj/structure/industrial_lift/target = lift.lift_platforms[1]
- var/target_z = target.z
-
- lift.set_controls(LIFT_PLATFORM_LOCKED)
- ///The z level to which the elevator should travel
- var/targetZ = (abs(loc.z)) //The target Z (where the elevator should move to) is not our z level (we are just some assembly in nullspace) but actually the Z level of whatever we are contained in (e.g. elevator button)
- ///The amount of z levels between the our and targetZ
- var/difference = abs(targetZ - target_z)
- ///Direction (up/down) needed to go to reach targetZ
- var/direction = target_z < targetZ ? UP : DOWN
- ///How long it will/should take us to reach the target Z level
- var/travel_duration = FLOOR_TRAVEL_TIME * difference //100 / 2 floors up = 50 seconds on every floor, will always reach destination in the same time
- addtimer(VARSET_CALLBACK(src, cooldown, FALSE), travel_duration)
-
- for(var/i in 1 to difference)
- sleep(FLOOR_TRAVEL_TIME)//hey this should be alright... right?
- if(QDELETED(lift) || QDELETED(src))//elevator control or button gone = don't go up anymore
- return
- lift.MoveLift(direction, null)
- lift.set_controls(LIFT_PLATFORM_UNLOCKED)
-
-///check if any of the lift platforms are already here
-/obj/item/assembly/control/elevator/proc/check_z(datum/lift_master/lift)
- for(var/obj/structure/industrial_lift/platform as anything in lift.lift_platforms)
- if(platform.z == loc.z)
- return FALSE
-
- return TRUE
-
-#undef FLOOR_TRAVEL_TIME
-
/obj/item/assembly/control/tram
name = "tram call button"
desc = "A small device used to bring trams to you."
diff --git a/code/modules/industrial_lift/elevator_button.dm b/code/modules/industrial_lift/elevator_button.dm
new file mode 100644
index 00000000000..fae1258d09d
--- /dev/null
+++ b/code/modules/industrial_lift/elevator_button.dm
@@ -0,0 +1,164 @@
+/obj/item/assembly/control/elevator
+ name = "elevator controller"
+ desc = "A small device used to call elevators to the current floor."
+ /// A weakref to the lift_master datum we control
+ var/datum/weakref/lift_weakref
+
+/obj/item/assembly/control/elevator/Initialize(mapload)
+ . = ..()
+
+ if(mapload)
+ return INITIALIZE_HINT_LATELOAD
+
+ var/datum/lift_master/lift = get_lift()
+ if(!lift)
+ return
+
+ lift_weakref = WEAKREF(lift)
+
+/obj/item/assembly/control/elevator/LateInitialize()
+ var/datum/lift_master/lift = get_lift()
+ if(!lift)
+ log_mapping("Elevator call button at [AREACOORD(src)] found no associated lift to link with, this may be a mapping error.")
+ return
+
+ lift_weakref = WEAKREF(lift)
+
+// Emagging elevator buttons will disable safeties
+/obj/item/assembly/control/elevator/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ return
+
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ return
+
+ for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
+ lift_platform.violent_landing = TRUE
+ lift_platform.warns_on_down_movement = FALSE
+ lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) * 0.5
+
+ // Note that we can either be emagged by having the button we are inside swiped,
+ // or by someone emagging the assembly directly after removing it (to be cheeky)
+ var/atom/balloon_alert_loc = get(src, /obj/machinery/button) || src
+ balloon_alert_loc.balloon_alert(user, "safeties overridden")
+ obj_flags |= EMAGGED
+ return TRUE
+
+// Multitooling emagged elevator buttons will fix the safeties
+/obj/item/assembly/control/elevator/multitool_act(mob/living/user)
+ if(!(obj_flags & EMAGGED))
+ return ..()
+
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ return ..()
+
+ for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
+ lift_platform.violent_landing = initial(lift_platform.violent_landing)
+ lift_platform.warns_on_down_movement = initial(lift_platform.warns_on_down_movement)
+ lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed)
+
+ // We can only be multitooled directly so just throw up the balloon alert
+ balloon_alert(user, "safeties reset")
+ obj_flags &= ~EMAGGED
+ return TRUE
+
+/obj/item/assembly/control/elevator/activate(mob/activator)
+ if(cooldown)
+ return
+
+ cooldown = TRUE
+ // Actually try to call the elevator - this sleeps.
+ // If we failed to call it, play a buzz sound.
+ if(!call_elevator(activator))
+ playsound(loc, 'sound/machines/buzz-two.ogg', 50, TRUE)
+
+ // Finally, give people a chance to get off after it's done before going back off cooldown
+ addtimer(VARSET_CALLBACK(src, cooldown, FALSE), 2 SECONDS)
+
+/// Actually calls the elevator.
+/// Returns FALSE if we failed to setup the move.
+/// Returns TRUE if the move setup was a success, EVEN IF the move itself fails afterwards
+/obj/item/assembly/control/elevator/proc/call_elevator(mob/activator)
+ // We can't call an elevator that doesn't exist
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ loc.balloon_alert(activator, "no elevator connected!")
+ return FALSE
+
+ // We can't call an elevator that's moving. You may say "you totally can do that", but that's not modelled
+ if(lift.controls_locked == LIFT_PLATFORM_LOCKED)
+ loc.balloon_alert(activator, "elevator is moving!")
+ return FALSE
+
+ // We can't call an elevator if it's already at this destination
+ var/obj/structure/industrial_lift/prime_lift = lift.return_closest_platform_to_z(loc.z)
+ if(prime_lift.z == loc.z)
+ loc.balloon_alert(activator, "elevator is here!")
+ return FALSE
+
+ // At this point, we can start moving.
+
+ // Give the user, if supplied, a balloon alert.
+ if(activator)
+ loc.balloon_alert(activator, "elevator called")
+
+ // Actually try to move the lift. This will sleep.
+ if(!lift.move_to_zlevel(loc.z, CALLBACK(src, .proc/check_button)))
+ loc.balloon_alert(activator, "elevator out of service!")
+ return FALSE
+
+ // From here on all returns are TRUE, as we successfully moved the lift, even if we maybe didn't reach our floor
+
+ // Our button was destroyed mid transit.
+ if(!check_button())
+ return TRUE
+
+ // Our lift platform survived, but it didn't reach our landing z.
+ if(!QDELETED(prime_lift) && prime_lift.z != loc.z)
+ if(!QDELETED(activator))
+ loc.balloon_alert(activator, "elevator out of service!")
+ playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, TRUE)
+ return TRUE
+
+ // Everything went according to plan
+ playsound(loc, 'sound/machines/ping.ogg', 50, TRUE)
+ if(!QDELETED(activator))
+ loc.balloon_alert(activator, "elevator arrived")
+
+ return TRUE
+
+/// Callback for move_to_zlevel / general proc to check if we're still in a button
+/obj/item/assembly/control/elevator/proc/check_button()
+ if(QDELETED(src))
+ return FALSE
+ if(!istype(loc, /obj/machinery/button))
+ return FALSE
+ return TRUE
+
+/// Gets the lift associated with our assembly / button
+/obj/item/assembly/control/elevator/proc/get_lift()
+ for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
+ if(possible_match.specific_lift_id != id)
+ continue
+
+ return possible_match
+
+ return null
+
+/obj/machinery/button/elevator
+ name = "elevator button"
+ desc = "Go back. Go back. Go back. Can you operate the elevator."
+ icon_state = "launcher"
+ skin = "launcher"
+ device_type = /obj/item/assembly/control/elevator
+ req_access = list()
+ id = 1
+
+/obj/machinery/button/elevator/Initialize(mapload, ndir, built)
+ . = ..()
+ // Kind of a cop-out
+ AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Call Elevator")
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/button/elevator, 28)
diff --git a/code/modules/industrial_lift/elevator_panel.dm b/code/modules/industrial_lift/elevator_panel.dm
new file mode 100644
index 00000000000..983fe42a1cb
--- /dev/null
+++ b/code/modules/industrial_lift/elevator_panel.dm
@@ -0,0 +1,361 @@
+/**
+ * # Elevator control panel
+ *
+ * A wallmounted simple machine that controls elevators,
+ * allowing users to enter a UI to move it up or down
+ *
+ * These can be placed in two methods:
+ * - You can place the control panel on the same turf as a lift. It will move up and down with the lift
+ * - You can place the control panel to the side of a lift, NOT attached to the lift. It will remain in position
+ * I don't recommend using both methods on the same elevator, as it might result in some jank, but it's functional.
+ */
+/obj/machinery/elevator_control_panel
+ name = "elevator panel"
+ // Fire alarm reference.
+ desc = "\"In case of emergency, please use the stairs.\" Thus, always use the stairs."
+ density = FALSE
+
+ icon = 'icons/obj/airlock_machines.dmi'
+ icon_state = "airlock_control_standby"
+ base_icon_state = "airlock_control"
+
+ power_channel = AREA_USAGE_ENVIRON
+ // Indestructible until someone wants to make these constructible, with all the chaos that implies
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+
+ /// Were we instantiated at mapload? Used to determine when we should link / throw errors
+ var/maploaded = FALSE
+
+ /// A weakref to the lift_master datum we control
+ var/datum/weakref/lift_weakref
+ /// What specific_lift_id do we link with?
+ var/linked_elevator_id
+
+ /// A list of all possible destinations this elevator can travel.
+ /// Assoc list of "Floor name" to "z level of destination".
+ /// By default the floor names will auto-generate ("Floor 1", "Floor 2", etc).
+ var/list/linked_elevator_destination
+ /// If you want to override what each floor is named as, you can do so with this list.
+ /// Make this an assoc list of "z level you want to rename" to "desired name".
+ /// So, if you want the z-level 2 destination to be named "Cargo", you would do list("2" = "Cargo").
+ var/list/preset_destination_names
+
+ /// What z-level did we move to last? Used for showing the user in the UI which direction we're moving.
+ var/last_move_target
+ /// TimerID to our door reset timer, made by emergency opening doors
+ var/door_reset_timerid
+
+/obj/machinery/elevator_control_panel/Initialize(mapload)
+ . = ..()
+
+ var/static/list/tool_behaviors = list(
+ TOOL_MULTITOOL = list(SCREENTIP_CONTEXT_LMB = "Reset Panel"),
+ )
+ AddElement(/datum/element/contextual_screentip_tools, tool_behaviors)
+ AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Elevator")
+
+ // Machinery returns lateload by default via parent,
+ // this is just here for redundancy's sake.
+ . = INITIALIZE_HINT_LATELOAD
+
+ maploaded = mapload
+ // Maploaded panels link in LateInitialize...
+ if(mapload)
+ return
+
+ // And non-mapload panels link in Initialize
+ var/datum/lift_master/lift = get_associated_lift()
+ if(!lift)
+ return
+
+ lift_weakref = WEAKREF(lift)
+ populate_destinations_list(lift)
+
+/obj/machinery/elevator_control_panel/LateInitialize()
+ . = ..()
+ // If we weren't maploaded, we probably already linked (or tried to link) in Initialize().
+ if(!maploaded)
+ return
+
+ // This is exclusively for linking in mapload, just to ensure all elevator parts are created,
+ // and also so we can throw mapping errors to let people know if they messed up setup.
+ var/datum/lift_master/lift = get_associated_lift()
+ if(!lift)
+ log_mapping("Elevator control panel at [AREACOORD(src)] found no associated lift to link with, this may be a mapping error.")
+ return
+
+ lift_weakref = WEAKREF(lift)
+ populate_destinations_list(lift)
+
+/obj/machinery/elevator_control_panel/emag_act(mob/user, obj/item/card/emag/emag_card)
+ if(obj_flags & EMAGGED)
+ return
+
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ return
+
+ for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
+ lift_platform.violent_landing = TRUE
+ lift_platform.warns_on_down_movement = FALSE
+ lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed) * 0.5
+
+ playsound(src, SFX_SPARKS, 100, TRUE, SHORT_RANGE_SOUND_EXTRARANGE)
+ balloon_alert(user, "safeties overridden")
+ obj_flags |= EMAGGED
+
+/obj/machinery/elevator_control_panel/multitool_act(mob/living/user)
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ return
+
+ balloon_alert(user, "resetting panel...")
+ playsound(src, 'sound/machines/locktoggle.ogg', 50, TRUE)
+ if(!do_after(user, 6 SECONDS, src))
+ balloon_alert(user, "interrupted!")
+ return TRUE
+
+ if(QDELETED(lift) || !length(lift.lift_platforms))
+ return
+
+ // If we were emagged, reset us
+ if(obj_flags & EMAGGED)
+ for(var/obj/structure/industrial_lift/lift_platform as anything in lift.lift_platforms)
+ lift_platform.violent_landing = initial(lift_platform.violent_landing)
+ lift_platform.warns_on_down_movement = initial(lift_platform.warns_on_down_movement)
+ lift_platform.elevator_vertical_speed = initial(lift_platform.elevator_vertical_speed)
+
+ obj_flags &= ~EMAGGED
+
+ // If we had doors open, stop the timer and reset them
+ if(door_reset_timerid)
+ deltimer(door_reset_timerid)
+ reset_doors()
+
+ // Be vague about whether something was accomplished or not
+ balloon_alert(user, "panel reset")
+ playsound(src, 'sound/machines/locktoggle.ogg', 50, TRUE)
+
+ return TRUE
+
+/// Find the elevator associated with our lift button.
+/obj/machinery/elevator_control_panel/proc/get_associated_lift()
+ for(var/datum/lift_master/possible_match as anything in GLOB.active_lifts_by_type[BASIC_LIFT_ID])
+ if(possible_match.specific_lift_id != linked_elevator_id)
+ continue
+
+ return possible_match
+
+ return null
+
+/// Goes through and populates the linked_elevator_destination list with all possible destinations the lift can go.
+/obj/machinery/elevator_control_panel/proc/populate_destinations_list(datum/lift_master/linked_lift)
+ // This list will track all the raw z-levels which we found that we can travel to
+ var/list/raw_destinations = list()
+
+ // Get a list of all the starting locs our elevator starts at
+ var/list/starting_locs = list()
+ for(var/obj/structure/industrial_lift/lift_piece as anything in linked_lift.lift_platforms)
+ starting_locs |= lift_piece.locs
+ // The raw destination list will start with all the z's we start at
+ raw_destinations |= lift_piece.z
+
+ // Get all destinations below us
+ add_destinations_in_a_direction_recursively(starting_locs, DOWN, raw_destinations)
+ // Get all destinations above us
+ add_destinations_in_a_direction_recursively(starting_locs, UP, raw_destinations)
+
+ linked_elevator_destination = list()
+ for(var/z_level in raw_destinations)
+ // Check if this z-level has a preset destination associated.
+ var/preset_name = preset_destination_names?[num2text(z_level)]
+ // If we don't have a preset name, use Floor z-1 for the title.
+ // z - 1 is used because the station z-level is 2, and goes up.
+ linked_elevator_destination["[z_level]"] = preset_name || "Floor [z_level - 1]"
+
+ // Reverse the destination list.
+ // By this point the list will go from bottom floor to top floor,
+ // which is unintuitive when passed to the UI to show to users.
+ // This way we have the top floors at the top, and the bottom floors the bottom.
+ reverse_range(linked_elevator_destination)
+ update_static_data_for_all_viewers()
+
+/**
+ * Recursively adds destinations to the list of linked_elevator_destination
+ * until it fails to find a valid stopping point in the passed direction.
+ */
+/obj/machinery/elevator_control_panel/proc/add_destinations_in_a_direction_recursively(list/turfs_to_check, direction, list/destinations)
+ // Only vertical elevators are supported - use trams for horizontal ones.
+ if(direction != UP && direction != DOWN)
+ CRASH("[type] was given an invalid direction in add_destinations_in_a_direction_recursively!")
+
+ var/list/turf/checked_turfs = list()
+ // Go through every turf passed in our list of turfs to check.
+ for(var/turf/place in turfs_to_check)
+ // If the place we're checking isn't openspace, then we can't go downwards
+ if(direction == DOWN && !istype(place, /turf/open/openspace))
+ return
+
+ // Check the turf at the next level (either above or below the place we're checking)
+ var/turf/next_level = get_step_multiz(place, direction)
+ // No turf = at the edge of a map vertically
+ if(!next_level)
+ return
+ // If the next level above us has a roof, we can't move up
+ if(direction == UP && !istype(next_level, /turf/open/openspace))
+ return
+
+ // Otherwise, we can feasibly move our direction with this turf
+ checked_turfs += next_level
+
+ // If we somehow found no turfs, BUT made it this far, something definitely went wrong.
+ if(!length(checked_turfs))
+ CRASH("[type] found no turfs in add_destinations_in_a_direction_recursively!")
+
+ // Add the Zs of all the found turfs as possible destinations
+ for(var/turf/found as anything in checked_turfs)
+ // We check all turfs we found in case of multi-z lift memes.
+ destinations |= found.z
+
+ // And recursively call the proc with all the turfs we found on the next level
+ add_destinations_in_a_direction_recursively(checked_turfs, direction, destinations)
+
+/obj/machinery/elevator_control_panel/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ElevatorPanel", name)
+ ui.open()
+
+/obj/machinery/elevator_control_panel/ui_status(mob/user)
+ // We moved up a z-level, probably via the lift itself, so don't preserve the UI.
+ if(user.z != z)
+ return UI_CLOSE
+
+ // Our lift is gone entirely - look, but don't touch.
+ if(!lift_weakref?.resolve())
+ return UI_UPDATE
+
+ // We're non-functional - don't do anything.
+ if(!check_panel())
+ return UI_DISABLED
+
+ // Otherwise, just check default state (is the user conscious and close, etc).
+ return ..()
+
+/obj/machinery/elevator_control_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ data["emergency_level"] = capitalize(SSsecurity_level.get_current_level_as_text())
+ data["is_emergency"] = SSsecurity_level.get_current_level_as_number() >= SEC_LEVEL_RED
+ data["doors_open"] = !!door_reset_timerid
+
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(lift)
+ data["lift_exists"] = TRUE
+ data["currently_moving"] = lift.controls_locked == LIFT_PLATFORM_LOCKED
+ data["currently_moving_to_floor"] = last_move_target
+ data["current_floor"] = lift.lift_platforms[1].z
+
+ else
+ data["lift_exists"] = FALSE
+ data["currently_moving"] = FALSE
+ data["current_floor"] = 0 // 0 shows up as "Floor -1" in the UI, which is fine for what it is
+
+ return data
+
+/obj/machinery/elevator_control_panel/ui_static_data(mob/user)
+ var/list/data = list()
+
+ data["all_floor_data"] = list()
+ for(var/destination in linked_elevator_destination)
+ data["all_floor_data"] += list(list(
+ "name" = linked_elevator_destination[destination],
+ "z_level" = text2num(destination),
+ ))
+
+ return data
+
+/obj/machinery/elevator_control_panel/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ if(!check_panel())
+ return TRUE // We shouldn't be usable right now, update UI
+
+ switch(action)
+ if("move_lift")
+ if(!allowed(usr))
+ balloon_alert(usr, "access denied!")
+ return
+
+ var/desired_z = params["z"]
+ // num2text here is required as the z is stored as strings in the list, but passed here as a number.
+ if(!(num2text(desired_z) in linked_elevator_destination))
+ return TRUE // Something is inaccurate, update UI
+
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift || lift.controls_locked == LIFT_PLATFORM_LOCKED)
+ return TRUE // We shouldn't be moving anything, update UI
+
+ INVOKE_ASYNC(lift, /datum/lift_master.proc/move_to_zlevel, desired_z, CALLBACK(src, .proc/check_panel), usr)
+ last_move_target = desired_z
+ return TRUE // Succcessfully initiated a move. Regardless of whether it actually works, update the UI
+
+ if("emergency_door")
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ return TRUE // Something is wrong, update UI
+
+ // The emergency door button is only available at red alert or higher.
+ // This is so people don't keep it in emergency mode 100% of the time.
+ if(SSsecurity_level.get_current_level_as_number() < SEC_LEVEL_RED)
+ return TRUE // The security level might have been lowered since last update, so update UI
+
+ // Open all lift doors, it's an emergency dang it!
+ lift.update_lift_doors(action = OPEN_DOORS)
+ door_reset_timerid = addtimer(CALLBACK(src, .proc/reset_doors), 3 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE)
+ return TRUE // We opened up all the doors, update the UI so the emergency button is replaced correctly
+
+ if("reset_doors")
+ if(!door_reset_timerid)
+ return TRUE // All the doors were shut since last update, so update UI
+
+ deltimer(door_reset_timerid)
+ reset_doors()
+ return TRUE // We closed all the doors, update the UI so the door button is replaced correctly
+
+/// Callback for move_to_zlevel to ensure the lift can continue to move.
+/obj/machinery/elevator_control_panel/proc/check_panel()
+ if(QDELETED(src))
+ return FALSE
+ if(machine_stat & (NOPOWER|BROKEN))
+ return FALSE
+
+ return TRUE
+
+/// Helper proc to go through all of our desetinations and reset all elevator doors,
+/// closing doors on z-levels the lift is away from, and opening doors on the z the lift is
+/obj/machinery/elevator_control_panel/proc/reset_doors()
+ var/datum/lift_master/lift = lift_weakref?.resolve()
+ if(!lift)
+ return
+
+ var/list/zs_we_are_present_on = lift.get_zs_we_are_on()
+ var/list/zs_we_are_absent = list()
+
+ for(var/z_level in linked_elevator_destination)
+ z_level = text2num(z_level) // Stored as strings.
+ if(z_level in zs_we_are_present_on)
+ continue
+
+ zs_we_are_absent |= z_level
+
+ // Open all the doors on the zs we should be open on,
+ // and close all doors we aren't on. Simple enough.
+ lift.update_lift_doors(zs_we_are_present_on, action = OPEN_DOORS)
+ lift.update_lift_doors(zs_we_are_absent, action = CLOSE_DOORS)
+
+ door_reset_timerid = null
+
+MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/elevator_control_panel, 30)
diff --git a/code/modules/industrial_lift/industrial_lift.dm b/code/modules/industrial_lift/industrial_lift.dm
index 8bd1f489e05..456aad74f21 100644
--- a/code/modules/industrial_lift/industrial_lift.dm
+++ b/code/modules/industrial_lift/industrial_lift.dm
@@ -51,10 +51,26 @@ GLOBAL_LIST_EMPTY(lifts)
///if TRUE, this platform will late initialize and then expand to become a multitile object across all other linked platforms on this z level
var/create_multitile_platform = FALSE
+ /// Does our elevator warn people (with visual effects) when moving down?
+ var/warns_on_down_movement = FALSE
+ /// if TRUE, we will gib anyone we land on top of. if FALSE, we will just apply damage with a serious wound penalty.
+ var/violent_landing = TRUE
+ /// How long does it take for the elevator to move vertically?
+ var/elevator_vertical_speed = 2 SECONDS
+
+ /// We use a radial to travel primarily, instead of a button / ui
+ var/radial_travel = TRUE
+ /// A lazylist of REFs to all mobs which have a radial open currently
+ var/list/current_operators
+
/obj/structure/industrial_lift/Initialize(mapload)
. = ..()
GLOB.lifts.Add(src)
+ // Yes if it's VV'd it won't be accurate but it probably shouldn't ever be
+ if(radial_travel)
+ AddElement(/datum/element/contextual_screentip_bare_hands, lmb_text = "Send Elevator")
+
set_movement_registrations()
//since lift_master datums find all connected platforms when an industrial lift first creates it and then
@@ -273,30 +289,41 @@ GLOBAL_LIST_EMPTY(lifts)
for(var/turf/dest_turf as anything in entering_locs)
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
- if(istype(dest_turf, /turf/closed/wall))
- var/turf/closed/wall/C = dest_turf
- do_sparks(2, FALSE, C)
- C.dismantle_wall(devastated = TRUE)
- for(var/mob/M in urange(8, src))
- shake_camera(M, 2, 3)
- playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
+ if(iswallturf(dest_turf))
+ var/turf/closed/wall/hit_wall = dest_turf
+ do_sparks(2, FALSE, hit_wall)
+ hit_wall.dismantle_wall(devastated = TRUE)
+ for(var/mob/nearby_witness in urange(8, src))
+ shake_camera(nearby_witness, 2, 3)
+ playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
for(var/mob/living/crushed in dest_turf.contents)
to_chat(crushed, span_userdanger("You are crushed by [src]!"))
- crushed.gib(FALSE,FALSE,FALSE)//the nicest kind of gibbing, keeping everything intact.
+ if(violent_landing)
+ // Violent landing = gibbed. But the nicest kind of gibbing, keeping everything intact.
+ crushed.gib(FALSE, FALSE, FALSE)
+ else
+ // Less violent landing simply crushes every bone in your body.
+ crushed.Paralyze(30 SECONDS, ignore_canstun = TRUE)
+ crushed.apply_damage(30, BRUTE, BODY_ZONE_CHEST, wound_bonus = 30)
+ crushed.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = 25)
+ crushed.apply_damage(15, BRUTE, BODY_ZONE_L_LEG, wound_bonus = 15)
+ crushed.apply_damage(15, BRUTE, BODY_ZONE_R_LEG, wound_bonus = 15)
+ crushed.apply_damage(15, BRUTE, BODY_ZONE_L_ARM, wound_bonus = 15)
+ crushed.apply_damage(15, BRUTE, BODY_ZONE_R_ARM, wound_bonus = 15)
else if(going == UP)
for(var/turf/dest_turf as anything in entering_locs)
///handles any special interactions objects could have with the lift/tram, handled on the item itself
SEND_SIGNAL(dest_turf, COMSIG_TURF_INDUSTRIAL_LIFT_ENTER, things_to_move)
- if(istype(dest_turf, /turf/closed/wall))
- var/turf/closed/wall/C = dest_turf
- do_sparks(2, FALSE, C)
- C.dismantle_wall(devastated = TRUE)
+ if(iswallturf(dest_turf))
+ var/turf/closed/wall/hit_wall = dest_turf
+ do_sparks(2, FALSE, hit_wall)
+ hit_wall.dismantle_wall(devastated = TRUE)
for(var/mob/client_mob in SSspatial_grid.orthogonal_range_search(src, SPATIAL_GRID_CONTENTS_TYPE_CLIENTS, 8))
shake_camera(client_mob, 2, 3)
- playsound(C, 'sound/effects/meteorimpact.ogg', 100, TRUE)
+ playsound(hit_wall, 'sound/effects/meteorimpact.ogg', 100, TRUE)
else
///potentially finds a spot to throw the victim at for daring to be hit by a tram. is null if we havent found anything to throw
@@ -507,47 +534,93 @@ GLOBAL_LIST_EMPTY(lifts)
return TRUE
-/obj/structure/industrial_lift/proc/use(mob/living/user)
- if(!isliving(user) || !in_range(src, user) || user.combat_mode)
+/// Callback / general proc to check if the lift is usable by the passed mob.
+/obj/structure/industrial_lift/proc/can_open_lift_radial(mob/living/user, starting_position)
+ // Gotta be a living mob
+ if(!isliving(user))
+ return FALSE
+ // Gotta be awake and aware
+ if(user.incapacitated())
+ return FALSE
+ // Maintain the god given right to fight an elevator
+ if(user.combat_mode)
+ return FALSE
+ // Gotta be by the lift
+ if(!user.Adjacent(src))
+ return FALSE
+ // If the lift moves while the radial is open close that shit
+ if(starting_position != loc)
+ return FALSE
+
+ return TRUE
+
+/// Opens the radial for the lift, allowing the user to move it around.
+/obj/structure/industrial_lift/proc/open_lift_radial(mob/living/user)
+ var/starting_position = loc
+ if(!can_open_lift_radial(user, starting_position))
+ return
+ // One radial per person
+ for(var/obj/structure/industrial_lift/other_platform as anything in lift_master_datum.lift_platforms)
+ if(REF(user) in other_platform.current_operators)
+ return
+
+
+ var/list/possible_directions = list()
+ if(lift_master_datum.Check_lift_move(UP))
+ var/static/image/up_arrow
+ if(!up_arrow)
+ up_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
+
+ possible_directions["Up"] = up_arrow
+
+ if(lift_master_datum.Check_lift_move(DOWN))
+ var/static/image/down_arrow
+ if(!down_arrow)
+ down_arrow = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
+
+ possible_directions["Down"] = down_arrow
+
+ add_fingerprint(user)
+ if(!length(possible_directions))
+ balloon_alert(user, "elevator out of service!")
return
- var/list/tool_list = list()
- if(lift_master_datum.Check_lift_move(UP))
- tool_list["Up"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = NORTH)
- if(lift_master_datum.Check_lift_move(DOWN))
- tool_list["Down"] = image(icon = 'icons/testing/turf_analysis.dmi', icon_state = "red_arrow", dir = SOUTH)
- if(!length(tool_list))
- to_chat(user, span_warning("[src] doesn't seem to able to move anywhere!"))
- add_fingerprint(user)
- return
- if(lift_master_datum.controls_locked)
- to_chat(user, span_warning("[src] has its controls locked! It must already be trying to do something!"))
- add_fingerprint(user)
- return
- var/result = show_radial_menu(user, src, tool_list, custom_check = CALLBACK(src, .proc/check_menu, user, src.loc), require_near = TRUE, tooltips = TRUE)
- if(!isliving(user) || !in_range(src, user) || user.combat_mode)
+ LAZYADD(current_operators, REF(user))
+ var/result = show_radial_menu(
+ user = user,
+ anchor = src,
+ choices = possible_directions,
+ custom_check = CALLBACK(src, .proc/can_open_lift_radial, user, starting_position),
+ require_near = TRUE,
+ tooltips = TRUE,
+ )
+
+ LAZYREMOVE(current_operators, REF(user))
+ if(!can_open_lift_radial(user, starting_position))
return //nice try
+ if(!isnull(result) && result != "Cancel" && lift_master_datum.controls_locked)
+ // Only show this message if they actually wanted to move
+ balloon_alert(user, "elevator controls locked!")
+ return
switch(result)
if("Up")
- // We have to make sure that they don't do illegal actions by not having their radial menu refresh from someone else moving the lift.
- if(!lift_master_datum.Check_lift_move(UP))
- to_chat(user, span_warning("[src] doesn't seem to able to move up!"))
- add_fingerprint(user)
+ // We have to make sure that they don't do illegal actions
+ // by not having their radial menu refresh from someone else moving the lift.
+ if(!lift_master_datum.simple_move_wrapper(UP, elevator_vertical_speed, user))
return
- lift_master_datum.MoveLift(UP, user)
- show_fluff_message(TRUE, user)
- use(user)
+
+ show_fluff_message(UP, user)
+ open_lift_radial(user)
+
if("Down")
- if(!lift_master_datum.Check_lift_move(DOWN))
- to_chat(user, span_warning("[src] doesn't seem to able to move down!"))
- add_fingerprint(user)
+ if(!lift_master_datum.simple_move_wrapper(DOWN, elevator_vertical_speed, user))
return
- lift_master_datum.MoveLift(DOWN, user)
- show_fluff_message(FALSE, user)
- use(user)
+
+ show_fluff_message(DOWN, user)
+ open_lift_radial(user)
+
if("Cancel")
return
- add_fingerprint(user)
/**
* Proc to ensure that the radial menu closes when it should.
@@ -567,45 +640,76 @@ GLOBAL_LIST_EMPTY(lifts)
. = ..()
if(.)
return
- use(user)
+ if(!radial_travel)
+ return ..()
+
+ return open_lift_radial(user)
//ai probably shouldn't get to use lifts but they sure are great for admins to crush people with
/obj/structure/industrial_lift/attack_ghost(mob/user)
. = ..()
if(.)
return
- if(isAdminGhostAI(user))
- use(user)
+ if(!radial_travel)
+ return
+ if(!isAdminGhostAI(user))
+ return
+
+ return open_lift_radial(user)
/obj/structure/industrial_lift/attack_paw(mob/user, list/modifiers)
- return use(user)
+ if(!radial_travel)
+ return ..()
-/obj/structure/industrial_lift/attackby(obj/item/W, mob/user, params)
- return use(user)
+ return open_lift_radial(user)
-/obj/structure/industrial_lift/attack_robot(mob/living/silicon/robot/R)
- if(R.Adjacent(src))
- return use(R)
+/obj/structure/industrial_lift/attackby(obj/item/attacking_item, mob/user, params)
+ if(!radial_travel)
+ return ..()
+
+ return open_lift_radial(user)
+
+/obj/structure/industrial_lift/attack_robot(mob/living/user)
+ if(!radial_travel)
+ return ..()
+
+ return open_lift_radial(user)
/**
* Shows a message indicating that the lift has moved up or down.
* Arguments:
- * * going_up - Boolean on whether or not we're going up, to adjust the message appropriately.
+ * * direction - What direction are we going
* * user - The mob that caused the lift to move, for the visible message.
*/
-/obj/structure/industrial_lift/proc/show_fluff_message(going_up, mob/user)
- if(going_up)
+/obj/structure/industrial_lift/proc/show_fluff_message(direction, mob/user)
+ if(direction == UP)
user.visible_message(span_notice("[user] moves the lift upwards."), span_notice("You move the lift upwards."))
- else
+
+ if(direction == DOWN)
user.visible_message(span_notice("[user] moves the lift downwards."), span_notice("You move the lift downwards."))
+// A subtype intended for "public use"
+/obj/structure/industrial_lift/public
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "rockvault"
+ base_icon_state = null
+ smoothing_flags = NONE
+ smoothing_groups = null
+ canSmoothWith = null
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ warns_on_down_movement = TRUE
+ violent_landing = FALSE
+ elevator_vertical_speed = 3 SECONDS
+ radial_travel = FALSE
+
/obj/structure/industrial_lift/debug
name = "transport platform"
desc = "A lightweight platform. It moves in any direction, except up and down."
color = "#5286b9ff"
lift_id = DEBUG_LIFT_ID
+ radial_travel = TRUE
-/obj/structure/industrial_lift/debug/use(mob/user)
+/obj/structure/industrial_lift/debug/open_lift_radial(mob/user)
if (!in_range(src, user))
return
//NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST
@@ -626,29 +730,29 @@ GLOBAL_LIST_EMPTY(lifts)
switch(result)
if("NORTH")
- lift_master_datum.MoveLiftHorizontal(NORTH, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(NORTH, z)
+ open_lift_radial(user)
if("NORTHEAST")
- lift_master_datum.MoveLiftHorizontal(NORTHEAST, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(NORTHEAST, z)
+ open_lift_radial(user)
if("EAST")
- lift_master_datum.MoveLiftHorizontal(EAST, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(EAST, z)
+ open_lift_radial(user)
if("SOUTHEAST")
- lift_master_datum.MoveLiftHorizontal(SOUTHEAST, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(SOUTHEAST, z)
+ open_lift_radial(user)
if("SOUTH")
- lift_master_datum.MoveLiftHorizontal(SOUTH, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(SOUTH, z)
+ open_lift_radial(user)
if("SOUTHWEST")
- lift_master_datum.MoveLiftHorizontal(SOUTHWEST, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(SOUTHWEST, z)
+ open_lift_radial(user)
if("WEST")
- lift_master_datum.MoveLiftHorizontal(WEST, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(WEST, z)
+ open_lift_radial(user)
if("NORTHWEST")
- lift_master_datum.MoveLiftHorizontal(NORTHWEST, z)
- use(user)
+ lift_master_datum.move_lift_horizontally(NORTHWEST, z)
+ open_lift_radial(user)
if("Cancel")
return
@@ -668,6 +772,7 @@ GLOBAL_LIST_EMPTY(lifts)
lift_id = TRAM_LIFT_ID
lift_master_type = /datum/lift_master/tram
+ radial_travel = FALSE
/// Set by the tram control console in late initialize
var/travelling = FALSE
@@ -699,9 +804,6 @@ GLOBAL_LIST_EMPTY(lifts)
src.travelling = travelling
SEND_SIGNAL(src, COMSIG_TRAM_SET_TRAVELLING, travelling)
-/obj/structure/industrial_lift/tram/use(mob/user) //dont click the floor dingus we use computers now
- return
-
/obj/structure/industrial_lift/tram/set_currently_z_moving()
return FALSE //trams can never z fall and shouldnt waste any processing time trying to do so
diff --git a/code/modules/industrial_lift/lift_master.dm b/code/modules/industrial_lift/lift_master.dm
index 45c2ae3b81a..f3095dff24d 100644
--- a/code/modules/industrial_lift/lift_master.dm
+++ b/code/modules/industrial_lift/lift_master.dm
@@ -31,8 +31,6 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
///overridable ID string to link control units to this specific lift_master datum. created by placing a lift id landmark object
///somewhere on the tram, if its anywhere on the tram we'll find it in init and set this to whatever it specifies
var/specific_lift_id
- ///what directions we're allowed to move
- var/allowed_travel_directions = ALL
///if true, the lift cannot be manually moved.
var/controls_locked = FALSE
@@ -239,6 +237,42 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
return winner
+/// Returns a lift platform on the z-level which is vertically closest to the passed target_z
+/datum/lift_master/proc/return_closest_platform_to_z(target_z)
+ var/obj/structure/industrial_lift/found_platform
+ for(var/obj/structure/industrial_lift/lift as anything in lift_platforms)
+ // Already at the same Z-level, we can stop
+ if(lift.z == target_z)
+ found_platform = lift
+ break
+
+ // Set up an initial lift to compare to
+ if(!found_platform)
+ found_platform = lift
+ continue
+
+ // Same level, we can go with the one we currently have
+ if(lift.z == found_platform.z)
+ continue
+
+ // If the difference between the current found platform and the target
+ // if less than the distance between the next lift and the target,
+ // our current platform is closer to the target than the next one, so we can skip it
+ if(abs(found_platform.z - target_z) < abs(lift.z - target_z))
+ continue
+
+ // The difference is smaller for this lift, so it's closer
+ found_platform = lift
+
+ return found_platform
+
+/// Returns a list of all the z-levels our lift is currently on.
+/datum/lift_master/proc/get_zs_we_are_on()
+ var/list/zs_we_are_present_on = list()
+ for(var/obj/structure/industrial_lift/lift as anything in lift_platforms)
+ zs_we_are_present_on |= lift.z
+ return zs_we_are_present_on
+
///returns all industrial_lifts associated with this tram on the given z level or given atoms z level
/datum/lift_master/proc/get_platforms_on_level(atom/atom_reference_OR_z_level_number)
var/z = atom_reference_OR_z_level_number
@@ -259,13 +293,12 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
/**
* Moves the lift UP or DOWN, this is what users invoke with their hand.
* This is a SAFE proc, ensuring every part of the lift moves SANELY.
- * It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
+ *
* Arguments:
* going - UP or DOWN directions, where the lift should go. Keep in mind by this point checks of whether it should go up or down have already been done.
* user - Whomever made the lift movement.
*/
-/datum/lift_master/proc/MoveLift(going, mob/user)
- set_controls(LIFT_PLATFORM_LOCKED)
+/datum/lift_master/proc/move_lift_vertically(going, mob/user)
//lift_platforms are sorted in order of lowest z to highest z, so going upwards we need to move them in reverse order to not collide
if(going == UP)
var/obj/structure/industrial_lift/platform_to_move
@@ -280,14 +313,208 @@ GLOBAL_LIST_EMPTY(active_lifts_by_type)
else if(going == DOWN)
for(var/obj/structure/industrial_lift/lift_platform as anything in lift_platforms)
lift_platform.travel(going)
+
+/**
+ * Moves the lift after a passed delay.
+ *
+ * This is a more "user friendly" or "realistic" lift move.
+ * It includes things like:
+ * - Allowing lift "travel time"
+ * - Shutting elevator safety doors
+ * - Sound effects while moving
+ * - Safety warnings for anyone below the lift (while it's moving downwards)
+ *
+ * Arguments:
+ * duration - required, how long do we wait to move the lift?
+ * door_duration - optional, how long should we wait to open the doors after arriving? If null, we won't open or close doors
+ * direction - which direction are we moving the lift?
+ * user - optional, who is moving the lift?
+ */
+/datum/lift_master/proc/move_after_delay(lift_move_duration, door_duration, direction, mob/user)
+ if(!isnum(lift_move_duration))
+ CRASH("[type] move_after_delay called with invalid duration ([lift_move_duration]).")
+ if(lift_move_duration <= 0 SECONDS)
+ move_lift_vertically(direction, user)
+ return
+
+ // Get the lowest or highest lift according to which direction we're moving
+ var/obj/structure/industrial_lift/prime_lift = return_closest_platform_to_z(direction == UP ? world.maxz : 0)
+
+ // If anyone changes the hydraulic sound effect I sure hope they update this variable...
+ var/hydraulic_sfx_duration = 2 SECONDS
+ // ...because we use the duration of the sound effect to make it last for roughly the duration of the lift travel
+ playsound(prime_lift, 'sound/mecha/hydraulic.ogg', 25, vary = TRUE, frequency = clamp(hydraulic_sfx_duration / lift_move_duration, 0.33, 3))
+
+ // Move the lift after a timer
+ addtimer(CALLBACK(src, .proc/move_lift_vertically, direction, user), lift_move_duration, TIMER_UNIQUE)
+ // Open doors after the set duration if supplied
+ if(isnum(door_duration))
+ addtimer(CALLBACK(src, .proc/open_lift_doors_callback), door_duration, TIMER_UNIQUE)
+
+ // Here on we only care about lifts going DOWN
+ if(direction != DOWN)
+ return
+
+ // Okay we're going down, let's try to display some warnings to people below
+ var/list/turf/lift_locs = list()
+ for(var/obj/structure/industrial_lift/going_to_move as anything in lift_platforms)
+ // This lift has no warnings so we don't even need to worry about it
+ if(!going_to_move.warns_on_down_movement)
+ continue
+ // Collect all the turfs our lift is found at
+ lift_locs |= going_to_move.locs
+
+ for(var/turf/moving in lift_locs)
+ // Find what's below the turf that's moving
+ var/turf/below_us = get_step_multiz(moving, DOWN)
+ // Hold up the turf below us is also in our locs list. Multi-z lift? Don't show a warning
+ if(below_us in lift_locs)
+ continue
+ // Display the warning for until we land
+ new /obj/effect/temp_visual/telegraphing/lift_travel(below_us, lift_move_duration)
+
+/**
+ * Simple wrapper for checking if we can move 1 zlevel, and if we can, do said move.
+ * Locks controls, closes all doors, then moves the lift and re-opens the doors afterwards.
+ *
+ * Arguments:
+ * direction - which direction are we moving?
+ * lift_move_duration - how long does the move take? can be 0 or null for instant move.
+ * door_duration - how long does it take for the doors to open after a move?
+ * user - optional, who moved it?
+ */
+/datum/lift_master/proc/simple_move_wrapper(direction, lift_move_duration, mob/user)
+ if(!Check_lift_move(direction))
+ return FALSE
+
+ // Lock controls, to prevent moving-while-moving memes
+ set_controls(LIFT_PLATFORM_LOCKED)
+ // Close all lift doors
+ update_lift_doors(action = CLOSE_DOORS)
+
+ if(isnull(lift_move_duration) || lift_move_duration <= 0 SECONDS)
+ // Do an instant move
+ move_lift_vertically(direction, user)
+ // Open doors on the zs we arrive at
+ update_lift_doors(get_zs_we_are_on(), action = OPEN_DOORS)
+ // And unlock the controls after
+ set_controls(LIFT_PLATFORM_UNLOCKED)
+ return TRUE
+
+ // Do a delayed move
+ move_after_delay(
+ lift_move_duration = lift_move_duration,
+ door_duration = lift_move_duration * 1.5,
+ direction = direction,
+ user = user,
+ )
+
+ addtimer(CALLBACK(src, .proc/set_controls, LIFT_PLATFORM_UNLOCKED), lift_move_duration * 1.5)
+ return TRUE
+
+/**
+ * Moves the lift to the passed z-level.
+ *
+ * Checks for validity of the move: Are we moving to the same z-level, can we actually move to that z-level?
+ * Does NOT check if the lift controls are currently locked.
+ *
+ * Moves to the passed z-level by calling move_after_delay repeatedly until the passed z-level is reached.
+ * This proc sleeps as it moves.
+ *
+ * Arguments:
+ * target_z - required, the Z we want to move to
+ * loop_callback - optional, an additional callback invoked during the l oop that allows the move to cancel.
+ * user - optional, who started the move
+ */
+/datum/lift_master/proc/move_to_zlevel(target_z, datum/callback/loop_callback, mob/user)
+ if(!isnum(target_z) || target_z <= 0)
+ CRASH("[type] move_to_zlevel was passed an invalid target_z ([target_z]).")
+
+ var/obj/structure/industrial_lift/prime_lift = return_closest_platform_to_z(target_z)
+ var/lift_z = prime_lift.z
+ // We're already at the desired z-level!
+ if(target_z == lift_z)
+ return FALSE
+
+ // The amount of z levels between the our and target_z
+ var/z_difference = abs(target_z - lift_z)
+ // Direction (up/down) needed to go to reach target_z
+ var/direction = lift_z < target_z ? UP : DOWN
+
+ // We can't go that way anymore, or possibly ever
+ if(!Check_lift_move(direction))
+ return FALSE
+
+ // Okay we're ready to start moving now.
+ set_controls(LIFT_PLATFORM_LOCKED)
+ var/travel_speed = prime_lift.elevator_vertical_speed
+
+ // Close all lift doors
+ update_lift_doors(action = CLOSE_DOORS)
+ // Approach the desired z-level one step at a time
+ for(var/i in 1 to z_difference)
+ if(!Check_lift_move(direction))
+ break
+ if(loop_callback && !loop_callback.Invoke())
+ break
+ // move_after_delay will set up a timer and cause us to move after a time
+ move_after_delay(
+ lift_move_duration = travel_speed,
+ direction = direction,
+ user = user,
+ )
+ // and we don't want to send another request until the timer's done
+ stoplag(travel_speed + 0.1 SECONDS)
+ if(QDELETED(src) || QDELETED(prime_lift))
+ return
+
+ addtimer(CALLBACK(src, .proc/open_lift_doors_callback), 2 SECONDS)
set_controls(LIFT_PLATFORM_UNLOCKED)
+ return TRUE
+
+/**
+ * Updates all blast doors and shutters that share an ID with our lift.
+ *
+ * Arguments:
+ * on_z_level - optional, only open doors on this z-level or list of z-levels
+ * action - how do we update the doors? OPEN_DOORS to make them open, CLOSE_DOORS to make them shut
+ */
+/datum/lift_master/proc/update_lift_doors(on_z_level, action)
+
+ if(!isnull(on_z_level) && !islist(on_z_level))
+ on_z_level = list(on_z_level)
+
+ var/played_ding = FALSE
+ for(var/obj/machinery/door/poddoor/elevator_door in GLOB.machines)
+ if(elevator_door.id != specific_lift_id)
+ continue
+ if(on_z_level && !(elevator_door.z in on_z_level))
+ continue
+
+ switch(action)
+ if(OPEN_DOORS)
+ INVOKE_ASYNC(elevator_door, /obj/machinery/door/poddoor.proc/open)
+
+ if(CLOSE_DOORS)
+ INVOKE_ASYNC(elevator_door, /obj/machinery/door/poddoor.proc/close)
+
+ else
+ stack_trace("Elevator lift update_lift_doors called with an improper action ([action]).")
+
+ if(!played_ding)
+ playsound(elevator_door, 'sound/machines/ding.ogg', 50, TRUE)
+ played_ding = TRUE
+
+/// Helper used in callbacks to open all the doors our lift is on
+/datum/lift_master/proc/open_lift_doors_callback()
+ update_lift_doors(get_zs_we_are_on(), action = OPEN_DOORS)
/**
* Moves the lift, this is what users invoke with their hand.
* This is a SAFE proc, ensuring every part of the lift moves SANELY.
* It also locks controls for the (miniscule) duration of the movement, so the elevator cannot be broken by spamming.
*/
-/datum/lift_master/proc/MoveLiftHorizontal(going)
+/datum/lift_master/proc/move_lift_horizontally(going)
set_controls(LIFT_PLATFORM_LOCKED)
if(multitile_platform)
diff --git a/code/modules/industrial_lift/tram_controls.dm b/code/modules/industrial_lift/tram_controls.dm
index 6b3bc12dc10..a13a66a066a 100644
--- a/code/modules/industrial_lift/tram_controls.dm
+++ b/code/modules/industrial_lift/tram_controls.dm
@@ -109,7 +109,7 @@
if(tram_part.controls_locked || tram_part.travelling) // someone else started already
return FALSE
tram_part.tram_travel(to_where)
- visible_message("The tram has been called to [to_where.name]")
+ say("The tram has been called to [to_where.name].")
return TRUE
/obj/item/circuit_component/tram_controls
diff --git a/code/modules/industrial_lift/tram_lift_master.dm b/code/modules/industrial_lift/tram_lift_master.dm
index 573e961a71d..55739e78896 100644
--- a/code/modules/industrial_lift/tram_lift_master.dm
+++ b/code/modules/industrial_lift/tram_lift_master.dm
@@ -125,7 +125,7 @@
var/start_time = TICK_USAGE
travel_distance--
- MoveLiftHorizontal(travel_direction)
+ move_lift_horizontally(travel_direction)
var/duration = TICK_USAGE_TO_MS(start_time)
if(slowed_down)
diff --git a/code/modules/wiremod/shell/assembly.dm b/code/modules/wiremod/shell/assembly.dm
index b93c96b885d..88ab3e97579 100644
--- a/code/modules/wiremod/shell/assembly.dm
+++ b/code/modules/wiremod/shell/assembly.dm
@@ -39,7 +39,7 @@
/obj/item/circuit_component/assembly_input/unregister_shell(atom/movable/shell)
UnregisterSignal(shell, list(COMSIG_ASSEMBLY_PULSED, COMSIG_ITEM_ATTACK_SELF))
-/obj/item/circuit_component/assembly_input/proc/on_pulsed()
+/obj/item/circuit_component/assembly_input/proc/on_pulsed(datum/source, mob/pulser)
SIGNAL_HANDLER
signal.set_output(COMPONENT_SIGNAL)
diff --git a/tgstation.dme b/tgstation.dme
index 5e7ff11df37..fbe377a1aa7 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -3153,6 +3153,8 @@
#include "code\modules\hydroponics\grown\weeds\nettle.dm"
#include "code\modules\hydroponics\grown\weeds\starthistle.dm"
#include "code\modules\industrial_lift\crossing_signal.dm"
+#include "code\modules\industrial_lift\elevator_button.dm"
+#include "code\modules\industrial_lift\elevator_panel.dm"
#include "code\modules\industrial_lift\industrial_lift.dm"
#include "code\modules\industrial_lift\lift_master.dm"
#include "code\modules\industrial_lift\tram_controls.dm"
diff --git a/tgui/packages/tgui/interfaces/ElevatorPanel.tsx b/tgui/packages/tgui/interfaces/ElevatorPanel.tsx
new file mode 100644
index 00000000000..d214917665a
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ElevatorPanel.tsx
@@ -0,0 +1,205 @@
+import { clamp } from 'common/math';
+import { BooleanLike } from 'common/react';
+import { useBackend } from '../backend';
+import { Box, Blink, Button, Dimmer, Icon, Section, Stack } from '../components';
+import { Window } from '../layouts';
+
+type FloorData = {
+ name: string;
+ z_level: number;
+};
+
+type ElevatorPanelData = {
+ // What Z the lift is on
+ current_floor: number;
+ // Station emergency level - "Red", "Green", etc
+ emergency_level: string;
+ // State: Is the station in red or delta alert?
+ is_emergency: BooleanLike;
+ // State: Are the doors emergency opened?
+ doors_open: BooleanLike;
+ // State: Does the lift exist?
+ lift_exists: BooleanLike;
+ // State: Is the lift moving?
+ currently_moving: BooleanLike;
+ // What floor are we currently moving to?
+ currently_moving_to_floor: number | null;
+ // A list of all floors we can move around to
+ all_floor_data: FloorData[];
+};
+
+export const ElevatorPanel = (props, context) => {
+ const { data, act } = useBackend(context);
+
+ const {
+ current_floor,
+ emergency_level,
+ is_emergency,
+ doors_open,
+ lift_exists,
+ currently_moving,
+ all_floor_data,
+ } = data;
+
+ /*
+ * We want to grow our UI if we have a lot of floors to display,
+ * while also leaving some whitespace - just so it's not too cluttered.
+ *
+ * 400 is chosen for the min, to prevent it from being too tiny.
+ *
+ * 600 is chosen for the max, as it fits 10 floors pretty comfortably.
+ * If you seriously need more than 10 floors for an elevator in this game,
+ * you should reconsider your map's layout. It's not worth it.
+ */
+ const calculatedHeight = clamp(all_floor_data.length * 90, 400, 600);
+
+ return (
+
+
+ {!lift_exists && }
+
+
+
+
+
+
+
+
+ {!!currently_moving && }
+
+ {all_floor_data.map((floor, index) => (
+
+
+
+ ))}
+
+
+
+
+
+ {doors_open ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+ );
+};
+
+const NoLiftDimmer = () => {
+ return (
+
+
+
+
+
+ No elevator connected.
+
+
+ );
+};
+
+const MovingDimmer = () => {
+ return (
+
+
+
+
+
+ Moving...
+
+
+ );
+};
+
+const FloorPanel = (props, context) => {
+ const { data } = useBackend(context);
+ const { current_floor, currently_moving, currently_moving_to_floor } = data;
+
+ return (
+
+
+
+
+ current_floor
+ }
+ />
+
+
+
+
+
+
+
+
+ {current_floor - 1}
+
+
+
+ );
+};
+
+const ArrowIcon = (props) => {
+ return props.is_moving ? (
+
+ ) : (
+
+ );
+};
diff --git a/tgui/packages/tgui/styles/themes/retro.scss b/tgui/packages/tgui/styles/themes/retro.scss
index e345f9115c5..c3e5d454343 100644
--- a/tgui/packages/tgui/styles/themes/retro.scss
+++ b/tgui/packages/tgui/styles/themes/retro.scss
@@ -28,7 +28,7 @@
'../components/Button.scss',
$with: (
'color-default': #e8e4c9,
- 'color-disabled': #363636,
+ 'color-disabled': #505046,
'color-selected': #9d0808,
'color-caution': #be6209,
'color-danger': #9a9d00
@@ -55,6 +55,17 @@
outline: base.em(1px) solid #161613;
}
+ .Button--disabled {
+ color: #c5c5c2;
+ font-family: monospace;
+ &:hover {
+ color: #ffffff;
+ }
+ &:focus {
+ color: #ffffff;
+ }
+ }
+
.Layout__content {
background-image: none;
}