diff --git a/_maps/map_files/Delta/delta.dmm b/_maps/map_files/Delta/delta.dmm
index 02f36f5ea40..28907e9e05e 100644
--- a/_maps/map_files/Delta/delta.dmm
+++ b/_maps/map_files/Delta/delta.dmm
@@ -47144,7 +47144,7 @@
pixel_y = 3
},
/obj/item/circuitboard/powermonitor,
-/obj/item/circuitboard/stationalert_all{
+/obj/item/circuitboard/stationalert{
pixel_x = 3;
pixel_y = -3
},
diff --git a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm
index bcbbe8b2351..4152ad16382 100644
--- a/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.v41A.II.dmm
@@ -31415,7 +31415,7 @@
pixel_x = -2;
pixel_y = 2
},
-/obj/item/circuitboard/stationalert_all{
+/obj/item/circuitboard/stationalert{
pixel_x = 1;
pixel_y = -1
},
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/debris1.dmm b/_maps/map_files/RandomRuins/SpaceRuins/debris1.dmm
new file mode 100644
index 00000000000..af01a5bc274
--- /dev/null
+++ b/_maps/map_files/RandomRuins/SpaceRuins/debris1.dmm
@@ -0,0 +1,614 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/template_noop,
+/area/template_noop)
+"b" = (
+/obj/structure/disposalpipe/segment,
+/turf/space,
+/area/space)
+"c" = (
+/obj/structure/lattice,
+/obj/structure/disposalpipe/broken{
+ dir = 1
+ },
+/turf/space,
+/area/space)
+"d" = (
+/obj/structure/lattice,
+/turf/space,
+/area/template_noop)
+"e" = (
+/obj/structure/grille/broken,
+/turf/space,
+/area/space)
+"g" = (
+/obj/structure/disposalpipe/broken{
+ dir = 4
+ },
+/turf/space,
+/area/space)
+"h" = (
+/obj/structure/disposalpipe/broken{
+ dir = 8
+ },
+/obj/item/stack/cable_coil/cut,
+/turf/space,
+/area/space)
+"l" = (
+/obj/item/stack/cable_coil/cut,
+/turf/space,
+/area/space)
+"m" = (
+/obj/effect/spawner/random_spawners/grille_often,
+/turf/space,
+/area/space)
+"n" = (
+/obj/effect/spawner/random_spawners/grille_maybe,
+/turf/space,
+/area/space)
+"o" = (
+/obj/random/tool,
+/turf/space,
+/area/space)
+"p" = (
+/obj/structure/girder/displaced,
+/turf/space,
+/area/space)
+"r" = (
+/turf/space,
+/area/template_noop)
+"t" = (
+/obj/structure/cable{
+ d1 = 2;
+ d2 = 8;
+ icon_state = "2-8"
+ },
+/turf/space,
+/area/space)
+"u" = (
+/obj/structure/door_assembly/door_assembly_sec,
+/obj/item/airlock_electronics,
+/turf/space,
+/area/space)
+"v" = (
+/obj/structure/lattice,
+/turf/space,
+/area/space)
+"x" = (
+/obj/structure/disposalpipe/junction,
+/turf/space,
+/area/space)
+"y" = (
+/turf/simulated/floor/plating/burnt,
+/area/space)
+"z" = (
+/obj/item/stack/sheet/metal,
+/turf/simulated/floor/plating,
+/area/space)
+"B" = (
+/obj/structure/lattice,
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/space)
+"C" = (
+/obj/structure/disposalpipe/broken{
+ dir = 1
+ },
+/turf/space,
+/area/space)
+"D" = (
+/obj/structure/cable{
+ d1 = 4;
+ d2 = 8;
+ icon_state = "4-8";
+ pixel_y = 0;
+ tag = ""
+ },
+/turf/simulated/floor/plating/burnt,
+/area/space)
+"E" = (
+/obj/item/stack/rods,
+/turf/space,
+/area/space)
+"F" = (
+/turf/simulated/floor/plating,
+/area/space)
+"G" = (
+/obj/random/toolbox,
+/turf/simulated/floor/plating/burnt,
+/area/space)
+"I" = (
+/obj/structure/grille/broken,
+/obj/structure/lattice,
+/turf/space,
+/area/space)
+"J" = (
+/obj/structure/disposalpipe/broken,
+/turf/space,
+/area/space)
+"K" = (
+/obj/item/shard,
+/turf/space,
+/area/space)
+"N" = (
+/obj/structure/lattice,
+/obj/structure/lattice,
+/turf/space,
+/area/space)
+"O" = (
+/obj/machinery/power/apc/worn_out{
+ pixel_y = -24
+ },
+/obj/structure/cable,
+/turf/simulated/floor/plating,
+/area/space)
+"Q" = (
+/obj/effect/decal/cleanable/shreds{
+ pixel_x = 4;
+ pixel_y = 3
+ },
+/turf/simulated/floor/plating,
+/area/space)
+"T" = (
+/obj/structure/girder,
+/turf/simulated/floor/plating/burnt,
+/area/space)
+"U" = (
+/obj/effect/spawner/random_barrier/wall_probably,
+/turf/space,
+/area/space)
+"V" = (
+/turf/simulated/wall,
+/area/space)
+"X" = (
+/turf/space,
+/area/space)
+"Y" = (
+/obj/item/shard,
+/turf/simulated/floor/plating,
+/area/space)
+"Z" = (
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/space)
+
+(1,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+a
+r
+r
+r
+r
+r
+r
+r
+r
+r
+d
+d
+d
+r
+r
+d
+r
+a
+a
+"}
+(3,1,1) = {"
+a
+d
+v
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+r
+a
+"}
+(4,1,1) = {"
+a
+d
+X
+m
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+v
+Z
+X
+X
+d
+a
+"}
+(5,1,1) = {"
+a
+r
+X
+g
+F
+X
+E
+I
+X
+X
+v
+X
+F
+v
+v
+X
+X
+X
+r
+a
+"}
+(6,1,1) = {"
+a
+r
+J
+x
+C
+E
+v
+F
+X
+E
+X
+v
+g
+Z
+X
+X
+F
+X
+r
+a
+"}
+(7,1,1) = {"
+a
+r
+X
+X
+y
+E
+X
+E
+Y
+X
+n
+X
+h
+E
+F
+X
+v
+X
+r
+a
+"}
+(8,1,1) = {"
+a
+r
+X
+X
+X
+B
+l
+p
+X
+X
+y
+p
+X
+X
+X
+E
+X
+X
+r
+a
+"}
+(9,1,1) = {"
+a
+r
+X
+v
+X
+o
+X
+X
+Z
+l
+X
+E
+a
+X
+X
+V
+X
+X
+r
+a
+"}
+(10,1,1) = {"
+a
+r
+X
+Z
+Q
+v
+X
+y
+v
+D
+y
+U
+X
+X
+E
+X
+X
+n
+r
+a
+"}
+(11,1,1) = {"
+a
+r
+X
+X
+I
+v
+v
+X
+v
+t
+O
+V
+X
+X
+F
+E
+Z
+X
+d
+a
+"}
+(12,1,1) = {"
+a
+r
+X
+X
+X
+X
+J
+b
+c
+v
+G
+V
+X
+E
+e
+X
+X
+X
+r
+a
+"}
+(13,1,1) = {"
+a
+r
+X
+X
+X
+V
+X
+Z
+X
+X
+X
+u
+X
+X
+X
+X
+e
+Z
+r
+a
+"}
+(14,1,1) = {"
+a
+r
+X
+X
+X
+X
+v
+X
+v
+N
+E
+E
+K
+X
+v
+o
+X
+X
+r
+a
+"}
+(15,1,1) = {"
+a
+r
+X
+E
+X
+X
+X
+X
+E
+J
+C
+X
+X
+X
+X
+X
+X
+X
+d
+a
+"}
+(16,1,1) = {"
+a
+r
+X
+X
+E
+X
+T
+X
+Z
+X
+n
+v
+X
+z
+n
+X
+Z
+X
+d
+a
+"}
+(17,1,1) = {"
+a
+r
+X
+X
+X
+X
+X
+X
+X
+X
+F
+X
+X
+X
+p
+X
+X
+X
+r
+a
+"}
+(18,1,1) = {"
+a
+r
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+X
+v
+X
+X
+X
+X
+r
+a
+"}
+(19,1,1) = {"
+a
+a
+r
+r
+r
+r
+d
+r
+d
+r
+r
+r
+r
+r
+r
+r
+r
+r
+a
+a
+"}
+(20,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/debris2.dmm b/_maps/map_files/RandomRuins/SpaceRuins/debris2.dmm
new file mode 100644
index 00000000000..6a6cd81f32a
--- /dev/null
+++ b/_maps/map_files/RandomRuins/SpaceRuins/debris2.dmm
@@ -0,0 +1,631 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/template_noop,
+/area/template_noop)
+"b" = (
+/turf/simulated/floor/plating/airless,
+/area/space)
+"c" = (
+/turf/simulated/floor/plasteel/airless,
+/area/space)
+"d" = (
+/turf/simulated/wall/r_wall,
+/area/space)
+"e" = (
+/obj/item/stack/cable_coil/cut,
+/turf/space,
+/area/template_noop)
+"h" = (
+/obj/structure/girder,
+/turf/space,
+/area/template_noop)
+"j" = (
+/obj/structure/grille/broken,
+/turf/space,
+/area/space)
+"l" = (
+/obj/structure/girder,
+/turf/simulated/floor/plating,
+/area/space)
+"m" = (
+/obj/structure/grille/broken,
+/turf/template_noop,
+/area/space)
+"n" = (
+/obj/item/stack/cable_coil/cut,
+/turf/template_noop,
+/area/space)
+"o" = (
+/obj/machinery/door/airlock/command{
+ max_integrity = 40
+ },
+/turf/simulated/floor/plating/burnt,
+/area/space)
+"p" = (
+/mob/living/simple_animal/hostile/carp,
+/turf/space,
+/area/template_noop)
+"q" = (
+/obj/structure/lattice,
+/turf/space,
+/area/space)
+"s" = (
+/obj/item/stack/rods,
+/turf/space,
+/area/template_noop)
+"t" = (
+/obj/structure/door_assembly/door_assembly_com,
+/turf/space,
+/area/template_noop)
+"u" = (
+/obj/item/clothing/head/bio_hood/virology,
+/turf/space,
+/area/space)
+"w" = (
+/obj/structure/lattice,
+/turf/template_noop,
+/area/space)
+"x" = (
+/turf/template_noop,
+/area/space)
+"y" = (
+/obj/structure/lattice,
+/obj/structure/door_assembly/door_assembly_vault{
+ anchored = 1
+ },
+/turf/simulated/floor/plating,
+/area/space)
+"z" = (
+/obj/item/stack/rods,
+/turf/template_noop,
+/area/space)
+"A" = (
+/obj/structure/closet/crate,
+/obj/item/reagent_containers/glass/beaker/bluespace,
+/turf/simulated/floor/plasteel/airless,
+/area/space)
+"B" = (
+/obj/item/shard{
+ icon_state = "small"
+ },
+/turf/space,
+/area/space)
+"C" = (
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/template_noop)
+"D" = (
+/obj/effect/landmark/burnturf,
+/turf/simulated/floor/plating/airless,
+/area/space)
+"E" = (
+/obj/item/reagent_containers/glass/beaker/large,
+/turf/space,
+/area/template_noop)
+"F" = (
+/obj/effect/landmark/burnturf,
+/turf/simulated/floor,
+/area/space)
+"G" = (
+/obj/structure/safe,
+/obj/item/stack/sheet/mineral/bananium{
+ amount = 5
+ },
+/obj/item/stack/ore/bluespace_crystal/refined{
+ amount = 3
+ },
+/obj/item/dnainjector/comic,
+/turf/simulated/floor/plasteel/airless,
+/area/space)
+"H" = (
+/obj/structure/table,
+/obj/item/shard{
+ icon_state = "medium"
+ },
+/turf/simulated/floor/plasteel/airless,
+/area/space)
+"I" = (
+/turf/space,
+/area/template_noop)
+"J" = (
+/mob/living/simple_animal/hostile/carp,
+/turf/space,
+/area/space)
+"L" = (
+/obj/item/shard,
+/turf/space,
+/area/space)
+"M" = (
+/turf/simulated/floor,
+/area/space)
+"N" = (
+/obj/item/clothing/suit/bio_suit/virology,
+/turf/space,
+/area/space)
+"O" = (
+/obj/item/shard{
+ icon_state = "small"
+ },
+/turf/space,
+/area/template_noop)
+"P" = (
+/obj/item/reagent_containers/spray/cleaner,
+/turf/space,
+/area/space)
+"R" = (
+/obj/item/stack/sheet/metal,
+/turf/template_noop,
+/area/space)
+"S" = (
+/obj/item/reagent_containers/spray/cleaner,
+/turf/space,
+/area/template_noop)
+"T" = (
+/turf/simulated/wall,
+/area/space)
+"U" = (
+/obj/effect/landmark/burnturf,
+/turf/simulated/floor/plasteel/airless,
+/area/space)
+"V" = (
+/obj/structure/lattice,
+/turf/space,
+/area/template_noop)
+"W" = (
+/obj/effect/landmark/burnturf,
+/turf/simulated/floor/plating/burnt,
+/area/space)
+"X" = (
+/turf/space,
+/area/space)
+"Y" = (
+/obj/structure/girder/reinforced,
+/turf/simulated/floor/plating/damaged,
+/area/space)
+"Z" = (
+/obj/machinery/computer/pandemic{
+ circuit = /obj/effect/decal/cleanable/shreds
+ },
+/turf/simulated/floor/plasteel/airless,
+/area/space)
+
+(1,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+d
+d
+d
+d
+d
+Y
+x
+S
+I
+I
+I
+I
+I
+I
+C
+I
+I
+I
+a
+"}
+(3,1,1) = {"
+a
+d
+G
+A
+U
+F
+d
+z
+T
+T
+T
+T
+o
+T
+I
+p
+O
+I
+I
+a
+"}
+(4,1,1) = {"
+a
+Y
+c
+U
+c
+U
+d
+X
+w
+M
+c
+U
+M
+T
+V
+I
+I
+I
+I
+a
+"}
+(5,1,1) = {"
+a
+Y
+M
+F
+F
+q
+d
+z
+m
+F
+M
+F
+M
+T
+I
+I
+I
+I
+I
+a
+"}
+(6,1,1) = {"
+a
+d
+d
+y
+d
+d
+Y
+X
+w
+X
+n
+X
+w
+T
+I
+I
+I
+C
+I
+a
+"}
+(7,1,1) = {"
+a
+X
+X
+X
+X
+X
+P
+J
+X
+T
+T
+T
+x
+T
+I
+I
+V
+I
+I
+a
+"}
+(8,1,1) = {"
+a
+X
+X
+X
+X
+X
+X
+X
+R
+X
+w
+X
+b
+x
+x
+I
+I
+I
+I
+a
+"}
+(9,1,1) = {"
+a
+p
+s
+T
+m
+w
+T
+R
+X
+B
+X
+w
+X
+x
+z
+I
+s
+I
+I
+a
+"}
+(10,1,1) = {"
+a
+s
+I
+T
+M
+F
+T
+X
+X
+X
+X
+X
+X
+x
+x
+I
+I
+I
+I
+a
+"}
+(11,1,1) = {"
+a
+I
+I
+T
+c
+F
+T
+X
+X
+c
+F
+b
+j
+D
+T
+I
+I
+I
+t
+a
+"}
+(12,1,1) = {"
+a
+C
+I
+T
+X
+M
+N
+s
+I
+I
+L
+q
+q
+b
+T
+I
+s
+I
+I
+a
+"}
+(13,1,1) = {"
+a
+I
+I
+l
+T
+T
+T
+I
+I
+u
+j
+W
+c
+U
+T
+I
+I
+I
+I
+a
+"}
+(14,1,1) = {"
+a
+I
+s
+p
+I
+I
+I
+I
+p
+I
+I
+c
+H
+Z
+T
+I
+I
+I
+I
+a
+"}
+(15,1,1) = {"
+a
+V
+I
+I
+s
+V
+C
+I
+I
+s
+h
+T
+T
+T
+T
+I
+I
+I
+I
+a
+"}
+(16,1,1) = {"
+a
+I
+I
+E
+I
+I
+V
+I
+O
+I
+V
+I
+I
+I
+I
+I
+C
+I
+I
+a
+"}
+(17,1,1) = {"
+a
+I
+I
+C
+I
+e
+I
+I
+I
+I
+I
+e
+I
+V
+I
+I
+I
+I
+I
+a
+"}
+(18,1,1) = {"
+a
+I
+I
+I
+I
+I
+I
+h
+I
+I
+C
+I
+I
+I
+I
+I
+I
+I
+I
+a
+"}
+(19,1,1) = {"
+a
+I
+I
+I
+I
+I
+I
+I
+I
+I
+I
+I
+I
+I
+C
+I
+I
+I
+I
+a
+"}
+(20,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/debris3.dmm b/_maps/map_files/RandomRuins/SpaceRuins/debris3.dmm
new file mode 100644
index 00000000000..54d8725b3c4
--- /dev/null
+++ b/_maps/map_files/RandomRuins/SpaceRuins/debris3.dmm
@@ -0,0 +1,824 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/template_noop,
+/area/template_noop)
+"b" = (
+/obj/item/stack/rods,
+/turf/simulated/floor/plating/damaged,
+/area/template_noop)
+"c" = (
+/obj/item/trash/popcorn,
+/turf/space,
+/area/template_noop)
+"d" = (
+/obj/effect/spawner/random_spawners/wall_rusted_maybe,
+/turf/space,
+/area/template_noop)
+"e" = (
+/obj/item/poster/random_contraband,
+/turf/space,
+/area/template_noop)
+"f" = (
+/obj/structure/lattice,
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/template_noop)
+"g" = (
+/obj/item/paperplane,
+/turf/space,
+/area/template_noop)
+"h" = (
+/obj/item/trash/can,
+/turf/space,
+/area/template_noop)
+"i" = (
+/obj/item/stack/rods,
+/turf/template_noop,
+/area/template_noop)
+"k" = (
+/obj/item/shard,
+/turf/space,
+/area/template_noop)
+"l" = (
+/obj/item/stack/cable_coil{
+ amount = 10
+ },
+/turf/space,
+/area/template_noop)
+"m" = (
+/turf/simulated/wall,
+/area/template_noop)
+"n" = (
+/obj/item/stack/sheet/glass,
+/turf/space,
+/area/template_noop)
+"p" = (
+/obj/random/plushie,
+/turf/space,
+/area/template_noop)
+"q" = (
+/obj/item/stack/rods,
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/template_noop)
+"t" = (
+/obj/machinery/computer/arcade/battle,
+/turf/simulated/floor/plasteel/airless,
+/area/template_noop)
+"u" = (
+/obj/random/tool,
+/turf/space,
+/area/template_noop)
+"v" = (
+/obj/item/trash/syndi_cakes,
+/turf/space,
+/area/template_noop)
+"w" = (
+/obj/item/stack/tickets,
+/turf/space,
+/area/template_noop)
+"x" = (
+/obj/structure/bookcase/random/fiction,
+/turf/simulated/floor/plating/damaged,
+/area/template_noop)
+"y" = (
+/obj/item/storage/pill_bottle/random_meds/labelled,
+/turf/space,
+/area/template_noop)
+"z" = (
+/turf/simulated/floor/plating/damaged,
+/area/template_noop)
+"A" = (
+/turf/space,
+/area/template_noop)
+"B" = (
+/obj/item/stack/rods,
+/turf/space,
+/area/template_noop)
+"D" = (
+/obj/item/paper,
+/obj/item/paper,
+/turf/space,
+/area/template_noop)
+"F" = (
+/obj/random/tool,
+/turf/simulated/floor/plating/damaged,
+/area/template_noop)
+"G" = (
+/obj/item/poster/random_official,
+/turf/space,
+/area/template_noop)
+"H" = (
+/obj/item/stack/sheet/wood,
+/turf/space,
+/area/template_noop)
+"I" = (
+/obj/item/stack/rods,
+/obj/item/stack/rods,
+/turf/space,
+/area/template_noop)
+"J" = (
+/obj/item/trash/pistachios,
+/turf/space,
+/area/template_noop)
+"K" = (
+/obj/structure/lattice,
+/obj/structure/girder,
+/turf/space,
+/area/template_noop)
+"L" = (
+/obj/structure/girder,
+/turf/space,
+/area/template_noop)
+"M" = (
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/template_noop)
+"O" = (
+/obj/structure/girder/displaced,
+/turf/space,
+/area/template_noop)
+"S" = (
+/obj/structure/lattice,
+/turf/space,
+/area/template_noop)
+"U" = (
+/obj/item/stack/sheet/wood,
+/obj/item/stack/sheet/wood,
+/turf/space,
+/area/template_noop)
+"V" = (
+/obj/item/circuitboard/arcade,
+/turf/space,
+/area/template_noop)
+"W" = (
+/obj/item/stack/tickets,
+/obj/item/stack/tickets,
+/obj/item/stack/tickets,
+/turf/space,
+/area/template_noop)
+"Z" = (
+/obj/item/paper,
+/turf/space,
+/area/template_noop)
+
+(1,1,1) = {"
+a
+a
+a
+i
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(2,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+i
+a
+a
+a
+a
+a
+i
+a
+a
+a
+"}
+(3,1,1) = {"
+a
+a
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+A
+a
+a
+"}
+(4,1,1) = {"
+a
+a
+A
+B
+A
+B
+A
+A
+A
+A
+A
+A
+A
+A
+B
+B
+A
+A
+a
+a
+"}
+(5,1,1) = {"
+i
+a
+A
+A
+A
+A
+A
+w
+A
+M
+A
+A
+A
+A
+A
+A
+A
+A
+a
+i
+"}
+(6,1,1) = {"
+a
+a
+A
+A
+I
+A
+B
+A
+A
+B
+B
+M
+A
+A
+M
+A
+A
+A
+a
+a
+"}
+(7,1,1) = {"
+a
+a
+A
+A
+k
+A
+A
+O
+A
+A
+A
+A
+m
+A
+w
+A
+M
+A
+a
+a
+"}
+(8,1,1) = {"
+a
+a
+A
+B
+A
+M
+A
+A
+H
+A
+G
+n
+m
+A
+A
+B
+A
+A
+a
+a
+"}
+(9,1,1) = {"
+a
+a
+A
+A
+m
+A
+w
+A
+M
+S
+A
+A
+d
+B
+M
+A
+O
+A
+a
+a
+"}
+(10,1,1) = {"
+a
+a
+A
+w
+A
+A
+Z
+A
+W
+B
+S
+h
+M
+L
+A
+c
+A
+A
+a
+a
+"}
+(11,1,1) = {"
+a
+i
+B
+w
+A
+S
+A
+F
+A
+A
+S
+A
+Z
+A
+A
+A
+M
+A
+a
+a
+"}
+(12,1,1) = {"
+a
+a
+A
+A
+A
+A
+H
+L
+S
+m
+m
+b
+A
+A
+Z
+w
+B
+A
+i
+a
+"}
+(13,1,1) = {"
+a
+a
+A
+A
+B
+O
+A
+B
+A
+M
+A
+D
+K
+z
+S
+H
+B
+A
+a
+a
+"}
+(14,1,1) = {"
+a
+a
+A
+n
+A
+y
+f
+B
+A
+z
+B
+A
+m
+t
+A
+A
+A
+A
+a
+a
+"}
+(15,1,1) = {"
+a
+a
+A
+A
+z
+H
+A
+A
+A
+S
+A
+q
+z
+z
+A
+A
+O
+A
+a
+a
+"}
+(16,1,1) = {"
+a
+a
+A
+A
+S
+A
+S
+U
+B
+B
+A
+A
+S
+A
+M
+B
+u
+A
+a
+i
+"}
+(17,1,1) = {"
+a
+a
+A
+M
+H
+w
+Z
+B
+x
+S
+B
+S
+A
+k
+J
+A
+A
+A
+a
+a
+"}
+(18,1,1) = {"
+a
+a
+B
+A
+A
+A
+A
+A
+A
+w
+B
+z
+A
+O
+A
+M
+A
+A
+a
+a
+"}
+(19,1,1) = {"
+i
+a
+A
+A
+M
+z
+S
+w
+S
+M
+A
+m
+p
+A
+A
+B
+A
+A
+a
+a
+"}
+(20,1,1) = {"
+a
+a
+A
+B
+A
+O
+H
+v
+A
+V
+H
+A
+B
+L
+A
+M
+A
+w
+a
+a
+"}
+(21,1,1) = {"
+a
+a
+A
+A
+A
+A
+e
+M
+A
+w
+Z
+w
+B
+A
+w
+A
+A
+A
+a
+a
+"}
+(22,1,1) = {"
+a
+a
+A
+A
+M
+O
+m
+m
+L
+A
+B
+A
+A
+q
+A
+A
+A
+A
+a
+a
+"}
+(23,1,1) = {"
+a
+a
+A
+B
+k
+A
+B
+A
+B
+g
+n
+A
+O
+A
+A
+B
+B
+A
+a
+a
+"}
+(24,1,1) = {"
+a
+a
+A
+A
+A
+A
+A
+M
+A
+A
+l
+A
+w
+A
+A
+A
+A
+A
+a
+a
+"}
+(25,1,1) = {"
+a
+a
+A
+w
+B
+A
+A
+A
+A
+A
+A
+B
+A
+A
+A
+w
+A
+A
+a
+a
+"}
+(26,1,1) = {"
+a
+a
+A
+A
+A
+A
+A
+A
+B
+A
+B
+A
+A
+B
+A
+B
+A
+A
+a
+a
+"}
+(27,1,1) = {"
+a
+a
+B
+A
+w
+A
+B
+A
+w
+A
+B
+A
+A
+A
+A
+A
+A
+w
+a
+a
+"}
+(28,1,1) = {"
+a
+a
+A
+A
+A
+A
+A
+A
+A
+w
+A
+A
+A
+A
+A
+A
+A
+A
+a
+i
+"}
+(29,1,1) = {"
+i
+a
+a
+a
+a
+a
+a
+i
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+"}
+(30,1,1) = {"
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+a
+i
+a
+a
+"}
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
new file mode 100644
index 00000000000..ec11a787510
--- /dev/null
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndiecakesfactory.dmm
@@ -0,0 +1,2284 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"aM" = (
+/obj/item/stack/rods,
+/obj/item/stack/sheet/metal,
+/turf/template_noop,
+/area/space)
+"bb" = (
+/obj/structure/table/reinforced,
+/obj/item/stack/sheet/mineral/plasma,
+/obj/item/stack/sheet/mineral/plasma,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"bk" = (
+/obj/structure/grille/broken,
+/turf/template_noop,
+/area/space)
+"cc" = (
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/turf/simulated/floor/plating,
+/area/space)
+"dy" = (
+/obj/structure/lattice,
+/turf/unsimulated,
+/area/template_noop)
+"es" = (
+/obj/structure/cable{
+ d1 = 1;
+ d2 = 8;
+ icon_state = "1-8"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"eY" = (
+/turf/simulated/floor/plating,
+/area/ruin)
+"fq" = (
+/obj/machinery/autolathe,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"fU" = (
+/obj/machinery/door/airlock/mining/glass{
+ locked = 1;
+ name = "Egg processing"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"fY" = (
+/obj/item/stack/sheet/metal,
+/turf/template_noop,
+/area/template_noop)
+"hm" = (
+/obj/structure/cable{
+ d1 = 1;
+ d2 = 2;
+ icon_state = "1-2"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"ho" = (
+/turf/space,
+/area/ruin/space)
+"hs" = (
+/turf/unsimulated/wall/fakeglass{
+ dir = 4
+ },
+/area/ruin/unpowered)
+"iK" = (
+/obj/item/stack/sheet/metal,
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/ruin/space)
+"jf" = (
+/obj/item/trash/syndi_cakes,
+/turf/template_noop,
+/area/template_noop)
+"jv" = (
+/obj/item/stack/cable_coil/cut,
+/turf/template_noop,
+/area/space)
+"jR" = (
+/obj/structure/table/reinforced,
+/obj/item/storage/fancy/cigarettes/cigpack_random,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"ke" = (
+/obj/structure/lattice,
+/turf/space,
+/area/ruin)
+"kh" = (
+/obj/structure/lattice,
+/obj/item/stack/rods,
+/turf/template_noop,
+/area/space)
+"ky" = (
+/obj/structure/cable{
+ d1 = 1;
+ d2 = 2;
+ icon_state = "1-2";
+ tag = ""
+ },
+/turf/simulated/wall,
+/area/ruin/unpowered)
+"ls" = (
+/obj/structure/lattice,
+/turf/template_noop,
+/area/space)
+"lK" = (
+/obj/machinery/power/apc/noalarm{
+ area = null;
+ dir = 8;
+ keep_preset_name = 1;
+ light_power = 0;
+ locked = 0;
+ max_integrity = 0;
+ name = "Manufacturing control room APC";
+ pixel_y = -24;
+ shorted = 1;
+ start_charge = -1
+ },
+/obj/structure/cable{
+ d2 = 4;
+ icon_state = "0-4"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"mc" = (
+/obj/structure/grille/broken,
+/turf/space,
+/area/ruin/space)
+"mx" = (
+/obj/item/trash/syndi_cakes,
+/turf/template_noop,
+/area/space)
+"nV" = (
+/obj/machinery/conveyor/east{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/space)
+"ob" = (
+/obj/structure/lattice,
+/turf/template_noop,
+/area/ruin/unpowered)
+"om" = (
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/turf/simulated/floor/plasteel,
+/area/space)
+"oJ" = (
+/obj/item/stack/rods,
+/turf/template_noop,
+/area/space)
+"oO" = (
+/obj/machinery/door/airlock/mining/glass{
+ name = "Nexus"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"pb" = (
+/obj/structure/lattice/catwalk,
+/turf/template_noop,
+/area/space)
+"pd" = (
+/turf/template_noop,
+/area/space)
+"pp" = (
+/turf/unsimulated,
+/area/space)
+"pR" = (
+/obj/machinery/conveyor/northeast{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"qS" = (
+/obj/machinery/door/airlock/mining/glass{
+ name = "EVA Storage"
+ },
+/obj/structure/cable{
+ d1 = 4;
+ d2 = 8;
+ icon_state = "4-8";
+ pixel_y = 0;
+ tag = ""
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"rJ" = (
+/obj/structure/lattice,
+/turf/space,
+/area/ruin/space)
+"rL" = (
+/obj/item/reagent_containers/food/snacks/syndicake,
+/turf/simulated/floor/plasteel/airless,
+/area/ruin)
+"rU" = (
+/obj/machinery/space_heater,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"sd" = (
+/turf/simulated/floor/plating,
+/area/space)
+"sN" = (
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"ub" = (
+/obj/item/stack/rods,
+/turf/space,
+/area/space)
+"uA" = (
+/obj/structure/table/reinforced,
+/obj/item/reagent_containers/food/snacks/syndicake,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"uF" = (
+/obj/machinery/conveyor/east{
+ id = null
+ },
+/obj/machinery/gibber/autogibber,
+/obj/structure/plasticflaps{
+ canmove = 0
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"wB" = (
+/obj/item/trash/syndi_cakes,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"xT" = (
+/turf/unsimulated/floor/plating/airless,
+/area/space)
+"yU" = (
+/obj/item/stack/sheet/metal,
+/obj/item/stack/sheet/metal,
+/turf/space,
+/area/space)
+"zd" = (
+/obj/machinery/power/apc/noalarm{
+ area = null;
+ dir = 8;
+ keep_preset_name = 1;
+ locked = 0;
+ name = "EVA room APC";
+ pixel_x = -24;
+ start_charge = -1
+ },
+/obj/structure/cable{
+ d2 = 4;
+ icon_state = "0-4"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"zo" = (
+/turf/simulated/wall,
+/area/space)
+"zt" = (
+/obj/item/stack/sheet/metal,
+/turf/template_noop,
+/area/space)
+"zF" = (
+/turf/unsimulated/wall/fakeglass{
+ dir = 8
+ },
+/area/ruin/unpowered)
+"zP" = (
+/obj/structure/rack{
+ dir = 4
+ },
+/obj/random/tool,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"zY" = (
+/obj/structure/table/reinforced,
+/obj/machinery/light,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"AH" = (
+/obj/structure/lattice,
+/obj/item/stack/sheet/metal,
+/turf/template_noop,
+/area/template_noop)
+"AJ" = (
+/obj/effect/spawner/random_spawners/wall_rusted_probably,
+/turf/simulated/wall,
+/area/space)
+"BG" = (
+/obj/machinery/door/airlock/mining/glass{
+ locked = 1;
+ name = "Manufacturing Control Room"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"BI" = (
+/obj/structure/reagent_dispensers/fueltank,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Cp" = (
+/obj/structure/cable{
+ d1 = 1;
+ d2 = 2;
+ icon_state = "1-2";
+ tag = ""
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"CY" = (
+/obj/item/stack/rods,
+/turf/template_noop,
+/area/template_noop)
+"Da" = (
+/turf/space,
+/area/space)
+"Dz" = (
+/mob/living/simple_animal/pet/dog/corgi,
+/obj/machinery/conveyor/east{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Ej" = (
+/obj/structure/closet/emcloset,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"EJ" = (
+/turf/simulated/wall,
+/area/ruin/unpowered)
+"Fw" = (
+/obj/item/stack/cable_coil/cut,
+/turf/template_noop,
+/area/template_noop)
+"FO" = (
+/obj/machinery/suit_storage_unit/syndicate,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Gm" = (
+/obj/structure/cable{
+ d1 = 4;
+ d2 = 8;
+ icon_state = "4-8";
+ pixel_y = 0;
+ tag = ""
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"GB" = (
+/mob/living/simple_animal/pet/dog/corgi,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Hx" = (
+/mob/living/simple_animal/pet/dog/corgi,
+/mob/living/simple_animal/pet/dog/corgi,
+/obj/machinery/conveyor/east{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Hy" = (
+/turf/template_noop,
+/area/template_noop)
+"HQ" = (
+/obj/structure/table/reinforced,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Ic" = (
+/turf/simulated/floor/plating/burnt,
+/area/ruin)
+"Id" = (
+/obj/effect/spawner/random_spawners/oil_maybe,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"IB" = (
+/obj/structure/lattice,
+/turf/space,
+/area/ruin/unpowered)
+"JI" = (
+/obj/effect/spawner/random_spawners/syndicate/loot,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Ki" = (
+/obj/structure/lattice,
+/turf/template_noop,
+/area/template_noop)
+"Kr" = (
+/obj/structure/girder,
+/turf/simulated/floor/plating,
+/area/ruin/unpowered)
+"KD" = (
+/turf/simulated/floor/plating,
+/area/ruin/unpowered)
+"KS" = (
+/turf/unsimulated/floor/plating/airless,
+/area/ruin)
+"Le" = (
+/obj/structure/cable{
+ d2 = 4;
+ icon_state = "0-4"
+ },
+/obj/machinery/power/port_gen/pacman,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Mi" = (
+/turf/unsimulated,
+/area/template_noop)
+"Mz" = (
+/turf/simulated/floor/plasteel,
+/area/ruin)
+"Nw" = (
+/obj/machinery/power/smes,
+/obj/structure/cable/blue{
+ d2 = 2;
+ icon_state = "0-2"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"NQ" = (
+/obj/structure/lattice,
+/turf/space,
+/area/space)
+"OA" = (
+/obj/structure/computerframe,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"OK" = (
+/obj/item/reagent_containers/food/snacks/syndicake,
+/turf/space,
+/area/space)
+"OU" = (
+/turf/simulated/floor/plasteel/airless,
+/area/ruin)
+"Qv" = (
+/obj/machinery/light{
+ dir = 8
+ },
+/obj/item/trash/syndi_cakes,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"QF" = (
+/obj/machinery/conveyor/east{
+ dir = 2;
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin)
+"QI" = (
+/obj/effect/spawner/random_spawners/wall_rusted_probably,
+/turf/simulated/wall,
+/area/ruin)
+"Ra" = (
+/obj/machinery/power/terminal{
+ dir = 4
+ },
+/obj/structure/cable{
+ d2 = 8;
+ icon_state = "0-8"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Rg" = (
+/obj/machinery/door/airlock/mining/glass{
+ locked = 1;
+ max_integrity = 300000;
+ name = "Corgi processing";
+ normal_integrity = 300000
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Ry" = (
+/obj/item/stack/cable_coil/random,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"RB" = (
+/mob/living/simple_animal/hostile/alien/sentinel{
+ animal_species = /mob/living/simple_animal/pet/dog;
+ attack_sound = 'sound/weapons/bite.ogg';
+ attacktext = "bites";
+ damage_coeff = list("brute" = 1, "fire" = 1, "tox" = 1, "clone" = 1, "stamina" = 1, "oxy" = 1);
+ death_sound = null;
+ deathmessage = "";
+ desc = "This is no longer a goodboy. Not anymore. He has seen too much.";
+ icon = 'icons/mob/pets.dmi';
+ icon_living = "corgi";
+ icon_state = "corgi";
+ name = "angry corgi";
+ retreat_distance = 2
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Tj" = (
+/obj/structure/table/reinforced,
+/obj/item/storage/toolbox/syndicate,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Tt" = (
+/obj/machinery/door/airlock/external,
+/turf/simulated/floor/plating,
+/area/ruin/powered)
+"TM" = (
+/obj/structure/chair/office/dark{
+ dir = 4;
+ icon_state = "officechair_dark";
+ tag = "icon-officechair_dark (EAST)"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"TT" = (
+/obj/structure/cable{
+ d1 = 1;
+ d2 = 8;
+ icon_state = "1-8";
+ tag = ""
+ },
+/obj/structure/cable{
+ d1 = 1;
+ d2 = 4;
+ icon_state = "1-4";
+ tag = "90Curve"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Uq" = (
+/obj/machinery/conveyor/east{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin)
+"UC" = (
+/obj/machinery/tcomms/relay/ruskie{
+ network_id = "SYNDICAKES-FACTORY"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"UR" = (
+/obj/item/trash/syndi_cakes,
+/turf/space,
+/area/space)
+"Vf" = (
+/obj/machinery/light{
+ dir = 8
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Vt" = (
+/obj/machinery/conveyor/north/ccw{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"VH" = (
+/obj/structure/lattice,
+/obj/random/tool,
+/turf/space,
+/area/space)
+"VR" = (
+/obj/structure/lattice,
+/turf/unsimulated,
+/area/space)
+"WL" = (
+/turf/space,
+/area/ruin/unpowered)
+"Xd" = (
+/obj/structure/cable{
+ d1 = 2;
+ d2 = 8;
+ icon_state = "2-8"
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"XZ" = (
+/obj/machinery/light/small{
+ dir = 1
+ },
+/obj/structure/dispenser/oxygen,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"YR" = (
+/obj/machinery/light{
+ dir = 4
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"YU" = (
+/obj/structure/chair/office/dark,
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Zs" = (
+/obj/machinery/conveyor/east{
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/ruin/unpowered)
+"Zt" = (
+/obj/machinery/conveyor/east{
+ dir = 2;
+ id = null
+ },
+/turf/simulated/floor/plasteel,
+/area/space)
+
+(1,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(2,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(3,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+ob
+ob
+ob
+ob
+ob
+ob
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+Ki
+Ki
+Ki
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(4,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+Le
+Vf
+zd
+rU
+sN
+FO
+EJ
+EJ
+EJ
+pb
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(5,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+JI
+GB
+Vf
+sN
+sN
+EJ
+Ra
+sN
+Gm
+sN
+sN
+sN
+Tt
+KD
+Tt
+pb
+pb
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(6,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+sN
+Hx
+GB
+Dz
+sN
+EJ
+Nw
+hm
+TT
+Id
+sN
+Ej
+EJ
+EJ
+EJ
+pb
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(7,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+sN
+Dz
+sN
+Dz
+sN
+EJ
+UC
+sN
+Gm
+sN
+sN
+zY
+EJ
+pd
+pd
+ls
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(8,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+sN
+Zs
+sN
+Zs
+sN
+EJ
+fq
+sN
+Gm
+sN
+YU
+OA
+EJ
+pd
+pd
+ls
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(9,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+sN
+Zs
+sN
+Zs
+Id
+EJ
+sN
+GB
+Gm
+TM
+sN
+sN
+EJ
+pd
+pd
+ls
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(10,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+pR
+Vt
+Vt
+Vt
+sN
+EJ
+XZ
+BI
+Gm
+uA
+OA
+jR
+EJ
+pd
+pd
+ls
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(11,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+Zs
+EJ
+EJ
+EJ
+Rg
+EJ
+EJ
+EJ
+qS
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+EJ
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(12,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+Zs
+zF
+TM
+Qv
+lK
+EJ
+sN
+Vf
+Gm
+sN
+sN
+Ry
+Kr
+OU
+OU
+OU
+EJ
+pd
+pd
+pd
+OK
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(13,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+Zs
+hs
+Tj
+zP
+Xd
+ky
+Cp
+Cp
+es
+sN
+rJ
+rJ
+Kr
+rL
+eY
+OU
+EJ
+pd
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(14,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+Zs
+EJ
+sN
+wB
+sN
+BG
+sN
+sN
+sN
+sN
+sN
+sN
+oO
+OU
+eY
+eY
+EJ
+pd
+pd
+pd
+pd
+oJ
+Hy
+Hy
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+"}
+(15,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+Zs
+zF
+TM
+RB
+wB
+EJ
+IB
+sN
+sN
+sN
+sN
+sN
+EJ
+eY
+eY
+rJ
+EJ
+pd
+pd
+mx
+pd
+pd
+pd
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(16,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+Zs
+hs
+bb
+OA
+sN
+EJ
+WL
+IB
+sN
+YR
+HQ
+HQ
+EJ
+OU
+eY
+rJ
+EJ
+zt
+oJ
+pd
+pd
+pd
+jv
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(17,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+EJ
+uF
+EJ
+EJ
+EJ
+fU
+EJ
+EJ
+EJ
+oO
+EJ
+EJ
+EJ
+EJ
+OU
+eY
+OK
+Da
+pd
+pd
+NQ
+zt
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(18,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+VR
+Uq
+OU
+OU
+KS
+KS
+Mz
+ho
+OU
+OU
+OU
+OU
+ho
+iK
+eY
+Da
+OK
+OK
+pd
+pd
+NQ
+zo
+pd
+pd
+pd
+pd
+OK
+Hy
+Hy
+fY
+Hy
+Hy
+Hy
+"}
+(19,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+pd
+rJ
+rJ
+rJ
+KS
+rJ
+KS
+rJ
+Ic
+Ic
+ub
+NQ
+OU
+eY
+NQ
+NQ
+Da
+OK
+yU
+Da
+NQ
+AJ
+pd
+OK
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(20,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+pd
+nV
+xT
+mc
+KS
+mc
+KS
+KS
+Da
+NQ
+Da
+NQ
+Da
+Da
+UR
+ub
+Da
+Da
+pd
+pd
+NQ
+NQ
+pd
+pd
+pd
+zt
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(21,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Ki
+pp
+pd
+Zt
+rJ
+Zt
+Zt
+NQ
+Ic
+Da
+Da
+Da
+Da
+OK
+Da
+Da
+OK
+OK
+Da
+pd
+NQ
+Da
+Da
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+Hy
+Ki
+Hy
+Hy
+"}
+(22,1,1) = {"
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+Hy
+pp
+pd
+pd
+zo
+pd
+zo
+zo
+NQ
+ub
+Da
+Da
+Da
+Da
+Da
+Da
+Da
+Da
+Da
+ub
+zt
+pd
+pd
+pd
+mx
+pd
+pd
+pd
+pd
+OK
+Hy
+Hy
+Hy
+Hy
+"}
+(23,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Ki
+Mi
+dy
+VR
+pd
+pd
+pd
+pd
+pd
+zt
+pd
+pd
+Ic
+OU
+OU
+Da
+NQ
+pd
+pd
+pd
+pd
+pd
+pd
+oJ
+pd
+zt
+ls
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+"}
+(24,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Ki
+Hy
+Hy
+Hy
+Ki
+Hy
+pd
+oJ
+OK
+pd
+pd
+bk
+NQ
+QF
+QF
+QF
+QF
+Da
+sd
+oJ
+pd
+pd
+pd
+cc
+VH
+pd
+pd
+pd
+pd
+pd
+pd
+ls
+Hy
+Hy
+Hy
+Hy
+"}
+(25,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+Hy
+pd
+pd
+ke
+OK
+pd
+zo
+NQ
+NQ
+QI
+NQ
+NQ
+pd
+pd
+zt
+NQ
+NQ
+om
+zo
+pd
+OK
+pd
+oJ
+pd
+pd
+zt
+Hy
+Hy
+Hy
+Hy
+"}
+(26,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Ki
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+NQ
+ls
+pd
+pd
+pd
+pd
+pd
+zo
+zo
+Da
+Da
+pd
+oJ
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+"}
+(27,1,1) = {"
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+Hy
+AH
+CY
+CY
+Hy
+fY
+Hy
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+OK
+pd
+oJ
+pd
+mx
+pd
+pd
+OK
+pd
+pd
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+"}
+(28,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+pd
+NQ
+OK
+pd
+pd
+aM
+pd
+mx
+pd
+pd
+pd
+oJ
+pd
+pd
+pd
+pd
+pd
+pd
+aM
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+jf
+"}
+(29,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+Fw
+Hy
+pd
+oJ
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+OK
+oJ
+pd
+kh
+pd
+pd
+pd
+pd
+pd
+pd
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+"}
+(30,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+CY
+CY
+Hy
+pd
+pd
+pd
+pd
+ls
+pd
+OK
+pd
+pd
+pd
+pd
+pd
+pd
+pd
+oJ
+zt
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(31,1,1) = {"
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+Ki
+Hy
+Hy
+Hy
+Ki
+Hy
+CY
+Hy
+Hy
+Hy
+Hy
+pd
+pd
+pd
+pd
+pd
+ls
+pd
+pd
+oJ
+pd
+pd
+aM
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(32,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+kh
+pd
+pd
+zt
+pd
+pd
+ls
+pd
+pd
+pd
+pd
+pd
+pd
+Hy
+Hy
+Fw
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(33,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+CY
+Hy
+CY
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+Hy
+jf
+Hy
+Hy
+Hy
+Hy
+pd
+pd
+pd
+pd
+pd
+NQ
+zt
+pd
+pd
+oJ
+pd
+fY
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(34,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+CY
+Hy
+fY
+Hy
+CY
+Hy
+Hy
+pd
+oJ
+pd
+pd
+OK
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(35,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Ki
+Hy
+Hy
+Hy
+CY
+CY
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+pd
+pd
+ls
+pd
+pd
+pd
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(36,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+CY
+Hy
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(37,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Fw
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(38,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(39,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+jf
+Hy
+Hy
+Hy
+Hy
+fY
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
+(40,1,1) = {"
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+Hy
+"}
diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 2c12ac11e44..8f6f8d77f7f 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -35244,7 +35244,7 @@
},
/area/bridge)
"bnW" = (
-/obj/machinery/computer/station_alert/all,
+/obj/machinery/computer/station_alert,
/turf/simulated/floor/plasteel{
dir = 0;
icon_state = "yellow"
@@ -72578,7 +72578,7 @@
pixel_x = -2;
pixel_y = 2
},
-/obj/item/circuitboard/stationalert_all{
+/obj/item/circuitboard/stationalert{
pixel_x = 1;
pixel_y = -1
},
@@ -75846,7 +75846,7 @@
pixel_x = 32;
pixel_y = 0
},
-/obj/machinery/computer/station_alert/all,
+/obj/machinery/computer/station_alert,
/obj/structure/cable/yellow{
d1 = 4;
d2 = 8;
@@ -77210,6 +77210,10 @@
pixel_x = -22
},
/obj/item/stack/tape_roll,
+/obj/machinery/camera/motion{
+ c_tag = "EVA Motion Sensor";
+ dir = 4
+ },
/turf/simulated/floor/plasteel{
icon_state = "dark"
},
diff --git a/_maps/map_files/shuttles/admin_armory.dmm b/_maps/map_files/shuttles/admin_armory.dmm
new file mode 100644
index 00000000000..837f867a52b
--- /dev/null
+++ b/_maps/map_files/shuttles/admin_armory.dmm
@@ -0,0 +1,1691 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"ae" = (
+/obj/structure/sign/securearea,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall4";
+ tag = "icon-swall14"
+ },
+/area/shuttle/administration)
+"al" = (
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/structure/table/reinforced,
+/obj/item/folder,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"aI" = (
+/obj/structure/table/reinforced,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/machinery/recharger,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"aP" = (
+/obj/machinery/vending/security,
+/obj/machinery/light/spot{
+ dir = 8;
+ icon_state = "tube1";
+ tag = "icon-tube1 (WEST)"
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"bi" = (
+/obj/structure/table/reinforced,
+/obj/item/storage/firstaid,
+/obj/item/storage/firstaid/brute,
+/obj/item/storage/box/bodybags,
+/obj/item/storage/firstaid/surgery,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"bm" = (
+/obj/structure/grille,
+/obj/machinery/door/poddoor/shutters/preopen{
+ dir = 8;
+ id_tag = "adminarmoryshutters"
+ },
+/obj/structure/window/full/shuttle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"ce" = (
+/obj/machinery/autolathe/upgraded{
+ hacked = 1
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"cl" = (
+/obj/structure/chair/comfy/shuttle{
+ dir = 4
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"cV" = (
+/obj/structure/closet/emcloset,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"de" = (
+/obj/machinery/suit_storage_unit/security/secure,
+/obj/machinery/light/spot,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"df" = (
+/obj/structure/rack,
+/obj/item/storage/box/buck{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/storage/box/buck,
+/obj/structure/window/reinforced,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"dn" = (
+/obj/machinery/computer/camera_advanced/shuttle_docker/admin{
+ name = "NRV Sparta navigation computer"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"dH" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/gun/energy/gun,
+/obj/item/gun/energy/gun{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"dP" = (
+/obj/machinery/recharge_station/upgraded,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"eI" = (
+/obj/machinery/turretid/stun{
+ check_access = 0;
+ name = "Shuttle Turret Control";
+ pixel_y = 30
+ },
+/obj/machinery/computer/camera_advanced,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"eL" = (
+/obj/structure/chair{
+ dir = 8
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"fo" = (
+/obj/structure/chair{
+ dir = 1
+ },
+/obj/machinery/light/spot,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"fF" = (
+/obj/machinery/light/spot{
+ dir = 4;
+ icon_state = "tube1";
+ tag = "icon-tube1 (EAST)"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"fQ" = (
+/obj/structure/chair/comfy/shuttle{
+ dir = 1
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"gd" = (
+/obj/machinery/door/airlock/shuttle,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"gh" = (
+/obj/structure/chair/comfy/shuttle{
+ dir = 8
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"gj" = (
+/obj/machinery/computer/communications{
+ name = "NRV Sparta Communications Console"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"hN" = (
+/obj/structure/chair{
+ dir = 4
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"ir" = (
+/obj/structure/closet/emcloset,
+/obj/machinery/light/spot{
+ dir = 1;
+ icon_state = "tube1";
+ tag = "icon-tube1 (NORTH)"
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"iz" = (
+/obj/machinery/porta_turret{
+ installation = /obj/item/gun/energy/gun
+ },
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"jn" = (
+/obj/machinery/door/poddoor/shutters{
+ density = 0;
+ dir = 2;
+ icon_state = "open";
+ id_tag = "Asclshutters";
+ name = "Blast Shutters";
+ opacity = 0
+ },
+/obj/structure/grille,
+/obj/structure/window/full/shuttle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"jB" = (
+/obj/structure/table/reinforced,
+/obj/item/storage/lockbox/mindshield,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"jU" = (
+/obj/machinery/suit_storage_unit/security/secure,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"kf" = (
+/obj/structure/sign/securearea,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall8";
+ tag = "icon-swall12"
+ },
+/area/shuttle/administration)
+"mq" = (
+/obj/structure/grille,
+/obj/machinery/door/poddoor/shutters{
+ density = 0;
+ dir = 2;
+ icon_state = "open";
+ id_tag = "Asclshutters";
+ name = "Blast Shutters";
+ opacity = 0
+ },
+/obj/structure/window/full/shuttle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"mw" = (
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"mM" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall13"
+ },
+/area/shuttle/administration)
+"nG" = (
+/obj/structure/window/full/shuttle,
+/obj/structure/grille,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"oj" = (
+/obj/structure/chair/comfy/shuttle{
+ dir = 1
+ },
+/obj/machinery/light/spot,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"oY" = (
+/obj/structure/chair,
+/obj/machinery/light/spot{
+ dir = 1;
+ icon_state = "tube1";
+ tag = "icon-tube1 (NORTH)"
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"pa" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall11"
+ },
+/area/shuttle/administration)
+"pK" = (
+/obj/structure/shuttle/engine/propulsion{
+ dir = 4;
+ icon_state = "propulsion"
+ },
+/turf/simulated/shuttle/plating,
+/area/shuttle/administration)
+"pQ" = (
+/obj/structure/chair{
+ dir = 8
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"qQ" = (
+/obj/machinery/recharger/wallcharger{
+ pixel_y = 25
+ },
+/obj/machinery/recharger/wallcharger{
+ pixel_y = 35
+ },
+/obj/machinery/light/spot{
+ dir = 1;
+ icon_state = "tube1";
+ tag = "icon-tube1 (NORTH)"
+ },
+/obj/machinery/door_control{
+ id = "adminshuttlebridge";
+ name = "Bridge Privacy Shutters";
+ pixel_x = 25;
+ req_access_txt = "19"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"rb" = (
+/obj/machinery/door/window/brigdoor/southleft{
+ req_one_access_txt = "2;19"
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"rW" = (
+/obj/structure/grille,
+/obj/machinery/door/poddoor/shutters/preopen{
+ id_tag = "adminshuttlebridge"
+ },
+/obj/structure/window/full/shuttle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"sq" = (
+/obj/structure/table/reinforced,
+/obj/item/kitchen/knife/combat{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/kitchen/knife/combat,
+/obj/item/kitchen/knife/combat{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"tx" = (
+/obj/machinery/sleeper/upgraded{
+ dir = 4
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"ua" = (
+/obj/structure/chair{
+ dir = 8
+ },
+/obj/machinery/door_control{
+ dir = 4;
+ id = "Asclshutters";
+ name = "External Window Shutter Control";
+ pixel_x = -5;
+ pixel_y = 35;
+ req_access_txt = "19"
+ },
+/obj/machinery/keycard_auth{
+ pixel_y = 24
+ },
+/obj/machinery/door_control{
+ id = "asclblast";
+ name = "Airlock Blast Door Control";
+ pixel_x = 5;
+ pixel_y = 35;
+ req_access = null;
+ req_access_txt = "19"
+ },
+/obj/structure/chair/comfy/shuttle{
+ dir = 8
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"uz" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall4";
+ tag = "icon-swall14"
+ },
+/area/shuttle/administration)
+"uW" = (
+/obj/structure/shuttle/engine/heater{
+ dir = 4;
+ icon_state = "heater"
+ },
+/obj/structure/window/plasmareinforced{
+ dir = 8
+ },
+/turf/simulated/shuttle/plating,
+/area/shuttle/administration)
+"uY" = (
+/obj/structure/window/reinforced,
+/obj/structure/table/reinforced,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"vk" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall_s6"
+ },
+/area/shuttle/administration)
+"vy" = (
+/obj/structure/table/reinforced,
+/obj/item/stack/sheet/metal/fifty,
+/obj/item/stack/sheet/glass/fifty,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"vA" = (
+/obj/machinery/light/spot{
+ dir = 1;
+ icon_state = "tube1";
+ tag = "icon-tube1 (NORTH)"
+ },
+/obj/machinery/portable_atmospherics/canister/oxygen,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"vW" = (
+/obj/structure/table/reinforced,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/storage/box/handcuffs,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"wG" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/item/clothing/suit/armor/bulletproof{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/obj/item/clothing/head/helmet/alt,
+/obj/item/clothing/suit/armor/bulletproof{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/obj/item/clothing/head/helmet/alt,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"wH" = (
+/obj/machinery/light/spot{
+ dir = 8;
+ icon_state = "tube1";
+ tag = "icon-tube1 (WEST)"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"wQ" = (
+/obj/machinery/door/poddoor/shutters{
+ density = 0;
+ dir = 8;
+ icon_state = "open";
+ id_tag = "Asclshutters";
+ name = "Blast Shutters";
+ opacity = 0
+ },
+/obj/structure/grille,
+/obj/structure/window/full/shuttle,
+/turf/space,
+/area/shuttle/administration)
+"xm" = (
+/obj/structure/table/reinforced,
+/obj/item/rcd/preloaded,
+/obj/item/rcd_ammo,
+/obj/item/rcd_ammo{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/obj/item/rcd_ammo{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"xF" = (
+/obj/structure/rack,
+/obj/item/storage/box/buck{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/storage/box/buck,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"xH" = (
+/obj/machinery/door/airlock/shuttle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"xK" = (
+/obj/structure/sign/nosmoking_2,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall3";
+ tag = "icon-swall3"
+ },
+/area/shuttle/administration)
+"xQ" = (
+/obj/structure/chair{
+ dir = 4
+ },
+/obj/machinery/light/spot{
+ dir = 1;
+ icon_state = "tube1";
+ tag = "icon-tube1 (NORTH)"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"xU" = (
+/obj/structure/table/reinforced,
+/obj/item/stack/sheet/metal/fifty,
+/obj/item/stack/sheet/glass/fifty,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"xZ" = (
+/turf/space,
+/area/shuttle/administration)
+"yg" = (
+/obj/structure/table/reinforced,
+/obj/machinery/door/window/brigdoor/eastleft{
+ name = "Armory";
+ req_access_txt = "3"
+ },
+/obj/machinery/door/window/brigdoor/westleft{
+ name = "Armory"
+ },
+/obj/machinery/door/poddoor/shutters/preopen{
+ dir = 8;
+ id_tag = "adminarmoryshutters"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"yu" = (
+/obj/structure/table/reinforced,
+/obj/machinery/recharger{
+ pixel_x = -5
+ },
+/obj/machinery/recharger{
+ pixel_x = 8
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"yF" = (
+/obj/machinery/status_display,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall3"
+ },
+/area/shuttle/administration)
+"zY" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall14"
+ },
+/area/shuttle/administration)
+"AH" = (
+/obj/machinery/light/spot,
+/obj/machinery/porta_turret{
+ installation = /obj/item/gun/energy/gun
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Bc" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/shield/riot,
+/obj/item/shield/riot{
+ pixel_x = 2;
+ pixel_y = -4
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Bj" = (
+/obj/structure/fans/tiny,
+/obj/machinery/door/airlock/external{
+ req_one_access_txt = "2;19"
+ },
+/obj/machinery/door/poddoor{
+ density = 0;
+ icon_state = "open";
+ id_tag = "asclblast";
+ name = "Blast Door";
+ opacity = 0
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Bn" = (
+/obj/machinery/door/window/brigdoor/southleft{
+ req_one_access_txt = "2;19"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Bq" = (
+/obj/structure/sign/nosmoking_2,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall1";
+ tag = "icon-swall1"
+ },
+/area/shuttle/administration)
+"BZ" = (
+/obj/machinery/light/spot,
+/obj/machinery/porta_turret{
+ installation = /obj/item/gun/energy/gun
+ },
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Cl" = (
+/obj/structure/table/reinforced,
+/obj/item/storage/toolbox/emergency{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/storage/toolbox/mechanical,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Du" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced,
+/obj/item/gun/energy/gun,
+/obj/item/gun/energy/gun{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"DZ" = (
+/obj/machinery/light/spot,
+/obj/structure/closet/emcloset,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Eq" = (
+/obj/structure/sign/securearea,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall12"
+ },
+/area/shuttle/administration)
+"EQ" = (
+/obj/machinery/door/airlock/shuttle/glass,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Fd" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/gun/energy/gun,
+/obj/item/gun/energy/gun{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Fo" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/gun/energy/immolator/multi{
+ pixel_y = -3
+ },
+/obj/item/gun/energy/immolator/multi,
+/obj/item/gun/energy/immolator/multi{
+ pixel_y = 3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"FF" = (
+/obj/structure/sign/poster/official/enlist,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall12"
+ },
+/area/shuttle/administration)
+"FT" = (
+/obj/structure/rack,
+/obj/item/storage/box/buck{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/storage/box/buck,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Gf" = (
+/obj/structure/sign/vacuum{
+ pixel_x = -32
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Gn" = (
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/rack,
+/obj/item/gun/projectile/shotgun/riot/buckshot{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/gun/projectile/shotgun/riot/buckshot,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Gx" = (
+/obj/structure/fans/tiny,
+/obj/machinery/door/airlock/external,
+/obj/docking_port/mobile{
+ dir = 2;
+ dwidth = 9;
+ height = 18;
+ id = "admin";
+ name = "armory";
+ roundstart_move = "cc_bay_1";
+ timid = 1;
+ width = 19
+ },
+/obj/machinery/door/poddoor{
+ density = 0;
+ icon_state = "open";
+ id_tag = "asclblast";
+ name = "Blast Door";
+ opacity = 0
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Gy" = (
+/obj/structure/chair{
+ dir = 1
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Hc" = (
+/obj/machinery/porta_turret{
+ installation = /obj/item/gun/energy/gun
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"HN" = (
+/obj/structure/sign/poster/official/do_not_question,
+/obj/item/tank/emergency_oxygen/engi,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall3"
+ },
+/area/shuttle/administration)
+"Ir" = (
+/obj/machinery/computer/shuttle/admin{
+ name = "NRV Sparta shuttle console"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"IC" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall3"
+ },
+/area/shuttle/administration)
+"Jt" = (
+/obj/machinery/suit_storage_unit/standard_unit,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Ke" = (
+/obj/machinery/door/window/brigdoor/southright{
+ req_one_access_txt = "2;19"
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Kh" = (
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Ki" = (
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/clothing/suit/armor/bulletproof{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/obj/structure/rack,
+/obj/item/clothing/head/helmet/alt,
+/obj/item/clothing/suit/armor/bulletproof{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/obj/item/clothing/head/helmet/alt,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"KM" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall7"
+ },
+/area/shuttle/administration)
+"Lh" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/gun/energy/ionrifle{
+ pixel_x = 3;
+ pixel_y = 3
+ },
+/obj/item/gun/energy/ionrifle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"LK" = (
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/machinery/computer/secure_data,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"LP" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall_s9"
+ },
+/area/shuttle/administration)
+"Mi" = (
+/obj/structure/table/reinforced,
+/obj/machinery/recharger,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Mj" = (
+/obj/machinery/vending/medical,
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Mo" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/clothing/suit/armor/bulletproof{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/obj/item/clothing/head/helmet/alt,
+/obj/item/clothing/suit/armor/bulletproof{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/obj/item/clothing/head/helmet/alt,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"MN" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall12"
+ },
+/area/shuttle/administration)
+"MZ" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall3";
+ tag = "icon-swall3"
+ },
+/area/shuttle/administration)
+"Ni" = (
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/rack,
+/obj/item/gun/projectile/shotgun/riot/buckshot{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/gun/projectile/shotgun/riot/buckshot,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Ns" = (
+/obj/machinery/recharger/wallcharger{
+ pixel_y = 25
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"NF" = (
+/obj/structure/sign/poster/official/here_for_your_safety,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall3";
+ tag = "icon-swall3"
+ },
+/area/shuttle/administration)
+"NQ" = (
+/obj/machinery/door/poddoor/shutters{
+ density = 0;
+ dir = 1;
+ icon_state = "open";
+ id_tag = "Asclshutters";
+ name = "Blast Shutters";
+ opacity = 0
+ },
+/obj/structure/grille,
+/obj/structure/window/full/shuttle,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Op" = (
+/obj/structure/dispenser/oxygen,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Ou" = (
+/obj/machinery/autolathe/upgraded{
+ hacked = 1
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Pe" = (
+/obj/machinery/door/window/brigdoor/southright{
+ req_one_access_txt = "2;19"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Pn" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/clothing/suit/armor/riot,
+/obj/item/clothing/suit/armor/riot{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"PL" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall_s10"
+ },
+/area/shuttle/administration)
+"PR" = (
+/obj/machinery/light/spot{
+ dir = 8;
+ tag = "icon-tube1 (WEST)"
+ },
+/obj/machinery/door_control{
+ id = "adminarmoryshutters";
+ name = "Armory Internal Shutters";
+ pixel_x = -26;
+ req_access_txt = "3"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"PS" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/gun/energy/gun/nuclear{
+ pixel_y = -3
+ },
+/obj/item/gun/energy/gun/nuclear,
+/obj/item/gun/energy/gun/nuclear{
+ pixel_y = 3
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"PV" = (
+/obj/structure/rack,
+/obj/item/gun/energy/sniperrifle{
+ pixel_y = -4
+ },
+/obj/item/gun/energy/sniperrifle,
+/obj/item/gun/energy/sniperrifle{
+ pixel_y = 3
+ },
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"RM" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall8";
+ tag = "icon-swall12"
+ },
+/area/shuttle/administration)
+"TD" = (
+/obj/machinery/door/airlock/command{
+ name = "Command Center";
+ req_access = null;
+ req_access_txt = "19"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"TF" = (
+/obj/structure/table/reinforced,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/structure/window/reinforced,
+/obj/item/storage/fancy/donut_box,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Un" = (
+/obj/structure/table/reinforced,
+/obj/item/tank/emergency_oxygen/engi{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/obj/item/tank/emergency_oxygen/engi,
+/obj/item/tank/emergency_oxygen/engi{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/machinery/light/spot{
+ dir = 8;
+ icon_state = "tube1";
+ tag = "icon-tube1 (WEST)"
+ },
+/obj/item/clothing/mask/breath{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/clothing/mask/breath,
+/obj/item/clothing/mask/breath{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"Ur" = (
+/obj/machinery/porta_turret{
+ installation = /obj/item/gun/energy/gun
+ },
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/turf/simulated/shuttle/floor,
+/area/shuttle/administration)
+"UR" = (
+/obj/machinery/status_display,
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall12"
+ },
+/area/shuttle/administration)
+"UX" = (
+/obj/structure/fans/tiny,
+/obj/machinery/door/airlock/external,
+/obj/machinery/door/poddoor{
+ density = 0;
+ icon_state = "open";
+ id_tag = "asclblast";
+ name = "Blast Door";
+ opacity = 0
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"Vc" = (
+/turf/simulated/shuttle/wall{
+ dir = 2;
+ icon_state = "swall_s5"
+ },
+/area/shuttle/administration)
+"VC" = (
+/obj/machinery/door/airlock/shuttle{
+ name = "Shuttle Armory";
+ req_access_txt = "3"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"VQ" = (
+/obj/structure/rack,
+/obj/structure/window/reinforced,
+/obj/structure/window/reinforced{
+ dir = 8
+ },
+/obj/item/gun/energy/gun/advtaser{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/obj/item/gun/energy/gun/advtaser{
+ pixel_x = 0;
+ pixel_y = 0
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"WV" = (
+/obj/structure/table/reinforced,
+/obj/item/restraints/handcuffs{
+ pixel_x = 3;
+ pixel_y = -3
+ },
+/obj/item/restraints/handcuffs,
+/obj/item/restraints/handcuffs{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/machinery/light/spot{
+ dir = 4;
+ icon_state = "tube1";
+ tag = "icon-tube1 (EAST)"
+ },
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+"XL" = (
+/obj/structure/window/reinforced{
+ dir = 1;
+ layer = 2.9
+ },
+/obj/structure/rack,
+/obj/structure/window/reinforced{
+ dir = 4
+ },
+/obj/item/gun/projectile/shotgun/riot/buckshot{
+ pixel_x = -3;
+ pixel_y = 3
+ },
+/obj/item/gun/projectile/shotgun/riot/buckshot,
+/turf/simulated/shuttle/floor{
+ icon_state = "floor4"
+ },
+/area/shuttle/administration)
+
+(1,1,1) = {"
+vk
+MZ
+HN
+IC
+IC
+IC
+IC
+KM
+wQ
+wQ
+wQ
+wQ
+KM
+IC
+IC
+IC
+IC
+Vc
+"}
+(2,1,1) = {"
+MN
+Un
+xm
+Mj
+aP
+dP
+tx
+MN
+eI
+Ir
+dn
+gj
+MN
+vA
+cl
+cl
+DZ
+Eq
+"}
+(3,1,1) = {"
+MN
+Ns
+mw
+mw
+mw
+mw
+mw
+MN
+ua
+gh
+gh
+gh
+FF
+Op
+Kh
+Kh
+Kh
+Bj
+"}
+(4,1,1) = {"
+MN
+Ns
+mw
+mw
+mw
+mw
+Gy
+MN
+qQ
+Kh
+Kh
+Kh
+UR
+Jt
+Kh
+Kh
+Kh
+Bj
+"}
+(5,1,1) = {"
+MN
+Ns
+mw
+mw
+mw
+mw
+fo
+zY
+MZ
+TD
+rW
+rW
+pa
+MZ
+xH
+nG
+nG
+Eq
+"}
+(6,1,1) = {"
+MN
+bi
+Cl
+vy
+Ou
+mw
+mw
+MN
+cV
+Kh
+Kh
+Kh
+Bn
+Gf
+Kh
+Mi
+Mi
+MN
+"}
+(7,1,1) = {"
+zY
+nG
+nG
+nG
+nG
+gd
+nG
+MN
+xQ
+Kh
+hN
+Kh
+Pe
+Kh
+Kh
+Kh
+oj
+MN
+"}
+(8,1,1) = {"
+RM
+oY
+mw
+mw
+mw
+mw
+Ur
+kf
+vW
+al
+aI
+LK
+TF
+Kh
+Kh
+Kh
+fQ
+mq
+"}
+(9,1,1) = {"
+UX
+mw
+mw
+mw
+mw
+mw
+mw
+EQ
+mw
+mw
+mw
+mw
+rb
+Kh
+Kh
+Kh
+fQ
+jn
+"}
+(10,1,1) = {"
+Gx
+mw
+mw
+mw
+mw
+mw
+mw
+EQ
+mw
+mw
+mw
+mw
+Ke
+Kh
+Kh
+Kh
+Kh
+jn
+"}
+(11,1,1) = {"
+uz
+oY
+mw
+mw
+mw
+mw
+Hc
+ae
+ir
+pQ
+pQ
+pQ
+uY
+Kh
+Kh
+Kh
+BZ
+MN
+"}
+(12,1,1) = {"
+zY
+MZ
+Bq
+yg
+VC
+yg
+yF
+pa
+bm
+bm
+bm
+MZ
+MZ
+xK
+VC
+NF
+MZ
+mM
+"}
+(13,1,1) = {"
+MN
+Kh
+wH
+eL
+Kh
+eL
+Kh
+PR
+PV
+PS
+Fo
+wH
+VQ
+Bc
+Kh
+Kh
+AH
+MN
+"}
+(14,1,1) = {"
+NQ
+Kh
+dH
+Ni
+Kh
+FT
+Mo
+Kh
+Kh
+Kh
+Kh
+Kh
+Lh
+Pn
+Kh
+Kh
+Kh
+mq
+"}
+(15,1,1) = {"
+NQ
+Kh
+Du
+Gn
+Kh
+df
+wG
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+mq
+"}
+(16,1,1) = {"
+NQ
+Kh
+Fd
+XL
+Kh
+xF
+Ki
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+Kh
+mq
+"}
+(17,1,1) = {"
+MN
+fF
+Kh
+Kh
+fF
+Kh
+Kh
+fF
+iz
+yu
+sq
+WV
+xU
+ce
+jB
+jU
+de
+MN
+"}
+(18,1,1) = {"
+PL
+KM
+uW
+uW
+KM
+IC
+IC
+KM
+uW
+uW
+KM
+IC
+IC
+KM
+uW
+uW
+KM
+LP
+"}
+(19,1,1) = {"
+xZ
+PL
+pK
+pK
+LP
+xZ
+xZ
+PL
+pK
+pK
+LP
+xZ
+xZ
+PL
+pK
+pK
+LP
+xZ
+"}
diff --git a/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm b/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm
index 66ffca0aef5..5725a7a8e9b 100644
--- a/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm
@@ -128,66 +128,56 @@
/obj/machinery/atmospherics/unary/vent_pump/process_atmos()
..()
- if((stat & (NOPOWER|BROKEN)))
- return 0
+ if(stat & (NOPOWER|BROKEN))
+ return FALSE
if(!node)
- on = 0
+ on = FALSE
//broadcast_status() // from now air alarm/control computer should request update purposely --rastaf0
if(!on)
- return 0
+ return FALSE
if(welded)
if(air_contents.return_pressure() >= weld_burst_pressure && prob(5)) //the weld is on but the cover is welded shut, can it withstand the internal pressure?
visible_message("The welded cover of [src] bursts open!")
- for(var/mob/M in range(1, src))
+ for(var/mob/living/M in range(1))
unsafe_pressure_release(M, air_contents.return_pressure()) //let's send everyone flying
welded = FALSE
update_icon()
- return 0
+ return FALSE
var/datum/gas_mixture/environment = loc.return_air()
var/environment_pressure = environment.return_pressure()
-
if(pump_direction) //internal -> external
var/pressure_delta = 10000
-
- if(pressure_checks&1)
+ if(pressure_checks & 1)
pressure_delta = min(pressure_delta, (external_pressure_bound - environment_pressure))
- if(pressure_checks&2)
+ if(pressure_checks & 2)
pressure_delta = min(pressure_delta, (air_contents.return_pressure() - internal_pressure_bound))
- if(pressure_delta > 0.5)
- if(air_contents.temperature > 0)
- var/transfer_moles = pressure_delta*environment.volume/(air_contents.temperature * R_IDEAL_GAS_EQUATION)
-
- var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
-
- loc.assume_air(removed)
- air_update_turf()
-
- parent.update = 1
+ if(pressure_delta > 0.5 && air_contents.temperature > 0)
+ var/transfer_moles = pressure_delta * environment.volume / (air_contents.temperature * R_IDEAL_GAS_EQUATION)
+ var/datum/gas_mixture/removed = air_contents.remove(transfer_moles)
+ loc.assume_air(removed)
+ air_update_turf()
+ parent.update = TRUE
else //external -> internal
var/pressure_delta = 10000
- if(pressure_checks&1)
+ if(pressure_checks & 1)
pressure_delta = min(pressure_delta, (environment_pressure - external_pressure_bound))
- if(pressure_checks&2)
+ if(pressure_checks & 2)
pressure_delta = min(pressure_delta, (internal_pressure_bound - air_contents.return_pressure()))
- if(pressure_delta > 0.5)
- if(environment.temperature > 0)
- var/transfer_moles = pressure_delta*air_contents.volume/(environment.temperature * R_IDEAL_GAS_EQUATION)
+ if(pressure_delta > 0.5 && environment.temperature > 0)
+ var/transfer_moles = pressure_delta * air_contents.volume / (environment.temperature * R_IDEAL_GAS_EQUATION)
+ var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
+ if(isnull(removed)) //in space
+ return
+ air_contents.merge(removed)
+ air_update_turf()
+ parent.update = TRUE
- var/datum/gas_mixture/removed = loc.remove_air(transfer_moles)
- if(isnull(removed)) //in space
- return
-
- air_contents.merge(removed)
- air_update_turf()
-
- parent.update = 1
-
- return 1
+ return TRUE
//Radio remote control
@@ -386,11 +376,11 @@
if(I.use_tool(src, user, 20, volume = I.tool_volume))
if(!welded)
welded = TRUE
- visible_message("[user] welds [src] shut!",\
+ user.visible_message("[user] welds [src] shut!",\
"You weld [src] shut!")
else
welded = FALSE
- visible_message("[user] unwelds [src]!",\
+ user.visible_message("[user] unwelds [src]!",\
"You unweld [src]!")
update_icon()
diff --git a/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm
index ed06dd68ce6..bbd5e760d2f 100644
--- a/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm
@@ -404,10 +404,10 @@
if(I.use_tool(src, user, 20, volume = I.tool_volume))
if(!welded)
welded = TRUE
- visible_message("[user] welds [src] shut!",\
+ user.visible_message("[user] welds [src] shut!",\
"You weld [src] shut!")
else
welded = FALSE
- visible_message("[user] unwelds [src]!",\
+ user.visible_message("[user] unwelds [src]!",\
"You unweld [src]!")
update_icon()
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 10aa1718dda..26b1d2d44aa 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -726,3 +726,7 @@
#define COMSIG_XENO_TURF_CLICK_CTRL "xeno_turf_click_alt"
///from monkey CtrlClickOn(): (/mob)
#define COMSIG_XENO_MONKEY_CLICK_CTRL "xeno_monkey_click_ctrl"
+
+///SSalarm signals
+#define COMSIG_TRIGGERED_ALARM "ssalarm_triggered"
+#define COMSIG_CANCELLED_ALARM "ssalarm_cancelled"
diff --git a/code/__DEFINES/instruments.dm b/code/__DEFINES/instruments.dm
new file mode 100644
index 00000000000..64c77abc9fa
--- /dev/null
+++ b/code/__DEFINES/instruments.dm
@@ -0,0 +1,29 @@
+#define INSTRUMENT_MIN_OCTAVE 1
+#define INSTRUMENT_MAX_OCTAVE 9
+#define INSTRUMENT_MIN_KEY 0
+#define INSTRUMENT_MAX_KEY 127
+
+/// Max number of playing notes per instrument.
+#define CHANNELS_PER_INSTRUMENT 128
+
+/// Distance multiplier that makes us not be impacted by 3d sound as much. This is a multiplier so lower it is the closer we will pretend to be to people.
+#define INSTRUMENT_DISTANCE_FALLOFF_BUFF 0.2
+/// How many tiles instruments have no falloff for
+#define INSTRUMENT_DISTANCE_NO_FALLOFF 3
+
+/// Maximum length a note should ever go for
+#define INSTRUMENT_MAX_TOTAL_SUSTAIN (5 SECONDS)
+
+/// These are per decisecond.
+#define INSTRUMENT_EXP_FALLOFF_MIN 1.025 //100/(1.025^50) calculated for [INSTRUMENT_MIN_SUSTAIN_DROPOFF] to be 30.
+#define INSTRUMENT_EXP_FALLOFF_MAX 10
+
+/// Minimum volume for when the sound is considered dead.
+#define INSTRUMENT_MIN_SUSTAIN_DROPOFF 0.1
+
+#define SUSTAIN_LINEAR 1
+#define SUSTAIN_EXPONENTIAL 2
+
+// /datum/instrument instrument_flags
+#define INSTRUMENT_LEGACY (1<<0) //Legacy instrument. Implies INSTRUMENT_DO_NOT_AUTOSAMPLE
+#define INSTRUMENT_DO_NOT_AUTOSAMPLE (1<<1) //Do not automatically sample
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 7bdca42c3b3..0a36db1b185 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -242,28 +242,75 @@
0.4,0.6,0.0,\
0.2,0.2,0.6)
-#define LIST_REPLACE_RENAME list("rebeccapurple" = "dark purple", "darkslategrey" = "dark grey", "darkolivegreen" = "dark green", "darkslateblue" = "dark blue",\
- "darkkhaki" = "khaki", "darkseagreen" = "light green", "midnightblue" = "blue", "lightgrey" = "light grey", "darkgrey" = "dark grey",\
- "steelblue" = "blue", "goldenrod" = "gold")
+/*
+ Used for wire name appearances. Replaces the color name on the left with the one on the right.
+ The color on the left is the one used as the actual color of the wire, but it doesn't look good when written.
+ So, we need to replace the name to something that looks better.
+*/
+#define LIST_COLOR_RENAME \
+ list( \
+ "rebeccapurple" = "dark purple",\
+ "darkslategrey" = "dark grey", \
+ "darkolivegreen"= "dark green", \
+ "darkslateblue" = "dark blue", \
+ "darkkhaki" = "khaki", \
+ "darkseagreen" = "light green",\
+ "midnightblue" = "blue", \
+ "lightgrey" = "light grey", \
+ "darkgrey" = "dark grey", \
+ "steelblue" = "blue", \
+ "goldenrod" = "gold" \
+ )
-#define LIST_GREYSCALE_REPLACE list("red" = "lightgrey", "blue" = "grey", "green" = "grey", "orange" = "lightgrey", "brown" = "grey",\
- "gold" = "lightgrey", "cyan" = "lightgrey", "navy" = "grey", "purple" = "grey", "pink"= "lightgrey")
+/// Pure Black and white colorblindness. Every species except Vulpkanins and Tajarans will have this.
+#define GREYSCALE_COLOR_REPLACE \
+ list( \
+ "red" = "grey", \
+ "blue" = "grey", \
+ "green" = "grey", \
+ "orange" = "light grey", \
+ "brown" = "grey", \
+ "gold" = "light grey", \
+ "cyan" = "silver", \
+ "magenta" = "grey", \
+ "purple" = "grey", \
+ "pink" = "light grey" \
+ )
-#define LIST_VULP_REPLACE list("pink" = "beige", "orange" = "goldenrod", "gold" = "goldenrod", "red" = "darkolivegreen", "brown" = "darkolivegreen",\
- "green" = "darkslategrey", "cyan" = "steelblue", "purple" = "darkslategrey", "navy" = "midnightblue")
-
-#define LIST_TAJ_REPLACE list("red" = "rebeccapurple", "brown" = "rebeccapurple", "purple" = "darkslateblue", "blue" = "darkslateblue",\
- "green" = "darkolivegreen", "orange" = "darkkhaki", "gold" = "darkkhaki", "cyan" = "darkseagreen", \
- "navy" = "midnightblue", "pink" = "lightgrey")
+/// Red colorblindness. Vulpkanins/Wolpins have this.
+#define PROTANOPIA_COLOR_REPLACE \
+ list( \
+ "red" = "darkolivegreen", \
+ "green" = "darkslategrey", \
+ "orange" = "goldenrod", \
+ "gold" = "goldenrod", \
+ "brown" = "darkolivegreen", \
+ "cyan" = "steelblue", \
+ "magenta" = "blue", \
+ "purple" = "darkslategrey", \
+ "pink" = "beige" \
+ )
+/// Yellow-Blue colorblindness. Tajarans/Farwas have this.
+#define TRITANOPIA_COLOR_REPLACE \
+ list( \
+ "red" = "rebeccapurple", \
+ "blue" = "darkslateblue", \
+ "green" = "darkolivegreen", \
+ "orange" = "darkkhaki", \
+ "gold" = "darkkhaki", \
+ "brown" = "rebeccapurple", \
+ "cyan" = "darkseagreen", \
+ "magenta" = "darkslateblue", \
+ "purple" = "darkslateblue", \
+ "pink" = "lightgrey" \
+ )
//Gun trigger guards
#define TRIGGER_GUARD_ALLOW_ALL -1
#define TRIGGER_GUARD_NONE 0
#define TRIGGER_GUARD_NORMAL 1
-#define CLIENT_FROM_VAR(I) (ismob(I) ? I:client : (istype(I, /client) ? I : (istype(I, /datum/mind) ? I:current?:client : null)))
-
// Macro to get the current elapsed round time, rather than total world runtime
#define ROUND_TIME (SSticker.round_start_time ? (world.time - SSticker.round_start_time) : 0)
diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm
index c15dfd78b0f..3fcb9cc3410 100644
--- a/code/__DEFINES/sound.dm
+++ b/code/__DEFINES/sound.dm
@@ -12,6 +12,7 @@
#define CHANNEL_HIGHEST_AVAILABLE 1017
+#define MAX_INSTRUMENT_CHANNELS (128 * 6)
#define SOUND_MINIMUM_PRESSURE 10
#define FALLOFF_SOUNDS 0.5
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 153eca7f373..6e3603f83a3 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -45,11 +45,13 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
#define INIT_ORDER_TITLE 100 // This **MUST** load first or people will se blank lobby screens
-#define INIT_ORDER_GARBAGE 19
-#define INIT_ORDER_DBCORE 18
-#define INIT_ORDER_BLACKBOX 17
-#define INIT_ORDER_SERVER_MAINT 16
-#define INIT_ORDER_INPUT 15
+#define INIT_ORDER_GARBAGE 21
+#define INIT_ORDER_DBCORE 20
+#define INIT_ORDER_BLACKBOX 19
+#define INIT_ORDER_SERVER_MAINT 18
+#define INIT_ORDER_INPUT 17
+#define INIT_ORDER_SOUNDS 16
+#define INIT_ORDER_INSTRUMENTS 15
#define INIT_ORDER_RESEARCH 14
#define INIT_ORDER_EVENTS 13
#define INIT_ORDER_JOBS 12
@@ -80,8 +82,7 @@
#define INIT_ORDER_NANOMOB -23
#define INIT_ORDER_SQUEAK -40
#define INIT_ORDER_PATH -50
-#define INIT_ORDER_PERSISTENCE -95
-#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init.
+#define INIT_ORDER_PERSISTENCE -95
// Subsystem fire priority, from lowest to highest priority
// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child)
@@ -113,7 +114,6 @@
#define FIRE_PRIORITY_MOBS 100
#define FIRE_PRIORITY_NANOUI 110
#define FIRE_PRIORITY_TICKER 200
-#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_OVERLAYS 500
#define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost.
diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm
new file mode 100644
index 00000000000..f7ea1c3cf38
--- /dev/null
+++ b/code/__DEFINES/wires.dm
@@ -0,0 +1,77 @@
+// Wire defines for all machines/items.
+
+// Miscellaneous
+#define WIRE_DUD_PREFIX "__dud"
+
+// General
+#define WIRE_IDSCAN "ID Scan"
+#define WIRE_MAIN_POWER1 "Primary Power"
+#define WIRE_MAIN_POWER2 "Secondary Power"
+#define WIRE_AI_CONTROL "AI Control"
+#define WIRE_ELECTRIFY "Electrification"
+#define WIRE_SAFETY "Safety"
+
+// Vendors and smartfridges
+#define WIRE_THROW_ITEM "Item Throw"
+#define WIRE_CONTRABAND "Contraband"
+
+// Airlock
+#define WIRE_DOOR_BOLTS "Door Bolts"
+#define WIRE_BACKUP_POWER1 "Primary Backup Power"
+#define WIRE_OPEN_DOOR "Door State"
+#define WIRE_SPEED "Door Timing"
+#define WIRE_BOLT_LIGHT "Bolt Lights"
+
+// Air alarm
+#define WIRE_SYPHON "Siphon"
+#define WIRE_AALARM "Atmospherics Alarm"
+
+// Camera
+#define WIRE_FOCUS "Focus"
+
+// Mulebot
+#define WIRE_MOB_AVOIDANCE "Mob Avoidance"
+#define WIRE_LOADCHECK "Load Checking"
+#define WIRE_MOTOR1 "Primary Motor"
+#define WIRE_MOTOR2 "Secondary Motor"
+#define WIRE_REMOTE_RX "Signal Receiver"
+#define WIRE_REMOTE_TX "Signal Sender"
+#define WIRE_BEACON_RX "Beacon Receiver"
+
+// Explosives, bombs
+#define WIRE_EXPLODE "Explode" // Explodes if pulsed or cut while active, defuses a bomb that isn't active on cut.
+#define WIRE_BOMB_UNBOLT "Unbolt" // Unbolts the bomb if cut, hint on pulsed.
+#define WIRE_BOMB_DELAY "Delay" // Raises the timer on pulse, does nothing on cut.
+#define WIRE_BOMB_PROCEED "Proceed" // Lowers the timer, explodes if cut while the bomb is active.
+#define WIRE_BOMB_ACTIVATE "Activate" // Will start a bombs timer if pulsed, will hint if pulsed while already active, will stop a timer a bomb on cut.
+
+// Nuclear bomb
+#define WIRE_BOMB_LIGHT "Bomb Light"
+#define WIRE_BOMB_TIMING "Bomb Timing"
+#define WIRE_BOMB_SAFETY "Bomb Safety"
+
+// Particle accelerator
+#define WIRE_PARTICLE_POWER "Power Toggle" // Toggles whether the PA is on or not.
+#define WIRE_PARTICLE_STRENGTH "Strength" // Determines the strength of the PA.
+#define WIRE_PARTICLE_INTERFACE "Interface" // Determines the interface showing up.
+#define WIRE_PARTICLE_POWER_LIMIT "Maximum Power" // Determines how strong the PA can be.
+
+// Autolathe
+#define WIRE_AUTOLATHE_HACK "Hack"
+#define WIRE_AUTOLATHE_DISABLE "Disable"
+
+// Radio
+#define WIRE_RADIO_SIGNAL "Signal"
+#define WIRE_RADIO_RECEIVER "Receiver"
+#define WIRE_RADIO_TRANSMIT "Transmitter"
+
+// Cyborg
+#define WIRE_BORG_LOCKED "Lockdown"
+#define WIRE_BORG_CAMERA "Camera"
+#define WIRE_BORG_LAWCHECK "Law Check"
+
+// Suit storage unit
+#define WIRE_SSU_UV "UV wire"
+
+// Tesla coil
+#define WIRE_TESLACOIL_ZAP "Zap"
diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm
index 181d2a5140c..829c2c4fade 100644
--- a/code/__HELPERS/game.dm
+++ b/code/__HELPERS/game.dm
@@ -5,11 +5,11 @@
var/turf/T = get_turf(A)
return T ? T.loc : null
-/proc/get_area_name(N) //get area by its name
- for(var/area/A in world)
- if(A.name == N)
- return A
- return 0
+/proc/get_area_name(atom/X, format_text = FALSE)
+ var/area/A = isarea(X) ? X : get_area(X)
+ if(!A)
+ return null
+ return format_text ? format_text(A.name) : A.name
/proc/get_location_name(atom/X, format_text = FALSE)
var/area/A = isarea(X) ? X : get_area(X)
@@ -31,6 +31,24 @@
areas |= T.loc
return areas
+/proc/get_open_turf_in_dir(atom/center, dir)
+ var/turf/T = get_ranged_target_turf(center, dir, 1)
+ if(T && !T.density)
+ return T
+
+/proc/get_adjacent_open_turfs(atom/center)
+ . = list(get_open_turf_in_dir(center, NORTH),
+ get_open_turf_in_dir(center, SOUTH),
+ get_open_turf_in_dir(center, EAST),
+ get_open_turf_in_dir(center, WEST))
+ listclearnulls(.)
+
+/proc/get_adjacent_open_areas(atom/center)
+ . = list()
+ var/list/adjacent_turfs = get_adjacent_open_turfs(center)
+ for(var/I in adjacent_turfs)
+ . |= get_area(I)
+
// Like view but bypasses luminosity check
/proc/hear(var/range, var/atom/source)
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index c1cf5d53520..075f3370965 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -672,9 +672,6 @@ proc/dd_sortedObjectList(list/incoming)
/obj/machinery/camera/dd_SortValue()
return "[c_tag]"
-/datum/alarm/dd_SortValue()
- return "[sanitize(last_name)]"
-
//Picks from the list, with some safeties, and returns the "default" arg if it fails
#define DEFAULTPICK(L, default) ((istype(L, /list) && L:len) ? pick(L) : default)
diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm
index 21af95cab48..ab3bfa85451 100644
--- a/code/_globalvars/lists/objects.dm
+++ b/code/_globalvars/lists/objects.dm
@@ -47,7 +47,10 @@ GLOBAL_LIST_EMPTY(ladders)
GLOBAL_LIST_INIT(active_diseases, list()) //List of Active disease in all mobs; purely for quick referencing.
GLOBAL_LIST_EMPTY(mob_spawners) // All mob_spawn objects
-
+GLOBAL_LIST_EMPTY(alert_consoles) // Station alert consoles, /obj/machinery/computer/station_alert
GLOBAL_LIST_EMPTY(explosive_walls)
GLOBAL_LIST_EMPTY(engine_beacon_list)
+
+/// List of wire colors for each object type of that round. One for airlocks, one for vendors, etc.
+GLOBAL_LIST_EMPTY(wire_color_directory) // This is an associative list with the `holder_type` as the key, and a list of colors as the value.
diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm
index ada0ce8cd30..856f724f925 100644
--- a/code/_onclick/hud/ai.dm
+++ b/code/_onclick/hud/ai.dm
@@ -64,7 +64,7 @@
/obj/screen/ai/alerts/Click()
if(isAI(usr))
var/mob/living/silicon/ai/AI = usr
- AI.subsystem_alarm_monitor()
+ AI.ai_alerts()
/obj/screen/ai/announcement
name = "Make Announcement"
diff --git a/code/controllers/subsystem/alarm.dm b/code/controllers/subsystem/alarm.dm
index 289adf6de40..3d91d763be3 100644
--- a/code/controllers/subsystem/alarm.dm
+++ b/code/controllers/subsystem/alarm.dm
@@ -1,31 +1,31 @@
-SUBSYSTEM_DEF(alarms)
- name = "Alarms"
- init_order = INIT_ORDER_ALARMS // 2
- offline_implications = "Alarms (Power, camera, fire, etc) will no longer be checked. No immediate action is needed."
- var/datum/alarm_handler/atmosphere/atmosphere_alarm = new()
- var/datum/alarm_handler/burglar/burglar_alarm = new()
- var/datum/alarm_handler/camera/camera_alarm = new()
- var/datum/alarm_handler/fire/fire_alarm = new()
- var/datum/alarm_handler/motion/motion_alarm = new()
- var/datum/alarm_handler/power/power_alarm = new()
- var/list/datum/alarm/all_handlers
+SUBSYSTEM_DEF(alarm)
+ name = "Alarm"
+ flags = SS_NO_INIT | SS_NO_FIRE
+ var/list/alarms = list("Motion" = list(), "Fire" = list(), "Atmosphere" = list(), "Power" = list(), "Camera" = list(), "Burglar" = list())
-/datum/controller/subsystem/alarms/Initialize(start_timeofday)
- all_handlers = list(SSalarms.atmosphere_alarm, SSalarms.burglar_alarm, SSalarms.camera_alarm, SSalarms.fire_alarm, SSalarms.motion_alarm, SSalarms.power_alarm)
- return ..()
+/datum/controller/subsystem/alarm/proc/triggerAlarm(class, area/A, list/O, obj/alarmsource)
+ var/list/L = alarms[class]
+ for(var/I in L)
+ if(I == A.name)
+ var/list/alarm = L[I]
+ var/list/sources = alarm[3]
+ if(!(alarmsource.UID() in sources))
+ sources += alarmsource.UID()
+ return TRUE
+ L[A.name] = list(get_area_name(A, TRUE), O, list(alarmsource.UID()))
+ SEND_SIGNAL(SSalarm, COMSIG_TRIGGERED_ALARM, class, A, O, alarmsource)
+ return TRUE
-/datum/controller/subsystem/alarms/fire()
- for(var/datum/alarm_handler/AH in all_handlers)
- AH.process()
+/datum/controller/subsystem/alarm/proc/cancelAlarm(class, area/A, obj/origin)
+ var/list/L = alarms[class]
+ var/cleared = FALSE
+ for(var/I in L)
+ if(I == A.name)
+ var/list/alarm = L[I]
+ var/list/srcs = alarm[3]
+ srcs -= origin.UID()
+ if(!length(srcs))
+ cleared = TRUE
+ L -= I
-/datum/controller/subsystem/alarms/proc/active_alarms()
- var/list/all_alarms = new ()
- for(var/datum/alarm_handler/AH in all_handlers)
- var/list/alarms = AH.alarms
- all_alarms += alarms
-
- return all_alarms
-
-/datum/controller/subsystem/alarms/proc/number_of_active_alarms()
- var/list/alarms = active_alarms()
- return alarms.len
+ SEND_SIGNAL(SSalarm, COMSIG_CANCELLED_ALARM, class, A, origin, cleared)
diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm
deleted file mode 100644
index 4eb468a0952..00000000000
--- a/code/controllers/subsystem/chat.dm
+++ /dev/null
@@ -1,67 +0,0 @@
-SUBSYSTEM_DEF(chat)
- name = "Chat"
- flags = SS_TICKER|SS_NO_INIT
- wait = 1
- priority = FIRE_PRIORITY_CHAT
- init_order = INIT_ORDER_CHAT
- offline_implications = "Chat messages will no longer be cleanly queued. No immediate action is needed."
-
- var/list/payload = list()
-
-
-/datum/controller/subsystem/chat/fire()
- for(var/i in payload)
- var/client/C = i
- if(C)
- C << output(payload[C], "browseroutput:output")
- payload -= C
-
- if(MC_TICK_CHECK)
- return
-
-
-/datum/controller/subsystem/chat/proc/queue(target, message, flag)
- if(!target || !message)
- return
-
- if(!istext(message))
- stack_trace("to_chat called with invalid input type")
- return
-
- if(target == world)
- target = GLOB.clients
-
- //Some macros remain in the string even after parsing and fuck up the eventual output
- message = replacetext(message, "\improper", "")
- message = replacetext(message, "\proper", "")
- message += "
"
-
-
- //url_encode it TWICE, this way any UTF-8 characters are able to be decoded by the Javascript.
- //Do the double-encoding here to save nanoseconds
- var/twiceEncoded = url_encode(url_encode(message))
-
- if(islist(target))
- for(var/I in target)
- var/client/C = CLIENT_FROM_VAR(I) //Grab us a client if possible
-
- if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
- continue
-
- if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- continue
-
- payload[C] += twiceEncoded
-
- else
- var/client/C = CLIENT_FROM_VAR(target) //Grab us a client if possible
-
- if(!C?.chatOutput || C.chatOutput.broken) //A player who hasn't updated his skin file.
- return
-
- if(!C.chatOutput.loaded) //Client still loading, put their messages in a queue
- C.chatOutput.messageQueue += message
- return
-
- payload[C] += twiceEncoded
diff --git a/code/controllers/subsystem/processing/instruments.dm b/code/controllers/subsystem/processing/instruments.dm
new file mode 100644
index 00000000000..3d571d2a13d
--- /dev/null
+++ b/code/controllers/subsystem/processing/instruments.dm
@@ -0,0 +1,86 @@
+PROCESSING_SUBSYSTEM_DEF(instruments)
+ name = "Instruments"
+ init_order = INIT_ORDER_INSTRUMENTS
+ wait = 1
+ flags = SS_TICKER|SS_BACKGROUND|SS_KEEP_TIMING
+ offline_implications = "Instruments will no longer play. No immediate action is needed."
+
+ /// List of all instrument data, associative id = datum
+ var/list/datum/instrument/instrument_data
+ /// List of all song datums.
+ var/list/datum/song/songs
+ /// Max lines in songs
+ var/musician_maxlines = 600
+ /// Max characters per line in songs
+ var/musician_maxlinechars = 300
+ /// Deciseconds between hearchecks. Too high and instruments seem to lag when people are moving around in terms of who can hear it. Too low and the server lags from this.
+ var/musician_hearcheck_mindelay = 5
+ /// Maximum instrument channels total instruments are allowed to use. This is so you don't have instruments deadlocking all sound channels.
+ var/max_instrument_channels = MAX_INSTRUMENT_CHANNELS
+ /// Current number of channels allocated for instruments
+ var/current_instrument_channels = 0
+ /// Single cached list for synthesizer instrument ids, so you don't have to have a new list with every synthesizer.
+ var/list/synthesizer_instrument_ids
+
+/datum/controller/subsystem/processing/instruments/Initialize()
+ initialize_instrument_data()
+ synthesizer_instrument_ids = get_allowed_instrument_ids()
+ return ..()
+
+/**
+ * Initializes all instrument datums
+ */
+/datum/controller/subsystem/processing/instruments/proc/initialize_instrument_data()
+ instrument_data = list()
+ for(var/path in subtypesof(/datum/instrument))
+ var/datum/instrument/I = path
+ if(initial(I.abstract_type) == path)
+ continue
+ I = new path
+ I.Initialize()
+ if(!I.id)
+ qdel(I)
+ continue
+ else
+ instrument_data[I.id] = I
+ CHECK_TICK
+
+/**
+ * Reserves a sound channel for a given instrument datum
+ *
+ * Arguments:
+ * * I - The instrument datum
+ */
+/datum/controller/subsystem/processing/instruments/proc/reserve_instrument_channel(datum/instrument/I)
+ if(current_instrument_channels > max_instrument_channels)
+ return
+ . = SSsounds.reserve_sound_channel(I)
+ if(!isnull(.))
+ current_instrument_channels++
+
+/**
+ * Called when a datum/song is created
+ *
+ * Arguments:
+ * * S - The created datum/song
+ */
+/datum/controller/subsystem/processing/instruments/proc/on_song_new(datum/song/S)
+ LAZYADD(songs, S)
+
+/**
+ * Called when a datum/song is deleted
+ *
+ * Arguments:
+ * * S - The deleted datum/song
+ */
+/datum/controller/subsystem/processing/instruments/proc/on_song_del(datum/song/S)
+ LAZYREMOVE(songs, S)
+
+/**
+ * Returns the instrument datum at the given ID or path
+ *
+ * Arguments:
+ * * id_or_path - The ID or path of the instrument
+ */
+/datum/controller/subsystem/processing/instruments/proc/get_instrument(id_or_path)
+ return instrument_data["[id_or_path]"]
diff --git a/code/controllers/subsystem/sounds.dm b/code/controllers/subsystem/sounds.dm
new file mode 100644
index 00000000000..33d97fcfe04
--- /dev/null
+++ b/code/controllers/subsystem/sounds.dm
@@ -0,0 +1,165 @@
+#define DATUMLESS "NO_DATUM"
+
+SUBSYSTEM_DEF(sounds)
+ name = "Sounds"
+ init_order = INIT_ORDER_SOUNDS
+ flags = SS_NO_FIRE
+ offline_implications = "Sounds may not play correctly. Shuttle call recommended."
+
+ var/using_channels_max = CHANNEL_HIGHEST_AVAILABLE // BYOND max channels
+ /// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up.
+ var/random_channels_min = 50
+ // Hey uh these two needs to be initialized fast because the whole "things get deleted before init" thing.
+ /// Assoc list, "[channel]" = either the datum using it or TRUE for an unsafe-reserved (datumless reservation) channel
+ var/list/using_channels
+ /// Assoc list datum = list(channel1, channel2, ...) for what channels something reserved.
+ var/list/using_channels_by_datum
+ // Special datastructure for fast channel management
+ /// List of all channels as numbers
+ var/list/channel_list
+ /// Associative list of all reserved channels associated to their position. "[channel_number]" = index as number
+ var/list/reserved_channels
+ /// lower iteration position - Incremented and looped to get "random" sound channels for normal sounds. The channel at this index is returned when asking for a random channel.
+ var/channel_random_low
+ /// higher reserve position - decremented and incremented to reserve sound channels, anything above this is reserved. The channel at this index is the highest unreserved channel.
+ var/channel_reserve_high
+
+/datum/controller/subsystem/sounds/Initialize()
+ setup_available_channels()
+ return ..()
+
+/**
+ * Sets up all available sound channels
+ */
+/datum/controller/subsystem/sounds/proc/setup_available_channels()
+ channel_list = list()
+ reserved_channels = list()
+ using_channels = list()
+ using_channels_by_datum = list()
+ for(var/i in 1 to using_channels_max)
+ channel_list += i
+ channel_random_low = 1
+ channel_reserve_high = length(channel_list)
+
+/**
+ * Removes a channel from using list
+ *
+ * Arguments:
+ * * channel - The channel number
+ */
+/datum/controller/subsystem/sounds/proc/free_sound_channel(channel)
+ var/text_channel = num2text(channel)
+ var/using = using_channels[text_channel]
+ using_channels -= text_channel
+ if(!using) // datum channel
+ using_channels_by_datum[using] -= channel
+ if(!length(using_channels_by_datum[using]))
+ using_channels_by_datum -= using
+ free_channel(channel)
+
+/**
+ * Frees all the channels a datum is using
+ *
+ * Arguments:
+ * * D - The datum
+ */
+/datum/controller/subsystem/sounds/proc/free_datum_channels(datum/D)
+ var/list/L = using_channels_by_datum[D]
+ if(!L)
+ return
+ for(var/channel in L)
+ using_channels -= num2text(channel)
+ free_channel(channel)
+ using_channels_by_datum -= D
+
+/**
+ * Frees all datumless channels
+ */
+/datum/controller/subsystem/sounds/proc/free_datumless_channels()
+ free_datum_channels(DATUMLESS)
+
+/**
+ * NO AUTOMATIC CLEANUP - If you use this, you better manually free it later!
+ *
+ * Returns an integer for channel
+ */
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel_datumless()
+ . = reserve_channel()
+ if(!.) // oh no..
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = DATUMLESS
+ LAZYADD(using_channels_by_datum[DATUMLESS], .)
+
+/**
+ * Reserves a channel for a datum. Automatic cleanup only when the datum is deleted.
+ *
+ * Returns an integer for channel
+ * Arguments:
+ * * D - The datum
+ */
+/datum/controller/subsystem/sounds/proc/reserve_sound_channel(datum/D)
+ if(!D) // i don't like typechecks but someone will fuck it up
+ CRASH("Attempted to reserve sound channel without datum using the managed proc.")
+ . = reserve_channel()
+ if(!.)
+ return FALSE
+ var/text_channel = num2text(.)
+ using_channels[text_channel] = D
+ LAZYADD(using_channels_by_datum[D], .)
+
+/**
+ * Reserves a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/reserve_channel()
+ PRIVATE_PROC(TRUE)
+ if(channel_reserve_high <= random_channels_min) // out of channels
+ return
+ var/channel = channel_list[channel_reserve_high]
+ reserved_channels[num2text(channel)] = channel_reserve_high--
+ return channel
+
+/**
+ * Frees a channel and updates the datastructure. Private proc.
+ */
+/datum/controller/subsystem/sounds/proc/free_channel(number)
+ PRIVATE_PROC(TRUE)
+ var/text_channel = num2text(number)
+ var/index = reserved_channels[text_channel]
+ if(!index)
+ CRASH("Attempted to (internally) free a channel that wasn't reserved.")
+ reserved_channels -= text_channel
+ // push reserve index up, which makes it now on a channel that is reserved
+ channel_reserve_high++
+ // swap the reserved channel with the unreserved channel so the reserve index is now on an unoccupied channel and the freed channel is next to be used.
+ channel_list.Swap(channel_reserve_high, index)
+ // now, an existing reserved channel will likely (exception: unreserving last reserved channel) be at index
+ // get it, and update position.
+ var/text_reserved = num2text(channel_list[index])
+ if(!reserved_channels[text_reserved]) // if it isn't already reserved make sure we don't accidently mistakenly put it on reserved list!
+ return
+ reserved_channels[text_reserved] = index
+
+/**
+ * Random available channel, returns text
+ */
+/datum/controller/subsystem/sounds/proc/random_available_channel_text()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = "[channel_list[channel_random_low++]]"
+
+/**
+ * Random available channel, returns number
+ */
+/datum/controller/subsystem/sounds/proc/random_available_channel()
+ if(channel_random_low > channel_reserve_high)
+ channel_random_low = 1
+ . = channel_list[channel_random_low++]
+
+/**
+ * How many channels we have left
+ */
+/datum/controller/subsystem/sounds/proc/available_channels_left()
+ return length(channel_list) - random_channels_min
+
+#undef DATUMLESS
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index abb0b604069..8d02529ae6b 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -20,7 +20,7 @@
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
/client/proc/debug_controller(controller in list("failsafe", "Master", "Ticker", "Air", "Jobs", "Sun", "Radio", "Configuration", "pAI",
- "Cameras", "Garbage", "Event", "Alarm", "Nano", "Vote", "Fires",
+ "Cameras", "Garbage", "Event", "Nano", "Vote", "Fires",
"Mob", "NPC Pool", "Shuttle", "Timer", "Weather", "Space", "Mob Hunt Server","Input"))
set category = "Debug"
set name = "Debug Controller"
@@ -65,9 +65,6 @@
if("Event")
debug_variables(SSevents)
feedback_add_details("admin_verb","DEvent")
- if("Alarm")
- debug_variables(SSalarms)
- feedback_add_details("admin_verb", "DAlarm")
if("Nano")
debug_variables(SSnanoui)
feedback_add_details("admin_verb","DNano")
diff --git a/code/datums/action.dm b/code/datums/action.dm
index c1802da47c1..11ab496d386 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -190,9 +190,6 @@
/datum/action/item_action/toggle_mister
name = "Toggle Mister"
-/datum/action/item_action/toggle_headphones
- name = "Toggle Headphones"
-
/datum/action/item_action/toggle_helmet_light
name = "Toggle Helmet Light"
@@ -232,19 +229,6 @@
button.name = name
..()
-/datum/action/item_action/synthswitch
- name = "Change Synthesizer Instrument"
- desc = "Change the type of instrument your synthesizer is playing as."
-
-/datum/action/item_action/synthswitch/Trigger()
- if(istype(target, /obj/item/instrument/piano_synth))
- var/obj/item/instrument/piano_synth/synth = target
- var/chosen = input("Choose the type of instrument you want to use", "Instrument Selection", "piano") as null|anything in synth.insTypes
- if(!synth.insTypes[chosen])
- return
- return synth.changeInstrument(chosen)
- return ..()
-
/datum/action/item_action/vortex_recall
name = "Vortex Recall"
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it."
@@ -257,6 +241,9 @@
return 0
return ..()
+/datum/action/item_action/change_headphones_song
+ name = "Change Headphones Song"
+
/datum/action/item_action/toggle
/datum/action/item_action/toggle/New(Target)
diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm
new file mode 100644
index 00000000000..f5ee9c94666
--- /dev/null
+++ b/code/datums/components/spooky.dm
@@ -0,0 +1,58 @@
+/datum/component/spooky
+ var/too_spooky = TRUE //will it spawn a new instrument?
+
+/datum/component/spooky/Initialize()
+ RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/spectral_attack)
+
+/datum/component/spooky/proc/spectral_attack(datum/source, mob/living/carbon/C, mob/user)
+ if(ishuman(user)) //this weapon wasn't meant for mortals.
+ var/mob/living/carbon/human/U = user
+ if(!istype(U.dna.species, /datum/species/skeleton))
+ U.adjustStaminaLoss(35) //Extra Damage
+ U.Jitter(35)
+ U.stuttering = 20
+ if(U.getStaminaLoss() > 95)
+ to_chat(U, "Your ears weren't meant for this spectral sound.")
+ spectral_change(U)
+ return
+
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(istype(H.dna.species, /datum/species/skeleton))
+ return //undeads are unaffected by the spook-pocalypse.
+ C.Jitter(35)
+ C.stuttering = 20
+ if(!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman))
+ C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live
+ to_chat(C, "DOOT")
+ spectral_change(H)
+
+ else //the sound will spook monkeys.
+ C.Jitter(15)
+ C.stuttering = 20
+
+/datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user)
+ if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/diona) && !istype(H.dna.species, /datum/species/machine) && !istype(H.dna.species, /datum/species/slime) && !istype(H.dna.species, /datum/species/golem) && !istype(H.dna.species, /datum/species/plasmaman) && !istype(H.dna.species, /datum/species/skeleton)))
+ H.Stun(20)
+ H.set_species(/datum/species/skeleton)
+ H.visible_message("[H] has given up on life as a mortal.")
+ var/T = get_turf(H)
+ if(too_spooky)
+ if(prob(30))
+ new/obj/item/instrument/saxophone/spectral(T)
+ else if(prob(30))
+ new/obj/item/instrument/trumpet/spectral(T)
+ else if(prob(30))
+ new/obj/item/instrument/trombone/spectral(T)
+ else
+ to_chat(H, "The spooky gods forgot to ship your instrument. Better luck next unlife.")
+ to_chat(H, "You are the spooky skeleton!")
+ to_chat(H, "A new life and identity has begun. Help your fellow skeletons into bringing out the spooky-pocalypse. You haven't forgotten your past life, and are still beholden to past loyalties.")
+ change_name(H) //time for a new name!
+
+/datum/component/spooky/proc/change_name(mob/living/carbon/human/H)
+ var/t = stripped_input(H, "Enter your new skeleton name", H.real_name, null, MAX_NAME_LEN)
+ if(!t)
+ t = "spooky skeleton"
+ H.real_name = t
+ H.name = t
diff --git a/code/datums/looping_sounds/looping_sound.dm b/code/datums/looping_sounds/looping_sound.dm
index f44a87bdd7a..006e92c305c 100644
--- a/code/datums/looping_sounds/looping_sound.dm
+++ b/code/datums/looping_sounds/looping_sound.dm
@@ -71,7 +71,7 @@
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
- S.channel = open_sound_channel()
+ S.channel = SSsounds.random_available_channel()
S.volume = volume
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm
index 799a12f4db9..3541be89983 100644
--- a/code/datums/ruins/space.dm
+++ b/code/datums/ruins/space.dm
@@ -248,3 +248,29 @@
allow_duplicates = FALSE // I dont even want to think about what happens if you have 2 shuttles with the same ID. Likely scary stuff.
always_place = TRUE // Its designed to make exploring other space ruins more accessible
cost = 0 // Force spawned so shouldnt have a cost
+
+/datum/map_template/ruin/space/syndiecakesfactory
+ id = "Syndiecakes Factory"
+ suffix = "syndiecakesfactory.dmm"
+ name = "Syndicakes Factory"
+ description = "Syndicate used to get funds selling corgi cakes produced here. Was it hit by meteors or by a Nanotrasen comando?"
+ allow_duplicates = FALSE
+ cost = 2 //telecomms + multiple mobs
+
+/datum/map_template/ruin/space/debris1
+ id = "debris1"
+ suffix = "debris1.dmm"
+ name = "Debris field 1"
+ description = "A bunch of metal chunks, wires and space waste"
+
+/datum/map_template/ruin/space/debris2
+ id = "debris2"
+ suffix = "debris2.dmm"
+ name = "Debris field 2"
+ description = "A bunch of metal chunks, wires and space waste that used to be some kind of secure storage facility"
+
+/datum/map_template/ruin/space/debris3
+ id = "debris3"
+ suffix = "debris3.dmm"
+ name = "Debris field 3"
+ description = "A bunch of metal chunks, wires and space waste. It used to be an arcade."
diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm
index 4d6310041e5..dacc08dee31 100644
--- a/code/datums/shuttles.dm
+++ b/code/datums/shuttles.dm
@@ -133,3 +133,8 @@
suffix = "admin"
name = "NTV Argos"
description = "Default Admin ship. An older ship used for special operations."
+
+/datum/map_template/shuttle/admin/armory
+ suffix = "armory"
+ name = "NRV Sparta"
+ description = "Armory Shuttle, with plenty of guns to hand out and some general supplies."
diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm
index 8a172bb85b1..3562cfd9d80 100644
--- a/code/datums/uplink_item.dm
+++ b/code/datums/uplink_item.dm
@@ -736,6 +736,14 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 12 // normally 18
gamemodes = list(/datum/game_mode/nuclear)
+/datum/uplink_item/ammo/bulldog_XLmagsbag
+ name = "Bulldog - 12g XL Magazine Duffel Bag"
+ desc = "A duffel bag containing three 16 round drum magazines(Slug, Buckshot, Dragon's Breath)."
+ reference = "12XLDB"
+ item = /obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags
+ cost = 12 // normally 18
+ gamemodes = list(/datum/game_mode/nuclear)
+
/datum/uplink_item/ammo/smg
name = "C-20r - .45 Magazine"
desc = "An additional 20-round .45 magazine for use in the C-20r submachine gun. These bullets pack a lot of punch that can knock most targets down, but do limited overall damage."
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index dcde08d7ec1..6f7291a3a69 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -1,67 +1,29 @@
// Wires for airlocks
/datum/wires/airlock/secure
- random = 1
+ randomize = TRUE
/datum/wires/airlock
holder_type = /obj/machinery/door/airlock
- wire_count = 12
- window_x = 410
- window_y = 570
+ wire_count = 12 // 10 actual, 2 duds.
+ proper_name = "Airlock"
+ window_x = 400
+ window_y = 101
-#define AIRLOCK_WIRE_IDSCAN 1
-#define AIRLOCK_WIRE_MAIN_POWER1 2
-#define AIRLOCK_WIRE_DOOR_BOLTS 4
-#define AIRLOCK_WIRE_BACKUP_POWER1 8
-#define AIRLOCK_WIRE_OPEN_DOOR 16
-#define AIRLOCK_WIRE_AI_CONTROL 32
-#define AIRLOCK_WIRE_ELECTRIFY 64
-#define AIRLOCK_WIRE_SAFETY 128
-#define AIRLOCK_WIRE_SPEED 256
-#define AIRLOCK_WIRE_LIGHT 512
+/datum/wires/airlock/New(atom/_holder)
+ wires = list(
+ WIRE_IDSCAN, WIRE_MAIN_POWER1, WIRE_DOOR_BOLTS, WIRE_BACKUP_POWER1, WIRE_OPEN_DOOR,
+ WIRE_AI_CONTROL, WIRE_ELECTRIFY, WIRE_SAFETY, WIRE_SPEED, WIRE_BOLT_LIGHT
+ )
+ return ..()
-/datum/wires/airlock/GetWireName(index)
- switch(index)
- if(AIRLOCK_WIRE_IDSCAN)
- return "ID Scan"
-
- if(AIRLOCK_WIRE_MAIN_POWER1)
- return "Primary Power"
-
- if(AIRLOCK_WIRE_DOOR_BOLTS)
- return "Door Bolts"
-
- if(AIRLOCK_WIRE_BACKUP_POWER1)
- return "Primary Backup Power"
-
- if(AIRLOCK_WIRE_OPEN_DOOR)
- return "Door State"
-
- if(AIRLOCK_WIRE_AI_CONTROL)
- return "AI Control"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Electrification"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Door Safeties"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Door Timing"
-
- if(AIRLOCK_WIRE_ELECTRIFY)
- return "Bolt Lights"
-
-/datum/wires/airlock/CanUse(mob/living/L)
+/datum/wires/airlock/interactable(mob/user)
var/obj/machinery/door/airlock/A = holder
- if(iscarbon(L))
- if(A.Adjacent(L))
- if(A.isElectrified())
- if(A.shock(L, 100))
- return 0
+ if(iscarbon(user) && A.Adjacent(user) && A.isElectrified() && A.shock(user, 100))
+ return FALSE
if(A.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/airlock/get_status()
. = ..()
@@ -70,21 +32,20 @@
. += "The door bolts [A.locked ? "have fallen!" : "look up."]"
. += "The door bolt lights are [(A.lights && haspower) ? "on." : "off!"]"
- . += "The test light is [haspower ? "on." : "off!"]"
+ . += "The test light is [haspower ? "on." : "off!"]"
. += "The 'AI control allowed' light is [(A.aiControlDisabled == 0 && !A.emagged && haspower) ? "on" : "off"]."
. += "The 'Check Wiring' light is [(A.safe == 0 && haspower) ? "on" : "off"]."
. += "The 'Check Timing Mechanism' light is [(A.normalspeed == 0 && haspower) ? "on" : "off"]."
. += "The emergency lights are [(A.emergency && haspower) ? "on" : "off"]."
-/datum/wires/airlock/UpdateCut(index, mended)
-
+/datum/wires/airlock/on_cut(wire, mend)
var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(AIRLOCK_WIRE_IDSCAN)
- A.aiDisabledIdScanner = !mended
- if(AIRLOCK_WIRE_MAIN_POWER1)
+ switch(wire)
+ if(WIRE_IDSCAN)
+ A.aiDisabledIdScanner = !mend
+ if(WIRE_MAIN_POWER1)
- if(!mended)
+ if(!mend)
//Cutting either one disables the main door power, but unless backup power is also cut, the backup power re-powers the door in 10 seconds. While unpowered, the door may be crowbarred open, but bolts-raising will not work. Cutting these wires may electocute the user.
A.loseMainPower()
A.shock(usr, 50)
@@ -92,9 +53,9 @@
A.regainMainPower()
A.shock(usr, 50)
- if(AIRLOCK_WIRE_BACKUP_POWER1)
+ if(WIRE_BACKUP_POWER1)
- if(!mended)
+ if(!mend)
//Cutting either one disables the backup door power (allowing it to be crowbarred open, but disabling bolts-raising), but may electocute the user.
A.loseBackupPower()
A.shock(usr, 50)
@@ -102,16 +63,16 @@
A.regainBackupPower()
A.shock(usr, 50)
- if(AIRLOCK_WIRE_DOOR_BOLTS)
+ if(WIRE_DOOR_BOLTS)
- if(!mended)
+ if(!mend)
//Cutting this wire also drops the door bolts, and mending it does not raise them. (This is what happens now, except there are a lot more wires going to door bolts at present)
A.lock(1)
A.update_icon()
- if(AIRLOCK_WIRE_AI_CONTROL)
+ if(WIRE_AI_CONTROL)
- if(!mended)
+ if(!mend)
//one wire for AI control. Cutting this prevents the AI from controlling the door unless it has hacked the door through the power connection (which takes about a minute). If both main and backup power are cut, as well as this wire, then the AI cannot operate or hack the door at all.
//aiControlDisabled: If 1, AI control is disabled until the AI hacks back in and disables the lock. If 2, the AI has bypassed the lock. If -1, the control is enabled but the AI had bypassed it earlier, so if it is disabled again the AI would have no trouble getting back in.
if(A.aiControlDisabled == 0)
@@ -124,44 +85,44 @@
else if(A.aiControlDisabled == 2)
A.aiControlDisabled = -1
- if(AIRLOCK_WIRE_ELECTRIFY)
- if(!mended)
+ if(WIRE_ELECTRIFY)
+ if(!mend)
//Cutting this wire electrifies the door, so that the next person to touch the door without insulated gloves gets electrocuted.
A.electrify(-1)
else
A.electrify(0)
return // Don't update the dialog.
- if(AIRLOCK_WIRE_SAFETY)
- A.safe = mended
+ if(WIRE_SAFETY)
+ A.safe = mend
- if(AIRLOCK_WIRE_SPEED)
- A.autoclose = mended
- if(mended)
+ if(WIRE_SPEED)
+ A.autoclose = mend
+ if(mend)
if(!A.density)
- spawn(0)
- A.close()
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/close)
- if(AIRLOCK_WIRE_LIGHT)
- A.lights = mended
+ if(WIRE_BOLT_LIGHT)
+ A.lights = mend
A.update_icon()
..()
-/datum/wires/airlock/UpdatePulsed(index)
-
+/datum/wires/airlock/on_pulse(wire)
var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(AIRLOCK_WIRE_IDSCAN)
+ switch(wire)
+ if(WIRE_IDSCAN)
//Sending a pulse through flashes the red light on the door (if the door has power).
if(A.arePowerSystemsOn() && A.density)
A.do_animate("deny")
if(A.emergency)
A.emergency = 0
A.update_icon()
- if(AIRLOCK_WIRE_MAIN_POWER1)
+
+ if(WIRE_MAIN_POWER1)
//Sending a pulse through either one causes a breaker to trip, disabling the door for 10 seconds if backup power is connected, or 1 minute if not (or until backup power comes back on, whichever is shorter).
A.loseMainPower()
- if(AIRLOCK_WIRE_DOOR_BOLTS)
+
+ if(WIRE_DOOR_BOLTS)
//one wire for door bolts. Sending a pulse through this drops door bolts if they're not down (whether power's on or not),
//raises them if they are down (only if power's on)
if(!A.locked)
@@ -170,45 +131,40 @@
else if(A.unlock())
A.audible_message("You hear a click from the bottom of the door.", hearing_distance = 1)
- if(AIRLOCK_WIRE_BACKUP_POWER1)
+ if(WIRE_BACKUP_POWER1)
//two wires for backup power. Sending a pulse through either one causes a breaker to trip, but this does not disable it unless main power is down too (in which case it is disabled for 1 minute or however long it takes main power to come back, whichever is shorter).
A.loseBackupPower()
- if(AIRLOCK_WIRE_AI_CONTROL)
+
+ if(WIRE_AI_CONTROL)
if(A.aiControlDisabled == 0)
A.aiControlDisabled = 1
else if(A.aiControlDisabled == -1)
A.aiControlDisabled = 2
+ addtimer(CALLBACK(A, /obj/machinery/door/airlock/.proc/ai_control_callback), 1 SECONDS)
- spawn(10)
- if(A)
- if(A.aiControlDisabled == 1)
- A.aiControlDisabled = 0
- else if(A.aiControlDisabled == 2)
- A.aiControlDisabled = -1
-
- if(AIRLOCK_WIRE_ELECTRIFY)
+ if(WIRE_ELECTRIFY)
//one wire for electrifying the door. Sending a pulse through this electrifies the door for 30 seconds.
A.electrify(30)
- if(AIRLOCK_WIRE_OPEN_DOOR)
+
+ if(WIRE_OPEN_DOOR)
//tries to open the door without ID
//will succeed only if the ID wire is cut or the door requires no access and it's not emagged
if(A.emagged) return
if(!A.requiresID() || A.check_access(null))
- spawn(0)
- if(A.density)
- A.open()
- else
- A.close()
- if(AIRLOCK_WIRE_SAFETY)
+ if(A.density)
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/open)
+ else
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/close)
+
+ if(WIRE_SAFETY)
A.safe = !A.safe
if(!A.density)
- spawn(0)
- A.close()
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock/.proc/close)
- if(AIRLOCK_WIRE_SPEED)
+ if(WIRE_SPEED)
A.normalspeed = !A.normalspeed
- if(AIRLOCK_WIRE_LIGHT)
+ if(WIRE_BOLT_LIGHT)
A.lights = !A.lights
A.update_icon()
diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm
index f6b49b0dd99..cd13c9fe1d8 100644
--- a/code/datums/wires/alarm.dm
+++ b/code/datums/wires/alarm.dm
@@ -2,35 +2,22 @@
/datum/wires/alarm
holder_type = /obj/machinery/alarm
wire_count = 5
+ window_x = 385
+ window_y = 90
+ proper_name = "Air alarm"
-#define AALARM_WIRE_IDSCAN 1
-#define AALARM_WIRE_POWER 2
-#define AALARM_WIRE_SYPHON 4
-#define AALARM_WIRE_AI_CONTROL 8
-#define AALARM_WIRE_AALARM 16
+/datum/wires/alarm/New(atom/_holder)
+ wires = list(
+ WIRE_IDSCAN , WIRE_MAIN_POWER1 , WIRE_SYPHON,
+ WIRE_AI_CONTROL, WIRE_AALARM
+ )
+ return ..()
-/datum/wires/alarm/GetWireName(index)
- switch(index)
- if(AALARM_WIRE_IDSCAN)
- return "ID Scan"
-
- if(AALARM_WIRE_POWER)
- return "Power"
-
- if(AALARM_WIRE_SYPHON)
- return "Syphon"
-
- if(AALARM_WIRE_AI_CONTROL)
- return "AI Control"
-
- if(AALARM_WIRE_AALARM)
- return "Atmospherics Alarm"
-
-/datum/wires/alarm/CanUse(mob/living/L)
+/datum/wires/alarm/interactable(mob/user)
var/obj/machinery/alarm/A = holder
if(A.wiresexposed)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/alarm/get_status()
. = ..()
@@ -39,75 +26,60 @@
. += "The Air Alarm is [(A.shorted || (A.stat & (NOPOWER|BROKEN))) ? "offline." : "working properly!"]"
. += "The 'AI control allowed' light is [A.aidisabled ? "off" : "on"]."
-/datum/wires/alarm/UpdateCut(index, mended)
+/datum/wires/alarm/on_cut(wire, mend)
var/obj/machinery/alarm/A = holder
- switch(index)
- if(AALARM_WIRE_IDSCAN)
- if(!mended)
+ switch(wire)
+ if(WIRE_IDSCAN)
+ if(!mend)
A.locked = 1
-// to_chat(world, "Idscan wire cut")
- if(AALARM_WIRE_POWER)
+ if(WIRE_MAIN_POWER1)
A.shock(usr, 50)
- A.shorted = !mended
+ A.shorted = !mend
A.update_icon()
-// to_chat(world, "Power wire cut")
- if(AALARM_WIRE_AI_CONTROL)
- A.aidisabled = !mended
-// to_chat(world, "AI Control Wire Cut")
+ if(WIRE_AI_CONTROL)
+ A.aidisabled = !mend
- if(AALARM_WIRE_SYPHON)
- if(!mended)
+ if(WIRE_SYPHON)
+ if(!mend)
A.mode = 3 // AALARM_MODE_PANIC
A.apply_mode()
-// to_chat(world, "Syphon Wire Cut")
- if(AALARM_WIRE_AALARM)
- if(A.alarm_area.atmosalert(2, A))
- A.post_alert(2)
+ if(WIRE_AALARM)
+ if(A.alarm_area.atmosalert(ATMOS_ALARM_DANGER, A))
+ A.post_alert(ATMOS_ALARM_DANGER)
A.update_icon()
..()
-/datum/wires/alarm/UpdatePulsed(index)
+/datum/wires/alarm/on_pulse(wire)
var/obj/machinery/alarm/A = holder
- switch(index)
- if(AALARM_WIRE_IDSCAN)
+ switch(wire)
+ if(WIRE_IDSCAN)
A.locked = !A.locked
-// to_chat(world, "Idscan wire pulsed")
- if(AALARM_WIRE_POWER)
-// to_chat(world, "Power wire pulsed")
- if(A.shorted == 0)
- A.shorted = 1
+ if(WIRE_MAIN_POWER1)
+ if(!A.shorted)
+ A.shorted = TRUE
A.update_icon()
+ addtimer(CALLBACK(A, /obj/machinery/alarm/.proc/unshort_callback), 120 SECONDS)
- spawn(12000)
- if(A.shorted == 1)
- A.shorted = 0
- A.update_icon()
-
-
- if(AALARM_WIRE_AI_CONTROL)
-// to_chat(world, "AI Control wire pulsed")
- if(A.aidisabled == 0)
- A.aidisabled = 1
+ if(WIRE_AI_CONTROL)
+ if(!A.aidisabled)
+ A.aidisabled = TRUE
A.updateDialog()
- spawn(100)
- if(A.aidisabled == 1)
- A.aidisabled = 0
+ addtimer(CALLBACK(A, /obj/machinery/alarm/.proc/enable_ai_control_callback), 10 SECONDS)
- if(AALARM_WIRE_SYPHON)
-// to_chat(world, "Syphon wire pulsed")
+
+ if(WIRE_SYPHON)
if(A.mode == 1) // AALARM_MODE_SCRUB
A.mode = 3 // AALARM_MODE_PANIC
else
A.mode = 1 // AALARM_MODE_SCRUB
A.apply_mode()
- if(AALARM_WIRE_AALARM)
-// to_chat(world, "Aalarm wire pulsed")
- if(A.alarm_area.atmosalert(0, A))
- A.post_alert(0)
+ if(WIRE_AALARM)
+ if(A.alarm_area.atmosalert(ATMOS_ALARM_NONE, A))
+ A.post_alert(ATMOS_ALARM_NONE)
A.update_icon()
..()
diff --git a/code/datums/wires/apc.dm b/code/datums/wires/apc.dm
index b1076c8e29b..49931d8fa31 100644
--- a/code/datums/wires/apc.dm
+++ b/code/datums/wires/apc.dm
@@ -1,25 +1,13 @@
/datum/wires/apc
holder_type = /obj/machinery/power/apc
wire_count = 4
+ proper_name = "APC"
+ window_x = 355
+ window_y = 97
-#define APC_WIRE_IDSCAN 1
-#define APC_WIRE_MAIN_POWER1 2
-#define APC_WIRE_MAIN_POWER2 4
-#define APC_WIRE_AI_CONTROL 8
-
-/datum/wires/apc/GetWireName(index)
- switch(index)
- if(APC_WIRE_IDSCAN)
- return "ID Scan"
-
- if(APC_WIRE_MAIN_POWER1)
- return "Primary Power"
-
- if(APC_WIRE_MAIN_POWER2)
- return "Secondary Power"
-
- if(APC_WIRE_AI_CONTROL)
- return "AI Control"
+/datum/wires/apc/New(atom/_holder)
+ wires = list(WIRE_IDSCAN, WIRE_MAIN_POWER1, WIRE_MAIN_POWER2, WIRE_AI_CONTROL)
+ return ..()
/datum/wires/apc/get_status()
. = ..()
@@ -29,66 +17,53 @@
. += "The 'AI control allowed' light is [A.aidisabled ? "off" : "on"]."
-/datum/wires/apc/CanUse(mob/living/L)
+/datum/wires/apc/interactable(mob/user)
var/obj/machinery/power/apc/A = holder
if(A.panel_open && !A.opened)
return TRUE
return FALSE
-/datum/wires/apc/UpdatePulsed(index)
+/datum/wires/apc/on_pulse(wire)
var/obj/machinery/power/apc/A = holder
- switch(index)
+ switch(wire)
+ if(WIRE_IDSCAN)
+ A.locked = FALSE
+ addtimer(CALLBACK(A, /obj/machinery/power/apc/.proc/relock_callback), 30 SECONDS)
- if(APC_WIRE_IDSCAN)
- A.locked = 0
- spawn(300)
- if(A)
- A.locked = 1
- A.updateDialog()
+ if(WIRE_MAIN_POWER1, WIRE_MAIN_POWER2)
+ if(!A.shorted)
+ A.shorted = TRUE
+ addtimer(CALLBACK(A, /obj/machinery/power/apc/.proc/check_main_power_callback), 120 SECONDS)
- if(APC_WIRE_MAIN_POWER1, APC_WIRE_MAIN_POWER2)
- if(A.shorted == 0)
- A.shorted = 1
- spawn(1200)
- if(A && !IsIndexCut(APC_WIRE_MAIN_POWER1) && !IsIndexCut(APC_WIRE_MAIN_POWER2))
- A.shorted = 0
- A.updateDialog()
-
- if(APC_WIRE_AI_CONTROL)
- if(A.aidisabled == 0)
- A.aidisabled = 1
-
- spawn(10)
- if(A && !IsIndexCut(APC_WIRE_AI_CONTROL))
- A.aidisabled = 0
- A.updateDialog()
+ if(WIRE_AI_CONTROL)
+ if(!A.aidisabled)
+ A.aidisabled = TRUE
+ addtimer(CALLBACK(A, /obj/machinery/power/apc/.proc/check_ai_control_callback), 1 SECONDS)
..()
-/datum/wires/apc/UpdateCut(index, mended)
+/datum/wires/apc/on_cut(wire, mend)
var/obj/machinery/power/apc/A = holder
- switch(index)
- if(APC_WIRE_MAIN_POWER1, APC_WIRE_MAIN_POWER2)
-
- if(!mended)
+ switch(wire)
+ if(WIRE_MAIN_POWER1, WIRE_MAIN_POWER2)
+ if(!mend)
A.shock(usr, 50)
- A.shorted = 1
+ A.shorted = TRUE
- else if(!IsIndexCut(APC_WIRE_MAIN_POWER1) && !IsIndexCut(APC_WIRE_MAIN_POWER2))
- A.shorted = 0
+ else if(!is_cut(WIRE_MAIN_POWER1) && !is_cut(WIRE_MAIN_POWER2))
+ A.shorted = FALSE
A.shock(usr, 50)
- if(APC_WIRE_AI_CONTROL)
-
- if(!mended)
- if(A.aidisabled == 0)
- A.aidisabled = 1
+ if(WIRE_AI_CONTROL)
+ if(!mend)
+ if(!A.aidisabled)
+ A.aidisabled = TRUE
else
- if(A.aidisabled == 1)
- A.aidisabled = 0
+ if(A.aidisabled)
+ A.aidisabled = FALSE
..()
diff --git a/code/datums/wires/autolathe.dm b/code/datums/wires/autolathe.dm
index c1413abd0cc..0d2aab5a81f 100644
--- a/code/datums/wires/autolathe.dm
+++ b/code/datums/wires/autolathe.dm
@@ -1,21 +1,13 @@
/datum/wires/autolathe
holder_type = /obj/machinery/autolathe
wire_count = 10
+ proper_name = "Autolathe"
+ window_x = 340
+ window_y = 55
-#define AUTOLATHE_HACK_WIRE 1
-#define AUTOLATHE_SHOCK_WIRE 2
-#define AUTOLATHE_DISABLE_WIRE 4
-
-/datum/wires/autolathe/GetWireName(index)
- switch(index)
- if(AUTOLATHE_HACK_WIRE)
- return "Hack"
-
- if(AUTOLATHE_SHOCK_WIRE)
- return "Shock"
-
- if(AUTOLATHE_DISABLE_WIRE)
- return "Disable"
+/datum/wires/autolathe/New(atom/_holder)
+ wires = list(WIRE_AUTOLATHE_HACK, WIRE_ELECTRIFY, WIRE_AUTOLATHE_DISABLE)
+ return ..()
/datum/wires/autolathe/get_status()
. = ..()
@@ -24,51 +16,38 @@
. += "The green light is [A.shocked ? "off" : "on"]."
. += "The blue light is [A.hacked ? "off" : "on"]."
-/datum/wires/autolathe/CanUse()
+/datum/wires/autolathe/interactable(mob/user)
var/obj/machinery/autolathe/A = holder
+ if(iscarbon(user) && A.Adjacent(user) && A.shocked && A.shock(user, 100))
+ return FALSE
if(A.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/autolathe/UpdateCut(index, mended)
+/datum/wires/autolathe/on_cut(wire, mend)
var/obj/machinery/autolathe/A = holder
- switch(index)
- if(AUTOLATHE_HACK_WIRE)
- A.adjust_hacked(!mended)
- if(AUTOLATHE_SHOCK_WIRE)
- A.shocked = !mended
- if(AUTOLATHE_DISABLE_WIRE)
- A.disabled = !mended
+ switch(wire)
+ if(WIRE_AUTOLATHE_HACK)
+ A.adjust_hacked(!mend)
+ if(WIRE_ELECTRIFY)
+ A.shocked = !mend
+ if(WIRE_AUTOLATHE_DISABLE)
+ A.disabled = !mend
..()
-/datum/wires/autolathe/UpdatePulsed(index)
- if(IsIndexCut(index))
+/datum/wires/autolathe/on_pulse(wire)
+ if(is_cut(wire))
return
var/obj/machinery/autolathe/A = holder
- switch(index)
- if(AUTOLATHE_HACK_WIRE)
+ switch(wire)
+ if(WIRE_AUTOLATHE_HACK)
A.adjust_hacked(!A.hacked)
- updateUIs()
- spawn(50)
- if(A && !IsIndexCut(index))
- A.adjust_hacked(0)
- updateUIs()
- if(AUTOLATHE_SHOCK_WIRE)
- A.shocked = !A.shocked
- updateUIs()
- spawn(50)
- if(A && !IsIndexCut(index))
- A.shocked = 0
- updateUIs()
- if(AUTOLATHE_DISABLE_WIRE)
- A.disabled = !A.disabled
- updateUIs()
- spawn(50)
- if(A && !IsIndexCut(index))
- A.disabled = 0
- updateUIs()
+ addtimer(CALLBACK(A, /obj/machinery/autolathe/.proc/check_hacked_callback), 5 SECONDS)
-/datum/wires/autolathe/proc/updateUIs()
- SSnanoui.update_uis(src)
- if(holder)
- SSnanoui.update_uis(holder)
+ if(WIRE_ELECTRIFY)
+ A.shocked = !A.shocked
+ addtimer(CALLBACK(A, /obj/machinery/autolathe/.proc/check_electrified_callback), 5 SECONDS)
+
+ if(WIRE_AUTOLATHE_DISABLE)
+ A.disabled = !A.disabled
+ addtimer(CALLBACK(A, /obj/machinery/autolathe/.proc/check_disabled_callback), 5 SECONDS)
diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm
index a6b1549fbe1..39e0cb2c3e1 100644
--- a/code/datums/wires/camera.dm
+++ b/code/datums/wires/camera.dm
@@ -1,9 +1,15 @@
// Wires for cameras.
/datum/wires/camera
- random = 0
holder_type = /obj/machinery/camera
wire_count = 2
+ proper_name = "Camera"
+ window_x = 350
+ window_y = 95
+
+/datum/wires/camera/New(atom/_holder)
+ wires = list(WIRE_FOCUS, WIRE_MAIN_POWER1)
+ return ..()
/datum/wires/camera/get_status()
. = ..()
@@ -11,51 +17,40 @@
. += "The focus light is [(C.view_range == initial(C.view_range)) ? "on" : "off"]."
. += "The power link light is [C.can_use() ? "on" : "off"]."
-/datum/wires/camera/CanUse(mob/living/L)
+/datum/wires/camera/interactable(mob/user)
var/obj/machinery/camera/C = holder
if(!C.panel_open)
return FALSE
return TRUE
-#define CAMERA_WIRE_FOCUS 1
-#define CAMERA_WIRE_POWER 2
-
-/datum/wires/camera/GetWireName(index)
- switch(index)
- if(CAMERA_WIRE_FOCUS)
- return "Focus"
-
- if(CAMERA_WIRE_POWER)
- return "Power"
-
-/datum/wires/camera/UpdateCut(index, mended)
+/datum/wires/camera/on_cut(wire, mend)
var/obj/machinery/camera/C = holder
- switch(index)
- if(CAMERA_WIRE_FOCUS)
- var/range = (mended ? initial(C.view_range) : C.short_range)
+ switch(wire)
+ if(WIRE_FOCUS)
+ var/range = (mend ? initial(C.view_range) : C.short_range)
C.setViewRange(range)
- if(CAMERA_WIRE_POWER)
- if(C.status && !mended || !C.status && mended)
+ if(WIRE_MAIN_POWER1)
+ if(C.status && !mend || !C.status && mend)
C.toggle_cam(usr, TRUE)
C.obj_integrity = C.max_integrity //this is a pretty simplistic way to heal the camera, but there's no reason for this to be complex.
..()
-/datum/wires/camera/UpdatePulsed(index)
+/datum/wires/camera/on_pulse(wire)
var/obj/machinery/camera/C = holder
- if(IsIndexCut(index))
+ if(is_cut(wire))
return
- switch(index)
- if(CAMERA_WIRE_FOCUS)
+ switch(wire)
+ if(WIRE_FOCUS)
var/new_range = (C.view_range == initial(C.view_range) ? C.short_range : initial(C.view_range))
C.setViewRange(new_range)
- if(CAMERA_WIRE_POWER)
+ if(WIRE_MAIN_POWER1)
C.toggle_cam(null) // Deactivate the camera
..()
/datum/wires/camera/proc/CanDeconstruct()
- if(IsIndexCut(CAMERA_WIRE_POWER) && IsIndexCut(CAMERA_WIRE_FOCUS))
+ if(is_cut(WIRE_MAIN_POWER1) && is_cut(WIRE_FOCUS))
return TRUE
else
return FALSE
diff --git a/code/datums/wires/explosive.dm b/code/datums/wires/explosive.dm
index 47fd627d597..988aa0f37a8 100644
--- a/code/datums/wires/explosive.dm
+++ b/code/datums/wires/explosive.dm
@@ -1,36 +1,36 @@
/datum/wires/explosive
wire_count = 1
+ proper_name = "Explosive"
+ window_x = 320
+ window_y = 50
-#define WIRE_EXPLODE 1
-
-/datum/wires/explosive/GetWireName(index)
- switch(index)
- if(WIRE_EXPLODE)
- return "Explode"
+/datum/wires/explosive/New(atom/_holder)
+ wires = list(WIRE_EXPLODE)
+ return ..()
/datum/wires/explosive/proc/explode()
return
-/datum/wires/explosive/UpdatePulsed(index)
- switch(index)
+/datum/wires/explosive/on_pulse(wire)
+ switch(wire)
if(WIRE_EXPLODE)
explode()
..()
-/datum/wires/explosive/UpdateCut(index, mended)
- switch(index)
+/datum/wires/explosive/on_cut(wire, mend)
+ switch(wire)
if(WIRE_EXPLODE)
- if(!mended)
+ if(!mend)
explode()
..()
/datum/wires/explosive/gibtonite
holder_type = /obj/item/twohanded/required/gibtonite
-/datum/wires/explosive/gibtonite/CanUse(mob/L)
- return 1
+/datum/wires/explosive/gibtonite/interactable(mob/user)
+ return TRUE
-/datum/wires/explosive/gibtonite/UpdateCut(index, mended)
+/datum/wires/explosive/gibtonite/on_cut(wire, mend)
return
/datum/wires/explosive/gibtonite/explode()
diff --git a/code/datums/wires/mulebot.dm b/code/datums/wires/mulebot.dm
index 988b520854f..cb13b0d6718 100644
--- a/code/datums/wires/mulebot.dm
+++ b/code/datums/wires/mulebot.dm
@@ -1,90 +1,35 @@
/datum/wires/mulebot
- random = 1
+ randomize = TRUE
holder_type = /mob/living/simple_animal/bot/mulebot
wire_count = 10
- window_x = 410
+ proper_name = "Mulebot"
+ window_x = 370
+ window_y = -12
-#define MULEBOT_WIRE_POWER1 1 // power connections
-#define MULEBOT_WIRE_POWER2 2
-#define MULEBOT_WIRE_AVOIDANCE 4 // mob avoidance
-#define MULEBOT_WIRE_LOADCHECK 8 // load checking (non-crate)
-#define MULEBOT_WIRE_MOTOR1 16 // motor wires
-#define MULEBOT_WIRE_MOTOR2 32 //
-#define MULEBOT_WIRE_REMOTE_RX 64 // remote recv functions
-#define MULEBOT_WIRE_REMOTE_TX 128 // remote trans status
-#define MULEBOT_WIRE_BEACON_RX 256 // beacon ping recv
+/datum/wires/mulebot/New(atom/_holder)
+ wires = list(
+ WIRE_MAIN_POWER1, WIRE_MAIN_POWER2, WIRE_MOB_AVOIDANCE,
+ WIRE_LOADCHECK, WIRE_MOTOR1, WIRE_MOTOR2,
+ WIRE_REMOTE_RX, WIRE_REMOTE_TX, WIRE_BEACON_RX
+ )
+ return ..()
-/datum/wires/mulebot/GetWireName(index)
- switch(index)
- if(MULEBOT_WIRE_POWER1)
- return "Primary Power"
-
- if(MULEBOT_WIRE_POWER2)
- return "Secondary Power"
-
- if(MULEBOT_WIRE_AVOIDANCE)
- return "Mob Avoidance"
-
- if(MULEBOT_WIRE_LOADCHECK)
- return "Load Checking"
-
- if(MULEBOT_WIRE_MOTOR1)
- return "Primary Motor"
-
- if(MULEBOT_WIRE_MOTOR2)
- return "Secondary Motor"
-
- if(MULEBOT_WIRE_REMOTE_RX)
- return "Remote Signal Receiver"
-
- if(MULEBOT_WIRE_REMOTE_TX)
- return "Remote Signal Sender"
-
- if(MULEBOT_WIRE_BEACON_RX)
- return "Navigation Beacon Receiver"
-
-/datum/wires/mulebot/CanUse(mob/living/L)
+/datum/wires/mulebot/interactable(mob/user)
var/mob/living/simple_animal/bot/mulebot/M = holder
if(M.open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/mulebot/UpdatePulsed(index)
- switch(index)
- if(MULEBOT_WIRE_POWER1, MULEBOT_WIRE_POWER2)
+/datum/wires/mulebot/on_pulse(wire)
+ switch(wire)
+ if(WIRE_MAIN_POWER1, WIRE_MAIN_POWER2)
holder.visible_message("[bicon(holder)] The charge light flickers.")
- if(MULEBOT_WIRE_AVOIDANCE)
+ if(WIRE_MOB_AVOIDANCE)
holder.visible_message("[bicon(holder)] The external warning lights flash briefly.")
- if(MULEBOT_WIRE_LOADCHECK)
+ if(WIRE_LOADCHECK)
holder.visible_message("[bicon(holder)] The load platform clunks.")
- if(MULEBOT_WIRE_MOTOR1, MULEBOT_WIRE_MOTOR2)
+ if(WIRE_MOTOR1, WIRE_MOTOR2)
holder.visible_message("[bicon(holder)] The drive motor whines briefly.")
else
holder.visible_message("[bicon(holder)] You hear a radio crackle.")
..()
-
-// HELPER PROCS
-
-/datum/wires/mulebot/proc/Motor1()
- return !(wires_status & MULEBOT_WIRE_MOTOR1)
-
-/datum/wires/mulebot/proc/Motor2()
- return !(wires_status & MULEBOT_WIRE_MOTOR2)
-
-/datum/wires/mulebot/proc/HasPower()
- return !(wires_status & MULEBOT_WIRE_POWER1) && !(wires_status & MULEBOT_WIRE_POWER2)
-
-/datum/wires/mulebot/proc/LoadCheck()
- return !(wires_status & MULEBOT_WIRE_LOADCHECK)
-
-/datum/wires/mulebot/proc/MobAvoid()
- return !(wires_status & MULEBOT_WIRE_AVOIDANCE)
-
-/datum/wires/mulebot/proc/RemoteTX()
- return !(wires_status & MULEBOT_WIRE_REMOTE_TX)
-
-/datum/wires/mulebot/proc/RemoteRX()
- return !(wires_status & MULEBOT_WIRE_REMOTE_RX)
-
-/datum/wires/mulebot/proc/BeaconRX()
- return !(wires_status & MULEBOT_WIRE_BEACON_RX)
diff --git a/code/datums/wires/nuclearbomb.dm b/code/datums/wires/nuclearbomb.dm
index 3b8ff5db098..51918c02ff4 100644
--- a/code/datums/wires/nuclearbomb.dm
+++ b/code/datums/wires/nuclearbomb.dm
@@ -1,28 +1,20 @@
/datum/wires/nuclearbomb
holder_type = /obj/machinery/nuclearbomb
- random = 1
- wire_count = 7
+ randomize = TRUE
+ wire_count = 7 // 3 actual, 4 duds.
+ proper_name = "Nuclear bomb"
+ window_x = 345
+ window_y = 75
-#define NUCLEARBOMB_WIRE_LIGHT 1
-#define NUCLEARBOMB_WIRE_TIMING 2
-#define NUCLEARBOMB_WIRE_SAFETY 4
+/datum/wires/nuclearbomb/New(atom/_holder)
+ wires = list(WIRE_BOMB_LIGHT, WIRE_BOMB_TIMING, WIRE_BOMB_SAFETY)
+ return ..()
-/datum/wires/nuclearbomb/GetWireName(index)
- switch(index)
- if(NUCLEARBOMB_WIRE_LIGHT)
- return "Bomb Light"
-
- if(NUCLEARBOMB_WIRE_TIMING)
- return "Bomb Timing"
-
- if(NUCLEARBOMB_WIRE_SAFETY)
- return "Bomb Safety"
-
-/datum/wires/nuclearbomb/CanUse(mob/living/L)
+/datum/wires/nuclearbomb/interactable(mob/user)
var/obj/machinery/nuclearbomb/N = holder
if(N.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/nuclearbomb/get_status()
. = ..()
@@ -31,52 +23,36 @@
. += "The device is is [N.safety ? "quiet" : "whirring"]."
. += "The lights are [N.lighthack ? "static" : "functional"]."
-/datum/wires/nuclearbomb/UpdatePulsed(index)
+/datum/wires/nuclearbomb/on_pulse(wire)
var/obj/machinery/nuclearbomb/N = holder
- switch(index)
- if(NUCLEARBOMB_WIRE_LIGHT)
+ switch(wire)
+ if(WIRE_BOMB_LIGHT)
N.lighthack = !N.lighthack
- updateUIs()
- spawn(100)
- N.lighthack = !N.lighthack
- updateUIs()
- if(NUCLEARBOMB_WIRE_TIMING)
+ addtimer(CALLBACK(N, /obj/machinery/nuclearbomb/.proc/reset_lighthack_callback), 10 SECONDS)
+
+ if(WIRE_BOMB_TIMING)
if(N.timing)
message_admins("[key_name_admin(usr)] pulsed a nuclear bomb's detonation wire, causing it to explode (JMP)")
N.explode()
- if(NUCLEARBOMB_WIRE_SAFETY)
- N.safety = !N.safety
- updateUIs()
- spawn(100)
- N.safety = !N.safety
- if(N.safety == 1)
- if(!N.is_syndicate)
- set_security_level(N.previous_level)
- N.visible_message("The [N] quiets down.")
- if(!N.lighthack)
- if(N.icon_state == "nuclearbomb2")
- N.icon_state = "nuclearbomb1"
- else
- N.visible_message("The [N] emits a quiet whirling noise!")
- updateUIs()
-/datum/wires/nuclearbomb/UpdateCut(index, mended)
+ if(WIRE_BOMB_SAFETY)
+ N.safety = !N.safety
+ addtimer(CALLBACK(N, /obj/machinery/nuclearbomb/.proc/reset_safety_callback), 10 SECONDS)
+
+/datum/wires/nuclearbomb/on_cut(wire, mend)
var/obj/machinery/nuclearbomb/N = holder
- switch(index)
- if(NUCLEARBOMB_WIRE_SAFETY)
+ switch(wire)
+ if(WIRE_BOMB_SAFETY)
if(N.timing)
message_admins("[key_name_admin(usr)] cut a nuclear bomb's timing wire, causing it to explode (JMP)")
N.explode()
- if(NUCLEARBOMB_WIRE_TIMING)
+
+ if(WIRE_BOMB_TIMING)
if(!N.lighthack)
if(N.icon_state == "nuclearbomb2")
N.icon_state = "nuclearbomb1"
N.timing = 0
- GLOB.bomb_set = 0
- if(NUCLEARBOMB_WIRE_LIGHT)
- N.lighthack = !N.lighthack
+ GLOB.bomb_set = FALSE
-/datum/wires/nuclearbomb/proc/updateUIs()
- SSnanoui.update_uis(src)
- if(holder)
- SSnanoui.update_uis(holder)
+ if(WIRE_BOMB_LIGHT)
+ N.lighthack = !N.lighthack
diff --git a/code/datums/wires/particle_accelerator.dm b/code/datums/wires/particle_accelerator.dm
index d6e68a79cfb..e285f4cf898 100644
--- a/code/datums/wires/particle_accelerator.dm
+++ b/code/datums/wires/particle_accelerator.dm
@@ -1,67 +1,52 @@
/datum/wires/particle_acc/control_box
wire_count = 5
holder_type = /obj/machinery/particle_accelerator/control_box
+ proper_name = "Particle accelerator control"
+ window_x = 361
+ window_y = 22
-#define PARTICLE_TOGGLE_WIRE 1 // Toggles whether the PA is on or not.
-#define PARTICLE_STRENGTH_WIRE 2 // Determines the strength of the PA.
-#define PARTICLE_INTERFACE_WIRE 4 // Determines the interface showing up.
-#define PARTICLE_LIMIT_POWER_WIRE 8 // Determines how strong the PA can be.
+/datum/wires/particle_acc/control_box/New(atom/_holder)
+ wires = list(WIRE_PARTICLE_POWER, WIRE_PARTICLE_STRENGTH, WIRE_PARTICLE_INTERFACE, WIRE_PARTICLE_POWER_LIMIT)
+ return ..()
-/datum/wires/particle_acc/control_box/GetWireName(index)
- switch(index)
- if(PARTICLE_TOGGLE_WIRE)
- return "Power Toggle"
-
- if(PARTICLE_STRENGTH_WIRE)
- return "Strength"
-
- if(PARTICLE_INTERFACE_WIRE)
- return "Interface"
-
- if(PARTICLE_LIMIT_POWER_WIRE)
- return "Maximum Power"
-
-/datum/wires/particle_acc/control_box/CanUse(mob/living/L)
+/datum/wires/particle_acc/control_box/interactable(mob/user)
var/obj/machinery/particle_accelerator/control_box/C = holder
if(C.construction_state == 2)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/particle_acc/control_box/UpdatePulsed(index)
+/datum/wires/particle_acc/control_box/on_pulse(wire)
var/obj/machinery/particle_accelerator/control_box/C = holder
- switch(index)
-
- if(PARTICLE_TOGGLE_WIRE)
+ switch(wire)
+ if(WIRE_PARTICLE_POWER)
C.toggle_power()
- if(PARTICLE_STRENGTH_WIRE)
+ if(WIRE_PARTICLE_STRENGTH)
C.add_strength()
- if(PARTICLE_INTERFACE_WIRE)
+ if(WIRE_PARTICLE_INTERFACE)
C.interface_control = !C.interface_control
- if(PARTICLE_LIMIT_POWER_WIRE)
+ if(WIRE_PARTICLE_POWER_LIMIT)
C.visible_message("[bicon(C)][C] makes a large whirring noise.")
..()
-/datum/wires/particle_acc/control_box/UpdateCut(index, mended)
+/datum/wires/particle_acc/control_box/on_cut(wire, mend)
var/obj/machinery/particle_accelerator/control_box/C = holder
- switch(index)
-
- if(PARTICLE_TOGGLE_WIRE)
- if(C.active == !mended)
+ switch(wire)
+ if(WIRE_PARTICLE_POWER)
+ if(C.active == !mend)
C.toggle_power()
- if(PARTICLE_STRENGTH_WIRE)
-
- for(var/i = 1; i < 3; i++)
+ if(WIRE_PARTICLE_STRENGTH)
+ for(var/i in 1 to 2)
C.remove_strength()
- if(PARTICLE_INTERFACE_WIRE)
- C.interface_control = mended
+ if(WIRE_PARTICLE_INTERFACE)
+ C.interface_control = mend
- if(PARTICLE_LIMIT_POWER_WIRE)
- C.strength_upper_limit = (mended ? 2 : 3)
+ if(WIRE_PARTICLE_POWER_LIMIT)
+ C.strength_upper_limit = (mend ? 2 : 3)
if(C.strength_upper_limit < C.strength)
C.remove_strength()
..()
diff --git a/code/datums/wires/radio.dm b/code/datums/wires/radio.dm
index 90a72935c53..1efee71692d 100644
--- a/code/datums/wires/radio.dm
+++ b/code/datums/wires/radio.dm
@@ -1,52 +1,44 @@
/datum/wires/radio
holder_type = /obj/item/radio
wire_count = 3
+ proper_name = "Radio"
+ window_x = 330
+ window_y = 37
-#define RADIO_WIRE_SIGNAL 1
-#define RADIO_WIRE_RECEIVE 2
-#define RADIO_WIRE_TRANSMIT 4
+/datum/wires/radio/New(atom/_holder)
+ wires = list(WIRE_RADIO_SIGNAL, WIRE_RADIO_RECEIVER, WIRE_RADIO_TRANSMIT)
+ return ..()
-/datum/wires/radio/GetWireName(index)
- switch(index)
- if(RADIO_WIRE_SIGNAL)
- return "Signal"
-
- if(RADIO_WIRE_RECEIVE)
- return "Receiver"
-
- if(RADIO_WIRE_TRANSMIT)
- return "Transmitter"
-
-/datum/wires/radio/CanUse(mob/living/L)
+/datum/wires/radio/interactable(mob/user)
var/obj/item/radio/R = holder
if(R.b_stat)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/radio/UpdatePulsed(index)
+/datum/wires/radio/on_pulse(wire)
var/obj/item/radio/R = holder
- switch(index)
- if(RADIO_WIRE_SIGNAL)
- R.listening = !R.listening && !IsIndexCut(RADIO_WIRE_RECEIVE)
- R.broadcasting = R.listening && !IsIndexCut(RADIO_WIRE_TRANSMIT)
+ switch(wire)
+ if(WIRE_RADIO_SIGNAL)
+ R.listening = !R.listening && !is_cut(WIRE_RADIO_RECEIVER)
+ R.broadcasting = R.listening && !is_cut(WIRE_RADIO_TRANSMIT)
- if(RADIO_WIRE_RECEIVE)
- R.listening = !R.listening && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_RECEIVER)
+ R.listening = !R.listening && !is_cut(WIRE_RADIO_SIGNAL)
- if(RADIO_WIRE_TRANSMIT)
- R.broadcasting = !R.broadcasting && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_TRANSMIT)
+ R.broadcasting = !R.broadcasting && !is_cut(WIRE_RADIO_SIGNAL)
..()
-/datum/wires/radio/UpdateCut(index, mended)
+/datum/wires/radio/on_cut(wire, mend)
var/obj/item/radio/R = holder
- switch(index)
- if(RADIO_WIRE_SIGNAL)
- R.listening = mended && !IsIndexCut(RADIO_WIRE_RECEIVE)
- R.broadcasting = mended && !IsIndexCut(RADIO_WIRE_TRANSMIT)
+ switch(wire)
+ if(WIRE_RADIO_SIGNAL)
+ R.listening = mend && !is_cut(WIRE_RADIO_RECEIVER)
+ R.broadcasting = mend && !is_cut(WIRE_RADIO_TRANSMIT)
- if(RADIO_WIRE_RECEIVE)
- R.listening = mended && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_RECEIVER)
+ R.listening = mend && !is_cut(WIRE_RADIO_SIGNAL)
- if(RADIO_WIRE_TRANSMIT)
- R.broadcasting = mended && !IsIndexCut(RADIO_WIRE_SIGNAL)
+ if(WIRE_RADIO_TRANSMIT)
+ R.broadcasting = mend && !is_cut(WIRE_RADIO_SIGNAL)
..()
diff --git a/code/datums/wires/robot.dm b/code/datums/wires/robot.dm
index 04a9509cfe2..927980c57fa 100644
--- a/code/datums/wires/robot.dm
+++ b/code/datums/wires/robot.dm
@@ -1,32 +1,14 @@
/datum/wires/robot
- random = 1
+ randomize = TRUE
holder_type = /mob/living/silicon/robot
wire_count = 5
+ window_x = 340
+ window_y = 106
+ proper_name = "Cyborg"
-// /vg/ ordering
-
-#define BORG_WIRE_MAIN_POWER 1 // The power wires do nothing whyyyyyyyyyyyyy
-#define BORG_WIRE_LOCKED_DOWN 2
-#define BORG_WIRE_CAMERA 4
-#define BORG_WIRE_AI_CONTROL 8 // Not used on MoMMIs
-#define BORG_WIRE_LAWCHECK 16 // Not used on MoMMIs
-
-/datum/wires/robot/GetWireName(index)
- switch(index)
- if(BORG_WIRE_MAIN_POWER)
- return "Main Power"
-
- if(BORG_WIRE_LOCKED_DOWN)
- return "Lockdown"
-
- if(BORG_WIRE_CAMERA)
- return "Camera"
-
- if(BORG_WIRE_AI_CONTROL)
- return "AI Control"
-
- if(BORG_WIRE_LAWCHECK)
- return "Law Check"
+/datum/wires/robot/New(atom/_holder)
+ wires = list(WIRE_AI_CONTROL, WIRE_BORG_CAMERA, WIRE_BORG_LAWCHECK, WIRE_BORG_LOCKED)
+ return ..()
/datum/wires/robot/get_status()
. = ..()
@@ -36,70 +18,53 @@
. += "The Camera light is [(R.camera && R.camera.status == 1) ? "on" : "off"]."
. += "The lockdown light is [R.lockcharge ? "on" : "off"]."
-/datum/wires/robot/UpdateCut(index, mended)
-
+/datum/wires/robot/on_cut(wire, mend)
var/mob/living/silicon/robot/R = holder
- switch(index)
- if(BORG_WIRE_LAWCHECK) //Cut the law wire, and the borg will no longer receive law updates from its AI
- if(!mended)
- if(R.lawupdate == 1)
+ switch(wire)
+ if(WIRE_BORG_LAWCHECK) //Cut the law wire, and the borg will no longer receive law updates from its AI
+ if(!mend)
+ if(R.lawupdate)
to_chat(R, "LawSync protocol engaged.")
+ R.lawsync()
R.show_laws()
else
- if(R.lawupdate == 0 && !R.emagged)
- R.lawupdate = 1
+ if(!R.lawupdate && !R.emagged)
+ R.lawupdate = TRUE
- if(BORG_WIRE_AI_CONTROL) //Cut the AI wire to reset AI control
- if(!mended)
+ if(WIRE_AI_CONTROL) //Cut the AI wire to reset AI control
+ if(!mend)
if(R.connected_ai)
R.disconnect_from_ai()
- if(BORG_WIRE_CAMERA)
+ if(WIRE_BORG_CAMERA)
if(!isnull(R.camera) && !R.scrambledcodes)
- R.camera.status = mended
+ R.camera.status = mend
R.camera.toggle_cam(usr, 0) // Will kick anyone who is watching the Cyborg's camera.
- if(BORG_WIRE_LAWCHECK) //Forces a law update if the borg is set to receive them. Since an update would happen when the borg checks its laws anyway, not much use, but eh
- if(R.lawupdate)
- R.lawsync()
-
- if(BORG_WIRE_LOCKED_DOWN)
- R.SetLockdown(!mended)
+ if(WIRE_BORG_LOCKED)
+ R.SetLockdown(!mend)
..()
-/datum/wires/robot/UpdatePulsed(index)
-
+/datum/wires/robot/on_pulse(wire)
var/mob/living/silicon/robot/R = holder
- switch(index)
- if(BORG_WIRE_AI_CONTROL) //pulse the AI wire to make the borg reselect an AI
+ switch(wire)
+ if(WIRE_AI_CONTROL) //pulse the AI wire to make the borg reselect an AI
if(!R.emagged)
R.connect_to_ai(select_active_ai())
- if(BORG_WIRE_CAMERA)
+ if(WIRE_BORG_CAMERA)
if(!isnull(R.camera) && R.camera.can_use() && !R.scrambledcodes)
R.camera.toggle_cam(usr, 0) // Kick anyone watching the Cyborg's camera, doesn't display you disconnecting the camera.
R.visible_message("[R]'s camera lense focuses loudly.")
to_chat(R, "Your camera lense focuses loudly.")
- if(BORG_WIRE_LOCKED_DOWN)
+ if(WIRE_BORG_LOCKED)
R.SetLockdown(!R.lockcharge) // Toggle
..()
-/datum/wires/robot/CanUse(mob/living/L)
+/datum/wires/robot/interactable(mob/user)
var/mob/living/silicon/robot/R = holder
if(R.wiresexposed)
- return 1
- return 0
-
-/datum/wires/robot/proc/IsCameraCut()
- return wires_status & BORG_WIRE_CAMERA
-
-/datum/wires/robot/proc/LockedCut()
- return wires_status & BORG_WIRE_LOCKED_DOWN
-
-/datum/wires/robot/proc/CanLawCheck()
- return wires_status & BORG_WIRE_LAWCHECK
-
-/datum/wires/robot/proc/AIHasControl()
- return wires_status & BORG_WIRE_AI_CONTROL
+ return TRUE
+ return FALSE
diff --git a/code/datums/wires/smartfridge.dm b/code/datums/wires/smartfridge.dm
index a65377ead1a..3955b1849b0 100644
--- a/code/datums/wires/smartfridge.dm
+++ b/code/datums/wires/smartfridge.dm
@@ -1,35 +1,26 @@
/datum/wires/smartfridge
holder_type = /obj/machinery/smartfridge
wire_count = 3
+ proper_name = "Smartfridge"
+ window_x = 340
+ window_y = 103
+
+/datum/wires/smartfridge/New(atom/_holder)
+ wires = list(WIRE_ELECTRIFY, WIRE_IDSCAN, WIRE_THROW_ITEM)
+ return ..()
/datum/wires/smartfridge/secure
- random = 1
- wire_count = 4
+ randomize = TRUE
+ wire_count = 4 // 3 actual, 1 dud.
+ window_y = 97
-#define SMARTFRIDGE_WIRE_ELECTRIFY 1
-#define SMARTFRIDGE_WIRE_THROW 2
-#define SMARTFRIDGE_WIRE_IDSCAN 4
-
-/datum/wires/smartfridge/GetWireName(index)
- switch(index)
- if(SMARTFRIDGE_WIRE_ELECTRIFY)
- return "Electrification"
-
- if(SMARTFRIDGE_WIRE_THROW)
- return "Item Throw"
-
- if(SMARTFRIDGE_WIRE_IDSCAN)
- return "ID Scan"
-
-/datum/wires/smartfridge/CanUse(mob/living/L)
+/datum/wires/smartfridge/interactable(mob/user)
var/obj/machinery/smartfridge/S = holder
- if(!issilicon(L))
- if(S.seconds_electrified)
- if(S.shock(L, 100))
- return 0
+ if(iscarbon(user) && S.Adjacent(user) && S.seconds_electrified && S.shock(user, 100))
+ return FALSE
if(S.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/smartfridge/get_status()
. = ..()
@@ -38,27 +29,27 @@
. += "The red light is [S.shoot_inventory ? "off" : "blinking"]."
. += "A [S.scan_id ? "purple" : "yellow"] light is on."
-/datum/wires/smartfridge/UpdatePulsed(index)
+/datum/wires/smartfridge/on_pulse(wire)
var/obj/machinery/smartfridge/S = holder
- switch(index)
- if(SMARTFRIDGE_WIRE_THROW)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
S.shoot_inventory = !S.shoot_inventory
- if(SMARTFRIDGE_WIRE_ELECTRIFY)
+ if(WIRE_ELECTRIFY)
S.seconds_electrified = 30
- if(SMARTFRIDGE_WIRE_IDSCAN)
+ if(WIRE_IDSCAN)
S.scan_id = !S.scan_id
..()
-/datum/wires/smartfridge/UpdateCut(index, mended)
+/datum/wires/smartfridge/on_cut(wire, mend)
var/obj/machinery/smartfridge/S = holder
- switch(index)
- if(SMARTFRIDGE_WIRE_THROW)
- S.shoot_inventory = !mended
- if(SMARTFRIDGE_WIRE_ELECTRIFY)
- if(mended)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
+ S.shoot_inventory = !mend
+ if(WIRE_ELECTRIFY)
+ if(mend)
S.seconds_electrified = 0
else
S.seconds_electrified = -1
- if(SMARTFRIDGE_WIRE_IDSCAN)
- S.scan_id = 1
+ if(WIRE_IDSCAN)
+ S.scan_id = TRUE
..()
diff --git a/code/datums/wires/suitstorage.dm b/code/datums/wires/suitstorage.dm
index 175b36e6081..828e56884bf 100644
--- a/code/datums/wires/suitstorage.dm
+++ b/code/datums/wires/suitstorage.dm
@@ -1,24 +1,13 @@
/datum/wires/suitstorage
holder_type = /obj/machinery/suit_storage_unit
wire_count = 8
+ proper_name = "Suit storage unit"
+ window_x = 350
+ window_y = 85
-#define SSU_WIRE_ID 1
-#define SSU_WIRE_SHOCK 2
-#define SSU_WIRE_SAFETY 4
-#define SSU_WIRE_UV 8
-
-
-/datum/wires/suitstorage/GetWireName(index)
- switch(index)
- if(SSU_WIRE_ID)
- return "ID lock"
- if(SSU_WIRE_SHOCK)
- return "Shock wire"
- if(SSU_WIRE_SAFETY)
- return "Safety wire"
- if(SSU_WIRE_UV)
- return "UV wire"
-
+/datum/wires/suitstorage/New(atom/_holder)
+ wires = list(WIRE_IDSCAN, WIRE_ELECTRIFY, WIRE_SAFETY, WIRE_SSU_UV)
+ return ..()
/datum/wires/suitstorage/get_status()
. = ..()
@@ -28,42 +17,48 @@
. += "The green light is [A.shocked ? "on" : "off"]."
. += "The UV display shows [A.uv_super ? "15 nm" : "185 nm"]."
-datum/wires/suitstorage/CanUse()
+datum/wires/suitstorage/interactable(mob/user)
var/obj/machinery/suit_storage_unit/A = holder
+ if(iscarbon(user) && A.Adjacent(user) && A.shocked)
+ return A.shock(user, 100)
if(A.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/suitstorage/UpdateCut(index, mended)
+/datum/wires/suitstorage/on_cut(wire, mend)
var/obj/machinery/suit_storage_unit/A = holder
- switch(index)
- if(SSU_WIRE_ID)
- A.secure = mended
- if(SSU_WIRE_SAFETY)
- A.safeties = mended
- if(SSU_WIRE_SHOCK)
- A.shocked = !mended
+ switch(wire)
+ if(WIRE_IDSCAN)
+ A.secure = mend
+
+ if(WIRE_SAFETY)
+ A.safeties = mend
+
+ if(WIRE_ELECTRIFY)
+ A.shocked = !mend
A.shock(usr, 50)
- if(SSU_WIRE_UV)
- A.uv_super = !mended
+
+ if(WIRE_SSU_UV)
+ A.uv_super = !mend
..()
-datum/wires/suitstorage/UpdatePulsed(index)
+datum/wires/suitstorage/on_pulse(wire)
var/obj/machinery/suit_storage_unit/A = holder
- if(IsIndexCut(index))
+ if(is_cut(wire))
return
- switch(index)
- if(SSU_WIRE_ID)
+ switch(wire)
+ if(WIRE_IDSCAN)
A.secure = !A.secure
- if(SSU_WIRE_SAFETY)
+
+ if(WIRE_SAFETY)
A.safeties = !A.safeties
- if(SSU_WIRE_SHOCK)
+
+ if(WIRE_ELECTRIFY)
A.shocked = !A.shocked
if(A.shocked)
A.shock(usr, 100)
- spawn(50)
- if(A && !IsIndexCut(index))
- A.shocked = FALSE
- if(SSU_WIRE_UV)
+ addtimer(CALLBACK(A, /obj/machinery/suit_storage_unit/.proc/check_electrified_callback), 5 SECONDS)
+
+ if(WIRE_SSU_UV)
A.uv_super = !A.uv_super
..()
diff --git a/code/datums/wires/syndicatebomb.dm b/code/datums/wires/syndicatebomb.dm
index 430b5ce88aa..bd996f72fcc 100644
--- a/code/datums/wires/syndicatebomb.dm
+++ b/code/datums/wires/syndicatebomb.dm
@@ -1,47 +1,31 @@
/datum/wires/syndicatebomb
- random = TRUE
+ randomize = TRUE
holder_type = /obj/machinery/syndicatebomb
wire_count = 5
+ proper_name = "Syndicate bomb"
+ window_x = 320
+ window_y = 22
-#define BOMB_WIRE_BOOM 1 // Explodes if pulsed or cut while active, defuses a bomb that isn't active on cut
-#define BOMB_WIRE_UNBOLT 2 // Unbolts the bomb if cut, hint on pulsed
-#define BOMB_WIRE_DELAY 4 // Raises the timer on pulse, does nothing on cut
-#define BOMB_WIRE_PROCEED 8 // Lowers the timer, explodes if cut while the bomb is active
-#define BOMB_WIRE_ACTIVATE 16 // Will start a bombs timer if pulsed, will hint if pulsed while already active, will stop a timer a bomb on cut
+/datum/wires/syndicatebomb/New(atom/_holder)
+ wires = list(WIRE_BOMB_DELAY, WIRE_EXPLODE, WIRE_BOMB_UNBOLT,WIRE_BOMB_PROCEED, WIRE_BOMB_ACTIVATE)
+ return ..()
-/datum/wires/syndicatebomb/GetWireName(index)
- switch(index)
- if(BOMB_WIRE_BOOM)
- return "Explode"
-
- if(BOMB_WIRE_UNBOLT)
- return "Unbolt"
-
- if(BOMB_WIRE_DELAY)
- return "Delay"
-
- if(BOMB_WIRE_PROCEED)
- return "Proceed"
-
- if(BOMB_WIRE_ACTIVATE)
- return "Activate"
-
-/datum/wires/syndicatebomb/CanUse(mob/living/L)
+/datum/wires/syndicatebomb/interactable(mob/user)
var/obj/machinery/syndicatebomb/P = holder
if(P.open_panel)
return TRUE
return FALSE
-/datum/wires/syndicatebomb/UpdatePulsed(index)
+/datum/wires/syndicatebomb/on_pulse(wire)
var/obj/machinery/syndicatebomb/B = holder
- switch(index)
- if(BOMB_WIRE_BOOM)
+ switch(wire)
+ if(WIRE_EXPLODE)
if(B.active)
holder.visible_message("[bicon(B)] An alarm sounds! It's go-")
B.explode_now = TRUE
- if(BOMB_WIRE_UNBOLT)
+ if(WIRE_BOMB_UNBOLT)
holder.visible_message("[bicon(holder)] The bolts spin in place for a moment.")
- if(BOMB_WIRE_DELAY)
+ if(WIRE_BOMB_DELAY)
if(B.delayedbig)
holder.visible_message("[bicon(B)] The bomb has already been delayed.")
else
@@ -49,7 +33,7 @@
playsound(B, 'sound/machines/chime.ogg', 30, 1)
B.detonation_timer += 300
B.delayedbig = TRUE
- if(BOMB_WIRE_PROCEED)
+ if(WIRE_BOMB_PROCEED)
holder.visible_message("[bicon(B)] The bomb buzzes ominously!")
playsound(B, 'sound/machines/buzz-sigh.ogg', 30, 1)
var/seconds = B.seconds_remaining()
@@ -59,7 +43,7 @@
B.detonation_timer -= 100
else if(seconds >= 11) // Both to prevent negative timers and to have a little mercy.
B.detonation_timer = world.time + 100
- if(BOMB_WIRE_ACTIVATE)
+ if(WIRE_BOMB_ACTIVATE)
if(!B.active && !B.defused)
holder.visible_message("[bicon(B)] You hear the bomb start ticking!")
B.activate()
@@ -72,11 +56,11 @@
B.delayedlittle = TRUE
..()
-/datum/wires/syndicatebomb/UpdateCut(index, mended)
+/datum/wires/syndicatebomb/on_cut(wire, mend)
var/obj/machinery/syndicatebomb/B = holder
- switch(index)
- if(BOMB_WIRE_BOOM)
- if(mended)
+ switch(wire)
+ if(WIRE_EXPLODE)
+ if(mend)
B.defused = FALSE // Cutting and mending all the wires of an inactive bomb will thus cure any sabotage.
else
if(B.active)
@@ -84,17 +68,17 @@
B.explode_now = TRUE
else
B.defused = TRUE
- if(BOMB_WIRE_UNBOLT)
- if(!mended && B.anchored)
+ if(WIRE_BOMB_UNBOLT)
+ if(!mend && B.anchored)
holder.visible_message("[bicon(B)] The bolts lift out of the ground!")
playsound(B, 'sound/effects/stealthoff.ogg', 30, 1)
B.anchored = FALSE
- if(BOMB_WIRE_PROCEED)
- if(!mended && B.active)
+ if(WIRE_BOMB_PROCEED)
+ if(!mend && B.active)
holder.visible_message("[bicon(B)] An alarm sounds! It's go-")
B.explode_now = TRUE
- if(BOMB_WIRE_ACTIVATE)
- if(!mended && B.active)
+ if(WIRE_BOMB_ACTIVATE)
+ if(!mend && B.active)
holder.visible_message("[bicon(B)] The timer stops! The bomb has been defused!")
B.defused = TRUE
B.update_icon()
diff --git a/code/datums/wires/tesla_coil.dm b/code/datums/wires/tesla_coil.dm
index bc513c6c6c0..f4dbf39017a 100644
--- a/code/datums/wires/tesla_coil.dm
+++ b/code/datums/wires/tesla_coil.dm
@@ -1,23 +1,23 @@
/datum/wires/tesla_coil
wire_count = 1
holder_type = /obj/machinery/power/tesla_coil
+ proper_name = "Tesla coil"
+ window_x = 320
+ window_y = 50
-#define TESLACOIL_WIRE_ZAP 1
+/datum/wires/tesla_coil/New(atom/_holder)
+ wires = list(WIRE_TESLACOIL_ZAP)
+ return ..()
-/datum/wires/tesla_coil/GetWireName(index)
- switch(index)
- if(TESLACOIL_WIRE_ZAP)
- return "Zap"
-
-/datum/wires/tesla_coil/CanUse(mob/living/L)
+/datum/wires/tesla_coil/interactable(mob/user)
var/obj/machinery/power/tesla_coil/T = holder
if(T && T.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
-/datum/wires/tesla_coil/UpdatePulsed(index)
+/datum/wires/tesla_coil/on_pulse(wire)
var/obj/machinery/power/tesla_coil/T = holder
- switch(index)
- if(TESLACOIL_WIRE_ZAP)
+ switch(wire)
+ if(WIRE_TESLACOIL_ZAP)
T.zap()
..()
diff --git a/code/datums/wires/vending.dm b/code/datums/wires/vending.dm
index c9e6032afee..299e18d815a 100644
--- a/code/datums/wires/vending.dm
+++ b/code/datums/wires/vending.dm
@@ -1,35 +1,21 @@
/datum/wires/vending
holder_type = /obj/machinery/vending
wire_count = 4
+ window_y = 112
+ window_x = 350
+ proper_name = "Vending machine"
-#define VENDING_WIRE_THROW 1
-#define VENDING_WIRE_CONTRABAND 2
-#define VENDING_WIRE_ELECTRIFY 4
-#define VENDING_WIRE_IDSCAN 8
+/datum/wires/vending/New(atom/_holder)
+ wires = list(WIRE_THROW_ITEM, WIRE_IDSCAN, WIRE_ELECTRIFY, WIRE_CONTRABAND)
+ return ..()
-/datum/wires/vending/GetWireName(index)
- switch(index)
- if(VENDING_WIRE_THROW)
- return "Item Throw"
-
- if(VENDING_WIRE_CONTRABAND)
- return "Contraband"
-
- if(VENDING_WIRE_ELECTRIFY)
- return "Electrification"
-
- if(VENDING_WIRE_IDSCAN)
- return "ID Scan"
-
-/datum/wires/vending/CanUse(mob/living/L)
+/datum/wires/vending/interactable(mob/user)
var/obj/machinery/vending/V = holder
- if(!istype(L, /mob/living/silicon))
- if(V.seconds_electrified)
- if(V.shock(L, 100))
- return 0
+ if(!istype(user, /mob/living/silicon) && V.seconds_electrified && V.shock(user, 100))
+ return FALSE
if(V.panel_open)
- return 1
- return 0
+ return TRUE
+ return FALSE
/datum/wires/vending/get_status()
. = ..()
@@ -39,31 +25,31 @@
. += "The green light is [V.extended_inventory ? "on" : "off"]."
. += "A [V.scan_id ? "purple" : "yellow"] light is on."
-/datum/wires/vending/UpdatePulsed(index)
+/datum/wires/vending/on_pulse(wire)
var/obj/machinery/vending/V = holder
- switch(index)
- if(VENDING_WIRE_THROW)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
V.shoot_inventory = !V.shoot_inventory
- if(VENDING_WIRE_CONTRABAND)
+ if(WIRE_CONTRABAND)
V.extended_inventory = !V.extended_inventory
- if(VENDING_WIRE_ELECTRIFY)
+ if(WIRE_ELECTRIFY)
V.seconds_electrified = 30
- if(VENDING_WIRE_IDSCAN)
+ if(WIRE_IDSCAN)
V.scan_id = !V.scan_id
..()
-/datum/wires/vending/UpdateCut(index, mended)
+/datum/wires/vending/on_cut(wire, mend)
var/obj/machinery/vending/V = holder
- switch(index)
- if(VENDING_WIRE_THROW)
- V.shoot_inventory = !mended
- if(VENDING_WIRE_CONTRABAND)
+ switch(wire)
+ if(WIRE_THROW_ITEM)
+ V.shoot_inventory = !mend
+ if(WIRE_CONTRABAND)
V.extended_inventory = FALSE
- if(VENDING_WIRE_ELECTRIFY)
- if(mended)
+ if(WIRE_ELECTRIFY)
+ if(mend)
V.seconds_electrified = 0
else
V.seconds_electrified = -1
- if(VENDING_WIRE_IDSCAN)
- V.scan_id = 1
+ if(WIRE_IDSCAN)
+ V.scan_id = TRUE
..()
diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm
index 40aeda21b58..27d1448ac77 100644
--- a/code/datums/wires/wires.dm
+++ b/code/datums/wires/wires.dm
@@ -1,331 +1,456 @@
-// Wire datums. Created by Giacomand.
-// Was created to replace a horrible case of copy and pasted code with no care for maintability.
-// Goodbye Door wires, Cyborg wires, Vending Machine wires, Autolathe wires
-// Protolathe wires, APC wires and Camera wires!
-
-#define MAX_FLAG 65535
-
-GLOBAL_LIST_EMPTY(same_wires)
-// 12 colours, if you're adding more than 12 wires then add more colours here
-GLOBAL_LIST_INIT(wireColours, list("red", "blue", "green", "black", "orange", "brown", "gold", "gray", "cyan", "navy", "purple", "pink"))
/datum/wires
+ /// TRUE if the wires will be different every time a new wire datum is created.
+ var/randomize = FALSE
+ /// The atom the wires belong too. For example: an airlock.
+ var/atom/holder
+ /// The holder type; used to make sure that the holder is the correct type.
+ var/holder_type
+ /// The display name for the TGUI window. For example, given the var is "APC"...
+ /// When the TGUI window is opened, "wires" will be appended to it's title, and it would become "APC wires".
+ var/proper_name = "Unknown"
+ /// The total number of wires that our holder atom has.
+ var/wire_count = NONE
+ /// A list of all wires. For a list of valid wires defines that can go here, see `code/__DEFINES/wires.dm`
+ var/list/wires
+ /// A list of all cut wires. The same values that can go into `wires` will get added and removed from this list.
+ var/list/cut_wires
+ /// An associative list with the wire color as the key, and the wire define as the value.
+ var/list/colors
+ /// An associative list of signalers attached to the wires. The wire color is the key, and the signaler object reference is the value.
+ var/list/assemblies
+ /// The width of the wire TGUI window.
+ var/window_x = 300
+ /// The height of the wire TGUI window. Will get longer as needed, based on the `wire_count`.
+ var/window_y = 100
- var/random = 0 // Will the wires be different for every single instance.
- var/atom/holder = null // The holder
- var/holder_type = null // The holder type; used to make sure that the holder is the correct type.
- var/wire_count = 0 // Max is 16
- var/wires_status = 0 // BITFLAG OF WIRES
-
- var/list/wires = list()
- var/list/signallers = list()
-
- var/table_options = " align='center'"
- var/row_options1 = " width='80px'"
- var/row_options2 = " width='260px'"
- var/window_x = 370
- var/window_y = 470
-
-/datum/wires/New(atom/holder)
+/datum/wires/New(atom/_holder)
..()
- src.holder = holder
- if(!istype(holder, holder_type))
+ if(!istype(_holder, holder_type))
CRASH("Our holder is null/the wrong type!")
- // Generate new wires
- if(random)
- GenerateWires()
- // Get the same wires
+ holder = _holder
+ cut_wires = list()
+ colors = list()
+ assemblies = list()
+
+ // Add in the appropriate amount of dud wires.
+ var/wire_len = length(wires)
+ if(wire_len < wire_count) // If the amount of "real" wires is less than the total we're suppose to have...
+ add_duds(wire_count - wire_len) // Add in the appropriate amount of duds to reach `wire_count`.
+
+ // If the randomize is true, we need to generate a new set of wires and ignore any wire color directories.
+ if(randomize)
+ randomize()
+ return
+
+ if(!GLOB.wire_color_directory[holder_type])
+ randomize()
+ GLOB.wire_color_directory[holder_type] = colors
else
- // We don't have any wires to copy yet, generate some and then copy it.
- if(!GLOB.same_wires[holder_type])
- GenerateWires()
- GLOB.same_wires[holder_type] = src.wires.Copy()
- else
- var/list/wires = GLOB.same_wires[holder_type]
- src.wires = wires // Reference the wires list.
+ colors = GLOB.wire_color_directory[holder_type]
/datum/wires/Destroy()
holder = null
+ for(var/color in colors)
+ detach_assembly(color)
return ..()
-/datum/wires/proc/GenerateWires()
- var/list/colours_to_pick = GLOB.wireColours.Copy() // Get a copy, not a reference.
- var/list/indexes_to_pick = list()
- //Generate our indexes
- for(var/i = 1; i < MAX_FLAG && i < (1 << wire_count); i += i)
- indexes_to_pick += i
- colours_to_pick.len = wire_count // Downsize it to our specifications.
+/**
+ * Randomly generates a new set of wires. and corresponding colors from the given pool. Assigns the information as an associative list, to `colors`.
+ *
+ * In the `colors` list, the name of the color is the key, and the wire is the value.
+ * For example: `colors["red"] = WIRE_ELECTRIFY`. This will look like `list("red" = WIRE_ELECTRIFY)` internally.
+ */
+datum/wires/proc/randomize()
+ var/static/list/possible_colors = list("red", "blue", "green", "silver", "orange", "brown", "gold", "white", "cyan", "magenta", "purple", "pink")
+ var/list/my_possible_colors = possible_colors.Copy()
- while(colours_to_pick.len && indexes_to_pick.len)
- // Pick and remove a colour
- var/colour = pick_n_take(colours_to_pick)
-
- // Pick and remove an index
- var/index = pick_n_take(indexes_to_pick)
-
- src.wires[colour] = index
- //wires = shuffle(wires)
-
-/datum/wires/proc/get_status()
- return list()
+ for(var/wire in shuffle(wires))
+ colors[pick_n_take(my_possible_colors)] = wire
+/**
+ * Proc called when the user attempts to interact with wires UI.
+ *
+ * Checks if the user exists, is a mob, the wires are attached to something (`holder`) and makes sure `interactable(user)` returns TRUE.
+ * If all the checks succeed, open the TGUI interface for the user.
+ *
+ * Arugments:
+ * * user - the mob trying to interact with the wires.
+ */
/datum/wires/proc/Interact(mob/user)
- if(user && istype(user) && holder && CanUse(user))
- ui_interact(user)
+ if(user && istype(user) && holder && interactable(user))
+ tgui_interact(user)
-/datum/wires/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/**
+ * Base proc, intended to be overriden. Wire datum specific checks you want to run before the TGUI is shown to the user should go here.
+ */
+/datum/wires/proc/interactable(mob/user)
+ return TRUE
+
+/// Users will be interacting with our holder object and not the wire datum directly, therefore we need to return the holder.
+/datum/wires/tgui_host()
+ return holder
+
+/datum/wires/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_physical_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "wires.tmpl", holder.name, window_x, window_y)
+ ui = new(user, src, ui_key, "Wires", "[proper_name] wires", window_x, window_y + wire_count * 30, master_ui, state)
ui.open()
-/datum/wires/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.physical_state)
- var/data[0]
- var/list/replace_colours = null
+/datum/wires/tgui_data(mob/user)
+ var/list/data = list()
+ var/list/replace_colors
+
if(ishuman(user))
var/mob/living/carbon/human/H = user
var/obj/item/organ/internal/eyes/eyes = H.get_int_organ(/obj/item/organ/internal/eyes)
- if(eyes && (COLOURBLIND in H.mutations))
- replace_colours = eyes.replace_colours
+ if(eyes && (COLOURBLIND in H.mutations)) // Check if the human has colorblindness.
+ replace_colors = eyes.replace_colours // Get the colorblind replacement colors list.
+ var/list/wires_list = list()
- var/list/W[0]
- for(var/colour in wires)
- var/new_colour = colour
- var/colour_name = colour
- if(colour in replace_colours)
- new_colour = replace_colours[colour]
- if(new_colour in LIST_REPLACE_RENAME)
- colour_name = LIST_REPLACE_RENAME[new_colour]
+ for(var/color in colors)
+ var/replaced_color = color
+ var/color_name = color
+
+ if(color in replace_colors) // If this color is one that needs to be replaced using the colorblindness list.
+ replaced_color = replace_colors[color]
+ if(replaced_color in LIST_COLOR_RENAME) // If its an ugly written color name like "darkolivegreen", rename it to something like "dark green".
+ color_name = LIST_COLOR_RENAME[replaced_color]
else
- colour_name = new_colour
- else
- new_colour = colour
- colour_name = new_colour
- W[++W.len] = list("colour_name" = capitalize(colour_name), "seen_colour" = capitalize(new_colour),"colour" = capitalize(colour), "cut" = IsColourCut(colour), "index" = can_see_wire_index(user) ? GetWireName(GetIndex(colour)) : null, "attached" = IsAttached(colour))
+ color_name = replaced_color // Else just keep the normal color name
- if(W.len > 0)
- data["wires"] = W
+ wires_list += list(list(
+ "seen_color" = replaced_color, // The color of the wire that the mob will see. This will be the same as `color` if the user is NOT colorblind.
+ "color_name" = color_name, // The wire's name. This will be the same as `color` if the user is NOT colorblind.
+ "color" = color, // The "real" color of the wire. No replacements.
+ "wire" = can_see_wire_info(user) && !is_dud_color(color) ? get_wire(color) : null, // Wire define information like "Contraband" or "Door Bolts".
+ "cut" = is_color_cut(color), // Whether the wire is cut or not. Used to display "cut" or "mend".
+ "attached" = is_attached(color) // Whether or not a signaler is attached to this wire.
+ ))
+ data["wires"] = wires_list
+ // Get the information shown at the bottom of wire TGUI window, such as "The red light is blinking", etc.
+ // If the user is colorblind, we need to replace these colors as well.
var/list/status = get_status()
- if(replace_colours)
- var/i
- for(i=1, i<=status.len, i++)
- for(var/colour in replace_colours)
- var/new_colour = replace_colours[colour]
- if(new_colour in LIST_REPLACE_RENAME)
- new_colour = LIST_REPLACE_RENAME[new_colour]
- if(findtext(status[i],colour))
- status[i] = replacetext(status[i],colour,new_colour)
- break
- data["status_len"] = status.len
- data["status"] = status
+ if(replace_colors)
+ var/i
+ for(i in 1 to length(status))
+ for(var/color in replace_colors)
+ var/new_color = replace_colors[color]
+ if(new_color in LIST_COLOR_RENAME)
+ new_color = LIST_COLOR_RENAME[new_color]
+ if(findtext(status[i], color))
+ status[i] = replacetext(status[i], color, new_color)
+ break
+
+ data["status"] = status
return data
-/datum/wires/nano_host()
- return holder
+/datum/wires/tgui_act(action, list/params)
+ if(..())
+ return
-/datum/wires/proc/can_see_wire_index(mob/user)
+ var/mob/user = usr
+ if(!interactable(user))
+ return
+
+ var/obj/item/I = user.get_active_hand()
+ var/color = lowertext(params["wire"])
+ holder.add_hiddenprint(user)
+
+ switch(action)
+ // Toggles the cut/mend status.
+ if("cut")
+ if(!istype(I, /obj/item/wirecutters) && !user.can_admin_interact())
+ to_chat(user, "You need wirecutters!")
+ return
+
+ if(istype(I))
+ playsound(holder, I.usesound, 20, 1)
+ cut_color(color)
+ return TRUE
+
+ // Pulse a wire.
+ if("pulse")
+ if(!istype(I, /obj/item/multitool) && !user.can_admin_interact())
+ to_chat(user, "You need a multitool!")
+ return
+
+ playsound(holder, 'sound/weapons/empty.ogg', 20, 1)
+ pulse_color(color)
+
+ // If they pulse the electrify wire, call interactable() and try to shock them.
+ if(get_wire(color) == WIRE_ELECTRIFY)
+ interactable(user)
+
+ return TRUE
+
+ // Attach a signaler to a wire.
+ if("attach")
+ if(is_attached(color))
+ var/obj/item/O = detach_assembly(color)
+ if(O)
+ user.put_in_hands(O)
+ return TRUE
+
+ if(!istype(I, /obj/item/assembly/signaler))
+ to_chat(user, "You need a remote signaller!")
+ return
+
+ if(user.drop_item())
+ attach_assembly(color, I)
+ return TRUE
+ else
+ to_chat(user, "[user.get_active_hand()] is stuck to your hand!")
+
+/**
+ * Proc called to determine if the user can see wire define information, such as "Contraband", "Door Bolts", etc.
+ *
+ * If the user is an admin, or has a multitool which reveals wire information in their active hand, the proc returns TRUE.
+ *
+ * Arguments:
+ * * user - the mob who is interacting with the wires.
+ */
+/datum/wires/proc/can_see_wire_info(mob/user)
if(user.can_admin_interact())
return TRUE
else if(istype(user.get_active_hand(), /obj/item/multitool))
var/obj/item/multitool/M = user.get_active_hand()
if(M.shows_wire_information)
return TRUE
-
return FALSE
-/datum/wires/Topic(href, href_list)
- if(..())
- return 1
- var/mob/L = usr
- if(CanUse(L) && href_list["action"])
- var/obj/item/I = L.get_active_hand()
- var/colour = lowertext(href_list["wire"])
- holder.add_hiddenprint(L)
- switch(href_list["action"])
- if("cut") // Toggles the cut/mend status
- if(istype(I, /obj/item/wirecutters) || L.can_admin_interact())
- if(istype(I))
- playsound(holder, I.usesound, 20, 1)
- CutWireColour(colour)
- else
- to_chat(L, "You need wirecutters!")
- if("pulse")
- if(istype(I, /obj/item/multitool) || L.can_admin_interact())
- playsound(holder, 'sound/weapons/empty.ogg', 20, 1)
- PulseColour(colour)
- else
- to_chat(L, "You need a multitool!")
- if("attach")
- if(IsAttached(colour))
- var/obj/item/O = Detach(colour)
- if(O)
- L.put_in_hands(O)
- else
- if(istype(I, /obj/item/assembly/signaler))
- if(L.drop_item())
- Attach(colour, I)
- else
- to_chat(L, "[L.get_active_hand()] is stuck to your hand!")
- else
- to_chat(L, "You need a remote signaller!")
+/**
+ * Base proc, intended to be overwritten. Put wire information you'll see at the botton of the TGUI window here, such as "The red light is blinking".
+ */
+/datum/wires/proc/get_status()
+ return list()
- SSnanoui.update_uis(src)
- return 1
+/**
+ * Clears the `colors` list, and randomizes it to a new set of color-to-wire relations.
+ */
+/datum/wires/proc/shuffle_wires()
+ colors.Cut()
+ randomize()
-//
-// Overridable Procs
-//
+/**
+ * Repairs all cut wires.
+ */
+/datum/wires/proc/repair()
+ cut_wires.Cut()
-// Called when wires cut/mended.
-/datum/wires/proc/UpdateCut(index, mended)
- if(holder)
- SSnanoui.update_uis(holder)
+/**
+ * Adds in dud wires, which do nothing when cut/pulsed.
+ *
+ * Arguments:
+ * * duds - the amount of dud wires to generate.
+ */
+/datum/wires/proc/add_duds(duds)
+ while(duds)
+ var/dud = WIRE_DUD_PREFIX + "[--duds]"
+ if(dud in wires)
+ continue
+ wires += dud
-// Called when wire pulsed. Add code here.
-/datum/wires/proc/UpdatePulsed(index)
- if(holder)
- SSnanoui.update_uis(holder)
+/**
+ * Determines if the passed in wire is a dud or not. Returns TRUE if the wire is a dud, FALSE otherwise.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/is_dud(wire)
+ return findtext(wire, WIRE_DUD_PREFIX, 1, length(WIRE_DUD_PREFIX) + 1)
-/datum/wires/proc/CanUse(mob/L)
- return 1
+/**
+ * Returns TRUE if the wire that corresponds to the passed in color is a dud. FALSE otherwise.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/is_dud_color(color)
+ return is_dud(get_wire(color))
-/datum/wires/CanUseTopic(mob/user, datum/topic_state/state)
- if(!holder || !CanUse(user))
- return STATUS_CLOSE
- return ..()
+/**
+ * Gets the wire associated with the color passed in.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/get_wire(color)
+ return colors[color]
-// Example of use:
-/*
+/**
+ * Determines if the passed in wire is cut or not. Returns TRUE if it's cut, FALSE otherwise.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/is_cut(wire)
+ return (wire in cut_wires)
-#define NAME_WIRE_BOLTED 1
-#define NAME_WIRE_SHOCKED 2
-#define NAME_WIRE_SAFETY 4
-#define NAME_WIRE_POWER 8
+/**
+ * Determines if the wire associated with the passed in color, is cut or not. Returns TRUE if it's cut, FALSE otherwise.
+ *
+ * Arugments:
+ * * wire - a wire color.
+ */
+/datum/wires/proc/is_color_cut(color)
+ return is_cut(get_wire(color))
-/datum/wires/door/UpdateCut(var/index, var/mended)
- var/obj/machinery/door/airlock/A = holder
- switch(index)
- if(NAME_WIRE_BOLTED)
- if(!mended)
- A.bolt()
- if(NAME_WIRE_SHOCKED)
- A.shock()
- if(NAME_WIRE_SAFETY)
- A.safety()
+/**
+ * Determines if all of the wires are cut. Returns TRUE they're all cut, FALSE otherwise.
+ */
+/datum/wires/proc/is_all_cut()
+ return (length(cut_wires) == length(wires))
-*/
-
-
-//
-// Helper Procs
-//
-
-/datum/wires/proc/PulseColour(colour)
- PulseIndex(GetIndex(colour))
-
-/datum/wires/proc/PulseIndex(index)
- if(IsIndexCut(index))
- return
- UpdatePulsed(index)
-
-/datum/wires/proc/GetIndex(colour)
- if(wires[colour])
- var/index = wires[colour]
- return index
+/**
+ * Cut or mend a wire. Calls `on_cut()`.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/cut(wire)
+ if(is_cut(wire))
+ cut_wires -= wire
+ on_cut(wire, mend = TRUE)
else
- CRASH("[colour] is not a key in wires.")
+ cut_wires += wire
+ on_cut(wire, mend = FALSE)
-/datum/wires/proc/GetWireName(index)
+/**
+ * Cut the wire which corresponds with the passed in color.
+ *
+ * Arugments:
+ * * color - a wire color.
+ */
+/datum/wires/proc/cut_color(color)
+ cut(get_wire(color))
+
+/**
+ * Cuts a random wire.
+ */
+/datum/wires/proc/cut_random()
+ cut(wires[rand(1, length(wires))])
+
+/**
+ * Cuts all wires.
+ */
+/datum/wires/proc/cut_all()
+ for(var/wire in wires)
+ cut(wire)
+
+/**
+ * Proc called when any wire is cut.
+ *
+ * Base proc, intended to be overriden.
+ * Place an behavior you want to happen when certain wires are cut, into this proc.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT color. For example 'WIRE_ELECTRIFY'.
+ * * mend - TRUE if we're mending the wire. FALSE if we're cutting.
+ */
+/datum/wires/proc/on_cut(wire, mend = FALSE)
return
-//
-// Is Index/Colour Cut procs
-//
+/**
+ * Pulses the given wire. Calls `on_pulse()`.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT a color. For example `WIRE_ELECTRIFY`.
+ */
+/datum/wires/proc/pulse(wire)
+ if(is_cut(wire))
+ return
+ on_pulse(wire)
-/datum/wires/proc/IsColourCut(colour)
- var/index = GetIndex(colour)
- return IsIndexCut(index)
+/**
+ * Pulses the wire associated with the given color.
+ *
+ * Arugments:
+ * * wire - a wire color.
+ */
+/datum/wires/proc/pulse_color(color)
+ pulse(get_wire(color))
-/datum/wires/proc/IsIndexCut(index)
- return (index & wires_status)
+/**
+ * Proc called when any wire is pulsed.
+ *
+ * Base proc, intended to be overriden.
+ * Place behavior you want to happen when certain wires are pulsed, into this proc.
+ *
+ * Arugments:
+ * * wire - a wire define, NOT color. For example 'WIRE_ELECTRIFY'.
+ */
+/datum/wires/proc/on_pulse(wire)
+ return
-//
-// Signaller Procs
-//
+/**
+ * Proc called when an attached signaler receives a signal.
+ *
+ * Searches through the `assemblies` list for the wire that the signaler is attached to. Pulses the wire when it's found.
+ *
+ * Arugments:
+ * * S - the attached signaler receiving the signal.
+ */
+/datum/wires/proc/pulse_assembly(obj/item/assembly/signaler/S)
+ for(var/color in assemblies)
+ if(S == assemblies[color])
+ pulse_color(color)
+ return TRUE
-/datum/wires/proc/IsAttached(colour)
- if(signallers[colour])
- return 1
- return 0
+/**
+ * Proc called when a mob tries to attach a signaler to a wire.
+ *
+ * Makes sure that `S` is actually a signaler and that something is not already attached to the wire.
+ * Adds the signaler to the `assemblies` list as a value, with the `color` as a the key.
+ *
+ * Arguments:
+ * * color - the wire color.
+ * * S - the signaler that a mob is trying to attach.
+ */
+/datum/wires/proc/attach_assembly(color, obj/item/assembly/signaler/S)
+ if(S && istype(S) && !is_attached(color))
+ assemblies[color] = S
+ S.forceMove(holder)
+ S.connected = src
+ return S
-/datum/wires/proc/GetAttached(colour)
- if(signallers[colour])
- return signallers[colour]
+/**
+ * Proc called when a mob tries to detach a signaler from a wire.
+ *
+ * First checks if there is a signaler on the wire. If so, removes the signaler, and clears it from `assemblies` list.
+ *
+ * Arguments:
+ * * color - the wire color.
+ */
+/datum/wires/proc/detach_assembly(color)
+ var/obj/item/assembly/signaler/S = get_attached(color)
+ if(S && istype(S))
+ assemblies -= color
+ S.connected = null
+ S.forceMove(holder.drop_location())
+ return S
+
+/**
+ * Gets the signaler attached to the given wire color, if there is one.
+ *
+ * Arguments:
+ * * color - the wire color.
+ */
+/datum/wires/proc/get_attached(color)
+ if(assemblies[color])
+ return assemblies[color]
return null
-/datum/wires/proc/Attach(colour, obj/item/assembly/signaler/S)
- if(colour && S)
- if(!IsAttached(colour))
- signallers[colour] = S
- S.loc = holder
- S.connected = src
- return S
-
-/datum/wires/proc/Detach(colour)
- if(colour)
- var/obj/item/assembly/signaler/S = GetAttached(colour)
- if(S)
- signallers -= colour
- S.connected = null
- S.loc = holder.loc
- return S
-
-
-/datum/wires/proc/Pulse(obj/item/assembly/signaler/S)
-
- for(var/colour in signallers)
- if(S == signallers[colour])
- PulseColour(colour)
- break
-
-
-//
-// Cut Wire Colour/Index procs
-//
-
-/datum/wires/proc/CutWireColour(colour)
- var/index = GetIndex(colour)
- CutWireIndex(index)
-
-/datum/wires/proc/CutWireIndex(index)
- if(IsIndexCut(index))
- wires_status &= ~index
- UpdateCut(index, 1)
- else
- wires_status |= index
- UpdateCut(index, 0)
-
-/datum/wires/proc/RandomCut()
- var/r = rand(1, wires.len)
- CutWireIndex(r)
-
-/datum/wires/proc/CutAll()
- for(var/i = 1; i < MAX_FLAG && i < (1 << wire_count); i += i)
- CutWireIndex(i)
-
-/datum/wires/proc/IsAllCut()
- if(wires_status == (1 << wire_count) - 1)
- return 1
- return 0
-
-//
-//Shuffle and Mend
-//
-
-/datum/wires/proc/Shuffle()
- wires_status = 0
- GenerateWires()
+/**
+ * Checks if the given wire has a signaler on it.
+ *
+ * Arguments:
+ * * color - the wire color.
+ */
+/datum/wires/proc/is_attached(color)
+ if(assemblies[color])
+ return TRUE
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index c33e7609685..9a333d268f3 100644
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -50,10 +50,10 @@ NOTE: there are two lists of areas in the end of this file: centcom and station
/area/space/atmosalert()
return
-/area/space/fire_alert()
+/area/space/firealert(obj/source)
return
-/area/space/fire_reset()
+/area/space/firereset(obj/source)
return
/area/space/readyalert()
diff --git a/code/game/area/ai_monitored.dm b/code/game/area/ai_monitored.dm
index 8f65ac634f4..723574ee949 100644
--- a/code/game/area/ai_monitored.dm
+++ b/code/game/area/ai_monitored.dm
@@ -1,24 +1,30 @@
/area/ai_monitored
name = "AI Monitored Area"
- var/obj/machinery/camera/motioncamera = null
+ var/list/motioncameras = list()
+ var/list/motionTargets = list()
-
-/area/ai_monitored/LateInitialize()
+/area/ai_monitored/Initialize(mapload)
. = ..()
- // locate and store the motioncamera
- for(var/obj/machinery/camera/M in src)
- if(M.isMotion())
- motioncamera = M
- M.area_motion = src
- break
+ if(mapload)
+ for(var/obj/machinery/camera/M in src)
+ if(M.isMotion())
+ motioncameras.Add(M)
+ M.set_area_motion(src)
/area/ai_monitored/Entered(atom/movable/O)
..()
- if(ismob(O) && motioncamera)
- motioncamera.newTarget(O)
+ if(ismob(O) && length(motioncameras))
+ for(var/X in motioncameras)
+ var/obj/machinery/camera/cam = X
+ cam.newTarget(O)
+ return
/area/ai_monitored/Exited(atom/movable/O)
- if(ismob(O) && motioncamera)
- motioncamera.lostTarget(O)
+ ..()
+ if(ismob(O) && length(motioncameras))
+ for(var/X in motioncameras)
+ var/obj/machinery/camera/cam = X
+ cam.lostTargetRef(O.UID())
+ return
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index da80a6e775c..de913ac9f91 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -57,6 +57,11 @@
var/list/ambientsounds = GENERIC_SOUNDS
+ var/list/firedoors
+ var/list/cameras
+ var/list/firealarms
+ var/firedoors_last_closed_on = 0
+
var/fast_despawn = FALSE
var/can_get_auto_cryod = TRUE
var/hide_attacklogs = FALSE // For areas such as thunderdome, lavaland syndiebase, etc which generate a lot of spammy attacklogs. Reduces log priority.
@@ -125,35 +130,6 @@
cameras += C
return cameras
-
-/area/proc/atmosalert(danger_level, var/alarm_source, var/force = FALSE)
- if(report_alerts)
- if(danger_level == ATMOS_ALARM_NONE)
- SSalarms.atmosphere_alarm.clearAlarm(src, alarm_source)
- else
- SSalarms.atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level)
-
- //Check all the alarms before lowering atmosalm. Raising is perfectly fine. If force = 1 we don't care.
- for(var/obj/machinery/alarm/AA in src)
- if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level && !force)
- danger_level = max(danger_level, AA.danger_level)
-
- if(danger_level != atmosalm)
- if(danger_level < ATMOS_ALARM_WARNING && atmosalm >= ATMOS_ALARM_WARNING)
- //closing the doors on red and opening on green provides a bit of hysteresis that will hopefully prevent fire doors from opening and closing repeatedly due to noise
- air_doors_open()
- else if(danger_level >= ATMOS_ALARM_DANGER && atmosalm < ATMOS_ALARM_DANGER)
- air_doors_close()
-
- atmosalm = danger_level
- for(var/obj/machinery/alarm/AA in src)
- AA.update_icon()
-
- GLOB.air_alarm_repository.update_cache(src)
- return 1
- GLOB.air_alarm_repository.update_cache(src)
- return 0
-
/area/proc/air_doors_close()
if(!air_doors_activated)
air_doors_activated = TRUE
@@ -179,44 +155,151 @@
D.open()
-/area/proc/fire_alert()
- if(!fire)
- fire = 1 //used for firedoor checks
- updateicon()
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- air_doors_close()
+/area/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
-/area/proc/fire_reset()
- if(fire)
- fire = 0 //used for firedoor checks
- updateicon()
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- air_doors_open()
+/**
+ * Generate a power alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ */
+/area/proc/poweralert(state, obj/source)
+ if(state != poweralm)
+ poweralm = state
+ if(istype(source)) //Only report power alarms on the z-level where the source is located.
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ if(state)
+ C.network -= "Power Alarms"
+ else
+ C.network |= "Power Alarms"
- return
+ if(state)
+ SSalarm.cancelAlarm("Power", src, source)
+ else
+ SSalarm.triggerAlarm("Power", src, cameras, source)
-/area/proc/burglaralert(var/obj/trigger)
- if(always_unpowered == 1) //no burglar alarms in space/asteroid
+/**
+ * Generate an atmospheric alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ */
+/area/proc/atmosalert(danger_level, obj/source)
+ if(danger_level != atmosalm)
+ if(danger_level == ATMOS_ALARM_DANGER)
+
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network |= "Atmosphere Alarms"
+
+
+ SSalarm.triggerAlarm("Atmosphere", src, cameras, source)
+
+ else if(atmosalm == ATMOS_ALARM_DANGER)
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network -= "Atmosphere Alarms"
+
+ SSalarm.cancelAlarm("Atmosphere", src, source)
+
+ atmosalm = danger_level
+ return TRUE
+ return FALSE
+
+/**
+ * Try to close all the firedoors in the area
+ */
+/area/proc/ModifyFiredoors(opening)
+ if(firedoors)
+ firedoors_last_closed_on = world.time
+ for(var/FD in firedoors)
+ var/obj/machinery/door/firedoor/D = FD
+ var/cont = !D.welded
+ if(cont && opening) //don't open if adjacent area is on fire
+ for(var/I in D.affecting_areas)
+ var/area/A = I
+ if(A.fire)
+ cont = FALSE
+ break
+ if(cont && D.is_operational())
+ if(D.operating)
+ D.nextstate = opening ? FD_OPEN : FD_CLOSED
+ else if(!(D.density ^ opening))
+ INVOKE_ASYNC(D, (opening ? /obj/machinery/door/firedoor.proc/open : /obj/machinery/door/firedoor.proc/close))
+
+/**
+ * Generate a firealarm alert for this area
+ *
+ * Sends to all ai players, alert consoles, drones and alarm monitor programs in the world
+ *
+ * Also starts the area processing on SSobj
+ */
+/area/proc/firealert(obj/source)
+ if(always_unpowered) //no fire alarms in space/asteroid
return
- //Trigger alarm effect
- set_fire_alarm_effect()
+ if(!fire)
+ set_fire_alarm_effect()
+ ModifyFiredoors(FALSE)
+ for(var/item in firealarms)
+ var/obj/machinery/firealarm/F = item
+ F.update_icon()
- //Lockdown airlocks
- for(var/obj/machinery/door/airlock/A in src)
- spawn(0)
- A.close()
- if(A.density)
- A.lock()
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network |= "Fire Alarms"
- SSalarms.burglar_alarm.triggerAlarm(src, trigger)
- spawn(600)
- SSalarms.burglar_alarm.clearAlarm(src, trigger)
+ SSalarm.triggerAlarm("Fire", src, cameras, source)
-/area/proc/set_fire_alarm_effect()
- fire = 1
- updateicon()
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ START_PROCESSING(SSobj, src)
+
+/**
+ * Reset the firealarm alert for this area
+ *
+ * resets the alert sent to all ai players, alert consoles, drones and alarm monitor programs
+ * in the world
+ *
+ * Also cycles the icons of all firealarms and deregisters the area from processing on SSOBJ
+ */
+/area/proc/firereset(obj/source)
+ if(fire)
+ unset_fire_alarm_effects()
+ ModifyFiredoors(TRUE)
+ for(var/item in firealarms)
+ var/obj/machinery/firealarm/F = item
+ F.update_icon()
+
+ for(var/thing in cameras)
+ var/obj/machinery/camera/C = locateUID(thing)
+ if(!QDELETED(C) && is_station_level(C.z))
+ C.network -= "Fire Alarms"
+
+ SSalarm.cancelAlarm("Fire", src, source)
+
+ STOP_PROCESSING(SSobj, src)
+
+/**
+ * If 100 ticks has elapsed, toggle all the firedoors closed again
+ */
+/area/process()
+ if(firedoors_last_closed_on + 100 < world.time) //every 10 seconds
+ ModifyFiredoors(FALSE)
+
+/**
+ * Close and lock a door passed into this proc
+ *
+ * Does this need to exist on area? probably not
+ */
+/area/proc/close_and_lock_door(obj/machinery/door/DOOR)
+ set waitfor = FALSE
+ DOOR.close()
+ if(DOOR.density)
+ DOOR.lock()
/area/proc/readyalert()
if(!eject)
@@ -240,13 +323,62 @@
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
updateicon()
+/**
+ * Raise a burglar alert for this area
+ *
+ * Close and locks all doors in the area and alerts silicon mobs of a break in
+ *
+ * Alarm auto resets after 600 ticks
+ */
+/area/proc/burglaralert(obj/trigger)
+ if(always_unpowered) //no burglar alarms in space/asteroid
+ return
+
+ //Trigger alarm effect
+ set_fire_alarm_effect()
+ //Lockdown airlocks
+ for(var/obj/machinery/door/DOOR in src)
+ close_and_lock_door(DOOR)
+
+ if(SSalarm.triggerAlarm("Burglar", src, cameras, trigger))
+ //Cancel silicon alert after 1 minute
+ addtimer(CALLBACK(SSalarm, /datum/controller/subsystem/alarm.proc/cancelAlarm, "Burglar", src, trigger), 600)
+
+/**
+ * Trigger the fire alarm visual affects in an area
+ *
+ * Updates the fire light on fire alarms in the area and sets all lights to emergency mode
+ */
+/area/proc/set_fire_alarm_effect()
+ fire = TRUE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ for(var/alarm in firealarms)
+ var/obj/machinery/firealarm/F = alarm
+ F.update_fire_light(fire)
+ for(var/obj/machinery/light/L in src)
+ L.update()
+
+/**
+ * unset the fire alarm visual affects in an area
+ *
+ * Updates the fire light on fire alarms in the area and sets all lights to emergency mode
+ */
+/area/proc/unset_fire_alarm_effects()
+ fire = FALSE
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ for(var/alarm in firealarms)
+ var/obj/machinery/firealarm/F = alarm
+ F.update_fire_light(fire)
+ for(var/obj/machinery/light/L in src)
+ L.update()
+
/area/proc/updateicon()
- if((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc.
- if(fire && !eject && !party)
+ if((eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc.
+ if(!eject && !party)
icon_state = "red"
- else if(!fire && eject && !party)
+ else if(eject && !party)
icon_state = "red"
- else if(party && !fire && !eject)
+ else if(party && !eject)
icon_state = "party"
else
icon_state = "blue-red"
diff --git a/code/game/gamemodes/changeling/evolution_menu.dm b/code/game/gamemodes/changeling/evolution_menu.dm
index 9c33ffaaca5..b3b4000bbf1 100644
--- a/code/game/gamemodes/changeling/evolution_menu.dm
+++ b/code/game/gamemodes/changeling/evolution_menu.dm
@@ -370,16 +370,11 @@ GLOBAL_LIST_EMPTY(sting_paths)
mind.changeling.purchasedpowers += path
path.on_purchase(src)
else //for respec
- var/datum/action/changeling/hivemind_upload/S1 = new
+ var/datum/action/changeling/hivemind_pick/S1 = new
if(!mind.changeling.has_sting(S1))
mind.changeling.purchasedpowers+=S1
S1.Grant(src)
- var/datum/action/changeling/hivemind_download/S2 = new
- if(!mind.changeling.has_sting(S2))
- mind.changeling.purchasedpowers+=S2
- S2.Grant(src)
-
var/mob/living/carbon/C = src //only carbons have dna now, so we have to typecaste
mind.changeling.absorbed_dna |= C.dna.Clone()
mind.changeling.trim_dna()
diff --git a/code/game/gamemodes/changeling/powers/hivemind.dm b/code/game/gamemodes/changeling/powers/hivemind.dm
index 98336608da4..18a5477fd3f 100644
--- a/code/game/gamemodes/changeling/powers/hivemind.dm
+++ b/code/game/gamemodes/changeling/powers/hivemind.dm
@@ -12,27 +12,36 @@
var/datum/changeling/changeling=user.mind.changeling
changeling.changeling_speak = 1
to_chat(user, "Use say \":g message\" to communicate with the other changelings.")
- var/datum/action/changeling/hivemind_upload/S1 = new
+ var/datum/action/changeling/hivemind_pick/S1 = new
if(!changeling.has_sting(S1))
changeling.purchasedpowers+=S1
S1.Grant(user)
- var/datum/action/changeling/hivemind_download/S2 = new
- if(!changeling.has_sting(S2))
- S2.Grant(user)
- changeling.purchasedpowers+=S2
return
// HIVE MIND UPLOAD/DOWNLOAD DNA
GLOBAL_LIST_EMPTY(hivemind_bank)
-/datum/action/changeling/hivemind_upload
+/datum/action/changeling/hivemind_pick
name = "Hive Channel DNA"
- desc = "Allows us to channel DNA in the airwaves to allow other changelings to absorb it. Costs 10 chemicals."
- button_icon_state = "hivemind_channel"
+ desc = "Allows us to upload or absorb DNA in the airwaves. Does not count towards absorb objectives. Costs 10 chemicals."
+ button_icon_state = "hive_absorb"
chemical_cost = 10
dna_cost = -1
-/datum/action/changeling/hivemind_upload/sting_action(var/mob/user)
+/datum/action/changeling/hivemind_pick/sting_action(mob/user)
+ var/datum/changeling/changeling = user.mind.changeling
+ var/channel_pick = alert("Upload or Absorb DNA?", "Channel Select", "Upload", "Absorb")
+
+ if(channel_pick == "Upload")
+ dna_upload(user)
+ if(channel_pick == "Absorb")
+ if(changeling.using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it.
+ to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.")
+ return
+ else
+ dna_absorb(user)
+
+/datum/action/changeling/proc/dna_upload(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in (changeling.absorbed_dna+changeling.protected_dna))
@@ -56,23 +65,7 @@ GLOBAL_LIST_EMPTY(hivemind_bank)
feedback_add_details("changeling_powers","HU")
return 1
-/datum/action/changeling/hivemind_download
- name = "Hive Absorb DNA"
- desc = "Allows us to absorb DNA that has been channeled to the airwaves. Does not count towards absorb objectives. Costs 10 chemicals."
- button_icon_state = "hive_absorb"
- chemical_cost = 10
- dna_cost = -1
-
-/datum/action/changeling/hivemind_download/can_sting(var/mob/living/carbon/user)
- if(!..())
- return
- var/datum/changeling/changeling = user.mind.changeling
- if(changeling.using_stale_dna(user))//If our current DNA is the stalest, we gotta ditch it.
- to_chat(user, "We have reached our capacity to store genetic information! We must transform before absorbing more.")
- return
- return 1
-
-/datum/action/changeling/hivemind_download/sting_action(var/mob/user)
+/datum/action/changeling/proc/dna_absorb(mob/user)
var/datum/changeling/changeling = user.mind.changeling
var/list/names = list()
for(var/datum/dna/DNA in GLOB.hivemind_bank)
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index 526a2c01083..9f5db8d5dae 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -616,7 +616,7 @@
active = FALSE
return
var/turf/T = get_turf(owner_AI.eyeobj)
- new /obj/machinery/transformer/conveyor(T)
+ new /obj/machinery/transformer(T, owner_AI)
playsound(T, 'sound/effects/phasein.ogg', 100, 1)
owner_AI.can_shunt = FALSE
to_chat(owner, "You are no longer able to shunt your core to APCs.")
@@ -781,3 +781,17 @@
if(AI.eyeobj)
AI.eyeobj.relay_speech = TRUE
+/datum/AI_Module/large/cameracrack
+ module_name = "Core Camera Cracker"
+ mod_pick_name = "cameracrack"
+ description = "By shortcirucuting the camera network chip, it overheats, preventing the camera console from using your internal camera."
+ cost = 10
+ one_purchase = TRUE
+ upgrade = TRUE
+ unlock_text = "Network chip short circuited. Internal camera disconected from network. Minimal damage to other internal components."
+ unlock_sound = 'sound/items/wirecutter.ogg'
+
+/datum/AI_Module/large/cameracrack/upgrade(mob/living/silicon/ai/AI)
+ if(AI.builtInCamera)
+ QDEL_NULL(AI.builtInCamera)
+
diff --git a/code/game/gamemodes/nuclear/nuclearbomb.dm b/code/game/gamemodes/nuclear/nuclearbomb.dm
index 8e38f106cc7..c73293da519 100644
--- a/code/game/gamemodes/nuclear/nuclearbomb.dm
+++ b/code/game/gamemodes/nuclear/nuclearbomb.dm
@@ -42,6 +42,7 @@ GLOBAL_VAR(bomb_set)
GLOB.poi_list |= src
/obj/machinery/nuclearbomb/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
GLOB.poi_list.Remove(src)
return ..()
@@ -407,6 +408,20 @@ GLOBAL_VAR(bomb_set)
return
return
+/obj/machinery/nuclearbomb/proc/reset_lighthack_callback()
+ lighthack = !lighthack
+
+/obj/machinery/nuclearbomb/proc/reset_safety_callback()
+ safety = !safety
+ if(safety == 1)
+ if(!is_syndicate)
+ set_security_level(previous_level)
+ visible_message("The [src] quiets down.")
+ if(!lighthack)
+ if(icon_state == "nuclearbomb2")
+ icon_state = "nuclearbomb1"
+ else
+ visible_message("The [src] emits a quiet whirling noise!")
//==========DAT FUKKEN DISK===============
/obj/item/disk/nuclear
diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm
index 9fda2cfe45c..951f2a1e9cf 100644
--- a/code/game/machinery/alarm.dm
+++ b/code/game/machinery/alarm.dm
@@ -222,6 +222,7 @@
first_run()
/obj/machinery/alarm/Destroy()
+ SStgui.close_uis(wires)
GLOB.air_alarms -= src
if(SSradio)
SSradio.remove_object(src, frequency)
@@ -314,7 +315,7 @@
temperature_dangerlevel
)
- if(old_danger_level!=danger_level)
+ if(old_danger_level != danger_level)
apply_danger_level()
if(mode == AALARM_MODE_REPLACEMENT && environment_pressure < ONE_ATMOSPHERE * 0.05)
@@ -534,28 +535,35 @@
"checks"= 0,
))
-/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level)
- if(report_danger_level && alarm_area.atmosalert(new_danger_level, src))
- post_alert(new_danger_level)
+/obj/machinery/alarm/proc/apply_danger_level()
+ var/new_area_danger_level = ATMOS_ALARM_NONE
+ for(var/obj/machinery/alarm/AA in alarm_area)
+ if(!(AA.stat & (NOPOWER|BROKEN)) && !AA.shorted)
+ new_area_danger_level = max(new_area_danger_level, AA.danger_level)
+ if(alarm_area.atmosalert(new_area_danger_level, src)) //if area was in normal state or if area was in alert state
+ post_alert(new_area_danger_level)
update_icon()
/obj/machinery/alarm/proc/post_alert(alert_level)
+ if(!report_danger_level)
+ return
var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency)
+
if(!frequency)
return
var/datum/signal/alert_signal = new
alert_signal.source = src
alert_signal.transmission_method = 1
- alert_signal.data["zone"] = alarm_area.name
+ alert_signal.data["zone"] = get_area_name(src, TRUE)
alert_signal.data["type"] = "Atmospheric"
- if(alert_level==2)
+ if(alert_level == ATMOS_ALARM_DANGER)
alert_signal.data["alert"] = "severe"
- else if(alert_level==1)
+ else if(alert_level == ATMOS_ALARM_WARNING)
alert_signal.data["alert"] = "minor"
- else if(alert_level==0)
+ else if(alert_level == ATMOS_ALARM_NONE)
alert_signal.data["alert"] = "clear"
frequency.post_signal(src, alert_signal)
@@ -889,14 +897,14 @@
if(href_list["atmos_alarm"])
if(alarm_area.atmosalert(ATMOS_ALARM_DANGER, src))
- apply_danger_level(ATMOS_ALARM_DANGER)
+ post_alert(ATMOS_ALARM_DANGER)
alarmActivated = 1
update_icon()
return 1
if(href_list["atmos_reset"])
if(alarm_area.atmosalert(ATMOS_ALARM_NONE, src, TRUE))
- apply_danger_level(ATMOS_ALARM_NONE)
+ post_alert(ATMOS_ALARM_NONE)
alarmActivated = 0
update_icon()
return 1
@@ -951,7 +959,7 @@
to_chat(user, "It does nothing")
return
else
- if(allowed(usr) && !wires.IsIndexCut(AALARM_WIRE_IDSCAN))
+ if(allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
locked = !locked
to_chat(user, "You [ locked ? "lock" : "unlock"] the Air Alarm interface.")
updateUsrDialog()
@@ -1030,7 +1038,7 @@
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
- if(wires.wires_status == 31) // all wires cut
+ if(wires.is_all_cut()) // all wires cut
var/obj/item/stack/cable_coil/new_coil = new /obj/item/stack/cable_coil(user.drop_location())
new_coil.amount = 5
buildstage = AIR_ALARM_BUILDING
@@ -1076,6 +1084,15 @@
if(buildstage < 1)
. += "The circuit is missing."
+/obj/machinery/alarm/proc/unshort_callback()
+ if(shorted)
+ shorted = FALSE
+ update_icon()
+
+/obj/machinery/alarm/proc/enable_ai_control_callback()
+ if(aidisabled)
+ aidisabled = FALSE
+
/obj/machinery/alarm/all_access
name = "all-access air alarm"
desc = "This particular atmos control unit appears to have no access restrictions."
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index c22922e4d14..5c1b84d8cba 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -65,6 +65,7 @@
RefreshParts()
/obj/machinery/autolathe/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
var/datum/component/material_container/materials = GetComponent(/datum/component/material_container)
materials.retrieve_all()
@@ -467,3 +468,15 @@
for(var/datum/design/D in files.known_designs)
if("hacked" in D.category)
files.known_designs -= D.id
+
+/obj/machinery/autolathe/proc/check_hacked_callback()
+ if(!wires.is_cut(WIRE_AUTOLATHE_HACK))
+ adjust_hacked(FALSE)
+
+/obj/machinery/autolathe/proc/check_electrified_callback()
+ if(!wires.is_cut(WIRE_ELECTRIFY))
+ shocked = FALSE
+
+/obj/machinery/autolathe/proc/check_disabled_callback()
+ if(!wires.is_cut(WIRE_AUTOLATHE_DISABLE))
+ disabled = FALSE
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index b66cad8d328..4e185feb4a8 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -28,13 +28,14 @@
var/view_range = 7
var/short_range = 2
+ var/alarm_on = FALSE
var/busy = FALSE
var/emped = FALSE //Number of consecutive EMP's on this camera
var/in_use_lights = 0 // TO BE IMPLEMENTED
var/toggle_sound = 'sound/items/wirecutter.ogg'
-/obj/machinery/camera/Initialize()
+/obj/machinery/camera/Initialize(mapload)
. = ..()
wires = new(src)
assembly = new(src)
@@ -44,11 +45,17 @@
GLOB.cameranet.cameras += src
GLOB.cameranet.addCamera(src)
+ if(isturf(loc))
+ LAZYADD(myArea.cameras, UID())
if(is_station_level(z) && prob(3) && !start_active)
toggle_cam(null, FALSE)
- wires.CutAll()
+ wires.cut_all()
+
+/obj/machinery/camera/proc/set_area_motion(area/A)
+ area_motion = A
/obj/machinery/camera/Destroy()
+ SStgui.close_uis(wires)
toggle_cam(null, FALSE) //kick anyone viewing out
QDEL_NULL(assembly)
if(istype(bug))
@@ -59,10 +66,14 @@
QDEL_NULL(wires)
GLOB.cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that
GLOB.cameranet.cameras -= src
+ if(isarea(myArea))
+ LAZYREMOVE(myArea.cameras, UID())
var/area/ai_monitored/A = get_area(src)
if(istype(A))
- A.motioncamera = null
+ A.motioncameras -= src
area_motion = null
+ cancelCameraAlarm()
+ cancelAlarm()
return ..()
/obj/machinery/camera/emp_act(severity)
@@ -252,7 +263,7 @@
if(status && !(flags & NODECONSTRUCT))
triggerCameraAlarm()
toggle_cam(null, FALSE)
- wires.CutAll()
+ wires.cut_all()
/obj/machinery/camera/deconstruct(disassembled = TRUE)
if(!(flags & NODECONSTRUCT))
@@ -282,9 +293,16 @@
status = !status
if(can_use())
GLOB.cameranet.addCamera(src)
+ if(isturf(loc))
+ myArea = get_area(src)
+ LAZYADD(myArea.cameras, UID())
+ else
+ myArea = null
else
set_light(0)
GLOB.cameranet.removeCamera(src)
+ if(isarea(myArea))
+ LAZYREMOVE(myArea.cameras, UID())
GLOB.cameranet.updateChunk(x, y, z)
var/change_msg = "deactivates"
if(status)
@@ -313,12 +331,12 @@
to_chat(O, "The screen bursts into static.")
/obj/machinery/camera/proc/triggerCameraAlarm()
- if(is_station_contact(z))
- SSalarms.camera_alarm.triggerAlarm(loc, src)
+ alarm_on = TRUE
+ SSalarm.triggerAlarm("Camera", get_area(src), list(UID()), src)
/obj/machinery/camera/proc/cancelCameraAlarm()
- if(is_station_contact(z))
- SSalarms.camera_alarm.clearAlarm(loc, src)
+ alarm_on = FALSE
+ SSalarm.cancelAlarm("Camera", get_area(src), src)
/obj/machinery/camera/proc/can_use()
if(!status)
@@ -414,8 +432,8 @@
/obj/machinery/camera/portable //Cameras which are placed inside of things, such as helmets.
var/turf/prev_turf
-/obj/machinery/camera/portable/New()
- ..()
+/obj/machinery/camera/portable/Initialize(mapload)
+ . = ..()
assembly.state = 0 //These cameras are portable, and so shall be in the portable state if removed.
assembly.anchored = 0
assembly.update_icon()
diff --git a/code/game/machinery/camera/motion.dm b/code/game/machinery/camera/motion.dm
index 16fb6eca204..7d33361e4b9 100644
--- a/code/game/machinery/camera/motion.dm
+++ b/code/game/machinery/camera/motion.dm
@@ -1,60 +1,60 @@
/obj/machinery/camera
-
- var/list/motionTargets = list()
+ var/list/localMotionTargets = list()
var/detectTime = 0
var/area/ai_monitored/area_motion = null
- var/alarm_delay = 100
-
+ var/alarm_delay = 30 // Don't forget, there's another 3 seconds in queueAlarm()
/obj/machinery/camera/process()
// motion camera event loop
- if(stat & (EMPED|NOPOWER))
- return
if(!isMotion())
. = PROCESS_KILL
return
+ if(stat & (EMPED|NOPOWER))
+ return
if(detectTime > 0)
var/elapsed = world.time - detectTime
if(elapsed > alarm_delay)
triggerAlarm()
else if(detectTime == -1)
- for(var/mob/target in motionTargets)
- if(target.stat == 2) lostTarget(target)
- // If not detecting with motion camera...
- if(!area_motion)
- // See if the camera is still in range
- if(!in_range(src, target))
- // If they aren't in range, lose the target.
- lostTarget(target)
+ for(var/thing in getTargetList())
+ var/mob/target = locateUID(thing)
+ if(QDELETED(target) || target.stat == DEAD || (!area_motion && !in_range(src, target)))
+ //If not part of a monitored area and the camera is not in range or the target is dead
+ lostTargetRef(thing)
-/obj/machinery/camera/proc/newTarget(var/mob/target)
- if(istype(target, /mob/living/silicon/ai)) return 0
+/obj/machinery/camera/proc/getTargetList()
+ if(area_motion)
+ return area_motion.motionTargets
+ return localMotionTargets
+
+/obj/machinery/camera/proc/newTarget(mob/target)
+ if(isAI(target))
+ return FALSE
if(detectTime == 0)
detectTime = world.time // start the clock
- if(!(target in motionTargets))
- motionTargets += target
- return 1
+ var/list/targets = getTargetList()
+ targets |= target.UID()
+ return TRUE
-/obj/machinery/camera/proc/lostTarget(var/mob/target)
- if(target in motionTargets)
- motionTargets -= target
- if(motionTargets.len == 0)
+/obj/machinery/camera/proc/lostTargetRef(uid)
+ var/list/targets = getTargetList()
+ targets -= uid
+ if(length(targets))
cancelAlarm()
/obj/machinery/camera/proc/cancelAlarm()
- if(!status || (stat & NOPOWER))
- return FALSE
- if(detectTime == -1 && is_station_contact(z))
- SSalarms.motion_alarm.clearAlarm(loc, src)
+ if(detectTime == -1)
+ if(status)
+ SSalarm.cancelAlarm("Motion", get_area(src), src)
detectTime = 0
return TRUE
/obj/machinery/camera/proc/triggerAlarm()
- if(!status || (stat & NOPOWER))
+ if(!detectTime)
return FALSE
- if(!detectTime || !is_station_contact(z))
- return FALSE
- SSalarms.motion_alarm.triggerAlarm(loc, src)
+ if(status)
+ SSalarm.triggerAlarm("Motion", get_area(src), list(UID()), src)
+ visible_message("A red light flashes on the [src]!")
detectTime = -1
return TRUE
diff --git a/code/game/machinery/camera/presets.dm b/code/game/machinery/camera/presets.dm
index b7ae75cd13f..33c970639e5 100644
--- a/code/game/machinery/camera/presets.dm
+++ b/code/game/machinery/camera/presets.dm
@@ -2,7 +2,7 @@
// EMP
-/obj/machinery/camera/emp_proof/Initialize()
+/obj/machinery/camera/emp_proof/Initialize(mapload)
. = ..()
upgradeEmpProof()
@@ -11,19 +11,23 @@
/obj/machinery/camera/xray
icon_state = "xraycam" // Thanks to Krutchen for the icons.
-/obj/machinery/camera/xray/Initialize()
+/obj/machinery/camera/xray/Initialize(mapload)
. = ..()
upgradeXRay()
// MOTION
+/obj/machinery/camera/motion
+ name = "motion-sensitive security camera"
-/obj/machinery/camera/motion/Initialize()
+/obj/machinery/camera/motion/Initialize(mapload)
. = ..()
upgradeMotion()
// ALL UPGRADES
+/obj/machinery/camera/all
+ icon_state = "xraycamera" //mapping icon.
-/obj/machinery/camera/all/Initialize()
+/obj/machinery/camera/all/Initialize(mapload)
. = ..()
upgradeEmpProof()
upgradeXRay()
@@ -78,6 +82,10 @@
// If you are upgrading Motion, and it isn't in the camera's New(), add it to the machines list.
/obj/machinery/camera/proc/upgradeMotion()
+ if(isMotion())
+ return
+ if(name == initial(name))
+ name = "motion-sensitive security camera"
assembly.upgrades.Add(new /obj/item/assembly/prox_sensor(assembly))
setPowerUsage()
// Add it to machines that process
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 123cee3ae4e..0be4aa0449b 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -1,84 +1,90 @@
-GLOBAL_LIST_EMPTY(priority_air_alarms)
-GLOBAL_LIST_EMPTY(minor_air_alarms)
-
-
/obj/machinery/computer/atmos_alert
name = "atmospheric alert computer"
desc = "Used to access the station's atmospheric sensors."
circuit = /obj/item/circuitboard/atmos_alert
+ var/ui_x = 350
+ var/ui_y = 300
icon_keyboard = "atmos_key"
icon_screen = "alert:0"
light_color = LIGHT_COLOR_CYAN
+ var/list/priority_alarms = list()
+ var/list/minor_alarms = list()
+ var/receive_frequency = ATMOS_FIRE_FREQ
+ var/datum/radio_frequency/radio_connection
-/obj/machinery/computer/atmos_alert/New()
- ..()
- SSalarms.atmosphere_alarm.register(src, /obj/machinery/computer/station_alert/.proc/update_icon)
+/obj/machinery/computer/atmos_alert/Initialize(mapload)
+ . = ..()
+ set_frequency(receive_frequency)
/obj/machinery/computer/atmos_alert/Destroy()
- SSalarms.atmosphere_alarm.unregister(src)
- return ..()
+ SSradio.remove_object(src, receive_frequency)
+ return ..()
/obj/machinery/computer/atmos_alert/attack_hand(mob/user)
- ui_interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/atmos_alert/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
+/obj/machinery/computer/atmos_alert/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
- ui = new(user, src, ui_key, "atmos_alert.tmpl", src.name, 500, 500)
+ ui = new(user, src, ui_key, "AtmosAlertConsole", name, ui_x, ui_y, master_ui, state)
ui.open()
- ui.set_auto_update(1)
-/obj/machinery/computer/atmos_alert/ui_data(mob/user, datum/topic_state/state)
- var/data[0]
- var/major_alarms[0]
- var/minor_alarms[0]
+/obj/machinery/computer/atmos_alert/tgui_data(mob/user)
+ var/list/data = list()
- for(var/datum/alarm/alarm in SSalarms.atmosphere_alarm.major_alarms())
- major_alarms[++major_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]")
-
- for(var/datum/alarm/alarm in SSalarms.atmosphere_alarm.minor_alarms())
- minor_alarms[++minor_alarms.len] = list("name" = sanitize(alarm.alarm_name()), "ref" = "\ref[alarm]")
-
- data["priority_alarms"] = major_alarms
- data["minor_alarms"] = minor_alarms
+ data["priority"] = list()
+ for(var/zone in priority_alarms)
+ data["priority"] |= zone
+ data["minor"] = list()
+ for(var/zone in minor_alarms)
+ data["minor"] |= zone
return data
-/obj/machinery/computer/atmos_alert/update_icon()
- var/list/alarms = SSalarms.atmosphere_alarm.major_alarms()
- if(alarms.len)
- icon_screen = "alert:2"
- else
- alarms = SSalarms.atmosphere_alarm.minor_alarms()
- if(alarms.len)
- icon_screen = "alert:1"
- else
- icon_screen = "alert:0"
- ..()
-
-/obj/machinery/computer/atmos_alert/Topic(href, href_list)
+/obj/machinery/computer/atmos_alert/tgui_act(action, params)
if(..())
- return 1
+ return
+ switch(action)
+ if("clear")
+ var/zone = params["zone"]
+ if(zone in priority_alarms)
+ to_chat(usr, "Priority alarm for [zone] cleared.")
+ priority_alarms -= zone
+ . = TRUE
+ if(zone in minor_alarms)
+ to_chat(usr, "Minor alarm for [zone] cleared.")
+ minor_alarms -= zone
+ . = TRUE
+ update_icon()
- if(href_list["clear_alarm"])
- var/datum/alarm/alarm = locate(href_list["clear_alarm"]) in SSalarms.atmosphere_alarm.alarms
- if(alarm)
- for(var/datum/alarm_source/alarm_source in alarm.sources)
- var/obj/machinery/alarm/air_alarm = alarm_source.source
- if(istype(air_alarm))
- var/list/new_ref = list("atmos_reset" = 1)
- air_alarm.Topic(href, new_ref, state = GLOB.air_alarm_topic)
- update_icon()
- return 1
+/obj/machinery/computer/atmos_alert/proc/set_frequency(new_frequency)
+ SSradio.remove_object(src, receive_frequency)
+ receive_frequency = new_frequency
+ radio_connection = SSradio.add_object(src, receive_frequency, RADIO_ATMOSIA)
-GLOBAL_DATUM_INIT(air_alarm_topic, /datum/topic_state/air_alarm_topic, new)
+/obj/machinery/computer/atmos_alert/receive_signal(datum/signal/signal)
+ if(!signal)
+ return
-/datum/topic_state/air_alarm_topic/href_list(var/mob/user)
- var/list/extra_href = list()
- extra_href["remote_connection"] = 1
- extra_href["remote_access"] = 1
+ var/zone = signal.data["zone"]
+ var/severity = signal.data["alert"]
- return extra_href
+ if(!zone || !severity)
+ return
-/datum/topic_state/air_alarm_topic/can_use_topic(var/src_object, var/mob/user)
- return STATUS_INTERACTIVE
+ minor_alarms -= zone
+ priority_alarms -= zone
+ if(severity == "severe")
+ priority_alarms += zone
+ else if(severity == "minor")
+ minor_alarms += zone
+ update_icon()
+
+/obj/machinery/computer/atmos_alert/update_icon()
+ if(length(priority_alarms))
+ icon_screen = "alert:2"
+ else if(length(minor_alarms))
+ icon_screen = "alert:1"
+ else
+ icon_screen = "alert:0"
+ ..()
diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm
index b502d6e5441..e91e631cc62 100644
--- a/code/game/machinery/computer/buildandrepair.dm
+++ b/code/game/machinery/computer/buildandrepair.dm
@@ -165,12 +165,9 @@
/obj/item/circuitboard/stationalert_engineering
name = "Circuit Board (Station Alert Console (Engineering))"
build_path = /obj/machinery/computer/station_alert
-/obj/item/circuitboard/stationalert_security
- name = "Circuit Board (Station Alert Console (Security))"
+/obj/item/circuitboard/stationalert
+ name = "Circuit Board (Station Alert Console)"
build_path = /obj/machinery/computer/station_alert
-/obj/item/circuitboard/stationalert_all
- name = "Circuit Board (Station Alert Console (All))"
- build_path = /obj/machinery/computer/station_alert/all
/obj/item/circuitboard/atmos_alert
name = "Circuit Board (Atmospheric Alert Computer)"
build_path = /obj/machinery/computer/atmos_alert
@@ -236,7 +233,10 @@
/obj/item/circuitboard/brigcells
name = "Circuit board (Brig Cell Control)"
build_path = /obj/machinery/computer/brigcells
-
+/obj/item/circuitboard/sm_monitor
+ name = "Circuit board (Supermatter Monitoring Console)"
+ build_path = /obj/machinery/computer/sm_monitor
+ origin_tech = "programming=2;powerstorage=2"
// RD console circuits, so that {de,re}constructing one of the special consoles doesn't ruin everything forever
/obj/item/circuitboard/rdconsole
diff --git a/code/game/machinery/computer/sm_monitor.dm b/code/game/machinery/computer/sm_monitor.dm
new file mode 100644
index 00000000000..f2e01e6c4fa
--- /dev/null
+++ b/code/game/machinery/computer/sm_monitor.dm
@@ -0,0 +1,147 @@
+/obj/machinery/computer/sm_monitor
+ name = "supermatter monitoring console"
+ desc = "Used to monitor supermatter shards."
+ icon_keyboard = "power_key"
+ icon_screen = "smmon_0"
+ circuit = /obj/item/circuitboard/sm_monitor
+ light_color = LIGHT_COLOR_YELLOW
+ /// Cache-list of all supermatter shards
+ var/list/supermatters
+ /// Last status of the active supermatter for caching purposes
+ var/last_status
+ /// Reference to the active shard
+ var/obj/machinery/power/supermatter_shard/active
+
+/obj/machinery/computer/sm_monitor/Destroy()
+ active = null
+ return ..()
+
+/obj/machinery/computer/sm_monitor/attack_ai(mob/user)
+ attack_hand(user)
+
+/obj/machinery/computer/sm_monitor/attack_hand(mob/user)
+ add_fingerprint(user)
+ if(stat & (BROKEN|NOPOWER))
+ return
+ tgui_interact(user)
+
+/obj/machinery/computer/sm_monitor/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "SupermatterMonitor", name, 600, 325, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/sm_monitor/tgui_data(mob/user)
+ var/list/data = list()
+
+ if(istype(active))
+ var/turf/T = get_turf(active)
+ // If we somehow delam during this proc, handle it somewhat
+ if(!T)
+ active = null
+ refresh()
+ return
+ var/datum/gas_mixture/air = T.return_air()
+ if(!air)
+ active = null
+ return
+
+ data["active"] = TRUE
+ data["SM_integrity"] = active.get_integrity()
+ data["SM_power"] = active.power
+ data["SM_ambienttemp"] = air.temperature
+ data["SM_ambientpressure"] = air.return_pressure()
+ //data["SM_EPR"] = round((air.total_moles / air.group_multiplier) / 23.1, 0.01)
+ var/other_moles = air.total_trace_moles()
+ var/TM = air.total_moles()
+ if(TM)
+ data["SM_gas_O2"] = round(100*air.oxygen/TM, 0.01)
+ data["SM_gas_CO2"] = round(100*air.carbon_dioxide/TM, 0.01)
+ data["SM_gas_N2"] = round(100*air.nitrogen/TM, 0.01)
+ data["SM_gas_PL"] = round(100*air.toxins/TM, 0.01)
+ if(other_moles)
+ data["SM_gas_OTHER"] = round(100 * other_moles / TM, 0.01)
+ else
+ data["SM_gas_OTHER"] = 0
+ else
+ data["SM_gas_O2"] = 0
+ data["SM_gas_CO2"] = 0
+ data["SM_gas_N2"] = 0
+ data["SM_gas_PH"] = 0
+ data["SM_gas_OTHER"] = 0
+ else
+ var/list/SMS = list()
+ for(var/I in supermatters)
+ var/obj/machinery/power/supermatter_shard/S = I
+ var/area/A = get_area(S)
+ if(!A)
+ continue
+
+ SMS.Add(list(list(
+ "area_name" = A.name,
+ "integrity" = S.get_integrity(),
+ "uid" = S.UID()
+ )))
+
+ data["active"] = FALSE
+ data["supermatters"] = SMS
+
+ return data
+
+/**
+ * Supermatter List Refresher
+ *
+ * This proc loops through the list of supermatters in the atmos SS and adds them to this console's cache list
+ */
+/obj/machinery/computer/sm_monitor/proc/refresh()
+ supermatters = list()
+ var/turf/T = get_turf(tgui_host()) // Get the TGUI host incase this ever turned into a supermatter monitoring module for AIs to use or something
+ if(!T)
+ return
+ for(var/obj/machinery/power/supermatter_shard/S in SSair.atmos_machinery)
+ // Delaminating, not within coverage, not on a tile.
+ if(!(is_station_level(S.z) || is_mining_level(S.z) || atoms_share_level(S, T) || !istype(S.loc, /turf/simulated/)))
+ continue
+ supermatters.Add(S)
+
+ if(!(active in supermatters))
+ active = null
+
+/obj/machinery/computer/sm_monitor/process()
+ if(stat & (NOPOWER|BROKEN))
+ return FALSE
+
+ if(active)
+ var/new_status = active.get_status()
+ if(last_status != new_status)
+ last_status = new_status
+ if(last_status == SUPERMATTER_ERROR)
+ last_status = SUPERMATTER_INACTIVE
+ icon_screen = "smmon_[last_status]"
+ update_icon()
+
+ return TRUE
+
+/obj/machinery/computer/sm_monitor/tgui_act(action, params)
+ if(..())
+ return
+
+ if(stat & (BROKEN|NOPOWER))
+ return
+
+ . = TRUE
+
+ switch(action)
+ if("refresh")
+ refresh()
+
+ if("view")
+ var/newuid = params["view"]
+ for(var/obj/machinery/power/supermatter_shard/S in supermatters)
+ if(S.UID() == newuid)
+ active = S
+ break
+
+ if("back")
+ active = null
+
diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm
index 607918442ea..3b4631b171a 100644
--- a/code/game/machinery/computer/station_alert.dm
+++ b/code/game/machinery/computer/station_alert.dm
@@ -6,48 +6,91 @@
icon_screen = "alert:0"
light_color = LIGHT_COLOR_CYAN
circuit = /obj/item/circuitboard/stationalert_engineering
- var/datum/nano_module/alarm_monitor/alarm_monitor
- var/monitor_type = /datum/nano_module/alarm_monitor/engineering
+ var/ui_x = 325
+ var/ui_y = 500
+ var/list/alarms_listend_for = list("Fire", "Atmosphere", "Power")
-/obj/machinery/computer/station_alert/security
- monitor_type = /datum/nano_module/alarm_monitor/security
- circuit = /obj/item/circuitboard/stationalert_security
-
-/obj/machinery/computer/station_alert/all
- monitor_type = /datum/nano_module/alarm_monitor/all
- circuit = /obj/item/circuitboard/stationalert_all
-
-/obj/machinery/computer/station_alert/New()
- ..()
- alarm_monitor = new monitor_type(src)
- alarm_monitor.register(src, /obj/machinery/computer/station_alert/.proc/update_icon)
+/obj/machinery/computer/station_alert/Initialize(mapload)
+ . = ..()
+ GLOB.alert_consoles += src
+ RegisterSignal(SSalarm, COMSIG_TRIGGERED_ALARM, .proc/alarm_triggered)
+ RegisterSignal(SSalarm, COMSIG_CANCELLED_ALARM, .proc/alarm_cancelled)
/obj/machinery/computer/station_alert/Destroy()
- alarm_monitor.unregister(src)
- QDEL_NULL(alarm_monitor)
+ GLOB.alert_consoles -= src
return ..()
/obj/machinery/computer/station_alert/attack_ai(mob/user)
add_fingerprint(user)
if(stat & (BROKEN|NOPOWER))
return
- interact(user)
+ tgui_interact(user)
/obj/machinery/computer/station_alert/attack_hand(mob/user)
add_fingerprint(user)
if(stat & (BROKEN|NOPOWER))
return
- interact(user)
+ tgui_interact(user)
-/obj/machinery/computer/station_alert/interact(mob/user)
- alarm_monitor.ui_interact(user)
+/obj/machinery/computer/station_alert/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "StationAlertConsole", name, ui_x, ui_y, master_ui, state)
+ ui.open()
+
+/obj/machinery/computer/station_alert/tgui_data(mob/user)
+ var/list/data = list()
+
+ data["alarms"] = list()
+ for(var/class in SSalarm.alarms)
+ if(!(class in alarms_listend_for))
+ continue
+ data["alarms"][class] = list()
+ for(var/area in SSalarm.alarms[class])
+ for(var/thing in SSalarm.alarms[class][area][3])
+ var/atom/A = locateUID(thing)
+ if(atoms_share_level(A, src))
+ data["alarms"][class] += area
+
+ return data
+
+/obj/machinery/computer/station_alert/proc/alarm_triggered(src, class, area/A, list/O, obj/alarmsource)
+ if(!(class in alarms_listend_for))
+ return
+ if(alarmsource.z != z)
+ return
+ if(stat & (BROKEN))
+ return
+ update_icon()
+
+/obj/machinery/computer/station_alert/proc/alarm_cancelled(src, class, area/A, obj/origin, cleared)
+ if(!(class in alarms_listend_for))
+ return
+ if(origin.z != z)
+ return
+ if(stat & (BROKEN))
+ return
+ update_icon()
/obj/machinery/computer/station_alert/update_icon()
- if(alarm_monitor)
- var/list/alarms = alarm_monitor.major_alarms()
- if(alarms.len)
- icon_screen = "alert:2"
- else
- icon_screen = "alert:0"
+ var/active_alarms = FALSE
+ var/list/list/temp_alarm_list = SSalarm.alarms.Copy()
+ for(var/cat in temp_alarm_list)
+ if(!(cat in alarms_listend_for))
+ continue
+ var/list/list/L = temp_alarm_list[cat].Copy()
+ for(var/alarm in L)
+ var/list/list/alm = L[alarm].Copy()
+ var/list/list/sources = alm[3].Copy()
+ for(var/thing in sources)
+ var/atom/A = locateUID(thing)
+ if(A && A.z != z)
+ L -= alarm
+ if(length(L))
+ active_alarms = TRUE
+ if(active_alarms)
+ icon_screen = "alert:2"
+ else
+ icon_screen = "alert:0"
..()
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 42ff08ff00f..d2e983b3eb3 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -142,6 +142,7 @@ About the new airlock wires panel:
break
/obj/machinery/door/airlock/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(electronics)
QDEL_NULL(wires)
QDEL_NULL(note)
@@ -188,10 +189,6 @@ About the new airlock wires panel:
return 1
return 0
-/obj/machinery/door/airlock/proc/isWireCut(wireIndex)
- // You can find the wires in the datum folder.
- return wires.IsIndexCut(wireIndex)
-
/obj/machinery/door/airlock/proc/canAIControl()
return ((aiControlDisabled!=1) && (!isAllPowerLoss()))
@@ -204,27 +201,21 @@ About the new airlock wires panel:
return (main_power_lost_until==0 || backup_power_lost_until==0)
/obj/machinery/door/airlock/requiresID()
- return !(isWireCut(AIRLOCK_WIRE_IDSCAN) || aiDisabledIdScanner)
+ return !(wires.is_cut(WIRE_IDSCAN) || aiDisabledIdScanner)
/obj/machinery/door/airlock/proc/isAllPowerLoss()
if(stat & (NOPOWER|BROKEN))
return 1
- if(mainPowerCablesCut() && backupPowerCablesCut())
+ if(wires.is_cut(WIRE_MAIN_POWER1) && wires.is_cut(WIRE_BACKUP_POWER1))
return 1
return 0
-/obj/machinery/door/airlock/proc/mainPowerCablesCut()
- return isWireCut(AIRLOCK_WIRE_MAIN_POWER1)
-
-/obj/machinery/door/airlock/proc/backupPowerCablesCut()
- return isWireCut(AIRLOCK_WIRE_BACKUP_POWER1)
-
/obj/machinery/door/airlock/proc/loseMainPower()
- main_power_lost_until = mainPowerCablesCut() ? -1 : world.time + 60 SECONDS
+ main_power_lost_until = wires.is_cut(WIRE_MAIN_POWER1) ? -1 : world.time + 60 SECONDS
if(main_power_lost_until > 0)
main_power_timer = addtimer(CALLBACK(src, .proc/regainMainPower), 60 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
// If backup power is permanently disabled then activate in 10 seconds if possible, otherwise it's already enabled or a timer is already running
- if(backup_power_lost_until == -1 && !backupPowerCablesCut())
+ if(backup_power_lost_until == -1 && !wires.is_cut(WIRE_BACKUP_POWER1))
backup_power_lost_until = world.time + 10 SECONDS
backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), 10 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
// Disable electricity if required
@@ -232,7 +223,7 @@ About the new airlock wires panel:
electrify(0)
/obj/machinery/door/airlock/proc/loseBackupPower()
- backup_power_lost_until = backupPowerCablesCut() ? -1 : world.time + 60 SECONDS
+ backup_power_lost_until = wires.is_cut(WIRE_BACKUP_POWER1) ? -1 : world.time + 60 SECONDS
if(backup_power_lost_until > 0)
backup_power_timer = addtimer(CALLBACK(src, .proc/regainBackupPower), 60 SECONDS, TIMER_UNIQUE | TIMER_STOPPABLE)
@@ -243,7 +234,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/regainMainPower()
main_power_timer = null
- if(!mainPowerCablesCut())
+ if(!wires.is_cut(WIRE_MAIN_POWER1))
main_power_lost_until = 0
// If backup power is currently active then disable, otherwise let it count down and disable itself later
if(!backup_power_lost_until)
@@ -253,7 +244,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/regainBackupPower()
backup_power_timer = null
- if(!backupPowerCablesCut())
+ if(!wires.is_cut(WIRE_BACKUP_POWER1))
// Restore backup power only if main power is offline, otherwise permanently disable
backup_power_lost_until = main_power_lost_until == 0 ? -1 : 0
update_icon()
@@ -264,7 +255,7 @@ About the new airlock wires panel:
electrified_timer = null
var/message = ""
- if(isWireCut(AIRLOCK_WIRE_ELECTRIFY) && arePowerSystemsOn())
+ if(wires.is_cut(WIRE_ELECTRIFY) && arePowerSystemsOn())
message = text("The electrification wire is cut - Door permanently electrified.")
electrified_until = -1
else if(duration && !arePowerSystemsOn())
@@ -763,7 +754,7 @@ About the new airlock wires panel:
var/activate = text2num(href_list["activate"])
switch(href_list["command"])
if("idscan")
- if(isWireCut(AIRLOCK_WIRE_IDSCAN))
+ if(wires.is_cut(WIRE_IDSCAN))
to_chat(usr, "The IdScan wire has been cut - IdScan feature permanently disabled.")
else if(activate && aiDisabledIdScanner)
aiDisabledIdScanner = 0
@@ -780,14 +771,14 @@ About the new airlock wires panel:
loseBackupPower()
update_icon()
if("bolts")
- if(isWireCut(AIRLOCK_WIRE_DOOR_BOLTS))
+ if(wires.is_cut(WIRE_DOOR_BOLTS))
to_chat(usr, "The door bolt control wire has been cut - Door bolts permanently dropped.")
else if(activate && lock())
to_chat(usr, "The door bolts have been dropped.")
else if(!activate && unlock())
to_chat(usr, "The door bolts have been raised.")
if("electrify_temporary")
- if(activate && isWireCut(AIRLOCK_WIRE_ELECTRIFY))
+ if(activate && wires.is_cut(WIRE_ELECTRIFY))
to_chat(usr, text("The electrification wire is cut - Door permanently electrified."))
else if(!activate && electrified_until != 0)
to_chat(usr, "The door is now un-electrified.")
@@ -799,7 +790,7 @@ About the new airlock wires panel:
to_chat(usr, "The door is now electrified for thirty seconds.")
electrify(30)
if("electrify_permanently")
- if(isWireCut(AIRLOCK_WIRE_ELECTRIFY))
+ if(wires.is_cut(WIRE_ELECTRIFY))
to_chat(usr, text("The electrification wire is cut - Cannot electrify the door."))
else if(!activate && electrified_until != 0)
to_chat(usr, "The door is now un-electrified.")
@@ -821,7 +812,7 @@ About the new airlock wires panel:
close()
if("safeties")
// Safeties! We don't need no stinking safeties!
- if(isWireCut(AIRLOCK_WIRE_SAFETY))
+ if(wires.is_cut(WIRE_SAFETY))
to_chat(usr, text("The safety wire is cut - Cannot secure the door."))
else if(activate && safe)
safe = 0
@@ -829,7 +820,7 @@ About the new airlock wires panel:
safe = 1
if("timing")
// Door speed control
- if(isWireCut(AIRLOCK_WIRE_SPEED))
+ if(wires.is_cut(WIRE_SPEED))
to_chat(usr, text("The timing wire is cut - Cannot alter timing."))
else if(activate && normalspeed)
normalspeed = 0
@@ -837,7 +828,7 @@ About the new airlock wires panel:
normalspeed = 1
if("lights")
// Bolt lights
- if(isWireCut(AIRLOCK_WIRE_LIGHT))
+ if(wires.is_cut(WIRE_BOLT_LIGHT))
to_chat(usr, "The bolt lights wire has been cut - The door bolt lights are permanently disabled.")
else if(!activate && lights)
lights = 0
@@ -1126,7 +1117,7 @@ About the new airlock wires panel:
if(operating || welded || locked || emagged)
return 0
if(!forced)
- if(!arePowerSystemsOn() || isWireCut(AIRLOCK_WIRE_OPEN_DOOR))
+ if(!arePowerSystemsOn() || wires.is_cut(WIRE_OPEN_DOOR))
return 0
use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people
if(forced)
@@ -1163,7 +1154,7 @@ About the new airlock wires panel:
if(!forced)
//despite the name, this wire is for general door control.
//Bolts are already covered by the check for locked, above
- if(!arePowerSystemsOn() || isWireCut(AIRLOCK_WIRE_OPEN_DOOR))
+ if(!arePowerSystemsOn() || wires.is_cut(WIRE_OPEN_DOOR))
return
if(safe)
for(var/turf/turf in locs)
@@ -1219,7 +1210,7 @@ About the new airlock wires panel:
return
if(!forced)
- if(operating || !arePowerSystemsOn() || isWireCut(AIRLOCK_WIRE_DOOR_BOLTS))
+ if(operating || !arePowerSystemsOn() || wires.is_cut(WIRE_DOOR_BOLTS))
return
locked = 0
@@ -1314,7 +1305,7 @@ About the new airlock wires panel:
stat |= BROKEN
if(!panel_open)
panel_open = TRUE
- wires.CutAll()
+ wires.cut_all()
update_icon()
/obj/machinery/door/airlock/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
@@ -1407,6 +1398,12 @@ About the new airlock wires panel:
A.name = name
qdel(src)
+/obj/machinery/door/airlock/proc/ai_control_callback()
+ if(aiControlDisabled == 1)
+ aiControlDisabled = 0
+ else if(aiControlDisabled == 2)
+ aiControlDisabled = -1
+
#undef AIRLOCK_CLOSED
#undef AIRLOCK_CLOSING
#undef AIRLOCK_OPEN
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index 37dbdb6c9c0..c509675868b 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -28,6 +28,11 @@
var/nextstate = null
var/boltslocked = TRUE
var/active_alarm = FALSE
+ var/list/affecting_areas
+
+/obj/machinery/door/firedoor/Initialize(mapload)
+ . = ..()
+ CalculateAffectingAreas()
/obj/machinery/door/firedoor/examine(mob/user)
. = ..()
@@ -40,11 +45,31 @@
else
. += "The bolt locks have been unscrewed, but the bolts themselves are still wrenched to the floor."
+/obj/machinery/door/firedoor/proc/CalculateAffectingAreas()
+ remove_from_areas()
+ affecting_areas = get_adjacent_open_areas(src) | get_area(src)
+ for(var/I in affecting_areas)
+ var/area/A = I
+ LAZYADD(A.firedoors, src)
+
/obj/machinery/door/firedoor/closed
icon_state = "door_closed"
opacity = TRUE
density = TRUE
+//see also turf/AfterChange for adjacency shennanigans
+
+/obj/machinery/door/firedoor/proc/remove_from_areas()
+ if(affecting_areas)
+ for(var/I in affecting_areas)
+ var/area/A = I
+ LAZYREMOVE(A.firedoors, src)
+
+/obj/machinery/door/firedoor/Destroy()
+ remove_from_areas()
+ affecting_areas.Cut()
+ return ..()
+
/obj/machinery/door/firedoor/Bumped(atom/AM)
if(panel_open || operating)
return
diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm
index f12361f1664..b387f404d8c 100644
--- a/code/game/machinery/firealarm.dm
+++ b/code/game/machinery/firealarm.dm
@@ -25,6 +25,11 @@ FIRE ALARM
active_power_usage = 6
power_channel = ENVIRON
resistance_flags = FIRE_PROOF
+
+ light_power = 0
+ light_range = 7
+ light_color = "#ff3232"
+
var/last_process = 0
var/wiresexposed = 0
var/buildstage = 2 // 2 = complete, 1 = no wires, 0 = circuit gone
@@ -191,6 +196,7 @@ FIRE ALARM
/obj/machinery/firealarm/obj_break(damage_flag)
if(!(stat & BROKEN) && !(flags & NODECONSTRUCT) && buildstage != 0) //can't break the electronics if there isn't any inside.
stat |= BROKEN
+ LAZYREMOVE(myArea.firealarms, src)
update_icon()
/obj/machinery/firealarm/deconstruct(disassembled = TRUE)
@@ -203,6 +209,14 @@ FIRE ALARM
new /obj/item/stack/cable_coil(loc, 3)
qdel(src)
+/obj/machinery/firealarm/proc/update_fire_light(fire)
+ if(fire == !!light_power)
+ return // do nothing if we're already active
+ if(fire)
+ set_light(l_power = 0.8)
+ else
+ set_light(l_power = 0)
+
/obj/machinery/firealarm/process()//Note: this processing was mostly phased out due to other code, and only runs when needed
if(stat & (NOPOWER|BROKEN))
return
@@ -286,26 +300,16 @@ FIRE ALARM
time = min(max(round(time), 0), 120)
/obj/machinery/firealarm/proc/reset()
- if(!working)
+ if(!working || !report_fire_alarms)
return
var/area/A = get_area(src)
- A.fire_reset()
+ A.firereset(src)
- for(var/obj/machinery/firealarm/FA in A)
- if(is_station_contact(z) && FA.report_fire_alarms)
- SSalarms.fire_alarm.clearAlarm(loc, FA)
-
-/obj/machinery/firealarm/proc/alarm(var/duration = 0)
- if(!working)
+/obj/machinery/firealarm/proc/alarm()
+ if(!working || !report_fire_alarms)
return
-
var/area/A = get_area(src)
- for(var/obj/machinery/firealarm/FA in A)
- if(is_station_contact(z) && FA.report_fire_alarms)
- SSalarms.fire_alarm.triggerAlarm(loc, FA, duration)
- else
- A.fire_alert() // Manually trigger alarms if the alarm isn't reported
-
+ A.firealert(src) // Manually trigger alarms if the alarm isn't reported
update_icon()
/obj/machinery/firealarm/New(location, direction, building)
@@ -323,8 +327,14 @@ FIRE ALARM
else
overlays += image('icons/obj/monitors.dmi', "overlay_green")
+ myArea = get_area(src)
+ LAZYADD(myArea.firealarms, src)
update_icon()
+/obj/machinery/firealarm/Destroy()
+ LAZYREMOVE(myArea.firealarms, src)
+ return ..()
+
/*
FIRE ALARM CIRCUIT
Just a object used in constructing fire alarms
diff --git a/code/game/machinery/slotmachine.dm b/code/game/machinery/slotmachine.dm
index 5952108b6a9..1624ef4bb92 100644
--- a/code/game/machinery/slotmachine.dm
+++ b/code/game/machinery/slotmachine.dm
@@ -12,29 +12,26 @@
var/resultlvl = null
/obj/machinery/slot_machine/attack_hand(mob/user as mob)
+ tgui_interact(user)
+
+/obj/machinery/slot_machine/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, src, ui_key, "SlotMachine", name, 350, 200, master_ui, state)
+ ui.open()
+
+/obj/machinery/slot_machine/tgui_data(mob/user)
+ var/list/data = list()
+ // Get account
account = user.get_worn_id_account()
if(!account)
if(istype(user.get_active_hand(), /obj/item/card/id))
account = get_card_account(user.get_active_hand())
else
account = null
- ui_interact(user)
-/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I)
- . = TRUE
- if(!I.tool_use_check(user, 0))
- return
- default_unfasten_wrench(user, I)
-/obj/machinery/slot_machine/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, src, ui_key, "slotmachine.tmpl", name, 350, 200)
- ui.open()
- ui.set_auto_update(1)
-
-/obj/machinery/slot_machine/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
+ // Send data
data["working"] = working
data["money"] = account ? account.money : null
data["plays"] = plays
@@ -42,22 +39,23 @@
data["resultlvl"] = resultlvl
return data
-/obj/machinery/slot_machine/Topic(href, href_list)
+/obj/machinery/slot_machine/tgui_act(action, params)
+ if(..())
+ return
add_fingerprint(usr)
- if(href_list["ops"])
- if(text2num(href_list["ops"])) // Play
- if(working)
- return
- if(!account || account.money < 10)
- return
- if(!account.charge(10, null, "Bet", "Slot Machine", "Slot Machine"))
- return
- plays++
- working = 1
- icon_state = "slots-on"
- playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
- addtimer(CALLBACK(src, .proc/spin_slots, usr.name), 25)
+ if(action == "spin")
+ if(working)
+ return
+ if(!account || account.money < 10)
+ return
+ if(!account.charge(10, null, "Bet", "Slot Machine", "Slot Machine"))
+ return
+ plays++
+ working = TRUE
+ icon_state = "slots-on"
+ playsound(src.loc, 'sound/machines/ding.ogg', 50, 1)
+ addtimer(CALLBACK(src, .proc/spin_slots, usr.name), 25)
/obj/machinery/slot_machine/proc/spin_slots(userName)
switch(rand(1,4050))
@@ -65,44 +63,45 @@
atom_say("JACKPOT! [userName] has won a MILLION CREDITS!")
GLOB.event_announcement.Announce("Congratulations to [userName] on winning the Jackpot of ONE MILLION CREDITS!", "Jackpot Winner")
result = "JACKPOT! You win one million credits!"
- resultlvl = "highlight"
+ resultlvl = "teal"
win_money(1000000, 'sound/goonstation/misc/airraid_loop.ogg')
if(2 to 5) // .07%
atom_say("Big Winner! [userName] has won a hundred thousand credits!")
GLOB.event_announcement.Announce("Congratulations to [userName] on winning a hundred thousand credits!", "Big Winner")
result = "Big Winner! You win a hundred thousand credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(100000, 'sound/goonstation/misc/klaxon.ogg')
if(6 to 50) // 1.08%
atom_say("Big Winner! [userName] has won ten thousand credits!")
result = "You win ten thousand credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(10000, 'sound/goonstation/misc/klaxon.ogg')
if(51 to 100) // 1.21%
atom_say("Winner! [userName] has won a thousand credits!")
result = "You win a thousand credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(1000, 'sound/goonstation/misc/bell.ogg')
if(101 to 200) // 2.44%
atom_say("Winner! [userName] has won a hundred credits!")
result = "You win a hundred credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(100, 'sound/goonstation/misc/bell.ogg')
if(201 to 300) // 2.44%
atom_say("Winner! [userName] has won fifty credits!")
result = "You win fifty credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(50)
if(301 to 1000) // 17.26%
atom_say("Winner! [userName] has won ten credits!")
result = "You win ten credits!"
- resultlvl = "good"
+ resultlvl = "green"
win_money(10)
else // 75.31%
- result = "No luck!"
- resultlvl = "average"
- working = 0
+ result = "No luck!"
+ resultlvl = "orange"
+ working = FALSE
icon_state = "slots-off"
+ SStgui.update_uis(src) // Push a UI update
/obj/machinery/slot_machine/proc/win_money(amt, sound='sound/machines/ping.ogg')
if(sound)
@@ -110,3 +109,9 @@
if(!account)
return
account.credit(amt, "Slot Winnings", "Slot Machine", account.owner_name)
+
+/obj/machinery/slot_machine/wrench_act(mob/user, obj/item/I)
+ . = TRUE
+ if(!I.tool_use_check(user, 0))
+ return
+ default_unfasten_wrench(user, I)
diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm
index cd6b33d0a95..822c4af3e1a 100644
--- a/code/game/machinery/suit_storage_unit.dm
+++ b/code/game/machinery/suit_storage_unit.dm
@@ -255,6 +255,7 @@
occupant_typecache = typecacheof(occupant_typecache)
/obj/machinery/suit_storage_unit/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(suit)
QDEL_NULL(helmet)
QDEL_NULL(mask)
@@ -802,3 +803,7 @@
/obj/machinery/suit_storage_unit/attack_ai(mob/user as mob)
return attack_hand(user)
+
+/obj/machinery/suit_storage_unit/proc/check_electrified_callback()
+ if(!wires.is_cut(WIRE_ELECTRIFY))
+ shocked = FALSE
diff --git a/code/game/machinery/syndicatebomb.dm b/code/game/machinery/syndicatebomb.dm
index 181d5e494da..2a476c215ef 100644
--- a/code/game/machinery/syndicatebomb.dm
+++ b/code/game/machinery/syndicatebomb.dm
@@ -91,6 +91,7 @@
..()
/obj/machinery/syndicatebomb/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
QDEL_NULL(countdown)
STOP_PROCESSING(SSfastprocess, src)
@@ -174,7 +175,7 @@
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
- if(open_panel && wires.IsAllCut())
+ if(open_panel && wires.is_all_cut())
if(payload)
to_chat(user, "You carefully pry out [payload].")
payload.loc = user.loc
@@ -188,7 +189,7 @@
/obj/machinery/syndicatebomb/welder_act(mob/user, obj/item/I)
. = TRUE
- if(payload || !wires.IsAllCut() || !open_panel)
+ if(payload || !wires.is_all_cut() || !open_panel)
return
if(!I.tool_use_check(user, 0))
return
@@ -264,9 +265,6 @@
investigate_log("[key_name(user)] has has primed a [name] ([payload]) for detonation at [A.name] [COORD(bombturf)]", INVESTIGATE_BOMB)
payload.adminlog = "\The [src] that [key_name(user)] had primed detonated!"
-/obj/machinery/syndicatebomb/proc/isWireCut(var/index)
- return wires.IsIndexCut(index)
-
///Bomb Subtypes///
/obj/machinery/syndicatebomb/training
@@ -297,7 +295,7 @@
/obj/machinery/syndicatebomb/empty/New()
..()
- wires.CutAll()
+ wires.cut_all()
/obj/machinery/syndicatebomb/self_destruct
name = "self destruct device"
@@ -368,7 +366,7 @@
var/obj/machinery/syndicatebomb/holder = loc
if(istype(holder))
if(holder.wires)
- holder.wires.Shuffle()
+ holder.wires.shuffle_wires()
holder.defused = 0
holder.open_panel = 0
holder.delayedbig = FALSE
diff --git a/code/game/machinery/transformer.dm b/code/game/machinery/transformer.dm
index cc9fc3e4666..18abeba7400 100644
--- a/code/game/machinery/transformer.dm
+++ b/code/game/machinery/transformer.dm
@@ -6,17 +6,45 @@
layer = MOB_LAYER+1 // Overhead
anchored = 1
density = 1
- var/transform_dead = 0
- var/transform_standing = 0
- var/cooldown_duration = 600 // 1 minute
- var/cooldown = 0
- var/robot_cell_charge = 5000
+ /// TRUE if the factory can transform dead mobs.
+ var/transform_dead = TRUE
+ /// TRUE if the mob can be standing and still be transformed.
+ var/transform_standing = TRUE
+ /// Cooldown between each transformation, in deciseconds.
+ var/cooldown_duration = 1 MINUTES
+ /// If the factory is currently on cooldown from its last transformation.
+ var/is_on_cooldown = FALSE
+ /// The type of cell that newly created borgs get.
+ var/robot_cell_type = /obj/item/stock_parts/cell/high/plus
+ /// The direction that mobs must moving in to get transformed.
var/acceptdir = EAST
+ /// The AI who placed this factory.
+ var/mob/living/silicon/ai/masterAI
-/obj/machinery/transformer/New()
- // On us
- ..()
- new /obj/machinery/conveyor/auto(loc, WEST)
+/obj/machinery/transformer/Initialize(mapload, mob/living/silicon/ai/_ai = null)
+ . = ..()
+ if(_ai)
+ masterAI = _ai
+ initialize_belts()
+
+/// Used to create all of the belts the transformer will be using. All belts should be pushing `WEST`.
+/obj/machinery/transformer/proc/initialize_belts()
+ var/turf/T = get_turf(src)
+ if(!T)
+ return
+
+ // Belt under the factory.
+ new /obj/machinery/conveyor/auto(T, WEST)
+
+ // Get the turf 1 tile to the EAST.
+ var/turf/east = locate(T.x + 1, T.y, T.z)
+ if(istype(east, /turf/simulated/floor))
+ new /obj/machinery/conveyor/auto(east, WEST)
+
+ // Get the turf 1 tile to the WEST.
+ var/turf/west = locate(T.x - 1, T.y, T.z)
+ if(istype(west, /turf/simulated/floor))
+ new /obj/machinery/conveyor/auto(west, WEST)
/obj/machinery/transformer/power_change()
..()
@@ -24,7 +52,7 @@
/obj/machinery/transformer/update_icon()
..()
- if(stat & (BROKEN|NOPOWER) || cooldown == 1)
+ if(is_on_cooldown || stat & (BROKEN|NOPOWER))
icon_state = "separator-AO0"
else
icon_state = initial(icon_state)
@@ -35,120 +63,76 @@
C.setDir(newdir)
acceptdir = turn(newdir, 180)
-/obj/machinery/transformer/Bumped(var/atom/movable/AM)
+/// Resets `is_on_cooldown` to `FALSE` and updates our icon. Used in a callback after the transformer does a transformation.
+/obj/machinery/transformer/proc/reset_cooldown()
+ is_on_cooldown = FALSE
+ update_icon()
- if(cooldown == 1)
+/obj/machinery/transformer/Bumped(atom/movable/AM)
+ // They have to be human to be transformed.
+ if(is_on_cooldown || !ishuman(AM))
return
- // Crossed didn't like people lying down.
- if(ishuman(AM))
- // Only humans can enter from the west side, while lying down.
- var/move_dir = get_dir(loc, AM.loc)
- var/mob/living/carbon/human/H = AM
- if((transform_standing || H.lying) && move_dir == acceptdir)// || move_dir == WEST)
- AM.loc = src.loc
- do_transform(AM)
+ var/mob/living/carbon/human/H = AM
+ var/move_dir = get_dir(loc, H.loc)
-/obj/machinery/transformer/proc/do_transform(var/mob/living/carbon/human/H)
- if(stat & (BROKEN|NOPOWER))
- return
- if(cooldown == 1)
+ if((transform_standing || H.lying) && move_dir == acceptdir)
+ H.forceMove(drop_location())
+ do_transform(H)
+
+/// Transforms a human mob into a cyborg, connects them to the malf AI which placed the factory.
+/obj/machinery/transformer/proc/do_transform(mob/living/carbon/human/H)
+ if(is_on_cooldown || stat & (BROKEN|NOPOWER))
return
if(!transform_dead && H.stat == DEAD)
- playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
+ playsound(loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
return
- playsound(src.loc, 'sound/items/welder.ogg', 50, 1)
- H.emote("scream") // It is painful
- H.adjustBruteLoss(max(0, 80 - H.getBruteLoss())) // Hurt the human, don't try to kill them though.
-
- // Sleep for a couple of ticks to allow the human to see the pain
- sleep(5)
-
+ playsound(loc, 'sound/items/welder.ogg', 50, 1)
use_power(5000) // Use a lot of power.
- var/mob/living/silicon/robot/R = H.Robotize(1) // Delete the items or they'll all pile up in a single tile and lag
-
- R.cell.maxcharge = robot_cell_charge
- R.cell.charge = robot_cell_charge
-
- // So he can't jump out the gate right away.
- R.lockcharge = !R.lockcharge
- spawn(50)
- playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
- sleep(30)
- if(R)
- R.lockcharge = !R.lockcharge
- R.notify_ai(1)
// Activate the cooldown
- cooldown = 1
+ is_on_cooldown = TRUE
update_icon()
- spawn(cooldown_duration)
- cooldown = 0
- update_icon()
-
-/obj/machinery/transformer/conveyor/New()
- ..()
- var/turf/T = loc
- if(T)
- // Spawn Conveyour Belts
-
- //East
- var/turf/east = locate(T.x + 1, T.y, T.z)
- if(istype(east, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(east, WEST)
-
- // West
- var/turf/west = locate(T.x - 1, T.y, T.z)
- if(istype(west, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(west, WEST)
+ addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown_duration)
+ addtimer(CALLBACK(null, .proc/playsound, loc, 'sound/machines/ping.ogg', 50, 0), 3 SECONDS)
+ H.emote("scream")
+ if(!masterAI) // If the factory was placed via admin spawning or other means, it wont have an owner_AI.
+ H.Robotize(robot_cell_type)
+ return
+ var/mob/living/silicon/robot/R = H.Robotize(robot_cell_type, FALSE, masterAI)
+ if(R.mind && !R.client && !R.grab_ghost()) // Make sure this is an actual player first and not just a humanized monkey or something.
+ message_admins("[key_name_admin(R)] was just transformed by a borg factory, but they were SSD. Polling ghosts for a replacement.")
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a malfunctioning cyborg?", ROLE_TRAITOR, poll_time = 15 SECONDS)
+ if(!length(candidates))
+ return
+ var/mob/dead/observer/O = pick(candidates)
+ R.key= O.key
/obj/machinery/transformer/mime
name = "Mimetech Greyscaler"
desc = "Turns anything placed inside black and white."
-
-/obj/machinery/transformer/mime/conveyor/New()
- ..()
- var/turf/T = loc
- if(T)
- // Spawn Conveyour Belts
-
- //East
- var/turf/east = locate(T.x + 1, T.y, T.z)
- if(istype(east, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(east, WEST)
-
- // West
- var/turf/west = locate(T.x - 1, T.y, T.z)
- if(istype(west, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(west, WEST)
-
-/obj/machinery/transformer/mime/Bumped(var/atom/movable/AM)
-
- if(cooldown == 1)
+/obj/machinery/transformer/mime/Bumped(atom/movable/AM)
+ if(is_on_cooldown)
return
// Crossed didn't like people lying down.
- if(isatom(AM))
- AM.loc = src.loc
+ if(istype(AM))
+ AM.forceMove(drop_location())
do_transform_mime(AM)
else
to_chat(AM, "Only items can be greyscaled.")
return
-/obj/machinery/transformer/proc/do_transform_mime(var/obj/item/I)
- if(stat & (BROKEN|NOPOWER))
- return
- if(cooldown == 1)
+/obj/machinery/transformer/proc/do_transform_mime(obj/item/I)
+ if(is_on_cooldown || stat & (BROKEN|NOPOWER))
return
- playsound(src.loc, 'sound/items/welder.ogg', 50, 1)
- // Sleep for a couple of ticks to allow the human to see the pain
- sleep(5)
+ playsound(loc, 'sound/items/welder.ogg', 50, 1)
use_power(5000) // Use a lot of power.
var/icon/newicon = new(I.icon, I.icon_state)
@@ -156,42 +140,27 @@
I.icon = newicon
// Activate the cooldown
- cooldown = 1
+ is_on_cooldown = TRUE
update_icon()
- spawn(cooldown_duration)
- cooldown = 0
- update_icon()
+ addtimer(CALLBACK(src, .proc/reset_cooldown), cooldown_duration)
/obj/machinery/transformer/xray
name = "Automatic X-Ray 5000"
desc = "A large metalic machine with an entrance and an exit. A sign on the side reads, 'backpack go in, backpack come out', 'human go in, irradiated human come out'."
+ acceptdir = WEST
-/obj/machinery/transformer/xray/Initialize(mapload)
- . = ..()
- // On us
- new /obj/machinery/conveyor/auto(loc, EAST)
-
-/obj/machinery/transformer/xray/conveyor/New()
- ..()
- var/turf/T = loc
+/obj/machinery/transformer/xray/initialize_belts()
+ var/turf/T = get_turf(src)
if(T)
- // Spawn Conveyour Belts
+ // This handles the belt under the transformer and 1 tile to the left and right.
+ . = ..()
- //East
- var/turf/east = locate(T.x + 1, T.y, T.z)
- if(istype(east, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(east, EAST)
- //East2
+ // Get the turf 2 tiles to the EAST.
var/turf/east2 = locate(T.x + 2, T.y, T.z)
if(istype(east2, /turf/simulated/floor))
new /obj/machinery/conveyor/auto(east2, EAST)
- // West
- var/turf/west = locate(T.x - 1, T.y, T.z)
- if(istype(west, /turf/simulated/floor))
- new /obj/machinery/conveyor/auto(west, EAST)
-
- // West2
+ // Get the turf 2 tiles to the WEST.
var/turf/west2 = locate(T.x - 2, T.y, T.z)
if(istype(west2, /turf/simulated/floor))
new /obj/machinery/conveyor/auto(west2, EAST)
@@ -207,30 +176,30 @@
else
icon_state = initial(icon_state)
-/obj/machinery/transformer/xray/Bumped(var/atom/movable/AM)
-
- if(cooldown == 1)
+/obj/machinery/transformer/xray/Bumped(atom/movable/AM)
+ if(is_on_cooldown)
return
// Crossed didn't like people lying down.
if(ishuman(AM))
// Only humans can enter from the west side, while lying down.
- var/move_dir = get_dir(loc, AM.loc)
var/mob/living/carbon/human/H = AM
- if(H.lying && move_dir == WEST)// || move_dir == WEST)
- AM.loc = src.loc
- irradiate(AM)
+ var/move_dir = get_dir(loc, H.loc)
- else if(isatom(AM))
- AM.loc = src.loc
+ if(H.lying && move_dir == acceptdir)
+ H.forceMove(drop_location())
+ irradiate(H)
+
+ else if(istype(AM))
+ AM.forceMove(drop_location())
scan(AM)
-/obj/machinery/transformer/xray/proc/irradiate(var/mob/living/carbon/human/H)
+/obj/machinery/transformer/xray/proc/irradiate(mob/living/carbon/human/H)
if(stat & (BROKEN|NOPOWER))
return
flick("separator-AO0",src)
- playsound(src.loc, 'sound/effects/alert.ogg', 50, 0)
+ playsound(loc, 'sound/effects/alert.ogg', 50, 0)
sleep(5)
H.apply_effect((rand(150,200)),IRRADIATE,0)
if(prob(5))
@@ -242,15 +211,15 @@
domutcheck(H,null,1)
-/obj/machinery/transformer/xray/proc/scan(var/obj/item/I)
+/obj/machinery/transformer/xray/proc/scan(obj/item/I)
if(scan_rec(I))
- playsound(src.loc, 'sound/effects/alert.ogg', 50, 0)
+ playsound(loc, 'sound/effects/alert.ogg', 50, 0)
flick("separator-AO0",src)
else
- playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
+ playsound(loc, 'sound/machines/ping.ogg', 50, 0)
sleep(30)
-/obj/machinery/transformer/xray/proc/scan_rec(var/obj/item/I)
+/obj/machinery/transformer/xray/proc/scan_rec(obj/item/I)
if(istype(I, /obj/item/gun))
return TRUE
if(istype(I, /obj/item/transfer_valve))
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index afed001f63e..fdca5c56194 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -101,6 +101,7 @@
power_change()
/obj/machinery/vending/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
QDEL_NULL(coin)
QDEL_NULL(inserted_item)
@@ -1371,7 +1372,6 @@
/obj/item/clothing/glasses/gglasses = 1,
/obj/item/clothing/shoes/jackboots = 1,
/obj/item/clothing/under/schoolgirl = 1,
- /obj/item/clothing/head/kitty = 1,
/obj/item/clothing/under/blackskirt = 1,
/obj/item/clothing/suit/toggle/owlwings = 1,
/obj/item/clothing/under/owl = 1,
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 9446448b8ac..9a24edb4da3 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -331,6 +331,8 @@
else
occupant.clear_alert("mechaport")
if(leg_overload_mode)
+ log_message("Leg Overload damage.")
+ take_damage(1, BRUTE, FALSE, FALSE)
if(obj_integrity < max_integrity - max_integrity / 3)
leg_overload_mode = FALSE
step_in = initial(step_in)
@@ -502,7 +504,7 @@
check_for_internal_damage(list(MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST))
else
check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT))
- if(. >= 5 || prob(33))
+ if((. >= 5 || prob(33)) && !(. == 1 && leg_overload_mode)) //If it takes 1 damage and leg_overload_mode is true, do not say TAKING DAMAGE! to the user several times a second.
occupant_message("Taking damage!")
log_message("Took [damage_amount] points of damage. Damage type: [damage_type]")
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index 5f5c62abd21..97e46e3abb5 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -221,6 +221,12 @@
A.contents += thing
thing.change_area(old_area, A)
+ var/area/oldA = get_area(get_turf(usr))
+ var/list/firedoors = oldA.firedoors
+ for(var/door in firedoors)
+ var/obj/machinery/door/firedoor/FD = door
+ FD.CalculateAffectingAreas()
+
interact()
area_created = TRUE
return area_created
@@ -236,6 +242,10 @@
return
set_area_machinery_title(A,str,prevname)
A.name = str
+ if(A.firedoors)
+ for(var/D in A.firedoors)
+ var/obj/machinery/door/firedoor/FD = D
+ FD.CalculateAffectingAreas()
to_chat(usr, "You rename the '[prevname]' to '[str]'.")
interact()
return 1
diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm
index 59d2c2a99bd..c8122fc34d6 100644
--- a/code/game/objects/items/devices/radio/intercom.dm
+++ b/code/game/objects/items/devices/radio/intercom.dm
@@ -186,10 +186,10 @@
update_icon()
START_PROCESSING(SSobj, src)
for(var/i, i<= 5, i++)
- wires.UpdateCut(i,1)
+ wires.on_cut(i, 1)
/obj/item/radio/intercom/wirecutter_act(mob/user, obj/item/I)
- if(!(buildstage == 3 && b_stat && wires.IsAllCut()))
+ if(!(buildstage == 3 && b_stat && wires.is_all_cut()))
return
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
@@ -270,4 +270,4 @@
/obj/item/radio/intercom/locked/prison/New()
..()
- wires.CutWireIndex(RADIO_WIRE_TRANSMIT)
+ wires.cut(WIRE_RADIO_TRANSMIT)
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index f18c862f878..528be3c5f87 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -76,6 +76,7 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
GLOB.global_radios |= src
/obj/item/radio/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
if(SSradio)
SSradio.remove_object(src, frequency)
@@ -127,8 +128,8 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
data["freq"] = format_frequency(frequency)
data["rawfreq"] = num2text(frequency)
- data["mic_cut"] = (wires.IsIndexCut(RADIO_WIRE_TRANSMIT) || wires.IsIndexCut(RADIO_WIRE_SIGNAL))
- data["spk_cut"] = (wires.IsIndexCut(RADIO_WIRE_RECEIVE) || wires.IsIndexCut(RADIO_WIRE_SIGNAL))
+ data["mic_cut"] = (wires.is_cut(WIRE_RADIO_TRANSMIT) || wires.is_cut(WIRE_RADIO_SIGNAL))
+ data["spk_cut"] = (wires.is_cut(WIRE_RADIO_RECEIVER) || wires.is_cut(WIRE_RADIO_SIGNAL))
var/list/chanlist = list_channels(user)
if(islist(chanlist) && chanlist.len)
@@ -184,10 +185,10 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
return can_admin_interact()
/obj/item/radio/proc/ToggleBroadcast()
- broadcasting = !broadcasting && !(wires.IsIndexCut(RADIO_WIRE_TRANSMIT) || wires.IsIndexCut(RADIO_WIRE_SIGNAL))
+ broadcasting = !broadcasting && !(wires.is_cut(WIRE_RADIO_TRANSMIT) || wires.is_cut(WIRE_RADIO_SIGNAL))
/obj/item/radio/proc/ToggleReception()
- listening = !listening && !(wires.IsIndexCut(RADIO_WIRE_RECEIVE) || wires.IsIndexCut(RADIO_WIRE_SIGNAL))
+ listening = !listening && !(wires.is_cut(WIRE_RADIO_RECEIVER) || wires.is_cut(WIRE_RADIO_SIGNAL))
/obj/item/radio/Topic(href, href_list)
if(..())
@@ -334,7 +335,7 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
// Uncommenting this. To the above comment:
// The permacell radios aren't suppose to be able to transmit, this isn't a bug and this "fix" is just making radio wires useless. -Giacom
- if(wires.IsIndexCut(RADIO_WIRE_TRANSMIT)) // The device has to have all its wires and shit intact
+ if(wires.is_cut(WIRE_RADIO_TRANSMIT)) // The device has to have all its wires and shit intact
return 0
if(!M.IsVocal())
@@ -515,7 +516,7 @@ GLOBAL_LIST_INIT(default_medbay_channels, list(
var/is_listening = TRUE
if(!on)
is_listening = FALSE
- if(!wires || wires.IsIndexCut(RADIO_WIRE_RECEIVE))
+ if(!wires || wires.is_cut(WIRE_RADIO_RECEIVER))
is_listening = FALSE
if(!listening)
is_listening = FALSE
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index c65a9c3e50b..43697ac4776 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -72,8 +72,7 @@ REAGENT SCANNER
var/turf/U = O.loc
if(U && U.intact)
O.invisibility = 101
- if(O)
- O.alpha = 255
+ O.alpha = 255
for(var/mob/living/M in T.contents)
var/oldalpha = M.alpha
if(M.alpha < 255 && istype(M))
diff --git a/code/game/objects/items/flag.dm b/code/game/objects/items/flag.dm
index 14bb468e049..f33e504a3e6 100644
--- a/code/game/objects/items/flag.dm
+++ b/code/game/objects/items/flag.dm
@@ -251,6 +251,7 @@
message_admins("[key_name_admin(user)] has lit the [src] trapped with [boobytrap] by [key_name_admin(trapper)] at [A.name] (JMP).")
log_game("[key_name_admin(user)] has lit the [src] trapped with [boobytrap] by [key_name_admin(trapper)] at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]).")
investigate_log("[key_name_admin(user)] has lit the [src] trapped with [boobytrap] by [key_name_admin(trapper)] at [A.name] ([bombturf.x],[bombturf.y],[bombturf.z]).", INVESTIGATE_BOMB)
+ burn()
else
return ..()
@@ -267,8 +268,16 @@
/obj/item/flag/chameleon/burn()
if(boobytrap)
- boobytrap.prime()
- ..()
+ fire_act()
+ addtimer(CALLBACK(src, .proc/prime_boobytrap), boobytrap.det_time)
+ else
+ ..()
+
+/obj/item/flag/chameleon/proc/prime_boobytrap()
+ boobytrap.forceMove(get_turf(loc))
+ boobytrap.prime()
+ boobytrap = null
+ burn()
/obj/item/flag/chameleon/updateFlagIcon()
icon_state = updated_icon_state
diff --git a/code/game/objects/items/weapons/garrote.dm b/code/game/objects/items/weapons/garrote.dm
index 4bddaf551ea..e3c145e790b 100644
--- a/code/game/objects/items/weapons/garrote.dm
+++ b/code/game/objects/items/weapons/garrote.dm
@@ -104,7 +104,7 @@
playsound(src.loc, 'sound/weapons/cablecuff.ogg', 15, 1, -1)
M.visible_message("[U] comes from behind and begins garroting [M] with the [src]!", \
- "[U]\ begins garroting you with the [src]![improvised ? "" : " You are unable to speak!"]", \
+ "[U] begins garroting you with the [src]![improvised ? "" : " You are unable to speak!"]", \
"You hear struggling and wire strain against flesh!")
return
diff --git a/code/game/objects/items/weapons/grenades/smokebomb.dm b/code/game/objects/items/weapons/grenades/smokebomb.dm
index 6dd07485f5b..53061e5fa61 100644
--- a/code/game/objects/items/weapons/grenades/smokebomb.dm
+++ b/code/game/objects/items/weapons/grenades/smokebomb.dm
@@ -19,7 +19,7 @@
/obj/item/grenade/smokebomb/prime()
playsound(src.loc, 'sound/effects/smoke.ogg', 50, 1, -3)
- src.smoke.set_up(10, 0, usr.loc)
+ smoke.set_up(10, 0)
spawn(0)
src.smoke.start()
sleep(10)
diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm
index 26045731bd9..e7880fd0868 100644
--- a/code/game/objects/items/weapons/storage/backpack.dm
+++ b/code/game/objects/items/weapons/storage/backpack.dm
@@ -372,6 +372,15 @@
new /obj/item/ammo_box/magazine/m12g/buckshot(src)
new /obj/item/ammo_box/magazine/m12g/dragon(src)
+/obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags
+ desc = "A large duffelbag, containing three types of extended drum magazines."
+
+/obj/item/storage/backpack/duffel/syndie/ammo/shotgunXLmags/New()
+ ..()
+ new /obj/item/ammo_box/magazine/m12g/XtrLrg(src)
+ new /obj/item/ammo_box/magazine/m12g/XtrLrg/buckshot(src)
+ new /obj/item/ammo_box/magazine/m12g/XtrLrg/dragon(src)
+
/obj/item/storage/backpack/duffel/mining_conscript/
name = "mining conscription kit"
desc = "A kit containing everything a crewmember needs to support a shaft miner in the field."
diff --git a/code/game/objects/items/weapons/storage/bags.dm b/code/game/objects/items/weapons/storage/bags.dm
index 6d5a192c641..121782b2e8e 100644
--- a/code/game/objects/items/weapons/storage/bags.dm
+++ b/code/game/objects/items/weapons/storage/bags.dm
@@ -380,12 +380,13 @@
w_class = WEIGHT_CLASS_BULKY
flags = CONDUCT
materials = list(MAT_METAL=3000)
+ cant_hold = list(/obj/item/disk/nuclear) // Prevents some cheesing
-/obj/item/storage/bag/tray/attack(mob/living/M as mob, mob/living/user as mob)
+/obj/item/storage/bag/tray/attack(mob/living/M, mob/living/user)
..()
// Drop all the things. All of them.
var/list/obj/item/oldContents = contents.Copy()
- quick_empty()
+ drop_inventory(user)
// Make each item scatter a bit
for(var/obj/item/I in oldContents)
diff --git a/code/game/objects/items/weapons/storage/storage.dm b/code/game/objects/items/weapons/storage/storage.dm
index 16bd6f4caa8..49b6c6a11ea 100644
--- a/code/game/objects/items/weapons/storage/storage.dm
+++ b/code/game/objects/items/weapons/storage/storage.dm
@@ -438,8 +438,11 @@
if((!ishuman(usr) && (src.loc != usr)) || usr.stat || usr.restrained())
return
+ drop_inventory(usr)
+
+/obj/item/storage/proc/drop_inventory(user)
var/turf/T = get_turf(src)
- hide_from(usr)
+ hide_from(user)
for(var/obj/item/I in contents)
remove_from_storage(I, T)
CHECK_TICK
@@ -499,10 +502,9 @@
/obj/item/storage/attack_self(mob/user)
- //Clicking on itself will empty it, if it has the verb to do that.
- if(user.is_in_active_hand(src))
- if(verbs.Find(/obj/item/storage/verb/quick_empty))
- quick_empty()
+ //Clicking on itself will empty it, if allow_quick_empty is TRUE
+ if(allow_quick_empty && user.is_in_active_hand(src))
+ drop_inventory(user)
//Returns the storage depth of an atom. This is the number of storage items the atom is contained in before reaching toplevel (the area).
//Returns -1 if the atom was not found on container.
diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm
index 9ded7947202..abc267d3a34 100644
--- a/code/game/objects/items/weapons/tanks/tanks.dm
+++ b/code/game/objects/items/weapons/tanks/tanks.dm
@@ -43,35 +43,31 @@
/obj/item/tank/proc/toggle_internals(mob/user, silent = FALSE)
var/mob/living/carbon/C = user
if(!istype(C))
- return 0
+ return FALSE
if(C.internal == src)
to_chat(C, "You close \the [src] valve.")
C.internal = null
else
- var/can_open_valve = 0
- if(C.get_organ_slot("breathing_tube"))
- can_open_valve = 1
- else if(C.wear_mask && C.wear_mask.flags & AIRTIGHT)
- can_open_valve = 1
- else if(ishuman(C))
- var/mob/living/carbon/human/H = C
- if(H.head && H.head.flags & AIRTIGHT)
- can_open_valve = 1
+ if(!C.get_organ_slot("breathing_tube")) // Breathing tubes can always use internals, if they have one, skip ahead and turn internals on/off
+ if(!C.wear_mask) // Do we have a mask equipped?
+ return FALSE
- if(can_open_valve)
+ var/obj/item/clothing/mask/M = C.wear_mask
+ // If the "mask" isn't actually a mask OR That mask isn't internals compatible AND Their headgear isn't internals compatible
+ if(!istype(M) || (!(initial(M.flags) & AIRTIGHT) && !(C.head.flags & AIRTIGHT)))
+ if(!silent)
+ to_chat(C, "You are not wearing a suitable mask or helmet.")
+ return FALSE
+ if(M.mask_adjusted) // If the mask is equipped but pushed down
+ M.adjustmask(C) // Adjust it back
+
+ if(!silent)
if(C.internal)
- if(!silent)
- to_chat(C, "You switch your internals to [src].")
+ to_chat(C, "You switch your internals to [src].")
else
- if(!silent)
- to_chat(C, "You open \the [src] valve.")
- C.internal = src
- else
- if(!silent)
- to_chat(C, "You are not wearing a suitable mask or helmet.")
- return 0
-
+ to_chat(C, "You open \the [src] valve.")
+ C.internal = src
C.update_action_buttons_icon()
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
index fd67afcf797..311bc4a38c7 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/scientist.dm
@@ -45,8 +45,6 @@
new /obj/item/clothing/suit/storage/labcoat(src)
new /obj/item/radio/headset/headset_sci(src)
new /obj/item/radio/headset/headset_sci(src)
- new /obj/item/reagent_containers/food/drinks/oilcan(src)
- new /obj/item/reagent_containers/food/drinks/oilcan(src)
/obj/structure/closet/secure_closet/RD
name = "research director's locker"
diff --git a/code/game/objects/structures/musician.dm b/code/game/objects/structures/musician.dm
deleted file mode 100644
index c3d63c47862..00000000000
--- a/code/game/objects/structures/musician.dm
+++ /dev/null
@@ -1,341 +0,0 @@
-
-
-/datum/song
- var/name = "Untitled"
- var/list/lines = new()
- var/tempo = 5 // delay between notes
-
- var/playing = 0 // if we're playing
- var/help = 0 // if help is open
- var/repeat = 0 // number of times remaining to repeat
- var/max_repeat = 10 // maximum times we can repeat
-
- var/instrumentDir = "piano" // the folder with the sounds
- var/instrumentExt = "ogg" // the file extension
- var/obj/instrumentObj = null // the associated obj playing the sound
-
-/datum/song/New(dir, obj, ext = "ogg")
- tempo = sanitize_tempo(tempo)
- instrumentDir = dir
- instrumentObj = obj
- instrumentExt = ext
-
-/datum/song/Destroy()
- instrumentObj = null
- return ..()
-
-// note is a number from 1-7 for A-G
-// acc is either "b", "n", or "#"
-// oct is 1-8 (or 9 for C)
-/datum/song/proc/playnote(note, acc as text, oct)
- // handle accidental -> B<>C of E<>F
- if(acc == "b" && (note == 3 || note == 6)) // C or F
- if(note == 3)
- oct--
- note--
- acc = "n"
- else if(acc == "#" && (note == 2 || note == 5)) // B or E
- if(note == 2)
- oct++
- note++
- acc = "n"
- else if(acc == "#" && (note == 7)) //G#
- note = 1
- acc = "b"
- else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
- acc = "b"
- note++
-
- // check octave, C is allowed to go to 9
- if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
- return
-
- // now generate name
- var/soundfile = "sound/instruments/[instrumentDir]/[ascii2text(note+64)][acc][oct].[instrumentExt]"
- soundfile = file(soundfile)
- // make sure the note exists
- if(!fexists(soundfile))
- return
- // and play
- var/turf/source = get_turf(instrumentObj)
- var/sound/music_played = sound(soundfile)
- for(var/A in hearers(15, source))
- var/mob/M = A
- if(!M.client || !(M.client.prefs.sound & SOUND_INSTRUMENTS))
- continue
- M.playsound_local(source, null, 100, falloff = 5, S = music_played)
-
-/datum/song/proc/shouldStopPlaying(mob/user)
- if(instrumentObj)
- //if(!user.canUseTopic(instrumentObj))
- //return 1
- return !instrumentObj.anchored // add special cases to stop in subclasses
- else
- return 1
-
-/datum/song/proc/playsong(mob/user)
- while(repeat >= 0)
- var/cur_oct[7]
- var/cur_acc[7]
- for(var/i = 1 to 7)
- cur_oct[i] = 3
- cur_acc[i] = "n"
-
- for(var/line in lines)
- for(var/beat in splittext(lowertext(line), ","))
- var/list/notes = splittext(beat, "/")
- for(var/note in splittext(notes[1], "-"))
- if(!playing || shouldStopPlaying(user)) //If the instrument is playing, or special case
- playing = 0
- return
- if(length(note) == 0)
- continue
- var/cur_note = text2ascii(note) - 96
- if(cur_note < 1 || cur_note > 7)
- continue
- for(var/i=2 to length(note))
- var/ni = copytext(note,i,i+1)
- if(!text2num(ni))
- if(ni == "#" || ni == "b" || ni == "n")
- cur_acc[cur_note] = ni
- else if(ni == "s")
- cur_acc[cur_note] = "#" // so shift is never required
- else
- cur_oct[cur_note] = text2num(ni)
- playnote(cur_note, cur_acc[cur_note], cur_oct[cur_note])
- if(notes.len >= 2 && text2num(notes[2]))
- sleep(sanitize_tempo(tempo / text2num(notes[2])))
- else
- sleep(tempo)
- repeat--
- playing = 0
- repeat = 0
-
-/datum/song/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- if(!instrumentObj)
- return
-
- ui = SSnanoui.try_update_ui(user, instrumentObj, ui_key, ui, force_open)
- if(!ui)
- ui = new(user, instrumentObj, ui_key, "song.tmpl", instrumentObj.name, 700, 500)
- ui.open()
- ui.set_auto_update(1)
-
-/datum/song/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- var/data[0]
-
- data["lines"] = lines
- data["tempo"] = tempo
-
- data["playing"] = playing
- data["help"] = help
- data["repeat"] = repeat
- data["maxRepeat"] = max_repeat
- data["minTempo"] = world.tick_lag
- data["maxTempo"] = 600
-
- return data
-
-/datum/song/Topic(href, href_list)
- if(!in_range(instrumentObj, usr) || (issilicon(usr) && instrumentObj.loc != usr) || !isliving(usr) || usr.incapacitated())
- usr << browse(null, "window=instrument")
- usr.unset_machine()
- return 1
-
- instrumentObj.add_fingerprint(usr)
-
- if(href_list["newsong"])
- playing = 0
- lines = new()
- tempo = sanitize_tempo(5) // default 120 BPM
- name = ""
- SSnanoui.update_uis(src)
-
- else if(href_list["import"])
- playing = 0
- var/t = ""
- do
- t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
- if(!in_range(instrumentObj, usr))
- return
-
- if(length(t) >= 12000)
- var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
- if(cont == "no")
- break
- while(length(t) > 12000)
-
- //split into lines
- spawn()
- lines = splittext(t, "\n")
- if(lines.len == 0)
- return 1
- if(copytext(lines[1],1,6) == "BPM: ")
- tempo = sanitize_tempo(600 / text2num(copytext(lines[1],6)))
- lines.Cut(1,2)
- else
- tempo = sanitize_tempo(5) // default 120 BPM
- if(lines.len > 200)
- to_chat(usr, "Too many lines!")
- lines.Cut(201)
- var/linenum = 1
- for(var/l in lines)
- if(length(l) > 200)
- to_chat(usr, "Line [linenum] too long!")
- lines.Remove(l)
- else
- linenum++
- SSnanoui.update_uis(src)
-
- else if(href_list["help"])
- help = !help
- SSnanoui.update_uis(src)
-
- if(href_list["repeat"]) //Changing this from a toggle to a number of repeats to avoid infinite loops.
- if(playing)
- return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
- repeat += round(text2num(href_list["repeat"]))
- if(repeat < 0)
- repeat = 0
- if(repeat > max_repeat)
- repeat = max_repeat
- SSnanoui.update_uis(src)
-
- else if(href_list["tempo"])
- tempo = sanitize_tempo(tempo + text2num(href_list["tempo"]) * world.tick_lag)
- SSnanoui.update_uis(src)
-
- else if(href_list["play"])
- if(playing)
- return
- playing = 1
- spawn()
- playsong(usr)
- SSnanoui.update_uis(src)
-
- else if(href_list["insertline"])
- var/num = round(text2num(href_list["insertline"]))
- if(num < 1 || num > lines.len + 1)
- return
-
- var/newline = html_encode(input("Enter your line: ", instrumentObj.name) as text|null)
- if(!newline || !in_range(instrumentObj, usr))
- return
- if(lines.len > 200)
- return
- if(length(newline) > 200)
- newline = copytext(newline, 1, 200)
-
- lines.Insert(num, newline)
- SSnanoui.update_uis(src)
-
- else if(href_list["deleteline"])
- var/num = round(text2num(href_list["deleteline"]))
- if(num > lines.len || num < 1)
- return
- lines.Cut(num, num + 1)
- SSnanoui.update_uis(src)
-
- else if(href_list["modifyline"])
- var/num = round(text2num(href_list["modifyline"]))
- var/content = html_encode(input("Enter your line: ", instrumentObj.name, lines[num]) as text|null)
- if(!content || !in_range(instrumentObj, usr))
- return
- if(length(content) > 200)
- content = copytext(content, 1, 200)
- if(num > lines.len || num < 1)
- return
- lines[num] = content
- SSnanoui.update_uis(src)
-
- else if(href_list["stop"])
- playing = 0
- SSnanoui.update_uis(src)
-
-/datum/song/proc/sanitize_tempo(new_tempo)
- new_tempo = abs(new_tempo)
- return max(round(new_tempo, world.tick_lag), world.tick_lag)
-
-// subclass for handheld instruments, like violin
-/datum/song/handheld
-
-/datum/song/handheld/shouldStopPlaying()
- if(instrumentObj)
- return !isliving(instrumentObj.loc)
- else
- return 1
-
-
-//////////////////////////////////////////////////////////////////////////
-
-
-/obj/structure/piano
- name = "space minimoog"
- icon = 'icons/obj/musician.dmi'
- icon_state = "minimoog"
- anchored = 1
- density = 1
- var/datum/song/song
-
-
-/obj/structure/piano/New()
- ..()
- song = new("piano", src)
-
- if(prob(50))
- name = "space minimoog"
- desc = "This is a minimoog, like a space piano, but more spacey!"
- icon_state = "minimoog"
- else
- name = "space piano"
- desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
- icon_state = "piano"
-
-/obj/structure/piano/Destroy()
- QDEL_NULL(song)
- return ..()
-
-/obj/structure/piano/Initialize()
- if(song)
- song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
- ..()
-
-/obj/structure/piano/attack_hand(mob/user as mob)
- ui_interact(user)
-
-/obj/structure/piano/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1)
- if(!isliving(user) || user.incapacitated() || !anchored)
- return
-
- song.ui_interact(user, ui_key, ui, force_open)
-
-/obj/structure/piano/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- return song.ui_data(user, ui_key, state)
-
-/obj/structure/piano/Topic(href, href_list)
- song.Topic(href, href_list)
-
-/obj/structure/piano/wrench_act(mob/user, obj/item/I)
- . = TRUE
- if(!I.tool_use_check(user, 0))
- return
- if(!anchored && !isinspace())
- WRENCH_ANCHOR_MESSAGE
- if(!I.use_tool(src, user, 20, volume = I.tool_volume))
- return
- user.visible_message( \
- "[user] tightens [src]'s casters.", \
- " You have tightened [src]'s casters. Now it can be played again.", \
- "You hear ratchet.")
- anchored = TRUE
- else if(anchored)
- to_chat(user, " You begin to loosen [src]'s casters...")
- if(!I.use_tool(src, user, 40, volume = I.tool_volume))
- return
- user.visible_message( \
- "[user] loosens [src]'s casters.", \
- " You have loosened [src]. Now it can be pulled somewhere else.", \
- "You hear ratchet.")
- anchored = FALSE
- else
- to_chat(user, "[src] needs to be bolted to the floor!")
diff --git a/code/game/objects/structures/signs.dm b/code/game/objects/structures/signs.dm
index ba5258004a0..5a8a122be21 100644
--- a/code/game/objects/structures/signs.dm
+++ b/code/game/objects/structures/signs.dm
@@ -292,12 +292,12 @@
/obj/structure/sign/directions/engineering
name = "\improper Engineering Department"
- desc = "A direction sign, pointing out which way the Engineering department is."
+ desc = "A direction sign, pointing out which way the Engineering Department is."
icon_state = "direction_eng"
/obj/structure/sign/directions/security
name = "\improper Security Department"
- desc = "A direction sign, pointing out which way the Security department is."
+ desc = "A direction sign, pointing out which way the Security Department is."
icon_state = "direction_sec"
/obj/structure/sign/directions/medical
@@ -307,12 +307,12 @@
/obj/structure/sign/directions/evac
name = "\improper Escape Arm"
- desc = "A direction sign, pointing out which way escape shuttle dock is."
+ desc = "A direction sign, pointing out which way Escape Shuttle Dock is."
icon_state = "direction_evac"
/obj/structure/sign/directions/cargo
name = "\improper Cargo Department"
- desc = "A direction sign, pointing out which way the Cargo department is."
+ desc = "A direction sign, pointing out which way the Cargo Department is."
icon_state = "direction_supply"
/obj/structure/sign/explosives
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 870ac5e6966..6882c27df3d 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -4,12 +4,13 @@
return
var/turf/turf_source = get_turf(source)
-
if(!turf_source)
return
+ if(!SSsounds.channel_list) // Not ready yet
+ return
//allocate a channel if necessary now so its the same for everyone
- channel = channel || open_sound_channel()
+ channel = channel || SSsounds.random_available_channel()
// Looping through the player list has the added bonus of working for mobs inside containers
var/sound/S = sound(get_sfx(soundin))
@@ -33,7 +34,7 @@
if(distance <= maxdistance)
M.playsound_local(turf_source, soundin, vol, vary, frequency, falloff, channel, pressure_affected, S)
-/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S)
+/mob/proc/playsound_local(turf/turf_source, soundin, vol as num, vary, frequency, falloff, channel = 0, pressure_affected = TRUE, sound/S, distance_multiplier = 1)
if(!client || !can_hear())
return
@@ -41,7 +42,7 @@
S = sound(get_sfx(soundin))
S.wait = 0 //No queue
- S.channel = channel || open_sound_channel()
+ S.channel = channel || SSsounds.random_available_channel()
S.volume = vol
if(vary)
@@ -55,6 +56,7 @@
//sound volume falloff with distance
var/distance = get_dist(T, turf_source)
+ distance *= distance_multiplier
S.volume -= max(distance - world.view, 0) * 2 //multiplicative falloff to add on top of natural audio falloff.
@@ -81,9 +83,9 @@
return //No sound
var/dx = turf_source.x - T.x // Hearing from the right/left
- S.x = dx
+ S.x = dx * distance_multiplier
var/dz = turf_source.y - T.y // Hearing from infront/behind
- S.z = dz
+ S.z = dz * distance_multiplier
// The y value is for above your head, but there is no ceiling in 2d spessmens.
S.y = 1
S.falloff = (falloff ? falloff : FALLOFF_SOUNDS)
@@ -98,15 +100,14 @@
var/mob/M = m
M.playsound_local(M, null, volume, vary, frequency, falloff, channel, pressure_affected, S)
-/proc/open_sound_channel()
- var/static/next_channel = 1 //loop through the available 1024 - (the ones we reserve) channels and pray that its not still being used
- . = ++next_channel
- if(next_channel > CHANNEL_HIGHEST_AVAILABLE)
- next_channel = 1
-
/mob/proc/stop_sound_channel(chan)
SEND_SOUND(src, sound(null, repeat = 0, wait = 0, channel = chan))
+/mob/proc/set_sound_channel_volume(channel, volume)
+ var/sound/S = sound(null, FALSE, FALSE, channel, volume)
+ S.status = SOUND_UPDATE
+ SEND_SOUND(src, S)
+
/client/proc/playtitlemusic()
if(!SSticker || !SSticker.login_music || config.disable_lobby_music)
return
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 8d1f3920c0c..2f90e4d7cc4 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -263,6 +263,13 @@
if(SSair && !ignore_air)
SSair.add_to_active(src)
+ //update firedoor adjacency
+ var/list/turfs_to_check = get_adjacent_open_turfs(src) | src
+ for(var/I in turfs_to_check)
+ var/turf/T = I
+ for(var/obj/machinery/door/firedoor/FD in T)
+ FD.CalculateAffectingAreas()
+
if(!keep_cabling && !can_have_cabling())
for(var/obj/structure/cable/C in contents)
qdel(C)
diff --git a/code/game/world.dm b/code/game/world.dm
index 02c613e9ac8..c279fa24b54 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -340,6 +340,8 @@ GLOBAL_VAR_INIT(world_topic_spam_protect_time, world.timeofday)
#endif
for(var/client/C in GLOB.clients)
+ var/secs_before_auto_reconnect = 10 // TODO: make it higher if server is due for an update @AffectedArc07
+ C << output(list2params(list(secs_before_auto_reconnect)), "browseroutput:reboot")
if(config.server) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
C << link("byond://[config.server]")
diff --git a/code/modules/alarm/alarm.dm b/code/modules/alarm/alarm.dm
deleted file mode 100644
index 752d3232d23..00000000000
--- a/code/modules/alarm/alarm.dm
+++ /dev/null
@@ -1,136 +0,0 @@
-#define ALARM_RESET_DELAY 100 // How long will the alarm/trigger remain active once origin/source has been found to be gone?
-
-/datum/alarm_source
- var/source = null // The source trigger
- var/source_name = "" // The name of the source should it be lost (for example a destroyed camera)
- var/duration = 0 // How long this source will be alarming, 0 for indefinetely.
- var/severity = 1 // How severe the alarm from this source is.
- var/start_time = 0 // When this source began alarming.
- var/end_time = 0 // Use to set when this trigger should clear, in case the source is lost.
-
-/datum/alarm_source/New(var/atom/source)
- src.source = source
- start_time = world.time
- source_name = source.get_source_name()
-
-/datum/alarm
- var/atom/origin //Used to identify the alarm area.
- var/list/sources = new() //List of sources triggering the alarm. Used to determine when the alarm should be cleared.
- var/list/sources_assoc = new() //Associative list of source triggers. Used to efficiently acquire the alarm source.
- var/list/cameras //List of cameras that can be switched to, if the player has that capability.
- var/area/last_area //The last acquired area, used should origin be lost (for example a destroyed borg containing an alarming camera).
- var/area/last_name //The last acquired name, used should origin be lost
- var/area/last_camera_area //The last area in which cameras where fetched, used to see if the camera list should be updated.
- var/end_time //Used to set when this alarm should clear, in case the origin is lost.
-
-/datum/alarm/New(var/atom/origin, var/atom/source, var/duration, var/severity)
- src.origin = origin
-
- cameras() // Sets up both cameras and last alarm area.
- set_source_data(source, duration, severity)
-
-/datum/alarm/process()
- // Has origin gone missing?
- if(!origin && !end_time)
- end_time = world.time + ALARM_RESET_DELAY
- for(var/datum/alarm_source/AS in sources)
- // Has the alarm passed its best before date?
- if((AS.end_time && world.time > AS.end_time) || (AS.duration && world.time > (AS.start_time + AS.duration)))
- sources -= AS
- // Has the source gone missing? Then reset the normal duration and set end_time
- if(!AS.source && !AS.end_time) // end_time is used instead of duration to ensure the reset doesn't remain in the future indefinetely.
- AS.duration = 0
- AS.end_time = world.time + ALARM_RESET_DELAY
-
-/datum/alarm/proc/set_source_data(var/atom/source, var/duration, var/severity)
- var/datum/alarm_source/AS = sources_assoc[source]
- if(!AS)
- AS = new/datum/alarm_source(source)
- sources += AS
- sources_assoc[source] = AS
- // Currently only non-0 durations can be altered (normal alarms VS EMP blasts)
- if(AS.duration)
- duration = duration SECONDS
- AS.duration = duration
- AS.severity = severity
-
-/datum/alarm/proc/clear(var/source)
- var/datum/alarm_source/AS = sources_assoc[source]
- sources -= AS
- sources_assoc -= source
-
-/datum/alarm/proc/alarm_area()
- if(!origin)
- return last_area
-
- last_area = origin.get_alarm_area()
- return last_area
-
-/datum/alarm/proc/alarm_name()
- if(!origin)
- return last_name
-
- last_name = origin.get_alarm_name()
- return last_name
-
-/datum/alarm/proc/cameras()
- // If the alarm origin has changed area, for example a borg containing an alarming camera, reset the list of cameras
- if(cameras && (last_camera_area != alarm_area()))
- cameras = null
-
- // The list of cameras is also reset by /proc/invalidateCameraCache()
- if(!cameras)
- cameras = origin ? origin.get_alarm_cameras() : last_area.get_alarm_cameras()
-
- last_camera_area = last_area
- return cameras
-
-/datum/alarm/proc/max_severity()
- var/max_severity = 0
- for(var/datum/alarm_source/AS in sources)
- max_severity = max(AS.severity, max_severity)
-
- return max_severity
-
-/******************
-* Assisting procs *
-******************/
-/atom/proc/get_alarm_area()
- var/area/A = get_area(src)
- return A
-
-/area/get_alarm_area()
- return src
-
-/atom/proc/get_alarm_name()
- var/area/A = get_area(src)
- return A.name
-
-/area/get_alarm_name()
- return name
-
-/mob/get_alarm_name()
- return name
-
-/atom/proc/get_source_name()
- return name
-
-/obj/machinery/camera/get_source_name()
- return c_tag
-
-/atom/proc/get_alarm_cameras()
- var/area/A = get_area(src)
- return A.get_cameras()
-
-/area/get_alarm_cameras()
- return get_cameras()
-
-/mob/living/silicon/robot/get_alarm_cameras()
- var/list/cameras = ..()
- if(camera)
- cameras += camera
-
- return cameras
-
-/mob/living/silicon/robot/syndicate/get_alarm_cameras()
- return list()
diff --git a/code/modules/alarm/alarm_handler.dm b/code/modules/alarm/alarm_handler.dm
deleted file mode 100644
index 56a673bb5b4..00000000000
--- a/code/modules/alarm/alarm_handler.dm
+++ /dev/null
@@ -1,103 +0,0 @@
-#define ALARM_RAISED 1
-#define ALARM_CLEARED 0
-
-/datum/alarm_handler
- var/category = ""
- var/list/datum/alarm/alarms = new // All alarms, to handle cases when an origin has been deleted with one or more active alarms
- var/list/datum/alarm/alarms_assoc = new // Associative list of alarms, to efficiently acquire them based on origin.
- var/list/listeners = new // A list of all objects interested in alarm changes.
-
-/datum/alarm_handler/process()
- for(var/datum/alarm/A in alarms)
- A.process()
- check_alarm_cleared(A)
-
-/datum/alarm_handler/proc/triggerAlarm(var/atom/origin, var/atom/source, var/duration = 0, var/severity = 1)
- var/new_alarm
- //Proper origin and source mandatory
- if(!(origin && source))
- return
- origin = origin.get_alarm_origin()
-
- new_alarm = 0
- //see if there is already an alarm of this origin
- var/datum/alarm/existing = alarms_assoc[origin]
- if(existing)
- existing.set_source_data(source, duration, severity)
- else
- existing = new/datum/alarm(origin, source, duration, severity)
- new_alarm = 1
-
- alarms |= existing
- alarms_assoc[origin] = existing
- if(new_alarm)
- alarms = dd_sortedObjectList(alarms)
- on_alarm_change(existing, ALARM_RAISED)
-
- return new_alarm
-
-/datum/alarm_handler/proc/clearAlarm(var/atom/origin, var/source)
- //Proper origin and source mandatory
- if(!(origin && source))
- return
- origin = origin.get_alarm_origin()
-
- var/datum/alarm/existing = alarms_assoc[origin]
- if(existing)
- existing.clear(source)
- return check_alarm_cleared(existing)
-
-/datum/alarm_handler/proc/has_major_alarms()
- if(alarms && alarms.len)
- return 1
- return 0
-
-/datum/alarm_handler/proc/major_alarms()
- return alarms
-
-/datum/alarm_handler/proc/minor_alarms()
- return alarms
-
-/datum/alarm_handler/proc/check_alarm_cleared(var/datum/alarm/alarm)
- if((alarm.end_time && world.time > alarm.end_time) || !alarm.sources.len)
- alarms -= alarm
- alarms_assoc -= alarm.origin
- on_alarm_change(alarm, ALARM_CLEARED)
- return 1
- return 0
-
-/datum/alarm_handler/proc/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
- for(var/obj/machinery/camera/C in alarm.cameras())
- if(was_raised)
- C.network.Add(category)
- else
- C.network.Remove(category)
- notify_listeners(alarm, was_raised)
-
-/datum/alarm_handler/proc/get_alarm_severity_for_origin(var/atom/origin)
- if(!origin)
- return
-
- origin = origin.get_alarm_origin()
- var/datum/alarm/existing = alarms_assoc[origin]
- if(!existing)
- return
-
- return existing.max_severity()
-
-/atom/proc/get_alarm_origin()
- return src
-
-/turf/get_alarm_origin()
- var/area/area = get_area(src)
- return area // Very important to get area.master, as dynamic lightning can and will split areas.
-
-/datum/alarm_handler/proc/register(var/object, var/procName)
- listeners[object] = procName
-
-/datum/alarm_handler/proc/unregister(var/object)
- listeners -= object
-
-/datum/alarm_handler/proc/notify_listeners(var/alarm, var/was_raised)
- for(var/listener in listeners)
- call(listener, listeners[listener])(src, alarm, was_raised)
diff --git a/code/modules/alarm/atmosphere_alarm.dm b/code/modules/alarm/atmosphere_alarm.dm
deleted file mode 100644
index cc29b40ac44..00000000000
--- a/code/modules/alarm/atmosphere_alarm.dm
+++ /dev/null
@@ -1,19 +0,0 @@
-/datum/alarm_handler/atmosphere
- category = "Atmosphere Alarms"
-
-/datum/alarm_handler/atmosphere/triggerAlarm(var/atom/origin, var/atom/source, var/duration = 0, var/severity = 1)
- ..()
-
-/datum/alarm_handler/atmosphere/major_alarms()
- var/list/major_alarms = new()
- for(var/datum/alarm/A in alarms)
- if(A.max_severity() > 1)
- major_alarms.Add(A)
- return major_alarms
-
-/datum/alarm_handler/atmosphere/minor_alarms()
- var/list/minor_alarms = new()
- for(var/datum/alarm/A in alarms)
- if(A.max_severity() == 1)
- minor_alarms.Add(A)
- return minor_alarms
diff --git a/code/modules/alarm/burglar_alarm.dm b/code/modules/alarm/burglar_alarm.dm
deleted file mode 100644
index c55cb12deef..00000000000
--- a/code/modules/alarm/burglar_alarm.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/datum/alarm_handler/burglar
- category = "Burglar Alarms"
diff --git a/code/modules/alarm/camera_alarm.dm b/code/modules/alarm/camera_alarm.dm
deleted file mode 100644
index bef53ad466f..00000000000
--- a/code/modules/alarm/camera_alarm.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/datum/alarm_handler/camera
- category = "Camera Alarms"
diff --git a/code/modules/alarm/fire_alarm.dm b/code/modules/alarm/fire_alarm.dm
deleted file mode 100644
index dfae3cc8177..00000000000
--- a/code/modules/alarm/fire_alarm.dm
+++ /dev/null
@@ -1,11 +0,0 @@
-/datum/alarm_handler/fire
- category = "Fire Alarms"
-
-/datum/alarm_handler/fire/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
- var/area/A = alarm.origin
- if(istype(A))
- if(was_raised)
- A.fire_alert()
- else
- A.fire_reset()
- ..()
diff --git a/code/modules/alarm/motion_alarm.dm b/code/modules/alarm/motion_alarm.dm
deleted file mode 100644
index fd7e6febe48..00000000000
--- a/code/modules/alarm/motion_alarm.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/datum/alarm_handler/motion
- category = "Motion Alarms"
diff --git a/code/modules/alarm/power_alarm.dm b/code/modules/alarm/power_alarm.dm
deleted file mode 100644
index 4a0947a8f94..00000000000
--- a/code/modules/alarm/power_alarm.dm
+++ /dev/null
@@ -1,10 +0,0 @@
-/datum/alarm_handler/power
- category = "Power Alarms"
-
-/datum/alarm_handler/power/on_alarm_change(var/datum/alarm/alarm, var/was_raised)
- var/area/A = alarm.origin
- if(istype(A))
- A.power_alert(was_raised)
- ..()
-
-/area/proc/power_alert(var/alarming)
diff --git a/code/modules/assembly/assembly.dm b/code/modules/assembly/assembly.dm
index 5fea7baa019..6567dd62ede 100644
--- a/code/modules/assembly/assembly.dm
+++ b/code/modules/assembly/assembly.dm
@@ -1,3 +1,9 @@
+#define WIRE_RECEIVE (1<<0) //Allows pulse(0) to call Activate()
+#define WIRE_PULSE (1<<1) //Allows pulse(0) to act on the holder
+#define WIRE_PULSE_SPECIAL (1<<2) //Allows pulse(0) to act on the holders special assembly
+#define WIRE_RADIO_RECEIVE (1<<3) //Allows pulse(1) to call Activate()
+#define WIRE_RADIO_PULSE (1<<4) //Allows pulse(1) to send a radio message
+
/obj/item/assembly
name = "assembly"
desc = "A small electronic device that should never exist."
@@ -22,21 +28,12 @@
var/wires = WIRE_RECEIVE | WIRE_PULSE
var/datum/wires/connected = null // currently only used by timer/signaler
- var/const/WIRE_RECEIVE = 1 //Allows Pulsed(0) to call Activate()
- var/const/WIRE_PULSE = 2 //Allows Pulse(0) to act on the holder
- var/const/WIRE_PULSE_SPECIAL = 4 //Allows Pulse(0) to act on the holders special assembly
- var/const/WIRE_RADIO_RECEIVE = 8 //Allows Pulsed(1) to call Activate()
- var/const/WIRE_RADIO_PULSE = 16 //Allows Pulse(1) to send a radio message
-
/obj/item/assembly/proc/activate() //What the device does when turned on
return
/obj/item/assembly/proc/pulsed(radio = FALSE) //Called when another assembly acts on this one, var/radio will determine where it came from for wire calcs
return
-/obj/item/assembly/proc/pulse(radio = FALSE) //Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct
- return
-
/obj/item/assembly/proc/toggle_secure() //Code that has to happen when the assembly is un\secured goes here
return
@@ -79,7 +76,11 @@
activate()
return TRUE
-/obj/item/assembly/pulse(radio = FALSE)
+//Called when this device attempts to act on another device, var/radio determines if it was sent via radio or direct
+/obj/item/assembly/proc/pulse(radio = FALSE)
+ if(connected && wires)
+ connected.pulse_assembly(src)
+ return TRUE
if(holder && (wires & WIRE_PULSE))
holder.process_activation(src, 1, 0)
if(holder && (wires & WIRE_PULSE_SPECIAL))
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 2759d12c21a..0c3fcfec3ac 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -128,12 +128,6 @@
if(usr)
GLOB.lastsignalers.Add("[time] : [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) : [format_frequency(frequency)]/[code]")
-/obj/item/assembly/signaler/pulse(var/radio = FALSE)
- if(connected && wires)
- connected.Pulse(src)
- else
- return ..(radio)
-
/obj/item/assembly/signaler/receive_signal(datum/signal/signal)
if(!receiving || !signal)
return FALSE
diff --git a/code/modules/client/preference/loadout/loadout_hat.dm b/code/modules/client/preference/loadout/loadout_hat.dm
index 4993c8502ea..7e277677f94 100644
--- a/code/modules/client/preference/loadout/loadout_hat.dm
+++ b/code/modules/client/preference/loadout/loadout_hat.dm
@@ -166,7 +166,3 @@
/datum/gear/hat/flowerpin
display_name = "hair flower"
path = /obj/item/clothing/head/hairflower
-
-/datum/gear/hat/kitty
- display_name = "kitty headband"
- path = /obj/item/clothing/head/kitty
diff --git a/code/modules/clothing/ears/ears.dm b/code/modules/clothing/ears/ears.dm
index ea8f4198f50..d00751e2cfb 100644
--- a/code/modules/clothing/ears/ears.dm
+++ b/code/modules/clothing/ears/ears.dm
@@ -8,21 +8,3 @@
strip_delay = 15
put_on_delay = 25
resistance_flags = FLAMMABLE
-
-/obj/item/clothing/ears/headphones
- name = "headphones"
- desc = "Unce unce unce unce."
- var/on = 0
- icon_state = "headphones0"
- item_state = null
- actions_types = list(/datum/action/item_action/toggle_headphones)
-
-/obj/item/clothing/ears/headphones/attack_self(mob/user)
- on = !on
- icon_state = "headphones[on]"
-
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtonIcon()
-
- user.update_inv_ears()
diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm
index 24bef5398a8..bdcd27c24e3 100644
--- a/code/modules/clothing/suits/armor.dm
+++ b/code/modules/clothing/suits/armor.dm
@@ -11,7 +11,8 @@
resistance_flags = NONE
armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
sprite_sheets = list(
- "Vox" = 'icons/mob/species/vox/suit.dmi'
+ "Vox" = 'icons/mob/species/vox/suit.dmi',
+ "Grey" = 'icons/mob/species/grey/suit.dmi'
)
w_class = WEIGHT_CLASS_NORMAL
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index a7bd3fb2627..6ffa40fe727 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -162,7 +162,7 @@
desc = "A slick, authoritative cloak designed for the Chief Engineer."
icon_state = "cemantle"
item_state = "cemantle"
- allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/t_scanner, /obj/item/rcd)
+ allowed = list(/obj/item/flashlight, /obj/item/tank, /obj/item/t_scanner, /obj/item/rcd, /obj/item/rpd)
//Chief Medical Officer
/obj/item/clothing/suit/mantle/labcoat/chief_medical_officer
@@ -231,7 +231,7 @@
icon_state = "hazard"
item_state = "hazard"
blood_overlay_type = "armor"
- allowed = list (/obj/item/flashlight, /obj/item/t_scanner, /obj/item/tank/emergency_oxygen)
+ allowed = list (/obj/item/flashlight, /obj/item/t_scanner, /obj/item/tank/emergency_oxygen, /obj/item/rcd, /obj/item/rpd)
resistance_flags = NONE
sprite_sheets = list(
diff --git a/code/modules/events/apc_short.dm b/code/modules/events/apc_short.dm
index c19b6cbe8b7..fcb31972204 100644
--- a/code/modules/events/apc_short.dm
+++ b/code/modules/events/apc_short.dm
@@ -46,10 +46,10 @@
if(prob(APC_BREAK_PROBABILITY))
// if it has internal wires, cut the power wires
if(C.wires)
- if(!C.wires.IsIndexCut(APC_WIRE_MAIN_POWER1))
- C.wires.CutWireIndex(APC_WIRE_MAIN_POWER1)
- if(!C.wires.IsIndexCut(APC_WIRE_MAIN_POWER2))
- C.wires.CutWireIndex(APC_WIRE_MAIN_POWER2)
+ if(!C.wires.is_cut(WIRE_MAIN_POWER1))
+ C.wires.cut(WIRE_MAIN_POWER1)
+ if(!C.wires.is_cut(WIRE_MAIN_POWER2))
+ C.wires.cut(WIRE_MAIN_POWER2)
// if it was operating, toggle off the breaker
if(C.operating)
C.toggle_breaker()
diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm
index 8d91635c458..35cc199c3ff 100644
--- a/code/modules/events/brand_intelligence.dm
+++ b/code/modules/events/brand_intelligence.dm
@@ -32,7 +32,7 @@
log_debug("Original brand intelligence machine: [originMachine] ([ADMIN_VV(originMachine,"VV")]) [ADMIN_JMP(originMachine)]")
/datum/event/brand_intelligence/tick()
- if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.IsAllCut()) //if the original vending machine is missing or has it's voice switch flipped
+ if(!originMachine || QDELETED(originMachine) || originMachine.shut_up || originMachine.wires.is_all_cut()) //if the original vending machine is missing or has it's voice switch flipped
for(var/obj/machinery/vending/saved in infectedMachines)
saved.shoot_inventory = 0
if(originMachine)
diff --git a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
index f179de1459a..f6bb3c8e1c4 100644
--- a/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
+++ b/code/modules/food_and_drinks/kitchen_machinery/smartfridge.dm
@@ -70,6 +70,7 @@
max_n_of_items = 1500 * B.rating
/obj/machinery/smartfridge/Destroy()
+ SStgui.close_uis(wires)
QDEL_NULL(wires)
for(var/atom/movable/A in contents)
A.forceMove(loc)
diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm
index d6f8cae7519..0f2b54ab971 100644
--- a/code/modules/hydroponics/grown/replicapod.dm
+++ b/code/modules/hydroponics/grown/replicapod.dm
@@ -29,7 +29,8 @@
if(istype(W,/obj/item/reagent_containers/syringe))
if(!contains_sample)
for(var/datum/reagent/blood/bloodSample in W.reagents.reagent_list)
- if(bloodSample.data["mind"] && bloodSample.data["cloneable"] == 1)
+ var/datum/dna/dna = bloodSample.data["dna"]
+ if(bloodSample.data["mind"] && bloodSample.data["cloneable"] && !(NO_SCAN in dna.species.species_traits))
var/datum/mind/tempmind = bloodSample.data["mind"]
if(tempmind.is_revivable())
mind = bloodSample.data["mind"]
diff --git a/code/modules/instruments/_instrument_data.dm b/code/modules/instruments/_instrument_data.dm
new file mode 100644
index 00000000000..2fde3de59fb
--- /dev/null
+++ b/code/modules/instruments/_instrument_data.dm
@@ -0,0 +1,113 @@
+/**
+ * Get all non admin_only instruments as a list of text ids.
+ */
+/proc/get_allowed_instrument_ids()
+ . = list()
+ for(var/id in SSinstruments.instrument_data)
+ var/datum/instrument/I = SSinstruments.instrument_data[id]
+ if(!I.admin_only)
+ . += I.id
+
+/**
+ * # Instrument Datums
+ *
+ * Instrument datums hold the data for any given instrument, as well as data on how to play it and what bounds there are to playing it.
+ *
+ * The datums themselves are kept in SSinstruments in a list by their unique ID. The reason it uses ID instead of typepath is to support the runtime creation of instruments.
+ * Since songs cache them while playing, there isn't realistic issues regarding performance from accessing.
+ */
+/datum/instrument
+ /// Name of the instrument
+ var/name = "Generic instrument"
+ /// Uniquely identifies this instrument so runtime changes are possible as opposed to paths. If this is unset, things will use path instead.
+ var/id
+ /// Category
+ var/category = "Unsorted"
+ /// Used for categorization subtypes
+ var/abstract_type = /datum/instrument
+ /// Write here however many samples, follow this syntax: "%note num%"='%sample file%' eg. "27"='synthesizer/e2.ogg'. Key must never be lower than 0 and higher than 127
+ var/list/real_samples
+ /// assoc list key = /datum/instrument_key. do not fill this yourself!
+ var/list/samples
+ /// See __DEFINES/flags/instruments.dm
+ var/instrument_flags = NONE
+ /// For legacy instruments, the path to our notes
+ var/legacy_instrument_path
+ /// For legacy instruments, our file extension
+ var/legacy_instrument_ext
+ /// What songs are using us
+ var/list/datum/song/songs_using = list()
+ /// Don't touch this
+ var/static/HIGHEST_KEY = 127
+ /// Don't touch this x2
+ var/static/LOWEST_KEY = 0
+ /// Oh no - For truly troll instruments.
+ var/admin_only = FALSE
+ /// Volume multiplier. Synthesized instruments are quite loud and I don't like to cut off potential detail via editing. (someone correct me if this isn't a thing)
+ var/volume_multiplier = 0.33
+
+/datum/instrument/New()
+ if(isnull(id))
+ id = "[type]"
+
+/datum/instrument/Destroy()
+ SSinstruments.instrument_data -= id
+ for(var/i in songs_using)
+ var/datum/song/S = i
+ S.set_instrument(null)
+ real_samples = null
+ samples = null
+ songs_using = null
+ return ..()
+
+/**
+ * Initializes the instrument, calculating its samples if necessary.
+ */
+/datum/instrument/proc/Initialize()
+ if(instrument_flags & (INSTRUMENT_LEGACY | INSTRUMENT_DO_NOT_AUTOSAMPLE))
+ return
+ calculate_samples()
+
+/**
+ * Checks if this instrument is ready to play.
+ */
+/datum/instrument/proc/is_ready()
+ if(instrument_flags & INSTRUMENT_LEGACY)
+ return legacy_instrument_path && legacy_instrument_ext
+ else if(instrument_flags & INSTRUMENT_DO_NOT_AUTOSAMPLE)
+ return length(samples)
+ return length(samples) >= 128
+
+/**
+ * For synthesized instruments, this is how the instrument generates the "keys" that a [/datum/song] uses to play notes.
+ * Calculating them on the fly would be unperformant, so we do it during init and keep it all cached in a list.
+ */
+/datum/instrument/proc/calculate_samples()
+ if(!length(real_samples))
+ CRASH("No real samples defined for [id] [type] on calculate_samples() call.")
+ var/list/real_keys = list()
+ samples = list()
+ for(var/key in real_samples)
+ real_keys += text2num(key)
+ sortTim(real_keys, /proc/cmp_numeric_asc, associative = FALSE)
+
+ for(var/i in 1 to (length(real_keys) - 1))
+ var/from_key = real_keys[i]
+ var/to_key = real_keys[i + 1]
+ var/sample1 = real_samples[num2text(from_key)]
+ var/sample2 = real_samples[num2text(to_key)]
+ var/pivot = FLOOR((from_key + to_key) / 2, 1) //original code was a round but I replaced it because that's effectively a floor, thanks Baystation! who knows what was intended.
+ for(var/key in from_key to pivot)
+ samples[num2text(key)] = new /datum/instrument_key(sample1, key, key - from_key)
+ for(var/key in (pivot + 1) to to_key)
+ samples[num2text(key)] = new /datum/instrument_key(sample2, key, key - to_key)
+
+ // Fill in 0 to first key and last key to 127
+ var/first_key = real_keys[1]
+ var/last_key = real_keys[length(real_keys)]
+ var/first_sample = real_samples[num2text(first_key)]
+ var/last_sample = real_samples[num2text(last_key)]
+ for(var/key in LOWEST_KEY to (first_key - 1))
+ samples[num2text(key)] = new /datum/instrument_key(first_sample, key, key - first_key)
+ for(var/key in last_key to HIGHEST_KEY)
+ samples[num2text(key)] = new /datum/instrument_key(last_sample, key, key - last_key)
diff --git a/code/modules/instruments/_instrument_key.dm b/code/modules/instruments/_instrument_key.dm
new file mode 100644
index 00000000000..5c7cc0ce372
--- /dev/null
+++ b/code/modules/instruments/_instrument_key.dm
@@ -0,0 +1,33 @@
+#define KEY_TWELTH (1/12)
+
+/**
+ * Instrument key datums contain everything needed to know how to play a specific
+ * note of an instrument.*
+ */
+/datum/instrument_key
+ /// The numerical key of what this is, from 1 to 127 on a standard piano keyboard.
+ var/key
+ /// The actual sample file that will be loaded when playing.
+ var/sample
+ /// The frequency to play the sample to get our desired note.
+ var/frequency
+ /// Deviation up/down from the pivot point that uses its sample. Used to calculate frequency.
+ var/deviation
+
+/datum/instrument_key/New(sample, key, deviation, frequency)
+ src.sample = sample
+ src.key = key
+ src.deviation = deviation
+ src.frequency = frequency
+ if(!frequency && deviation)
+ calculate()
+
+/**
+ * Calculates and stores our deviation.
+ */
+/datum/instrument_key/proc/calculate()
+ if(!deviation)
+ CRASH("Invalid calculate call: No deviation or sample in instrument_key")
+ frequency = 2 ** (KEY_TWELTH * deviation)
+
+#undef KEY_TWELTH
diff --git a/code/modules/instruments/brass.dm b/code/modules/instruments/brass.dm
new file mode 100644
index 00000000000..7f8f103831f
--- /dev/null
+++ b/code/modules/instruments/brass.dm
@@ -0,0 +1,26 @@
+/datum/instrument/brass
+ name = "Generic brass instrument"
+ category = "Brass"
+ abstract_type = /datum/instrument/brass
+
+/datum/instrument/brass/crisis_section
+ name = "Crisis Brass Section"
+ id = "crbrass"
+ real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_brass/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/brass/crisis_brass/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/brass/crisis_brass/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/brass/crisis_brass/c5.ogg')
+
+/datum/instrument/brass/crisis_trombone
+ name = "Crisis Trombone"
+ id = "crtrombone"
+ real_samples = list("36"='sound/instruments/synthesis_samples/brass/crisis_trombone/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/brass/crisis_trombone/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/brass/crisis_trombone/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/brass/crisis_trombone/c5.ogg')
+
+/datum/instrument/brass/crisis_trumpet
+ name = "Crisis Trumpet"
+ id = "crtrumpet"
+ real_samples = list("60"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/brass/crisis_trumpet/c5.ogg')
diff --git a/code/modules/instruments/chromatic_percussion.dm b/code/modules/instruments/chromatic_percussion.dm
new file mode 100644
index 00000000000..cafa9e31edb
--- /dev/null
+++ b/code/modules/instruments/chromatic_percussion.dm
@@ -0,0 +1,31 @@
+/datum/instrument/chromatic
+ name = "Generic chromatic percussion instrument"
+ category = "Chromatic percussion"
+ abstract_type = /datum/instrument/chromatic
+
+/datum/instrument/chromatic/vibraphone1
+ name = "Crisis Vibraphone"
+ id = "crvibr"
+ real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/chromatic/vibraphone1/c5.ogg')
+
+/datum/instrument/chromatic/musicbox1
+ name = "SGM Music Box"
+ id = "sgmmbox"
+ real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/sgmbox/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/chromatic/sgmbox/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/chromatic/sgmbox/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/chromatic/sgmbox/c5.ogg')
+
+/datum/instrument/chromatic/fluid_celeste
+ name = "FluidR3 Celeste"
+ id = "r3celeste"
+ real_samples = list("36"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c5.ogg',
+ "84"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c6.ogg',
+ "96"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c7.ogg',
+ "108"='sound/instruments/synthesis_samples/chromatic/fluid_celeste/c8.ogg')
diff --git a/code/modules/instruments/fun.dm b/code/modules/instruments/fun.dm
new file mode 100644
index 00000000000..5a9b1292c42
--- /dev/null
+++ b/code/modules/instruments/fun.dm
@@ -0,0 +1,19 @@
+/datum/instrument/fun
+ name = "Generic Fun Instrument"
+ category = "Fun"
+ abstract_type = /datum/instrument/fun
+
+/datum/instrument/fun/honk
+ name = "!!HONK!!"
+ id = "honk"
+ real_samples = list("74"='sound/items/bikehorn.ogg') // Cluwne Heaven
+
+/datum/instrument/fun/signal
+ name = "Ping"
+ id = "ping"
+ real_samples = list("79"='sound/machines/ping.ogg')
+
+/datum/instrument/fun/chime
+ name = "Chime"
+ id = "chime"
+ real_samples = list("79"='sound/machines/chime.ogg')
diff --git a/code/modules/instruments/guitar.dm b/code/modules/instruments/guitar.dm
new file mode 100644
index 00000000000..be7cfbe467b
--- /dev/null
+++ b/code/modules/instruments/guitar.dm
@@ -0,0 +1,36 @@
+/datum/instrument/guitar
+ name = "Generic guitar-like instrument"
+ category = "Guitar"
+ abstract_type = /datum/instrument/guitar
+
+/datum/instrument/guitar/steel_crisis
+ name = "Crisis Steel String Guitar"
+ id = "csteelgt"
+ real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_steel/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/guitar/crisis_steel/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/guitar/crisis_steel/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/guitar/crisis_steel/c5.ogg')
+
+/datum/instrument/guitar/nylon_crisis
+ name = "Crisis Nylon String Guitar"
+ id = "cnylongt"
+ real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/guitar/crisis_nylon/c5.ogg')
+
+/datum/instrument/guitar/clean_crisis
+ name = "Crisis Clean Guitar"
+ id = "ccleangt"
+ real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_clean/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/guitar/crisis_clean/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/guitar/crisis_clean/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/guitar/crisis_clean/c5.ogg')
+
+/datum/instrument/guitar/muted_crisis
+ name = "Crisis Muted Guitar"
+ id = "cmutedgt"
+ real_samples = list("36"='sound/instruments/synthesis_samples/guitar/crisis_muted/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/guitar/crisis_muted/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/guitar/crisis_muted/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/guitar/crisis_muted/c5.ogg')
diff --git a/code/modules/instruments/hardcoded.dm b/code/modules/instruments/hardcoded.dm
new file mode 100644
index 00000000000..5db7e8b3bb4
--- /dev/null
+++ b/code/modules/instruments/hardcoded.dm
@@ -0,0 +1,86 @@
+//THESE ARE HARDCODED INSTRUMENT SAMPLES.
+//SONGS WILL BE AUTOMATICALLY SWITCHED TO LEGACY MODE IF THEY USE THIS KIND OF INSTRUMENT!
+//I'd prefer these stayed. They sound different from the mechanical synthesis of synthed instruments, and I quite like them that way. It's not legacy, it's hardcoded, old style. - kevinz000
+/datum/instrument/hardcoded
+ abstract_type = /datum/instrument/hardcoded
+ category = "Non-Synthesized"
+ instrument_flags = INSTRUMENT_LEGACY
+ volume_multiplier = 1 //not as loud as synth'd
+
+/datum/instrument/hardcoded/accordion
+ name = "Accordion"
+ id = "accordion"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "accordion"
+
+/datum/instrument/hardcoded/bikehorn
+ name = "Bike Horn"
+ id = "bikehorn"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "bikehorn"
+
+/datum/instrument/hardcoded/eguitar
+ name = "Electric Guitar"
+ id = "eguitar"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "eguitar"
+
+/datum/instrument/hardcoded/glockenspiel
+ name = "Glockenspiel"
+ id = "glockenspiel"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "glockenspiel"
+
+/datum/instrument/hardcoded/guitar
+ name = "Guitar"
+ id = "guitar"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "guitar"
+
+/datum/instrument/hardcoded/harmonica
+ name = "Harmonica"
+ id = "harmonica"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "harmonica"
+
+/datum/instrument/hardcoded/piano
+ name = "Piano"
+ id = "piano"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "piano"
+
+/datum/instrument/hardcoded/recorder
+ name = "Recorder"
+ id = "recorder"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "recorder"
+
+/datum/instrument/hardcoded/saxophone
+ name = "Saxophone"
+ id = "saxophone"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "saxophone"
+
+/datum/instrument/hardcoded/trombone
+ name = "Trombone"
+ id = "trombone"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "trombone"
+
+/datum/instrument/hardcoded/violin
+ name = "Violin"
+ id = "violin"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "violin"
+
+/datum/instrument/hardcoded/xylophone
+ name = "Xylophone"
+ id = "xylophone"
+ legacy_instrument_ext = "mid"
+ legacy_instrument_path = "xylophone"
+
+/datum/instrument/hardcoded/banjo
+ name = "Banjo"
+ id = "banjo"
+ legacy_instrument_ext = "ogg"
+ legacy_instrument_path = "banjo"
diff --git a/code/modules/instruments/objs/items/_instrument.dm b/code/modules/instruments/objs/items/_instrument.dm
new file mode 100644
index 00000000000..936a9b4c85d
--- /dev/null
+++ b/code/modules/instruments/objs/items/_instrument.dm
@@ -0,0 +1,53 @@
+//copy pasta of the space piano, don't hurt me -Pete
+/obj/item/instrument
+ name = "generic instrument"
+ force = 10
+ max_integrity = 100
+ resistance_flags = FLAMMABLE
+ icon = 'icons/obj/musician.dmi'
+ lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
+ /// Our song datum.
+ var/datum/song/handheld/song
+ /// Our allowed list of instrument ids. This is nulled on initialize.
+ var/list/allowed_instrument_ids
+ /// How far away our song datum can be heard.
+ var/instrument_range = 15
+
+/obj/item/instrument/Initialize(mapload)
+ . = ..()
+ song = new(src, allowed_instrument_ids, instrument_range)
+ allowed_instrument_ids = null //We don't need this clogging memory after it's used.
+
+/obj/item/instrument/Destroy()
+ QDEL_NULL(song)
+ return ..()
+
+/obj/item/instrument/suicide_act(mob/user)
+ user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/instrument/attack_self(mob/user)
+ tgui_interact(user)
+
+/obj/item/instrument/tgui_data(mob/user)
+ return song.tgui_data(user)
+
+/obj/item/instrument/tgui_interact(mob/user)
+ if(!isliving(user) || user.incapacitated())
+ return
+ song.tgui_interact(user)
+
+/obj/item/instrument/tgui_act(action, params)
+ if(..())
+ return
+ return song.tgui_act(action, params)
+
+/**
+ * Whether the instrument should stop playing
+ *
+ * Arguments:
+ * * user - The user
+ */
+/obj/item/instrument/proc/should_stop_playing(mob/user)
+ return !(src in user) || !isliving(user) || user.incapacitated()
diff --git a/code/modules/instruments/objs/items/headphones.dm b/code/modules/instruments/objs/items/headphones.dm
new file mode 100644
index 00000000000..ef1a76bdc3b
--- /dev/null
+++ b/code/modules/instruments/objs/items/headphones.dm
@@ -0,0 +1,80 @@
+/obj/item/clothing/ears/headphones
+ name = "headphones"
+ desc = "Unce unce unce unce."
+ icon_state = "headphones0"
+ item_state = "headphones0"
+ actions_types = list(/datum/action/item_action/change_headphones_song)
+ var/datum/song/headphones/song
+
+/obj/item/clothing/ears/headphones/Initialize(mapload)
+ . = ..()
+ song = new(src, "piano") // Piano is the default instrument but all instruments are allowed
+ song.instrument_range = 0
+ song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
+ // To update the icon
+ RegisterSignal(src, COMSIG_SONG_START, .proc/start_playing)
+ RegisterSignal(src, COMSIG_SONG_END, .proc/stop_playing)
+
+/obj/item/clothing/ears/headphones/Destroy()
+ QDEL_NULL(song)
+ return ..()
+
+/obj/item/clothing/ears/headphones/attack_self(mob/user)
+ tgui_interact(user)
+
+/obj/item/clothing/ears/headphones/tgui_data(mob/user)
+ return song.tgui_data(user)
+
+/obj/item/clothing/ears/headphones/tgui_interact(mob/user)
+ if(should_stop_playing(user) || user.incapacitated())
+ return
+ song.tgui_interact(user)
+
+/obj/item/clothing/ears/headphones/tgui_act(action, params)
+ if(..())
+ return
+ return song.tgui_act(action, params)
+
+/obj/item/clothing/ears/headphones/update_icon()
+ var/mob/living/carbon/human/user = loc
+ if(istype(user))
+ user.update_action_buttons_icon()
+ user.update_inv_ears()
+ ..()
+
+/obj/item/clothing/ears/headphones/item_action_slot_check(slot)
+ if(slot == slot_l_ear || slot == slot_r_ear)
+ return TRUE
+
+/**
+ * Called by a component signal when our song starts playing.
+ */
+/obj/item/clothing/ears/headphones/proc/start_playing()
+ icon_state = item_state = "headphones1"
+ update_icon()
+
+/**
+ * Called by a component signal when our song stops playing.
+ */
+/obj/item/clothing/ears/headphones/proc/stop_playing()
+ icon_state = item_state = "headphones0"
+ update_icon()
+
+/**
+ * Whether the headphone's song should stop playing
+ *
+ * Arguments:
+ * * user - The user
+ */
+/obj/item/clothing/ears/headphones/proc/should_stop_playing(mob/living/carbon/human/user)
+ return !(src in user) || !istype(user) || !((src == user.l_ear) || (src == user.r_ear))
+
+// special subtype so it uses the correct item type
+/datum/song/headphones
+
+/datum/song/headphones/should_stop_playing(mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ var/obj/item/clothing/ears/headphones/I = parent
+ return I.should_stop_playing(user)
diff --git a/code/game/objects/items/devices/instruments.dm b/code/modules/instruments/objs/items/instruments.dm
similarity index 54%
rename from code/game/objects/items/devices/instruments.dm
rename to code/modules/instruments/objs/items/instruments.dm
index 53ead129836..2426de9a5c9 100644
--- a/code/game/objects/items/devices/instruments.dm
+++ b/code/modules/instruments/objs/items/instruments.dm
@@ -1,55 +1,10 @@
-//copy pasta of the space piano, don't hurt me -Pete
-/obj/item/instrument
- name = "generic instrument"
- icon = 'icons/obj/musician.dmi'
- lefthand_file = 'icons/mob/inhands/equipment/instruments_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/instruments_righthand.dmi'
- resistance_flags = FLAMMABLE
- max_integrity = 100
- var/datum/song/handheld/song
- var/instrumentId = "generic"
- var/instrumentExt = "mid"
-
-/obj/item/instrument/New()
- song = new(instrumentId, src, instrumentExt)
- ..()
-
-/obj/item/instrument/Destroy()
- QDEL_NULL(song)
- return ..()
-
-/obj/item/instrument/suicide_act(mob/user)
- user.visible_message("[user] begins to play 'Gloomy Sunday'! It looks like [user.p_theyre()] trying to commit suicide!")
- return BRUTELOSS
-
-/obj/item/instrument/Initialize(mapload)
- song.tempo = song.sanitize_tempo(song.tempo) // tick_lag isn't set when the map is loaded
- ..()
-
-/obj/item/instrument/attack_self(mob/user)
- ui_interact(user)
-
-/obj/item/instrument/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1)
- if(!isliving(user) || user.incapacitated())
- return
-
- song.ui_interact(user, ui_key, ui, force_open)
-
-/obj/item/instrument/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.default_state)
- return song.ui_data(user, ui_key, state)
-
-/obj/item/instrument/Topic(href, href_list)
- song.Topic(href, href_list)
-
/obj/item/instrument/violin
name = "space violin"
desc = "A wooden musical instrument with four strings and a bow. \"The devil went down to space, he was looking for an assistant to grief.\""
icon_state = "violin"
item_state = "violin"
- instrumentExt = "ogg"
- force = 10
hitsound = "swing_hit"
- instrumentId = "violin"
+ allowed_instrument_ids = "violin"
/obj/item/instrument/violin/golden
name = "golden violin"
@@ -63,87 +18,146 @@
desc = "An advanced electronic synthesizer that can be used as various instruments."
icon_state = "synth"
item_state = "synth"
- instrumentId = "piano"
- instrumentExt = "ogg"
- var/static/list/insTypes = list("accordion" = "mid", "glockenspiel" = "mid", "guitar" = "ogg", "eguitar" = "ogg", "harmonica" = "mid", "piano" = "ogg", "recorder" = "mid", "saxophone" = "mid", "trombone" = "mid", "violin" = "ogg", "xylophone" = "mid")
- actions_types = list(/datum/action/item_action/synthswitch)
+ allowed_instrument_ids = "piano"
-/obj/item/instrument/piano_synth/proc/changeInstrument(name = "piano")
- song.instrumentDir = name
- song.instrumentExt = insTypes[name]
+/obj/item/instrument/piano_synth/Initialize(mapload)
+ . = ..()
+ song.allowed_instrument_ids = SSinstruments.synthesizer_instrument_ids
+
+/obj/item/instrument/banjo
+ name = "banjo"
+ desc = "A 'Mura' brand banjo. It's pretty much just a drum with a neck and strings."
+ icon_state = "banjo"
+ item_state = "banjo"
+ attack_verb = list("scruggs-styled", "hum-diggitied", "shin-digged", "clawhammered")
+ hitsound = 'sound/weapons/banjoslap.ogg'
+ allowed_instrument_ids = "banjo"
/obj/item/instrument/guitar
name = "guitar"
desc = "It's made of wood and has bronze strings."
icon_state = "guitar"
item_state = "guitar"
- instrumentExt = "ogg"
- force = 10
attack_verb = list("played metal on", "serenaded", "crashed", "smashed")
- hitsound = 'sound/effects/guitarsmash.ogg'
- instrumentId = "guitar"
+ hitsound = 'sound/weapons/guitarslam.ogg'
+ allowed_instrument_ids = "guitar"
/obj/item/instrument/eguitar
name = "electric guitar"
desc = "Makes all your shredding needs possible."
icon_state = "eguitar"
item_state = "eguitar"
- instrumentExt = "ogg"
force = 12
attack_verb = list("played metal on", "shredded", "crashed", "smashed")
hitsound = 'sound/weapons/stringsmash.ogg'
- instrumentId = "eguitar"
+ allowed_instrument_ids = "eguitar"
/obj/item/instrument/glockenspiel
name = "glockenspiel"
desc = "Smooth metal bars perfect for any marching band."
icon_state = "glockenspiel"
item_state = "glockenspiel"
- instrumentId = "glockenspiel"
+ allowed_instrument_ids = "glockenspiel"
/obj/item/instrument/accordion
name = "accordion"
desc = "Pun-Pun not included."
icon_state = "accordion"
item_state = "accordion"
- instrumentId = "accordion"
+ allowed_instrument_ids = "accordion"
+
+/obj/item/instrument/trumpet
+ name = "trumpet"
+ desc = "To announce the arrival of the king!"
+ icon_state = "trumpet"
+ item_state = "trumpet"
+ allowed_instrument_ids = "trombone"
+
+/obj/item/instrument/trumpet/spectral
+ name = "spectral trumpet"
+ desc = "Things are about to get spooky!"
+ icon_state = "spectral_trumpet"
+ item_state = "spectral_trumpet"
+ force = 0
+ attack_verb = list("played", "jazzed", "trumpeted", "mourned", "dooted", "spooked")
+
+/obj/item/instrument/trumpet/spectral/Initialize()
+ . = ..()
+ AddComponent(/datum/component/spooky)
+
+/obj/item/instrument/trumpet/spectral/attack(mob/living/carbon/C, mob/user)
+ playsound(src, 'sound/instruments/trombone/En4.mid', 100, 1, -1)
+ ..()
/obj/item/instrument/saxophone
name = "saxophone"
desc = "This soothing sound will be sure to leave your audience in tears."
icon_state = "saxophone"
item_state = "saxophone"
- instrumentId = "saxophone"
+ allowed_instrument_ids = "saxophone"
+
+/obj/item/instrument/saxophone/spectral
+ name = "spectral saxophone"
+ desc = "This spooky sound will be sure to leave mortals in bones."
+ icon_state = "saxophone"
+ item_state = "saxophone"
+ force = 0
+ attack_verb = list("played", "jazzed", "saxxed", "mourned", "dooted", "spooked")
+
+/obj/item/instrument/saxophone/spectral/Initialize()
+ . = ..()
+ AddComponent(/datum/component/spooky)
+
+/obj/item/instrument/saxophone/spectral/attack(mob/living/carbon/C, mob/user)
+ playsound(src, 'sound/instruments/saxophone/En4.mid', 100,1,-1)
+ ..()
/obj/item/instrument/trombone
name = "trombone"
desc = "How can any pool table ever hope to compete?"
icon_state = "trombone"
+ allowed_instrument_ids = "trombone"
item_state = "trombone"
- instrumentId = "trombone"
+
+/obj/item/instrument/trombone/spectral
+ name = "spectral trombone"
+ desc = "A skeleton's favorite instrument. Apply directly on the mortals."
+ icon_state = "trombone"
+ item_state = "trombone"
+ force = 0
+ attack_verb = list("played", "jazzed", "tromboned", "mourned", "dooted", "spooked")
+
+/obj/item/instrument/trombone/spectral/Initialize()
+ . = ..()
+ AddComponent(/datum/component/spooky)
+
+/obj/item/instrument/trombone/spectral/attack(mob/living/carbon/C, mob/user)
+ playsound (src, 'sound/instruments/trombone/Cn4.mid', 100,1,-1)
+ ..()
/obj/item/instrument/recorder
name = "recorder"
desc = "Just like in school, playing ability and all."
+ force = 5
icon_state = "recorder"
item_state = "recorder"
- instrumentId = "recorder"
+ allowed_instrument_ids = "recorder"
/obj/item/instrument/harmonica
name = "harmonica"
desc = "For when you get a bad case of the space blues."
icon_state = "harmonica"
item_state = "harmonica"
- instrumentId = "harmonica"
force = 5
w_class = WEIGHT_CLASS_SMALL
+ allowed_instrument_ids = "harmonica"
/obj/item/instrument/xylophone
name = "xylophone"
- desc = "a percussion instrument with a bright tone."
+ desc = "A percussion instrument with a bright tone."
icon_state = "xylophone"
item_state = "xylophone"
- instrumentId = "xylophone"
+ allowed_instrument_ids = "bikehorn"
/obj/item/instrument/bikehorn
name = "gilded bike horn"
@@ -153,14 +167,14 @@
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
attack_verb = list("beautifully honks")
- instrumentId = "bikehorn"
- instrumentExt = "ogg"
w_class = WEIGHT_CLASS_TINY
force = 0
throw_speed = 3
throw_range = 7
hitsound = 'sound/items/bikehorn.ogg'
+ allowed_instrument_ids = "bikehorn"
+// Crafting recipes
/datum/crafting_recipe/violin
name = "Violin"
result = /obj/item/instrument/violin
@@ -168,7 +182,7 @@
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- time = 80
+ time = 8 SECONDS
category = CAT_MISC
/datum/crafting_recipe/guitar
@@ -178,7 +192,7 @@
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- time = 80
+ time = 8 SECONDS
category = CAT_MISC
/datum/crafting_recipe/eguitar
@@ -188,5 +202,15 @@
/obj/item/stack/cable_coil = 6,
/obj/item/stack/tape_roll = 5)
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
- time = 80
+ time = 8 SECONDS
+ category = CAT_MISC
+
+/datum/crafting_recipe/banjo
+ name = "Banjo"
+ result = /obj/item/instrument/banjo
+ reqs = list(/obj/item/stack/sheet/wood = 5,
+ /obj/item/stack/cable_coil = 6,
+ /obj/item/stack/tape_roll = 5)
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ time = 8 SECONDS
category = CAT_MISC
diff --git a/code/modules/instruments/objs/structures/_musician.dm b/code/modules/instruments/objs/structures/_musician.dm
new file mode 100644
index 00000000000..542859b4aef
--- /dev/null
+++ b/code/modules/instruments/objs/structures/_musician.dm
@@ -0,0 +1,47 @@
+/obj/structure/musician
+ name = "Not A Piano"
+ desc = "Something broke!"
+ var/can_play_unanchored = FALSE
+ var/list/allowed_instrument_ids
+ var/datum/song/song
+
+/obj/structure/musician/Initialize(mapload)
+ . = ..()
+ song = new(src, allowed_instrument_ids)
+ allowed_instrument_ids = null
+
+/obj/structure/musician/Destroy()
+ QDEL_NULL(song)
+ return ..()
+
+/obj/structure/musician/attack_hand(mob/user)
+ add_fingerprint(user)
+ tgui_interact(user)
+
+/obj/structure/musician/tgui_data(mob/user)
+ return song.tgui_data(user)
+
+/obj/structure/musician/tgui_interact(mob/user)
+ song.tgui_interact(user)
+
+/obj/structure/musician/tgui_act(action, params)
+ if(..())
+ return
+ return song.tgui_act(action, params)
+
+/obj/structure/musician/wrench_act(mob/living/user, obj/item/I)
+ default_unfasten_wrench(user, I, 40)
+ return TRUE
+
+/**
+ * Whether the instrument should stop playing
+ *
+ * Arguments:
+ * * user - The user
+ */
+/obj/structure/musician/proc/should_stop_playing(mob/user)
+ if(!(anchored || can_play_unanchored))
+ return TRUE
+ if(!user)
+ return FALSE
+ return !CanUseTopic(user, GLOB.physical_state)
diff --git a/code/modules/instruments/objs/structures/piano.dm b/code/modules/instruments/objs/structures/piano.dm
new file mode 100644
index 00000000000..a68f07e53a3
--- /dev/null
+++ b/code/modules/instruments/objs/structures/piano.dm
@@ -0,0 +1,22 @@
+/obj/structure/piano
+ parent_type = /obj/structure/musician // TODO: Can't edit maps right now due to a freeze, remove and update path when it's done
+ name = "space minimoog"
+ icon = 'icons/obj/musician.dmi'
+ icon_state = "minimoog"
+ anchored = TRUE
+ density = TRUE
+ allowed_instrument_ids = "piano"
+
+/obj/structure/piano/unanchored
+ anchored = FALSE
+
+/obj/structure/piano/Initialize(mapload)
+ . = ..()
+ if(prob(50) && icon_state == initial(icon_state))
+ name = "space minimoog"
+ desc = "This is a minimoog, like a space piano, but more spacey!"
+ icon_state = "minimoog"
+ else
+ name = "space piano"
+ desc = "This is a space piano, like a regular piano, but always in tune! Even if the musician isn't."
+ icon_state = "piano"
diff --git a/code/modules/instruments/organ.dm b/code/modules/instruments/organ.dm
new file mode 100644
index 00000000000..424f63d7104
--- /dev/null
+++ b/code/modules/instruments/organ.dm
@@ -0,0 +1,43 @@
+/datum/instrument/organ
+ name = "Generic organ"
+ category = "Organ"
+ abstract_type = /datum/instrument/organ
+
+/datum/instrument/organ/crisis_church
+ name = "Crisis Church Organ"
+ id = "crichugan"
+ real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_church/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/organ/crisis_church/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/organ/crisis_church/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/organ/crisis_church/c5.ogg')
+
+/datum/instrument/organ/crisis_hammond
+ name = "Crisis Hammond Organ"
+ id = "crihamgan"
+ real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_hammond/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/organ/crisis_hammond/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/organ/crisis_hammond/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/organ/crisis_hammond/c5.ogg')
+
+/datum/instrument/organ/crisis_accordian
+ name = "Crisis Accordion"
+ id = "crack"
+ real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_accordian/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/organ/crisis_accordian/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/organ/crisis_accordian/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/organ/crisis_accordian/c5.ogg')
+
+/datum/instrument/organ/crisis_harmonica
+ name = "Crisis Harmonica"
+ id = "crharmony"
+ real_samples = list("48"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/organ/crisis_harmonica/c5.ogg')
+
+/datum/instrument/organ/crisis_tango_accordian
+ name = "Crisis Tango Accordion"
+ id = "crtango"
+ real_samples = list("36"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/organ/crisis_tangaccordian/c5.ogg')
diff --git a/code/modules/instruments/piano.dm b/code/modules/instruments/piano.dm
new file mode 100644
index 00000000000..fdd2f6e9382
--- /dev/null
+++ b/code/modules/instruments/piano.dm
@@ -0,0 +1,56 @@
+/datum/instrument/piano
+ name = "Generic piano"
+ category = "Piano"
+ abstract_type = /datum/instrument/piano
+
+/datum/instrument/piano/fluid_piano
+ name = "FluidR3 Grand Piano"
+ id = "r3grand"
+ real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_piano/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/piano/fluid_piano/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/piano/fluid_piano/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/piano/fluid_piano/c5.ogg',
+ "84"='sound/instruments/synthesis_samples/piano/fluid_piano/c6.ogg',
+ "96"='sound/instruments/synthesis_samples/piano/fluid_piano/c7.ogg',
+ "108"='sound/instruments/synthesis_samples/piano/fluid_piano/c8.ogg')
+
+/datum/instrument/piano/fluid_harpsichord
+ name = "FluidR3 Harpsichord"
+ id = "r3harpsi"
+ real_samples = list("36"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c5.ogg',
+ "84"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c6.ogg',
+ "96"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c7.ogg',
+ "108"='sound/instruments/synthesis_samples/piano/fluid_harpsi/c8.ogg')
+
+/datum/instrument/piano/crisis_harpsichord
+ name = "Crisis Harpsichord"
+ id = "crharpsi"
+ real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/piano/crisis_harpsichord/c5.ogg')
+
+/datum/instrument/piano/crisis_grandpiano_uni
+ name = "Crisis Grand Piano One"
+ id = "crgrand1"
+ real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c5.ogg',
+ "84"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c6.ogg',
+ "96"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c7.ogg',
+ "108"='sound/instruments/synthesis_samples/piano/crisis_grand_piano/c8.ogg')
+
+/datum/instrument/piano/crisis_brightpiano_uni
+ name = "Crisis Bright Piano One"
+ id = "crbright1"
+ real_samples = list("36"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c2.ogg',
+ "48"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c3.ogg',
+ "60"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c4.ogg',
+ "72"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c5.ogg',
+ "84"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c6.ogg',
+ "96"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c7.ogg',
+ "108"='sound/instruments/synthesis_samples/piano/crisis_bright_piano/c8.ogg')
diff --git a/code/modules/instruments/songs/_song.dm b/code/modules/instruments/songs/_song.dm
new file mode 100644
index 00000000000..142eab1fd2d
--- /dev/null
+++ b/code/modules/instruments/songs/_song.dm
@@ -0,0 +1,410 @@
+#define MUSICIAN_HEARCHECK_MINDELAY 4
+#define MUSIC_MAXLINES 1000
+#define MUSIC_MAXLINECHARS 300
+
+/**
+ * # Song datum
+ *
+ * These are the actual backend behind instruments.
+ * They attach to an atom and provide the editor + playback functionality.
+ */
+/datum/song
+ /// Name of the song
+ var/name = "Untitled"
+
+ /// The atom we're attached to/playing from
+ var/atom/parent
+
+ /// Our song lines
+ var/list/lines
+
+ /// delay between notes in deciseconds
+ var/tempo = 5
+
+ /// How far we can be heard
+ var/instrument_range = 15
+
+ /// Are we currently playing?
+ var/playing = FALSE
+
+ /// Are we currently editing?
+ var/editing = TRUE
+ /// Is the help screen open?
+ var/help = FALSE
+
+ /// Repeats left
+ var/repeat = 0
+ /// Maximum times we can repeat
+ var/max_repeats = 10
+
+ /// Our volume
+ var/volume = 75
+ /// Max volume
+ var/max_volume = 75
+ /// Min volume - This is so someone doesn't decide it's funny to set it to 0 and play invisible songs.
+ var/min_volume = 1
+
+ /// What instruments our built in picker can use. The picker won't show unless this is longer than one.
+ var/list/allowed_instrument_ids
+
+ //////////// Cached instrument variables /////////////
+ /// Instrument we are currently using
+ var/datum/instrument/using_instrument
+ /// Cached legacy ext for legacy instruments
+ var/cached_legacy_ext
+ /// Cached legacy dir for legacy instruments
+ var/cached_legacy_dir
+ /// Cached list of samples, referenced directly from the instrument for synthesized instruments
+ var/list/cached_samples
+ /// Are we operating in legacy mode (so if the instrument is a legacy instrument)
+ var/legacy = FALSE
+ //////////////////////////////////////////////////////
+
+ /////////////////// Playing variables ////////////////
+ /**
+ * Build by compile_chords()
+ * Must be rebuilt on instrument switch.
+ * Compilation happens when we start playing and is cleared after we finish playing.
+ * Format: list of chord lists, with chordlists having (key1, key2, key3, tempodiv)
+ */
+ var/list/compiled_chords
+ /// Current section of a long chord we're on, so we don't need to make a billion chords, one for every unit ticklag.
+ var/elapsed_delay
+ /// Amount of delay to wait before playing the next chord
+ var/delay_by
+ /// Current chord we're on.
+ var/current_chord
+ /// Channel as text = current volume percentage but it's 0 to 100 instead of 0 to 1.
+ var/list/channels_playing
+ /// List of channels that aren't being used, as text. This is to prevent unnecessary freeing and reallocations from SSsounds/SSinstruments.
+ var/list/channels_idle
+ /// Person playing us
+ var/mob/user_playing
+ //////////////////////////////////////////////////////
+
+ /// Last world.time we checked for who can hear us
+ var/last_hearcheck = 0
+ /// The list of mobs that can hear us
+ var/list/hearing_mobs
+ /// If this is enabled, some things won't be strictly cleared when they usually are (liked compiled_chords on play stop)
+ var/debug_mode = FALSE
+ /// Max sound channels to occupy
+ var/max_sound_channels = CHANNELS_PER_INSTRUMENT
+ /// Current channels, so we can save a length() call.
+ var/using_sound_channels = 0
+ /// Last channel to play. text.
+ var/last_channel_played
+ /// Should we not decay our last played note?
+ var/full_sustain_held_note = TRUE
+
+ /////////////////////// DO NOT TOUCH THESE ///////////////////
+ var/octave_min = INSTRUMENT_MIN_OCTAVE
+ var/octave_max = INSTRUMENT_MAX_OCTAVE
+ var/key_min = INSTRUMENT_MIN_KEY
+ var/key_max = INSTRUMENT_MAX_KEY
+ var/static/list/note_offset_lookup
+ var/static/list/accent_lookup
+ //////////////////////////////////////////////////////////////
+
+ ///////////// !!FUN!! - Only works in synthesized mode! /////////////////
+ /// Note numbers to shift.
+ var/note_shift = 0
+ var/note_shift_min = -100
+ var/note_shift_max = 100
+ var/can_noteshift = TRUE
+ /// The kind of sustain we're using
+ var/sustain_mode = SUSTAIN_LINEAR
+ /// When a note is considered dead if it is below this in volume
+ var/sustain_dropoff_volume = INSTRUMENT_MIN_SUSTAIN_DROPOFF
+ /// Total duration of linear sustain for 100 volume note to get to SUSTAIN_DROPOFF
+ var/sustain_linear_duration = 5
+ /// Exponential sustain dropoff rate per decisecond
+ var/sustain_exponential_dropoff = 1.4
+ ////////// DO NOT DIRECTLY SET THESE!
+ /// Do not directly set, use update_sustain()
+ var/cached_linear_dropoff = 10
+ /// Do not directly set, use update_sustain()
+ var/cached_exponential_dropoff = 1.045
+ /////////////////////////////////////////////////////////////////////////
+
+ var/static/list/valid_files[0] // Cache to avoid running fexists() every time
+
+/datum/song/New(atom/parent, list/instrument_ids, new_range)
+ SSinstruments.on_song_new(src)
+ lines = list()
+ tempo = sanitize_tempo(tempo)
+ src.parent = parent
+ if(instrument_ids)
+ allowed_instrument_ids = islist(instrument_ids) ? instrument_ids : list(instrument_ids)
+ if(length(allowed_instrument_ids))
+ set_instrument(allowed_instrument_ids[1])
+ hearing_mobs = list()
+ volume = clamp(volume, min_volume, max_volume)
+ channels_playing = list()
+ channels_idle = list()
+ if(!note_offset_lookup)
+ note_offset_lookup = list(9, 11, 0, 2, 4, 5, 7)
+ accent_lookup = list("b" = -1, "s" = 1, "#" = 1, "n" = 0)
+ update_sustain()
+ if(new_range)
+ instrument_range = new_range
+
+/datum/song/Destroy()
+ stop_playing()
+ SSinstruments.on_song_del(src)
+ lines = null
+ using_instrument = null
+ allowed_instrument_ids = null
+ parent = null
+ return ..()
+
+/**
+ * Checks and stores which mobs can hear us. Terminates sounds for mobs that leave our range.
+ */
+/datum/song/proc/do_hearcheck()
+ last_hearcheck = world.time
+ var/list/old = hearing_mobs.Copy()
+ hearing_mobs.len = 0
+ var/turf/source = get_turf(parent)
+ for(var/mob/M in GLOB.player_list)
+ var/dist = get_dist(M, source)
+ if(dist > instrument_range) // Distance check
+ continue
+ if(!isInSight(M, source)) // Visibility check (direct line of sight)
+ continue
+ hearing_mobs[M] = dist
+ var/list/exited = old - hearing_mobs
+ for(var/i in exited)
+ terminate_sound_mob(i)
+
+/**
+ * Sets our instrument, caching anything necessary for faster accessing. Accepts an ID, typepath, or instantiated instrument datum.
+ */
+/datum/song/proc/set_instrument(datum/instrument/I)
+ terminate_all_sounds()
+ var/old_legacy
+ if(using_instrument)
+ using_instrument.songs_using -= src
+ old_legacy = (using_instrument.instrument_flags & INSTRUMENT_LEGACY)
+ using_instrument = null
+ cached_samples = null
+ cached_legacy_ext = null
+ cached_legacy_dir = null
+ legacy = null
+ if(istext(I) || ispath(I))
+ I = SSinstruments.instrument_data[I]
+ if(istype(I))
+ using_instrument = I
+ I.songs_using += src
+ var/instrument_legacy = (I.instrument_flags & INSTRUMENT_LEGACY)
+ if(instrument_legacy)
+ cached_legacy_ext = I.legacy_instrument_ext
+ cached_legacy_dir = I.legacy_instrument_path
+ legacy = TRUE
+ else
+ cached_samples = I.samples
+ legacy = FALSE
+ if(isnull(old_legacy) || (old_legacy != instrument_legacy))
+ if(playing)
+ compile_chords()
+
+/**
+ * Attempts to start playing our song.
+ */
+/datum/song/proc/start_playing(mob/user)
+ if(playing)
+ return
+ if(!using_instrument?.is_ready())
+ to_chat(user, "An error has occured with [src]. Please reset the instrument.")
+ return
+ compile_chords()
+ if(!length(compiled_chords))
+ to_chat(user, "Song is empty.")
+ return
+ playing = TRUE
+ SStgui.update_uis(parent)
+ //we can not afford to runtime, since we are going to be doing sound channel reservations and if we runtime it means we have a channel allocation leak.
+ //wrap the rest of the stuff to ensure stop_playing() is called.
+ do_hearcheck()
+ SEND_SIGNAL(parent, COMSIG_SONG_START)
+ elapsed_delay = 0
+ delay_by = 0
+ current_chord = 1
+ user_playing = user
+ START_PROCESSING(SSinstruments, src)
+
+/**
+ * Stops playing, terminating all sounds if in synthesized mode. Clears hearing_mobs.
+ */
+/datum/song/proc/stop_playing()
+ if(!playing)
+ return
+ playing = FALSE
+ if(!debug_mode)
+ compiled_chords = null
+ STOP_PROCESSING(SSinstruments, src)
+ SEND_SIGNAL(parent, COMSIG_SONG_END)
+ terminate_all_sounds(TRUE)
+ hearing_mobs.len = 0
+ SStgui.update_uis(parent)
+ user_playing = null
+
+/**
+ * Processes our song.
+ */
+/datum/song/proc/process_song(wait)
+ if(!length(compiled_chords) || current_chord > length(compiled_chords) || should_stop_playing(user_playing))
+ stop_playing()
+ return
+ var/list/chord = compiled_chords[current_chord]
+ if(++elapsed_delay >= delay_by)
+ play_chord(chord)
+ elapsed_delay = 0
+ delay_by = tempodiv_to_delay(chord[length(chord)])
+ current_chord++
+ if(current_chord > length(compiled_chords))
+ if(repeat)
+ repeat--
+ current_chord = 1
+ SStgui.update_uis(parent)
+ return
+ else
+ stop_playing()
+ return
+
+/**
+ * Converts a tempodiv to ticks to elapse before playing the next chord, taking into account our tempo.
+ */
+/datum/song/proc/tempodiv_to_delay(tempodiv)
+ tempodiv = tempodiv || world.tick_lag // Default to world.tick_lag in case someone's trying to be smart
+ return max(1, round((tempo / tempodiv) / world.tick_lag, 1))
+
+/**
+ * Compiles chords.
+ */
+/datum/song/proc/compile_chords()
+ legacy ? compile_legacy() : compile_synthesized()
+
+/**
+ * Plays a chord.
+ */
+/datum/song/proc/play_chord(list/chord)
+ // last value is timing information
+ for(var/i in 1 to (length(chord) - 1))
+ legacy? playkey_legacy(chord[i][1], chord[i][2], chord[i][3], user_playing) : playkey_synth(chord[i], user_playing)
+
+/**
+ * Checks if we should halt playback.
+ */
+/datum/song/proc/should_stop_playing(mob/user)
+ return QDELETED(parent) || !using_instrument || !playing
+
+/**
+ * Sanitizes tempo to a value that makes sense and fits the current world.tick_lag.
+ */
+/datum/song/proc/sanitize_tempo(new_tempo)
+ new_tempo = abs(new_tempo)
+ return clamp(round(new_tempo, world.tick_lag), world.tick_lag, 5 SECONDS)
+
+/**
+ * Gets our beats per minute based on our tempo.
+ */
+/datum/song/proc/get_bpm()
+ return 600 / tempo
+
+/**
+ * Sets our tempo from a beats-per-minute, sanitizing it to a valid number first.
+ */
+/datum/song/proc/set_bpm(bpm)
+ tempo = sanitize_tempo(600 / bpm)
+
+/datum/song/process(wait)
+ if(!playing)
+ return PROCESS_KILL
+ // it's expected this ticks at every world.tick_lag. if it lags, do not attempt to catch up.
+ process_song(world.tick_lag)
+ process_decay(world.tick_lag)
+
+/**
+ * Updates our cached linear/exponential falloff stuff, saving calculations down the line.
+ */
+/datum/song/proc/update_sustain()
+ // Exponential is easy
+ cached_exponential_dropoff = sustain_exponential_dropoff
+ // Linear, not so much, since it's a target duration from 100 volume rather than an exponential rate.
+ var/target_duration = sustain_linear_duration
+ var/volume_diff = max(0, 100 - sustain_dropoff_volume)
+ var/volume_decrease_per_decisecond = volume_diff / target_duration
+ cached_linear_dropoff = volume_decrease_per_decisecond
+
+/**
+ * Setter for setting output volume.
+ */
+/datum/song/proc/set_volume(volume)
+ src.volume = clamp(volume, max(0, min_volume), min(100, max_volume))
+ update_sustain()
+ // We don't want to send the whole payload (song included) just for volume
+ var/datum/tgui/ui = SStgui.get_open_ui(usr, parent, "main")
+ if(ui)
+ ui.push_data(list("volume" = volume), force = TRUE)
+
+/**
+ * Setter for setting how low the volume has to get before a note is considered "dead" and dropped
+ */
+/datum/song/proc/set_dropoff_volume(volume, no_refresh = FALSE)
+ sustain_dropoff_volume = clamp(volume, INSTRUMENT_MIN_SUSTAIN_DROPOFF, 100)
+ update_sustain()
+ if(!no_refresh)
+ SStgui.update_uis(parent)
+
+/**
+ * Setter for setting exponential falloff factor.
+ */
+/datum/song/proc/set_exponential_drop_rate(drop, no_refresh = FALSE)
+ sustain_exponential_dropoff = clamp(drop, INSTRUMENT_EXP_FALLOFF_MIN, INSTRUMENT_EXP_FALLOFF_MAX)
+ update_sustain()
+ if(!no_refresh)
+ SStgui.update_uis(parent)
+
+/**
+ * Setter for setting linear falloff duration.
+ */
+/datum/song/proc/set_linear_falloff_duration(duration, no_refresh = FALSE)
+ sustain_linear_duration = clamp(duration, 0.1, INSTRUMENT_MAX_TOTAL_SUSTAIN)
+ update_sustain()
+ if(!no_refresh)
+ SStgui.update_uis(parent)
+
+/datum/song/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(.)
+ switch(var_name)
+ if(NAMEOF(src, volume))
+ set_volume(var_value)
+ if(NAMEOF(src, sustain_dropoff_volume))
+ set_dropoff_volume(var_value)
+ if(NAMEOF(src, sustain_exponential_dropoff))
+ set_exponential_drop_rate(var_value)
+ if(NAMEOF(src, sustain_linear_duration))
+ set_linear_falloff_duration(var_value)
+
+// subtype for handheld instruments, like violin
+/datum/song/handheld
+
+/datum/song/handheld/should_stop_playing(mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ var/obj/item/instrument/I = parent
+ return I.should_stop_playing(user)
+
+// subtype for stationary structures, like pianos
+/datum/song/stationary
+
+/datum/song/stationary/should_stop_playing(mob/user)
+ . = ..()
+ if(.)
+ return TRUE
+ var/obj/structure/musician/M = parent
+ return M.should_stop_playing(user)
+
diff --git a/code/modules/instruments/songs/_song_ui.dm b/code/modules/instruments/songs/_song_ui.dm
new file mode 100644
index 00000000000..cbd0f646f5c
--- /dev/null
+++ b/code/modules/instruments/songs/_song_ui.dm
@@ -0,0 +1,179 @@
+/datum/song/tgui_data(mob/user)
+ var/data[0]
+
+ // General
+ data["playing"] = playing
+ data["repeat"] = repeat
+ data["maxRepeats"] = max_repeats
+ data["editing"] = editing
+ data["lines"] = lines
+ data["tempo"] = tempo
+ data["minTempo"] = world.tick_lag
+ data["maxTempo"] = 5 SECONDS
+ data["tickLag"] = world.tick_lag
+ data["help"] = help
+
+ // Status
+ var/list/allowed_instrument_names = list()
+ for(var/i in allowed_instrument_ids)
+ var/datum/instrument/I = SSinstruments.get_instrument(i)
+ if(I)
+ allowed_instrument_names += I.name
+ data["allowedInstrumentNames"] = allowed_instrument_names
+ data["instrumentLoaded"] = !isnull(using_instrument)
+ if(using_instrument)
+ data["instrument"] = using_instrument.name
+ data["canNoteShift"] = can_noteshift
+ if(can_noteshift)
+ data["noteShift"] = note_shift
+ data["noteShiftMin"] = note_shift_min
+ data["noteShiftMax"] = note_shift_max
+ data["sustainMode"] = sustain_mode
+ switch(sustain_mode)
+ if(SUSTAIN_LINEAR)
+ data["sustainLinearDuration"] = sustain_linear_duration
+ if(SUSTAIN_EXPONENTIAL)
+ data["sustainExponentialDropoff"] = sustain_exponential_dropoff
+ data["ready"] = using_instrument?.is_ready()
+ data["legacy"] = legacy
+ data["volume"] = volume
+ data["minVolume"] = min_volume
+ data["maxVolume"] = max_volume
+ data["sustainDropoffVolume"] = sustain_dropoff_volume
+ data["sustainHeldNote"] = full_sustain_held_note
+
+ return data
+
+/datum/song/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
+ ui = SStgui.try_update_ui(user, parent, ui_key, ui, force_open)
+ if(!ui)
+ ui = new(user, parent, ui_key, "Instrument", parent?.name || "Instrument", 700, 500)
+ ui.open()
+ ui.set_autoupdate(FALSE) // NO!!! Don't auto-update this!!
+
+/datum/song/tgui_act(action, params)
+ . = TRUE
+ switch(action)
+ if("newsong")
+ lines = new()
+ tempo = sanitize_tempo(5) // default 120 BPM
+ name = ""
+ if("import")
+ var/t = ""
+ do
+ t = html_encode(input(usr, "Please paste the entire song, formatted:", text("[]", name), t) as message)
+ if(!in_range(parent, usr))
+ return
+
+ if(length_char(t) >= MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
+ var/cont = input(usr, "Your message is too long! Would you like to continue editing it?", "", "yes") in list("yes", "no")
+ if(cont == "no")
+ break
+ while(length_char(t) > MUSIC_MAXLINES * MUSIC_MAXLINECHARS)
+ parse_song(t)
+ return FALSE
+ if("help")
+ help = !help
+ if("edit")
+ editing = !editing
+ if("repeat") //Changing this from a toggle to a number of repeats to avoid infinite loops.
+ if(playing)
+ return //So that people cant keep adding to repeat. If the do it intentionally, it could result in the server crashing.
+ repeat = clamp(round(text2num(params["new"])), 0, max_repeats)
+ if("tempo")
+ tempo = sanitize_tempo(text2num(params["new"]))
+ if("play")
+ INVOKE_ASYNC(src, .proc/start_playing, usr)
+ if("newline")
+ var/newline = html_encode(input("Enter your line: ", parent.name) as text|null)
+ if(!newline || !in_range(parent, usr))
+ return
+ if(length(lines) > MUSIC_MAXLINES)
+ return
+ if(length(newline) > MUSIC_MAXLINECHARS)
+ newline = copytext(newline, 1, MUSIC_MAXLINECHARS)
+ lines.Add(newline)
+ if("deleteline")
+ var/num = round(text2num(params["line"]))
+ if(num > length(lines) || num < 1)
+ return
+ lines.Cut(num, num + 1)
+ if("modifyline")
+ var/num = round(text2num(params["line"]))
+ var/content = stripped_input(usr, "Enter your line: ", parent.name, lines[num], MUSIC_MAXLINECHARS)
+ if(!content || !in_range(parent, usr))
+ return
+ if(num > length(lines) || num < 1)
+ return
+ lines[num] = content
+ if("stop")
+ stop_playing()
+ if("setlinearfalloff")
+ set_linear_falloff_duration(round(text2num(params["new"]) * 10, world.tick_lag), TRUE)
+ if("setexpfalloff")
+ set_exponential_drop_rate(round(text2num(params["new"]), 0.00001), TRUE)
+ if("setvolume")
+ set_volume(round(text2num(params["new"]), 1))
+ if("setdropoffvolume")
+ set_dropoff_volume(round(text2num(params["new"]), 0.01), TRUE)
+ if("switchinstrument")
+ if(!length(allowed_instrument_ids))
+ return
+ else if(length(allowed_instrument_ids) == 1)
+ set_instrument(allowed_instrument_ids[1])
+ return
+ var/choice = params["name"]
+ for(var/i in allowed_instrument_ids)
+ var/datum/instrument/I = SSinstruments.get_instrument(i)
+ if(I && I.name == choice)
+ set_instrument(I)
+ if("setnoteshift")
+ note_shift = clamp(round(text2num(params["new"])), note_shift_min, note_shift_max)
+ if("setsustainmode")
+ var/static/list/sustain_modes
+ if(!length(sustain_modes))
+ sustain_modes = list("Linear" = SUSTAIN_LINEAR, "Exponential" = SUSTAIN_EXPONENTIAL)
+ var/choice = params["new"]
+ sustain_mode = sustain_modes[choice] || sustain_mode
+ if("togglesustainhold")
+ full_sustain_held_note = !full_sustain_held_note
+ if("reset")
+ var/default_instrument = allowed_instrument_ids[1]
+ if(using_instrument != SSinstruments.instrument_data[default_instrument])
+ set_instrument(default_instrument)
+ note_shift = initial(note_shift)
+ sustain_mode = initial(sustain_mode)
+ set_linear_falloff_duration(initial(sustain_linear_duration), TRUE)
+ set_exponential_drop_rate(initial(sustain_exponential_dropoff), TRUE)
+ set_dropoff_volume(initial(sustain_dropoff_volume), TRUE)
+ else
+ return FALSE
+ parent.add_fingerprint(usr)
+
+/**
+ * Parses a song the user has input into lines and stores them.
+ */
+/datum/song/proc/parse_song(text)
+ set waitfor = FALSE
+ //split into lines
+ stop_playing()
+ lines = splittext(text, "\n")
+ if(length(lines))
+ var/bpm_string = "BPM: "
+ if(findtext(lines[1], bpm_string, 1, length(bpm_string) + 1))
+ var/divisor = text2num(copytext(lines[1], length(bpm_string) + 1)) || 120 // default
+ tempo = sanitize_tempo(600 / round(divisor, 1))
+ lines.Cut(1, 2)
+ else
+ tempo = sanitize_tempo(5) // default 120 BPM
+ if(length(lines) > MUSIC_MAXLINES)
+ to_chat(usr, "Too many lines!")
+ lines.Cut(MUSIC_MAXLINES + 1)
+ var/linenum = 1
+ for(var/l in lines)
+ if(length_char(l) > MUSIC_MAXLINECHARS)
+ to_chat(usr, "Line [linenum] too long!")
+ lines.Remove(l)
+ else
+ linenum++
+ SStgui.update_uis(parent)
diff --git a/code/modules/instruments/songs/play_legacy.dm b/code/modules/instruments/songs/play_legacy.dm
new file mode 100644
index 00000000000..61d7964caf4
--- /dev/null
+++ b/code/modules/instruments/songs/play_legacy.dm
@@ -0,0 +1,95 @@
+/**
+ * Compiles our lines into "chords" with filenames for legacy playback. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
+ */
+/datum/song/proc/compile_legacy()
+ if(!length(src.lines))
+ return
+ var/list/lines = src.lines //cache for hyepr speed!
+ compiled_chords = list()
+ var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
+ var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
+ for(var/line in lines)
+ var/list/chords = splittext(lowertext(line), ",")
+ for(var/chord in chords)
+ var/list/compiled_chord = list()
+ var/tempodiv = 1
+ var/list/notes_tempodiv = splittext(chord, "/")
+ var/len = length(notes_tempodiv)
+ if(len >= 2)
+ tempodiv = text2num(notes_tempodiv[2])
+ if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
+ var/list/notes = splittext(notes_tempodiv[1], "-")
+ for(var/note in notes)
+ if(length(note) == 0)
+ continue
+ // 1-7, A-G
+ var/key = text2ascii(note) - 96
+ if((key < 1) || (key > 7))
+ continue
+ for(var/i in 2 to length(note))
+ var/oct_acc = copytext(note, i, i + 1)
+ var/num = text2num(oct_acc)
+ if(!num) //it's an accidental
+ accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
+ else //octave
+ octaves[key] = clamp(num, octave_min, octave_max)
+ compiled_chord[++compiled_chord.len] = list(key, accents[key], octaves[key])
+ compiled_chord += tempodiv //this goes last
+ if(length(compiled_chord))
+ compiled_chords[++compiled_chords.len] = compiled_chord
+
+/**
+ * Proc to play a legacy note. Just plays the sound to hearing mobs (and does hearcheck if necessary), no fancy channel/sustain/management.
+ *
+ * Arguments:
+ * * note - number from 1-7 for A-G
+ * * acc - either "b", "n", or "#"
+ * * oct - 1-8 (or 9 for C)
+ */
+/datum/song/proc/playkey_legacy(note, acc as text, oct, mob/user)
+ // handle accidental -> B<>C of E<>F
+ if(acc == "b" && (note == 3 || note == 6)) // C or F
+ if(note == 3)
+ oct--
+ note--
+ acc = "n"
+ else if(acc == "#" && (note == 2 || note == 5)) // B or E
+ if(note == 2)
+ oct++
+ note++
+ acc = "n"
+ else if(acc == "#" && (note == 7)) //G#
+ note = 1
+ acc = "b"
+ else if(acc == "#") // mass convert all sharps to flats, octave jump already handled
+ acc = "b"
+ note++
+
+ // check octave, C is allowed to go to 9
+ if(oct < 1 || (note == 3 ? oct > 9 : oct > 8))
+ return
+
+ // now generate name
+ var/filename = "sound/instruments/[cached_legacy_dir]/[ascii2text(note + 64)][acc][oct].[cached_legacy_ext]"
+ var/soundfile = file(filename)
+ // make sure the note exists
+ var/cached_fexists = valid_files[filename]
+ if(!isnull(cached_fexists))
+ if(!cached_fexists)
+ return
+ else if(!fexists(soundfile))
+ valid_files[filename] = FALSE
+ return
+ else
+ valid_files[filename] = TRUE
+ // and play
+ var/turf/source = get_turf(parent)
+ if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
+ do_hearcheck()
+ var/sound/music_played = sound(soundfile)
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ if(!(M.client?.prefs?.sound & SOUND_INSTRUMENTS))
+ continue
+ M.playsound_local(source, null, volume * using_instrument.volume_multiplier, falloff = 5, S = music_played)
+ // Could do environment and echo later but not for now
diff --git a/code/modules/instruments/songs/play_synthesized.dm b/code/modules/instruments/songs/play_synthesized.dm
new file mode 100644
index 00000000000..b2f16f6ab91
--- /dev/null
+++ b/code/modules/instruments/songs/play_synthesized.dm
@@ -0,0 +1,134 @@
+/**
+ * Compiles our lines into "chords" with numbers. This makes there have to be a bit of lag at the beginning of the song, but repeats will not have to parse it again, and overall playback won't be impacted by as much lag.
+ */
+/datum/song/proc/compile_synthesized()
+ if(!length(src.lines))
+ return
+ var/list/lines = src.lines //cache for hyepr speed!
+ compiled_chords = list()
+ var/list/octaves = list(3, 3, 3, 3, 3, 3, 3)
+ var/list/accents = list("n", "n", "n", "n", "n", "n", "n")
+ for(var/line in lines)
+ var/list/chords = splittext(lowertext(line), ",")
+ for(var/chord in chords)
+ var/list/compiled_chord = list()
+ var/tempodiv = 1
+ var/list/notes_tempodiv = splittext(chord, "/")
+ var/len = length(notes_tempodiv)
+ if(len >= 2)
+ tempodiv = text2num(notes_tempodiv[2])
+ if(len) //some dunkass is going to do ,,,, to make 3 rests instead of ,/1 because there's no standardization so let's be prepared for that.
+ var/list/notes = splittext(notes_tempodiv[1], "-")
+ for(var/note in notes)
+ if(length(note) == 0)
+ continue
+ // 1-7, A-G
+ var/key = text2ascii(note) - 96
+ if((key < 1) || (key > 7))
+ continue
+ for(var/i in 2 to length(note))
+ var/oct_acc = copytext(note, i, i + 1)
+ var/num = text2num(oct_acc)
+ if(!num) //it's an accidental
+ accents[key] = oct_acc //if they misspelled it/fucked up that's on them lmao, no safety checks.
+ else //octave
+ octaves[key] = clamp(num, octave_min, octave_max)
+ compiled_chord += clamp((note_offset_lookup[key] + octaves[key] * 12 + accent_lookup[accents[key]]), key_min, key_max)
+ compiled_chord += tempodiv //this goes last
+ if(length(compiled_chord))
+ compiled_chords[++compiled_chords.len] = compiled_chord
+
+/**
+ * Plays a specific numerical key from our instrument to anyone who can hear us.
+ * Does a hearing check if enough time has passed.
+ */
+/datum/song/proc/playkey_synth(key, mob/user)
+ if(can_noteshift)
+ key = clamp(key + note_shift, key_min, key_max)
+ if((world.time - MUSICIAN_HEARCHECK_MINDELAY) > last_hearcheck)
+ do_hearcheck()
+ var/datum/instrument_key/K = using_instrument.samples[num2text(key)] //See how fucking easy it is to make a number text? You don't need a complicated 9 line proc!
+ //Should probably add channel limiters here at some point but I don't care right now.
+ var/channel = pop_channel()
+ if(isnull(channel))
+ return FALSE
+ . = TRUE
+ var/sound/copy = sound(K.sample)
+ var/volume = src.volume * using_instrument.volume_multiplier
+ copy.frequency = K.frequency
+ copy.volume = volume
+ var/channel_text = num2text(channel)
+ channels_playing[channel_text] = 100
+ last_channel_played = channel_text
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ if(!(M.client?.prefs?.sound & SOUND_INSTRUMENTS))
+ continue
+ M.playsound_local(get_turf(parent), null, volume, FALSE, K.frequency, INSTRUMENT_DISTANCE_NO_FALLOFF, channel, null, copy, distance_multiplier = INSTRUMENT_DISTANCE_FALLOFF_BUFF)
+ // Could do environment and echo later but not for now
+
+/**
+ * Stops all sounds we are "responsible" for. Only works in synthesized mode.
+ */
+/datum/song/proc/terminate_all_sounds(clear_channels = TRUE)
+ for(var/i in hearing_mobs)
+ terminate_sound_mob(i)
+ if(clear_channels && channels_playing)
+ channels_playing.len = 0
+ channels_idle.len = 0
+ SSinstruments.current_instrument_channels -= using_sound_channels
+ using_sound_channels = 0
+ SSsounds.free_datum_channels(src)
+
+/**
+ * Stops all sounds we are responsible for in a given person. Only works in synthesized mode.
+ */
+/datum/song/proc/terminate_sound_mob(mob/M)
+ for(var/channel in channels_playing)
+ M.stop_sound_channel(text2num(channel))
+
+/**
+ * Pops a channel we have reserved so we don't have to release and re-request them from SSsounds every time we play a note. This is faster.
+ */
+/datum/song/proc/pop_channel()
+ if(length(channels_idle)) //just pop one off of here if we have one available
+ . = text2num(channels_idle[1])
+ channels_idle.Cut(1, 2)
+ return
+ if(using_sound_channels >= max_sound_channels)
+ return
+ . = SSinstruments.reserve_instrument_channel(src)
+ if(!isnull(.))
+ using_sound_channels++
+
+/**
+ * Decays our channels and updates their volumes to mobs who can hear us.
+ *
+ * Arguments:
+ * * wait_ds - the deciseconds we should decay by. This is to compensate for any lag, as otherwise songs would get pretty nasty during high time dilation.
+ */
+/datum/song/proc/process_decay(wait_ds)
+ var/linear_dropoff = cached_linear_dropoff * wait_ds
+ var/exponential_dropoff = cached_exponential_dropoff ** wait_ds
+ for(var/channel in channels_playing)
+ if(full_sustain_held_note && (channel == last_channel_played))
+ continue
+ var/current_volume = channels_playing[channel]
+ switch(sustain_mode)
+ if(SUSTAIN_LINEAR)
+ current_volume -= linear_dropoff
+ if(SUSTAIN_EXPONENTIAL)
+ current_volume /= exponential_dropoff
+ channels_playing[channel] = current_volume
+ var/dead = current_volume <= sustain_dropoff_volume
+ var/channelnumber = text2num(channel)
+ if(dead)
+ channels_playing -= channel
+ channels_idle += channel
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.stop_sound_channel(channelnumber)
+ else
+ for(var/i in hearing_mobs)
+ var/mob/M = i
+ M.set_sound_channel_volume(channelnumber, (current_volume * 0.01) * volume * using_instrument.volume_multiplier)
diff --git a/code/modules/instruments/synth_tones.dm b/code/modules/instruments/synth_tones.dm
new file mode 100644
index 00000000000..9ad9250f40d
--- /dev/null
+++ b/code/modules/instruments/synth_tones.dm
@@ -0,0 +1,19 @@
+/datum/instrument/tones
+ name = "Ideal tone"
+ category = "Tones"
+ abstract_type = /datum/instrument/tones
+
+/datum/instrument/tones/square_wave
+ name = "Ideal square wave"
+ id = "square"
+ real_samples = list("81"='sound/instruments/synthesis_samples/tones/Square.ogg')
+
+/datum/instrument/tones/sine_wave
+ name = "Ideal sine wave"
+ id = "sine"
+ real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sine.ogg')
+
+/datum/instrument/tones/saw_wave
+ name = "Ideal sawtooth wave"
+ id = "saw"
+ real_samples = list("81"='sound/instruments/synthesis_samples/tones/Sawtooth.ogg')
diff --git a/code/modules/mining/machine_vending.dm b/code/modules/mining/machine_vending.dm
index fd675bac32c..058b64d3c18 100644
--- a/code/modules/mining/machine_vending.dm
+++ b/code/modules/mining/machine_vending.dm
@@ -1,104 +1,21 @@
-/**********************Mining Equipment Locker**************************/
+// Use this define to register something as a purchasable!
+// * n — The proper name of the purchasable
+// * o — The object type path of the purchasable to spawn
+// * p — The price of the purchasable in mining points
+#define EQUIPMENT(n, o, p) n = new /datum/data/mining_equipment(n, o, p)
+
+/**********************Mining Equipment Vendor**************************/
/obj/machinery/mineral/equipment_vendor
name = "mining equipment vendor"
desc = "An equipment vendor for miners, points collected at an ore redemption machine can be spent here."
icon = 'icons/obj/machines/mining_machines.dmi'
icon_state = "mining"
- density = 1
- anchored = 1.0
+ density = TRUE
+ anchored = TRUE
var/obj/item/card/id/inserted_id
- var/list/prize_list = list( //if you add something to this, please, for the love of god, sort it by price/type. use tabs and not spaces.
- new /datum/data/mining_equipment("1 Marker Beacon", /obj/item/stack/marker_beacon, 10),
- new /datum/data/mining_equipment("10 Marker Beacons", /obj/item/stack/marker_beacon/ten, 100),
- new /datum/data/mining_equipment("30 Marker Beacons", /obj/item/stack/marker_beacon/thirty, 300),
- new /datum/data/mining_equipment("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
- new /datum/data/mining_equipment("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe/premium, 100),
- new /datum/data/mining_equipment("Cigar", /obj/item/clothing/mask/cigarette/cigar/havana, 150),
- new /datum/data/mining_equipment("Soap", /obj/item/soap/nanotrasen, 200),
- new /datum/data/mining_equipment("Laser Pointer", /obj/item/laser_pointer, 300),
- new /datum/data/mining_equipment("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
- new /datum/data/mining_equipment("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
- new /datum/data/mining_equipment("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
- new /datum/data/mining_equipment("Point Transfer Card", /obj/item/card/mining_point_card, 500),
- new /datum/data/mining_equipment("Fulton Beacon", /obj/item/fulton_core, 400),
- new /datum/data/mining_equipment("Shelter Capsule", /obj/item/survivalcapsule, 400),
- new /datum/data/mining_equipment("GAR Meson Scanners", /obj/item/clothing/glasses/meson/gar, 500),
- new /datum/data/mining_equipment("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
- new /datum/data/mining_equipment("Survival Medipen", /obj/item/reagent_containers/hypospray/autoinjector/survival, 500),
- new /datum/data/mining_equipment("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 600),
- new /datum/data/mining_equipment("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
- new /datum/data/mining_equipment("Jaunter", /obj/item/wormhole_jaunter, 750),
- new /datum/data/mining_equipment("Kinetic Crusher", /obj/item/twohanded/kinetic_crusher, 750),
- new /datum/data/mining_equipment("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
- new /datum/data/mining_equipment("Advanced Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
- new /datum/data/mining_equipment("Resonator", /obj/item/resonator, 800),
- new /datum/data/mining_equipment("Fulton Pack", /obj/item/extraction_pack, 1000),
- new /datum/data/mining_equipment("Lazarus Injector", /obj/item/lazarus_injector, 1000),
- new /datum/data/mining_equipment("Silver Pickaxe", /obj/item/pickaxe/silver, 1000),
- new /datum/data/mining_equipment("Mining Conscription Kit", /obj/item/storage/backpack/duffel/mining_conscript/full, 1500),
- new /datum/data/mining_equipment("Jetpack Upgrade", /obj/item/tank/jetpack/suit, 2000),
- new /datum/data/mining_equipment("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000),
- new /datum/data/mining_equipment("Diamond Pickaxe", /obj/item/pickaxe/diamond, 2000),
- new /datum/data/mining_equipment("Super Resonator", /obj/item/resonator/upgraded, 2500),
- new /datum/data/mining_equipment("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
- new /datum/data/mining_equipment("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
- new /datum/data/mining_equipment("Nanotrasen Minebot", /obj/item/mining_drone_cube, 800),
- new /datum/data/mining_equipment("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
- new /datum/data/mining_equipment("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
- new /datum/data/mining_equipment("Minebot Cooldown Upgrade", /obj/item/borg/upgrade/modkit/cooldown/minebot, 600),
- new /datum/data/mining_equipment("Minebot AI Upgrade", /obj/item/slimepotion/sentience/mining, 1000),
- new /datum/data/mining_equipment("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100),
- new /datum/data/mining_equipment("Lazarus Capsule", /obj/item/mobcapsule, 800),
- new /datum/data/mining_equipment("Lazarus Capsule belt", /obj/item/storage/belt/lazarus, 200),
- new /datum/data/mining_equipment("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100),
- new /datum/data/mining_equipment("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150),
- new /datum/data/mining_equipment("KA Super Chassis", /obj/item/borg/upgrade/modkit/chassis_mod, 250),
- new /datum/data/mining_equipment("KA Hyper Chassis", /obj/item/borg/upgrade/modkit/chassis_mod/orange, 300),
- new /datum/data/mining_equipment("KA Range Increase", /obj/item/borg/upgrade/modkit/range, 1000),
- new /datum/data/mining_equipment("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
- new /datum/data/mining_equipment("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
- new /datum/data/mining_equipment("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000)
- )
-
-/obj/machinery/mineral/equipment_vendor/golem
- name = "golem ship equipment vendor"
-
-/obj/machinery/mineral/equipment_vendor/golem/New()
- ..()
- component_parts = list()
- component_parts += new /obj/item/circuitboard/mining_equipment_vendor/golem(null)
- component_parts += new /obj/item/stock_parts/matter_bin(null)
- component_parts += new /obj/item/stock_parts/matter_bin(null)
- component_parts += new /obj/item/stock_parts/matter_bin(null)
- component_parts += new /obj/item/stack/sheet/glass(null)
- RefreshParts()
-
-/obj/machinery/mineral/equipment_vendor/golem/Initialize()
- . = ..()
- desc += "\nIt seems a few selections have been added."
- prize_list += list(
- new /datum/data/mining_equipment("Extra Id", /obj/item/card/id/golem, 250),
- new /datum/data/mining_equipment("Science Backpack", /obj/item/storage/backpack/science, 250),
- new /datum/data/mining_equipment("Full Toolbelt", /obj/item/storage/belt/utility/full/multitool, 250),
- new /datum/data/mining_equipment("Monkey Cube", /obj/item/reagent_containers/food/snacks/monkeycube, 250),
- new /datum/data/mining_equipment("Royal Cape of the Liberator", /obj/item/bedsheet/rd/royal_cape, 500),
- new /datum/data/mining_equipment("Grey Slime Extract", /obj/item/slime_extract/grey, 1000),
- new /datum/data/mining_equipment("KA Trigger Modification Kit", /obj/item/borg/upgrade/modkit/trigger_guard, 1000),
- new /datum/data/mining_equipment("Shuttle Console Board", /obj/item/circuitboard/shuttle/golem_ship, 2000),
- new /datum/data/mining_equipment("The Liberator's Legacy", /obj/item/storage/box/rndboards, 2000)
-
- )
-
-/datum/data/mining_equipment
- var/equipment_name = "generic"
- var/equipment_path = null
- var/cost = 0
-
-/datum/data/mining_equipment/New(name, path, equipment_cost)
- equipment_name = name
- equipment_path = path
- cost = equipment_cost
+ var/list/prize_list // Initialized just below! (if you're wondering why - check CONTRIBUTING.md, look for: "hidden" init proc)
+ var/dirty_items = FALSE // Used to refresh the static/redundant data in case the machine gets VV'd
/obj/machinery/mineral/equipment_vendor/New()
..()
@@ -110,6 +27,72 @@
component_parts += new /obj/item/stack/sheet/glass(null)
RefreshParts()
+/obj/machinery/mineral/equipment_vendor/Initialize(mapload)
+ . = ..()
+ prize_list = list()
+ prize_list["Gear"] = list(
+ EQUIPMENT("Advanced Scanner", /obj/item/t_scanner/adv_mining_scanner, 800),
+ EQUIPMENT("Explorer's Webbing", /obj/item/storage/belt/mining, 500),
+ EQUIPMENT("Fulton Beacon", /obj/item/fulton_core, 400),
+ EQUIPMENT("Mining Conscription Kit", /obj/item/storage/backpack/duffel/mining_conscript, 1500),
+ EQUIPMENT("Jetpack Upgrade", /obj/item/tank/jetpack/suit, 2000),
+ EQUIPMENT("Jump Boots", /obj/item/clothing/shoes/bhop, 2500),
+ EQUIPMENT("Lazarus Capsule", /obj/item/mobcapsule, 800),
+ EQUIPMENT("Lazarus Capsule belt", /obj/item/storage/belt/lazarus, 200),
+ EQUIPMENT("Mining Hardsuit", /obj/item/clothing/suit/space/hardsuit/mining, 2000),
+ EQUIPMENT("Tracking Implant Kit", /obj/item/storage/box/minertracker, 600),
+ )
+ prize_list["Consumables"] = list(
+ EQUIPMENT("10 Marker Beacons", /obj/item/stack/marker_beacon/ten, 100),
+ EQUIPMENT("Brute First-Aid Kit", /obj/item/storage/firstaid/brute, 600),
+ EQUIPMENT("Fulton Pack", /obj/item/extraction_pack, 1000),
+ EQUIPMENT("Jaunter", /obj/item/wormhole_jaunter, 750),
+ EQUIPMENT("Lazarus Injector", /obj/item/lazarus_injector, 1000),
+ EQUIPMENT("Point Transfer Card", /obj/item/card/mining_point_card, 500),
+ EQUIPMENT("Shelter Capsule", /obj/item/survivalcapsule, 400),
+ EQUIPMENT("Stabilizing Serum", /obj/item/hivelordstabilizer, 400),
+ EQUIPMENT("Survival Medipen", /obj/item/reagent_containers/hypospray/autoinjector/survival, 500),
+ )
+ prize_list["Kinetic Accelerator"] = list(
+ EQUIPMENT("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
+ EQUIPMENT("KA Adjustable Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer/adjustable, 150),
+ EQUIPMENT("KA AoE Damage", /obj/item/borg/upgrade/modkit/aoe/mobs, 2000),
+ EQUIPMENT("KA Cooldown Decrease", /obj/item/borg/upgrade/modkit/cooldown, 1000),
+ EQUIPMENT("KA Damage Increase", /obj/item/borg/upgrade/modkit/damage, 1000),
+ EQUIPMENT("KA Hyper Chassis", /obj/item/borg/upgrade/modkit/chassis_mod/orange, 300),
+ EQUIPMENT("KA Minebot Passthrough", /obj/item/borg/upgrade/modkit/minebot_passthrough, 100),
+ EQUIPMENT("KA Range Increase", /obj/item/borg/upgrade/modkit/range, 1000),
+ EQUIPMENT("KA Super Chassis", /obj/item/borg/upgrade/modkit/chassis_mod, 250),
+ EQUIPMENT("KA White Tracer Rounds", /obj/item/borg/upgrade/modkit/tracer, 100),
+ )
+ prize_list["Digging Tools"] = list(
+ EQUIPMENT("Diamond Pickaxe", /obj/item/pickaxe/diamond, 2000),
+ EQUIPMENT("Kinetic Accelerator", /obj/item/gun/energy/kinetic_accelerator, 750),
+ EQUIPMENT("Kinetic Crusher", /obj/item/twohanded/kinetic_crusher, 750),
+ EQUIPMENT("Resonator", /obj/item/resonator, 800),
+ EQUIPMENT("Silver Pickaxe", /obj/item/pickaxe/silver, 1000),
+ EQUIPMENT("Super Resonator", /obj/item/resonator/upgraded, 2500),
+ )
+ prize_list["Minebot"] = list(
+ EQUIPMENT("Nanotrasen Minebot", /obj/item/mining_drone_cube, 800),
+ EQUIPMENT("Minebot AI Upgrade", /obj/item/slimepotion/sentience/mining, 1000),
+ EQUIPMENT("Minebot Armor Upgrade", /obj/item/mine_bot_upgrade/health, 400),
+ EQUIPMENT("Minebot Cooldown Upgrade", /obj/item/borg/upgrade/modkit/cooldown/minebot, 600),
+ EQUIPMENT("Minebot Melee Upgrade", /obj/item/mine_bot_upgrade, 400),
+ )
+ prize_list["Miscellaneous"] = list(
+ EQUIPMENT("Absinthe", /obj/item/reagent_containers/food/drinks/bottle/absinthe/premium, 100),
+ EQUIPMENT("Alien Toy", /obj/item/clothing/mask/facehugger/toy, 300),
+ EQUIPMENT("Cigar", /obj/item/clothing/mask/cigarette/cigar/havana, 150),
+ EQUIPMENT("GAR Meson Scanners", /obj/item/clothing/glasses/meson/gar, 500),
+ EQUIPMENT("Laser Pointer", /obj/item/laser_pointer, 300),
+ EQUIPMENT("Luxury Shelter Capsule", /obj/item/survivalcapsule/luxury, 3000),
+ EQUIPMENT("Soap", /obj/item/soap/nanotrasen, 200),
+ EQUIPMENT("Space Cash", /obj/item/stack/spacecash/c1000, 2000),
+ EQUIPMENT("Whiskey", /obj/item/reagent_containers/food/drinks/bottle/whiskey, 100),
+ )
+ prize_list["Extra"] = list() // Used in child vendors
+
/obj/machinery/mineral/equipment_vendor/power_change()
..()
update_icon()
@@ -126,90 +109,126 @@
/obj/machinery/mineral/equipment_vendor/attack_hand(mob/user)
if(..())
return
- interact(user)
+ tgui_interact(user)
/obj/machinery/mineral/equipment_vendor/attack_ghost(mob/user)
- interact(user)
+ tgui_interact(user)
-/obj/machinery/mineral/equipment_vendor/interact(mob/user)
- user.set_machine(src)
+/obj/machinery/mineral/equipment_vendor/tgui_data(mob/user)
+ var/list/data[0]
- var/dat
- dat +="
| [prize.equipment_name] | [prize.cost] | Purchase |
- Lines are a series of chords, separated by commas (,), each with notes seperated by hyphens (-).
-
- Every note in a chord will play together, with the chord timed by the tempo as defined above.
-
- Notes are played by the names of the note, and optionally, the accidental, and/or the octave number.
-
- By default, every note is natural and in octave 3. Defining a different state for either is remembered for each note.
-
- Chords can be played simply by seperating each note with a hyphon: A-C#,Cn-E,E-G#,Gn-B.
- A pause may be denoted by an empty chord: C,E,,C,G.
-
- To make a chord be a different time, end it with /x, where the chord length will be length defined by tempo / x, eg: C,G/2,E/4.
-
- Combined, an example line is: E-E4/4,F#/2,G#/8,B/8,E3-E4/4. -
| {{:value.colour_name}} {{if value.index}}({{:value.index}}){{/if}} | -- {{:helper.link(value.cut ? 'Mend' : 'Cut', null, {'action' : 'cut', 'wire' : value.colour})}} - {{:helper.link('Pulse', null, {'action' : 'pulse', 'wire' : value.colour})}} - {{:helper.link(value.attached ? 'Detach' : 'Attach', null, {'action' : 'attach', 'wire' : value.colour})}} - | -