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; }