diff --git a/_maps/map_files/Delta/Lavaland.dmm b/_maps/map_files/Delta/Lavaland.dmm
index c309f799672..a2e56f62c80 100644
--- a/_maps/map_files/Delta/Lavaland.dmm
+++ b/_maps/map_files/Delta/Lavaland.dmm
@@ -1196,7 +1196,7 @@
/turf/simulated/floor/plating,
/area/mine/production)
"dW" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/disposalpipe/segment,
/obj/machinery/atmospherics/pipe/simple/hidden/cyan,
/obj/structure/cable{
@@ -2930,7 +2930,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/cyan{
dir = 6
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/mine/laborcamp)
"lP" = (
diff --git a/_maps/map_files/Delta/delta.dmm b/_maps/map_files/Delta/delta.dmm
index f7774086d51..841e8beda7a 100644
--- a/_maps/map_files/Delta/delta.dmm
+++ b/_maps/map_files/Delta/delta.dmm
@@ -2703,7 +2703,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 10
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/engine/controlroom)
"avD" = (
@@ -3779,7 +3779,7 @@
pixel_y = 23
},
/obj/effect/decal/warning_stripes/north,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/engine/supermatter)
"aDe" = (
@@ -4982,7 +4982,7 @@
dir = 4;
name = "Труба турбины"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/maintenance/turbine)
"aKq" = (
@@ -6080,7 +6080,7 @@
dir = 4;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"aQR" = (
@@ -6561,7 +6561,7 @@
dir = 4;
name = "Труба обработки"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -10264,7 +10264,7 @@
dir = 4
},
/obj/effect/decal/warning_stripes/north,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/engine/controlroom)
"bqH" = (
@@ -14238,7 +14238,7 @@
dir = 4;
name = "Труба фильтрации"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -15365,7 +15365,7 @@
dir = 1;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -15770,7 +15770,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 1
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
icon_state = "yellowfull"
},
@@ -15900,7 +15900,7 @@
dir = 1;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"bPy" = (
@@ -17211,7 +17211,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
icon_state = "yellowfull"
},
@@ -19313,7 +19313,7 @@
/turf/simulated/floor/plating,
/area/maintenance/fpmaint)
"cdT" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible,
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
@@ -23977,7 +23977,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/yellow{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall,
/area/atmos)
"cAf" = (
@@ -24994,7 +24994,7 @@
/obj/structure/cable{
icon_state = "4-8"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/manifold/visible,
/turf/simulated/floor/plasteel{
dir = 8;
@@ -25169,7 +25169,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 10
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall/coated,
/area/engine/supermatter)
"cEA" = (
@@ -25209,7 +25209,7 @@
dir = 6;
level = 1
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall/coated,
/area/engine/supermatter)
"cEJ" = (
@@ -28346,7 +28346,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/engine/controlroom)
"cRT" = (
@@ -28516,7 +28516,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 9
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
icon_state = "yellowfull"
},
@@ -28889,7 +28889,7 @@
dir = 4;
name = "Труба на фильтрацию"
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "wloop_atm_meter";
name = "Waste Loop"
},
@@ -32598,7 +32598,7 @@
dir = 4;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -42991,7 +42991,7 @@
dir = 6;
name = "Труба на фильтрацию"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"dYL" = (
@@ -43685,7 +43685,7 @@
dir = 4;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "mair_out_meter";
name = "Mixed Air Tank Out"
},
@@ -46402,7 +46402,7 @@
/area/chapel/main)
"eJk" = (
/obj/structure/grille,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible/green{
desc = "Труба проводящая газ по фильтрам, где он перемещается в камеры хранения";
dir = 4;
@@ -52051,7 +52051,7 @@
desc = "Труба хранит в себе набор газов для смешивания";
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "caution"
@@ -57972,7 +57972,7 @@
desc = "Труба хранит в себе набор газов для смешивания";
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -58116,7 +58116,7 @@
dir = 8;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "yellow"
@@ -58146,7 +58146,7 @@
"htt" = (
/obj/effect/decal/warning_stripes/yellow/hollow,
/obj/machinery/atmospherics/pipe/manifold4w/visible/purple,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 4;
icon_state = "yellow"
@@ -66350,7 +66350,7 @@
/obj/machinery/light{
dir = 8
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -66404,7 +66404,7 @@
dir = 1;
name = "Труба фильтрации"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "vault"
@@ -66878,7 +66878,7 @@
desc = "Труба содержит дыхательную смесь для подачи на станцию";
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "dloop_atm_meter";
name = "Distribution Loop"
},
@@ -70031,7 +70031,7 @@
desc = "Труба содержит газ для обработки и после возвращает его обратно в трубу смешивания";
name = "Труба обработки"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 8;
icon_state = "neutralfull"
@@ -74298,7 +74298,7 @@
dir = 1;
name = "Труба на фильтрацию"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"lrd" = (
@@ -74338,7 +74338,7 @@
dir = 4;
name = "Труба на фильтрацию"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"lrR" = (
@@ -74629,7 +74629,7 @@
dir = 1;
name = "Труба подачи азота в реактор"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -77418,7 +77418,7 @@
dir = 1;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"meO" = (
@@ -80293,7 +80293,7 @@
/area/maintenance/library)
"mJY" = (
/obj/structure/grille,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible/yellow{
dir = 4
},
@@ -81470,7 +81470,7 @@
/area/crew_quarters/bar)
"mXP" = (
/obj/machinery/atmospherics/pipe/simple/visible,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
icon_state = "yellowfull"
},
@@ -88405,7 +88405,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/engine/controlroom)
"oBd" = (
@@ -91776,7 +91776,7 @@
"poX" = (
/obj/effect/decal/warning_stripes/west,
/obj/machinery/atmospherics/pipe/manifold/visible/green,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"ppf" = (
@@ -92977,7 +92977,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 1
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/toxins/mixing)
"pCn" = (
@@ -111968,7 +111968,7 @@
/obj/machinery/atmospherics/pipe/simple/insulated{
dir = 10
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
icon_state = "white"
},
@@ -130456,7 +130456,7 @@
dir = 4;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "mair_in_meter";
name = "Mixed Air Tank In"
},
@@ -131900,7 +131900,7 @@
/area/lawoffice)
"yjG" = (
/obj/effect/decal/warning_stripes/southeast,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/manifold/hidden/cyan{
dir = 4
},
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/gasthelizards.dmm b/_maps/map_files/RandomRuins/SpaceRuins/gasthelizards.dmm
index 27aed81bde3..99f8a130ef3 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/gasthelizards.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/gasthelizards.dmm
@@ -326,7 +326,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 5
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
icon_state = "dark"
},
@@ -1011,7 +1011,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 8
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "1-4";
tag = "90Curve"
@@ -1238,7 +1238,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "4-8"
},
@@ -1371,7 +1371,7 @@
/area/ruin/space/gasthelizards/jail)
"Wo" = (
/obj/machinery/atmospherics/pipe/simple/visible,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall,
/area/ruin/space/gasthelizards/engineering)
"Wy" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/map_files/RandomRuins/SpaceRuins/oldstation.dmm
index 34a02fc1cd7..4c5c9a4442b 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/oldstation.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/oldstation.dmm
@@ -3571,7 +3571,7 @@
/area/ruin/space/ancientstation/thetacorridor)
"Do" = (
/obj/machinery/atmospherics/pipe/simple/hidden,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -6053,7 +6053,7 @@
icon_state = "1-2"
},
/obj/effect/decal/cleanable/dirt,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/ruin/space/ancientstation)
"Wc" = (
diff --git a/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm b/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm
index a884564003e..54a23bdb3ae 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/spacehotelv1.dmm
@@ -2255,7 +2255,7 @@
/turf/simulated/floor/plating,
/area/ruin/space/spacehotelv1/forestarboardmaints)
"qI" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -3744,7 +3744,7 @@
dir = 6;
name = "Труба на фильтрацию"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/ruin/space/spacehotelv1/aftmaints)
"AQ" = (
@@ -4192,7 +4192,7 @@
/turf/simulated/floor/plasteel/grimy,
/area/ruin/space/spacehotelv1/restoraunt1)
"Eq" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible/cyan{
dir = 9
},
diff --git a/_maps/map_files/RandomZLevels/moonoutpost19.dmm b/_maps/map_files/RandomZLevels/moonoutpost19.dmm
index 2bcc7d3248e..6fbebe4824a 100644
--- a/_maps/map_files/RandomZLevels/moonoutpost19.dmm
+++ b/_maps/map_files/RandomZLevels/moonoutpost19.dmm
@@ -7144,7 +7144,7 @@
dir = 6;
level = 1
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/moonoutpost19/mo19arrivals)
"oP" = (
diff --git a/_maps/map_files/RandomZLevels/spacebattle.dmm b/_maps/map_files/RandomZLevels/spacebattle.dmm
index 4350e0c5295..31b3eaae6ef 100644
--- a/_maps/map_files/RandomZLevels/spacebattle.dmm
+++ b/_maps/map_files/RandomZLevels/spacebattle.dmm
@@ -4566,7 +4566,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/mob/living/simple_animal/hostile/syndicate/ranged/space/autogib/spacebattle,
/turf/simulated/floor/shuttle{
icon_state = "floor4"
@@ -12642,7 +12642,7 @@
/area/awaymission/spacebattle/kitchen)
"Jc" = (
/obj/machinery/atmospherics/pipe/simple/visible,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/shuttle{
icon_state = "floor4"
},
diff --git a/_maps/map_files/RandomZLevels/spacehotel.dmm b/_maps/map_files/RandomZLevels/spacehotel.dmm
index 9dc57011594..1c340b1787a 100644
--- a/_maps/map_files/RandomZLevels/spacehotel.dmm
+++ b/_maps/map_files/RandomZLevels/spacehotel.dmm
@@ -3201,7 +3201,7 @@
name = "Electrical Room";
req_access = list(150)
},
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 2;
icon_state = "1-2"
@@ -3291,7 +3291,7 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"iz" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 2;
icon_state = "1-2"
@@ -3453,12 +3453,12 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"iW" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 4;
icon_state = "1-4"
},
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 2;
d2 = 4;
icon_state = "2-4"
@@ -3466,7 +3466,7 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"iX" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 4;
d2 = 8;
icon_state = "4-8"
@@ -3474,17 +3474,17 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"iY" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 2;
icon_state = "1-2"
},
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 4;
icon_state = "1-4"
},
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 8;
icon_state = "1-8"
@@ -3492,7 +3492,7 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"iZ" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 4;
d2 = 8;
icon_state = "4-8"
@@ -3501,12 +3501,12 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"ja" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 2;
d2 = 8;
icon_state = "2-8"
},
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 8;
icon_state = "1-8"
@@ -3771,7 +3771,7 @@
/turf/simulated/floor/indestructible/carpet,
/area/awaymission/spacehotel)
"jJ" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 2;
d2 = 4;
icon_state = "2-4"
@@ -3779,12 +3779,12 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"jK" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 4;
icon_state = "1-4"
},
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 1;
d2 = 8;
icon_state = "1-8"
@@ -3792,7 +3792,7 @@
/turf/simulated/floor/indestructible/plating,
/area/awaymission/spacehotel)
"jL" = (
-/obj/structure/cable/heavyduty{
+/obj/structure/cable{
d1 = 2;
d2 = 8;
icon_state = "2-8"
diff --git a/_maps/map_files/RandomZLevels/terrorspiders.dmm b/_maps/map_files/RandomZLevels/terrorspiders.dmm
index 796ebc28c79..b052f57a840 100644
--- a/_maps/map_files/RandomZLevels/terrorspiders.dmm
+++ b/_maps/map_files/RandomZLevels/terrorspiders.dmm
@@ -7066,7 +7066,7 @@
},
/area/awaymission/UO71/centralhall)
"qr" = (
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO71_mair_out_meter";
layer = 3.3;
name = "Mixed Air Tank Out"
@@ -7076,7 +7076,7 @@
/area/awaymission/UO71/eng)
"qs" = (
/obj/machinery/atmospherics/pipe/simple/visible/cyan,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO71_mair_in_meter";
layer = 3.3;
name = "Mixed Air Tank In"
@@ -8263,7 +8263,7 @@
/obj/structure/cable{
icon_state = "1-2"
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO71_dloop_atm_meter";
name = "Distribution Loop"
},
@@ -8288,7 +8288,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/yellow{
dir = 4
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/turf/simulated/wall,
@@ -8670,7 +8670,7 @@
/turf/simulated/floor/plasteel,
/area/awaymission/UO71/eng)
"tw" = (
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/obj/machinery/atmospherics/pipe/simple/visible/green{
@@ -8924,7 +8924,7 @@
/turf/simulated/floor/plasteel,
/area/awaymission/UO71/eng)
"ud" = (
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO71_wloop_atm_meter";
name = "Waste Loop"
},
@@ -9156,12 +9156,12 @@
/area/awaymission/UO71/centralhall)
"uB" = (
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall,
/area/awaymission/UO71/eng)
"uC" = (
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall,
/area/awaymission/UO71/eng)
"uD" = (
@@ -9432,14 +9432,14 @@
/turf/simulated/wall,
/area/awaymission/UO71/eng)
"vg" = (
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/obj/machinery/atmospherics/pipe/simple/visible/green,
/turf/simulated/wall,
/area/awaymission/UO71/eng)
"vh" = (
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/obj/machinery/atmospherics/pipe/simple/visible/cyan,
diff --git a/_maps/map_files/RandomZLevels/undergroundoutpost45.dmm b/_maps/map_files/RandomZLevels/undergroundoutpost45.dmm
index 5ba9d70c613..2ddd3d3e9c6 100644
--- a/_maps/map_files/RandomZLevels/undergroundoutpost45.dmm
+++ b/_maps/map_files/RandomZLevels/undergroundoutpost45.dmm
@@ -7566,7 +7566,7 @@
"qe" = (
/obj/structure/grille,
/obj/structure/window/full/reinforced,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO45_mair_out_meter";
layer = 3.3;
name = "Mixed Air Tank Out"
@@ -7578,7 +7578,7 @@
/obj/structure/grille,
/obj/structure/window/full/reinforced,
/obj/machinery/atmospherics/pipe/simple/visible/cyan,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO45_mair_in_meter";
layer = 3.3;
name = "Mixed Air Tank In"
@@ -8821,7 +8821,7 @@
icon_state = "1-2";
tag = ""
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO45_dloop_atm_meter";
name = "Distribution Loop"
},
@@ -8865,7 +8865,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/yellow{
dir = 4
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/turf/simulated/floor/plating,
@@ -9239,7 +9239,7 @@
"td" = (
/obj/structure/grille,
/obj/structure/window/full/reinforced,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/obj/machinery/atmospherics/pipe/simple/visible/green{
@@ -9541,7 +9541,7 @@
/turf/simulated/floor/plasteel,
/area/awaymission/UO45/engineering)
"tH" = (
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "UO45_wloop_atm_meter";
name = "Waste Loop"
},
@@ -9761,12 +9761,12 @@
/area/awaymission/UO45/caves)
"ud" = (
/obj/machinery/atmospherics/pipe/simple/hidden/supply,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall,
/area/awaymission/UO45/engineering)
"ue" = (
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall,
/area/awaymission/UO45/engineering)
"uf" = (
@@ -10046,7 +10046,7 @@
"uJ" = (
/obj/structure/grille,
/obj/structure/window/full/reinforced,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/obj/machinery/atmospherics/pipe/simple/visible/green,
@@ -10055,7 +10055,7 @@
"uK" = (
/obj/structure/grille,
/obj/structure/window/full/reinforced,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 3.3
},
/obj/machinery/atmospherics/pipe/simple/visible/cyan,
diff --git a/_maps/map_files/cerestation/Lavaland.dmm b/_maps/map_files/cerestation/Lavaland.dmm
index 3cf9addac6a..a6ad7d8f908 100644
--- a/_maps/map_files/cerestation/Lavaland.dmm
+++ b/_maps/map_files/cerestation/Lavaland.dmm
@@ -2582,7 +2582,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/cyan{
dir = 6
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/mine/laborcamp)
"lP" = (
diff --git a/_maps/map_files/cerestation/cerestation.dmm b/_maps/map_files/cerestation/cerestation.dmm
index 1ccdf8d7bcd..cbd0b477505 100644
--- a/_maps/map_files/cerestation/cerestation.dmm
+++ b/_maps/map_files/cerestation/cerestation.dmm
@@ -11134,7 +11134,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{
dir = 9
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -12107,7 +12107,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible/cyan{
dir = 8
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/engine,
/area/engine/engineering)
"bwu" = (
@@ -12525,14 +12525,14 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 10
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall/coated,
/area/engine/supermatter)
"byD" = (
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 6
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall/coated,
/area/engine/supermatter)
"byE" = (
@@ -35390,7 +35390,7 @@
/obj/machinery/newscaster{
pixel_y = 32
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/toxins/mixing)
"eUu" = (
@@ -44216,7 +44216,7 @@
dir = 1
},
/obj/machinery/atmospherics/pipe/manifold4w/visible/cyan,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/engine,
/area/engine/engineering)
"hAl" = (
@@ -58050,7 +58050,7 @@
dir = 8;
on = 1
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
pixel_x = 26
},
/turf/simulated/floor/engine,
@@ -67607,7 +67607,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/yellow{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/engine,
/area/engine/engineering)
"orl" = (
@@ -71056,7 +71056,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/green{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/engine,
/area/engine/engineering)
"pry" = (
@@ -87907,7 +87907,7 @@
dir = 4;
on = 1
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
pixel_x = 26
},
/turf/simulated/floor/engine,
@@ -94863,7 +94863,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/effect/decal/warning_stripes/east,
/turf/simulated/floor/plasteel{
dir = 8;
diff --git a/_maps/map_files/cyberiad/cyberiad.dmm b/_maps/map_files/cyberiad/cyberiad.dmm
index 0b6f1029206..0f1449269b9 100644
--- a/_maps/map_files/cyberiad/cyberiad.dmm
+++ b/_maps/map_files/cyberiad/cyberiad.dmm
@@ -13805,7 +13805,7 @@
/turf/simulated/floor/plating,
/area/maintenance/fpmaint)
"aJU" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -15528,7 +15528,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible/purple{
dir = 8
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/maintenance/fpmaint)
"aPb" = (
@@ -28921,7 +28921,7 @@
/turf/simulated/floor/plasteel,
/area/hallway/secondary/entry)
"bwy" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{
dir = 4
},
@@ -28982,7 +28982,7 @@
},
/area/medical/medbay2)
"bwK" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 4
},
@@ -30674,7 +30674,7 @@
/obj/structure/cable{
icon_state = "4-8"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 4
},
@@ -52553,7 +52553,7 @@
pixel_x = -24;
pixel_y = -6
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
},
@@ -53754,7 +53754,7 @@
pixel_y = -24;
req_access = list(12)
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
},
@@ -58922,7 +58922,7 @@
desc = "Труба хранит в себе набор газов для смешивания";
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/structure/cable{
icon_state = "1-2"
},
@@ -59146,7 +59146,7 @@
/area/atmos)
"cQf" = (
/obj/machinery/atmospherics/pipe/manifold4w/visible/purple,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "wloop_atm_meter";
name = "Waste Loop"
},
@@ -59172,7 +59172,7 @@
/obj/machinery/alarm{
pixel_y = 24
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "dloop_atm_meter";
name = "Distribution Loop"
},
@@ -59184,7 +59184,7 @@
dir = 1;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos/distribution)
"cQj" = (
@@ -59677,7 +59677,7 @@
dir = 4;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"cRo" = (
@@ -60166,7 +60166,7 @@
dir = 4
},
/obj/structure/grille,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall,
/area/atmos)
"cSq" = (
@@ -60679,7 +60679,7 @@
dir = 4;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos/distribution)
"cTs" = (
@@ -61940,7 +61940,7 @@
dir = 4;
name = "Труба на фильтрацию"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/effect/spawner/window/reinforced,
/turf/simulated/floor/plating,
/area/atmos/distribution)
@@ -63014,7 +63014,7 @@
dir = 1;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"cYb" = (
@@ -63228,7 +63228,7 @@
/area/space)
"cYy" = (
/obj/machinery/atmospherics/pipe/manifold4w/visible,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/effect/decal/cleanable/dirt,
/turf/simulated/floor/plasteel,
/area/maintenance/turbine)
@@ -63895,7 +63895,7 @@
dir = 4;
name = "Труба обработки"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"dah" = (
@@ -64364,7 +64364,7 @@
/area/maintenance/asmaint)
"dbN" = (
/obj/machinery/atmospherics/pipe/manifold4w/visible,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"dbO" = (
@@ -64592,7 +64592,7 @@
/turf/simulated/floor/plasteel,
/area/engine/break_room)
"dcF" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/manifold/visible/green{
dir = 8
},
@@ -64805,7 +64805,7 @@
/turf/simulated/floor/plating,
/area/maintenance/turbine)
"ddl" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/light{
dir = 4
},
@@ -65838,7 +65838,7 @@
dir = 4;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"dfK" = (
@@ -66156,7 +66156,7 @@
dir = 8;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/atmos)
"dgQ" = (
@@ -67171,7 +67171,7 @@
"djB" = (
/obj/machinery/atmospherics/pipe/simple/visible,
/obj/structure/grille,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/wall/r_wall,
/area/atmos)
"djC" = (
@@ -67315,7 +67315,7 @@
"djS" = (
/obj/machinery/atmospherics/pipe/simple/visible,
/obj/structure/grille,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "mair_in_meter";
name = "Mixed Air Tank In"
},
@@ -67330,7 +67330,7 @@
"djU" = (
/obj/machinery/atmospherics/pipe/simple/visible,
/obj/structure/grille,
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
id = "mair_out_meter";
name = "Mixed Air Tank Out"
},
@@ -67706,7 +67706,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 1
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/maintenance/asmaint)
"dkR" = (
@@ -69318,7 +69318,7 @@
/turf/simulated/wall/r_wall,
/area/aisat/atmospherics)
"dpe" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/hidden/yellow,
/turf/simulated/wall/r_wall,
/area/aisat/atmospherics)
@@ -76050,7 +76050,7 @@
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/maintenance/genetics)
"kiy" = (
@@ -76720,7 +76720,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 8
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/effect/decal/warning_stripes/west,
/turf/simulated/floor/plasteel{
icon_state = "darkgrey"
@@ -79713,7 +79713,7 @@
dir = 6;
icon_state = "intact"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/effect/decal/warning_stripes/north,
/turf/simulated/floor/plasteel{
icon_state = "darkgrey"
@@ -81364,7 +81364,7 @@
icon_state = "4-8"
},
/obj/machinery/atmospherics/pipe/manifold/visible,
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plating,
/area/maintenance/asmaint2)
"qrd" = (
@@ -82059,7 +82059,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 4
},
-/obj/machinery/meter{
+/obj/machinery/atmospherics/meter{
layer = 2;
pixel_x = 6
},
@@ -84428,7 +84428,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/effect/decal/warning_stripes/east,
/turf/simulated/floor/plasteel{
icon_state = "darkgrey"
diff --git a/_maps/map_files/generic/Lavaland.dmm b/_maps/map_files/generic/Lavaland.dmm
index 02377be3f69..588a4056a7d 100644
--- a/_maps/map_files/generic/Lavaland.dmm
+++ b/_maps/map_files/generic/Lavaland.dmm
@@ -1389,7 +1389,7 @@
/obj/structure/cable{
icon_state = "1-2"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/visible/cyan{
dir = 6
},
@@ -3775,7 +3775,7 @@
/obj/machinery/atmospherics/pipe/simple/visible/cyan{
dir = 6
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel,
/area/mine/laborcamp)
"lP" = (
diff --git a/_maps/map_files/generic/syndicatebase.dmm b/_maps/map_files/generic/syndicatebase.dmm
index 450d332cab2..4658d0e09f7 100644
--- a/_maps/map_files/generic/syndicatebase.dmm
+++ b/_maps/map_files/generic/syndicatebase.dmm
@@ -3432,7 +3432,7 @@
color = "#FFC0CB";
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -4311,7 +4311,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
color = "#FFC0CB"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -5201,7 +5201,7 @@
},
/area/syndicate/unpowered/syndicate_space_base/engineering)
"eQa" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/manifold4w/visible/yellow{
desc = "Труба хранит в себе набор газов для смешивания";
name = "Труба смешивания"
@@ -5332,7 +5332,7 @@
dir = 4;
name = "Труба дыхательной смеси"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -5436,7 +5436,7 @@
/obj/effect/turf_decal/tile/yellow{
dir = 1
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -5548,7 +5548,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible/yellow{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -5578,7 +5578,7 @@
dir = 1;
name = "Труба фильтрации"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -7305,7 +7305,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible/yellow{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -7546,7 +7546,7 @@
dir = 4;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -9148,7 +9148,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible/green{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -9346,7 +9346,7 @@
dir = 4;
name = "Труба фильтрации"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -10915,7 +10915,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 10
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -11041,7 +11041,7 @@
dir = 4;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -11779,7 +11779,7 @@
},
/area/syndicate/unpowered/syndicate_space_base/engineering)
"kSe" = (
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/manifold4w/visible/cyan{
desc = "Труба содержит дыхательную смесь для подачи на станцию";
name = "Труба дыхательной смеси"
@@ -11959,7 +11959,7 @@
dir = 5;
name = "Труба смешивания"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/obj/machinery/atmospherics/pipe/simple/hidden/supply{
dir = 4
},
@@ -13116,7 +13116,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 4
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -13148,7 +13148,7 @@
/obj/machinery/atmospherics/pipe/manifold/visible{
dir = 8
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -16266,7 +16266,7 @@
/obj/machinery/atmospherics/pipe/simple/visible{
dir = 6
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
@@ -16370,7 +16370,7 @@
dir = 4;
name = "Труба фильтрации"
},
-/obj/machinery/meter,
+/obj/machinery/atmospherics/meter,
/turf/simulated/floor/plasteel{
dir = 1;
icon_state = "dark"
diff --git a/code/ATMOSPHERICS/atmospherics.dm b/code/ATMOSPHERICS/atmospherics.dm
index dac64a0e4e3..552d5afef4d 100644
--- a/code/ATMOSPHERICS/atmospherics.dm
+++ b/code/ATMOSPHERICS/atmospherics.dm
@@ -10,7 +10,6 @@ Pipelines + Other Objects -> Pipe network
*/
/obj/machinery/atmospherics
anchored = 1
- layer = GAS_PIPE_HIDDEN_LAYER //under wires
resistance_flags = FIRE_PROOF
max_integrity = 200
plane = GAME_PLANE
@@ -19,6 +18,8 @@ Pipelines + Other Objects -> Pipe network
power_channel = ENVIRON
on_blueprints = TRUE
var/can_unwrench = 0
+ /// Can this be put under a tile?
+ var/can_be_undertile = FALSE
var/connect_types[] = list(1) //1=regular, 2=supply, 3=scrubber
var/connected_to = 1 //same as above, currently not used for anything
@@ -29,6 +30,8 @@ Pipelines + Other Objects -> Pipe network
var/pipe_color
var/obj/item/pipe/stored
var/image/pipe_image
+ layer = GAS_PIPE_HIDDEN_LAYER //under wires
+ var/layer_offset = 0.0 // generic over VISIBLE and HIDDEN, should be less than 0.01, or you'll reorder non-pipe things
/obj/machinery/atmospherics/New()
if (!armor)
@@ -70,14 +73,13 @@ Pipelines + Other Objects -> Pipe network
// Icons/overlays/underlays
/obj/machinery/atmospherics/update_icon()
- var/turf/T = get_turf(loc)
- if(T?.transparent_floor)
- plane = FLOOR_PLANE
- else
- if(!T || level == 2 || !T.intact)
- plane = GAME_PLANE
- else
+ switch(level)
+ if(1)
plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER + layer_offset
+ if(2)
+ plane = GAME_PLANE
+ layer = GAS_PIPE_VISIBLE_LAYER + layer_offset
/obj/machinery/atmospherics/proc/update_pipe_image()
pipe_image = image(src, loc, layer = ABOVE_HUD_LAYER, dir = dir) //the 20 puts it above Byond's darkness (not its opacity view)
@@ -181,7 +183,7 @@ Pipelines + Other Objects -> Pipe network
/obj/machinery/atmospherics/attackby(obj/item/W, mob/user)
var/turf/T = get_turf(src)
if(can_unwrench && istype(W, /obj/item/wrench))
- if(T.transparent_floor && istype(src, /obj/machinery/atmospherics/pipe) && layer != GAS_PIPE_VISIBLE_LAYER) //pipes on GAS_PIPE_VISIBLE_LAYER are above the transparent floor and should be interactable
+ if(level == 1 && T.transparent_floor && istype(src, /obj/machinery/atmospherics/pipe))
to_chat(user, span_danger("You can't interact with something that's under the floor!"))
return
if(level == 1 && isturf(T) && T.intact)
@@ -262,11 +264,10 @@ Pipelines + Other Objects -> Pipe network
initialize_directions = P
var/turf/T = loc
if(!T.transparent_floor)
- level = T.intact ? 2 : 1
+ level = (T.intact || !can_be_undertile) ? 2 : 1
else
level = 2
- plane = GAME_PLANE
- layer = GAS_PIPE_VISIBLE_LAYER
+ update_icon()
add_fingerprint(usr)
if(!SSair.initialized) //If there's no atmos subsystem, we can't really initialize pipenets
SSair.machinery_to_construct.Add(src)
diff --git a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
index 14a2d05604e..ea06ad98f3b 100644
--- a/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
+++ b/code/ATMOSPHERICS/components/binary_devices/binary_atmos_base.dm
@@ -3,7 +3,8 @@
initialize_directions = SOUTH|NORTH
use_power = IDLE_POWER_USE
- layer = GAS_PUMP_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_PUMP_OFFSET
+ layer_offset = GAS_PUMP_OFFSET
var/datum/gas_mixture/air1
var/datum/gas_mixture/air2
diff --git a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
index 9937eaf0db9..5aaf574eb09 100644
--- a/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
+++ b/code/ATMOSPHERICS/components/trinary_devices/trinary_base.dm
@@ -4,7 +4,8 @@
use_power = IDLE_POWER_USE
var/on = 0
- layer = GAS_FILTER_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_FILTER_OFFSET
+ layer_offset = GAS_FILTER_OFFSET
var/datum/gas_mixture/air1
var/datum/gas_mixture/air2
diff --git a/code/ATMOSPHERICS/components/unary_devices/outlet_injector.dm b/code/ATMOSPHERICS/components/unary_devices/outlet_injector.dm
index fdfdaf490a3..76c6062e5dd 100644
--- a/code/ATMOSPHERICS/components/unary_devices/outlet_injector.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/outlet_injector.dm
@@ -2,7 +2,8 @@
icon = 'icons/obj/pipes_and_stuff/atmospherics/atmos/injector.dmi'
icon_state = "map_injector"
use_power = IDLE_POWER_USE
- layer = GAS_SCRUBBER_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_SCRUBBER_OFFSET
+ layer_offset = GAS_SCRUBBER_OFFSET
resistance_flags = FIRE_PROOF | UNACIDABLE | ACID_PROOF //really helpful in building gas chambers for xenomorphs
diff --git a/code/ATMOSPHERICS/components/unary_devices/passive_vent.dm b/code/ATMOSPHERICS/components/unary_devices/passive_vent.dm
index e2f04d33ae1..a429af00a0e 100644
--- a/code/ATMOSPHERICS/components/unary_devices/passive_vent.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/passive_vent.dm
@@ -1,7 +1,8 @@
/obj/machinery/atmospherics/unary/passive_vent
icon = 'icons/obj/pipes_and_stuff/atmospherics/atmos/vent_pump.dmi'
icon_state = "map_vent"
- layer = GAS_SCRUBBER_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_SCRUBBER_OFFSET
+ layer_offset = GAS_SCRUBBER_OFFSET
name = "passive vent"
desc = "A large air vent"
diff --git a/code/ATMOSPHERICS/components/unary_devices/portables_connector.dm b/code/ATMOSPHERICS/components/unary_devices/portables_connector.dm
index 5843117a7a7..5c1458b963d 100644
--- a/code/ATMOSPHERICS/components/unary_devices/portables_connector.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/portables_connector.dm
@@ -6,7 +6,8 @@
desc = "For connecting portables devices related to atmospherics control."
can_unwrench = 1
- layer = GAS_FILTER_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_FILTER_OFFSET
+ layer_offset = GAS_FILTER_OFFSET
var/obj/machinery/portable_atmospherics/connected_device
diff --git a/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm b/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm
index 036066e888e..4a9248c536d 100644
--- a/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/vent_pump.dm
@@ -10,7 +10,8 @@
desc = "Has a valve and pump attached to it"
use_power = IDLE_POWER_USE
- layer = GAS_SCRUBBER_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_SCRUBBER_OFFSET
+ layer_offset = GAS_SCRUBBER_OFFSET
can_unwrench = 1
var/open = 0
diff --git a/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm
index 91a78e63f1d..40e5afcfd6b 100644
--- a/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm
+++ b/code/ATMOSPHERICS/components/unary_devices/vent_scrubber.dm
@@ -4,7 +4,8 @@
name = "air scrubber"
desc = "Has a valve and pump attached to it"
- layer = GAS_SCRUBBER_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_SCRUBBER_OFFSET
+ layer_offset = GAS_SCRUBBER_OFFSET
use_power = IDLE_POWER_USE
idle_power_usage = 10
diff --git a/code/ATMOSPHERICS/pipes/cap.dm b/code/ATMOSPHERICS/pipes/cap.dm
index ef3f3cd9bc7..17e280260a0 100644
--- a/code/ATMOSPHERICS/pipes/cap.dm
+++ b/code/ATMOSPHERICS/pipes/cap.dm
@@ -84,13 +84,16 @@
/obj/machinery/atmospherics/pipe/cap/visible
level = 2
icon_state = "cap"
+ plane = GAME_PLANE
+ layer = GAS_PIPE_VISIBLE_LAYER
/obj/machinery/atmospherics/pipe/cap/visible/scrubbers
name = "scrubbers pipe endcap"
desc = "An endcap for scrubbers pipes"
icon_state = "cap-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -99,7 +102,8 @@
desc = "An endcap for supply pipes"
icon_state = "cap-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
@@ -107,13 +111,16 @@
level = 1
icon_state = "cap"
alpha = 128
+ plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER
/obj/machinery/atmospherics/pipe/cap/hidden/scrubbers
name = "scrubbers pipe endcap"
desc = "An endcap for scrubbers pipes"
icon_state = "cap-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -122,6 +129,7 @@
desc = "An endcap for supply pipes"
icon_state = "cap-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
diff --git a/code/ATMOSPHERICS/pipes/manifold.dm b/code/ATMOSPHERICS/pipes/manifold.dm
index 642139bca04..ec91fdc8db9 100644
--- a/code/ATMOSPHERICS/pipes/manifold.dm
+++ b/code/ATMOSPHERICS/pipes/manifold.dm
@@ -160,13 +160,16 @@
/obj/machinery/atmospherics/pipe/manifold/visible
icon_state = "map"
level = 2
+ plane = GAME_PLANE
+ layer = GAS_PIPE_VISIBLE_LAYER
/obj/machinery/atmospherics/pipe/manifold/visible/scrubbers
name="Scrubbers pipe manifold"
desc = "A manifold composed of scrubbers pipes"
icon_state = "map-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -175,7 +178,8 @@
desc = "A manifold composed of supply pipes"
icon_state = "map-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
@@ -195,13 +199,16 @@
icon_state = "map"
level = 1
alpha = 128 //set for the benefit of mapping - this is reset to opaque when the pipe is spawned in game
+ plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER
/obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers
name="Scrubbers pipe manifold"
desc = "A manifold composed of scrubbers pipes"
icon_state = "map-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -210,7 +217,8 @@
desc = "A manifold composed of supply pipes"
icon_state = "map-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
diff --git a/code/ATMOSPHERICS/pipes/manifold4w.dm b/code/ATMOSPHERICS/pipes/manifold4w.dm
index fe3050d54a3..237251b3803 100644
--- a/code/ATMOSPHERICS/pipes/manifold4w.dm
+++ b/code/ATMOSPHERICS/pipes/manifold4w.dm
@@ -169,13 +169,16 @@
/obj/machinery/atmospherics/pipe/manifold4w/visible
icon_state = "map_4way"
level = 2
+ plane = GAME_PLANE
+ layer = GAS_PIPE_VISIBLE_LAYER
/obj/machinery/atmospherics/pipe/manifold4w/visible/scrubbers
name="4-way scrubbers pipe manifold"
desc = "A manifold composed of scrubbers pipes"
icon_state = "map_4way-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -184,7 +187,8 @@
desc = "A manifold composed of supply pipes"
icon_state = "map_4way-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
@@ -204,13 +208,16 @@
icon_state = "map_4way"
level = 1
alpha = 128 //set for the benefit of mapping - this is reset to opaque when the pipe is spawned in game
+ plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER
/obj/machinery/atmospherics/pipe/manifold4w/hidden/scrubbers
name="4-way scrubbers pipe manifold"
desc = "A manifold composed of scrubbers pipes"
icon_state = "map_4way-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -219,7 +226,8 @@
desc = "A manifold composed of supply pipes"
icon_state = "map_4way-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
diff --git a/code/ATMOSPHERICS/pipes/pipe.dm b/code/ATMOSPHERICS/pipes/pipe.dm
index 0b9b1c4107a..31e43b2e9a7 100644
--- a/code/ATMOSPHERICS/pipes/pipe.dm
+++ b/code/ATMOSPHERICS/pipes/pipe.dm
@@ -8,6 +8,7 @@
damage_deflection = 12
var/alert_pressure = 80*ONE_ATMOSPHERE //minimum pressure before check_pressure(...) should be called
resistance_flags = NO_MALF_EFFECT
+ can_be_undertile = TRUE
//Buckling
can_buckle = TRUE
@@ -25,7 +26,7 @@
QDEL_NULL(air_temporary)
var/turf/T = loc
- for(var/obj/machinery/meter/meter in T)
+ for(var/obj/machinery/atmospherics/meter/meter in T)
if(meter.target == src)
var/obj/item/pipe_meter/PM = new (T)
meter.transfer_fingerprints_to(PM)
diff --git a/code/ATMOSPHERICS/pipes/simple/pipe_simple_he.dm b/code/ATMOSPHERICS/pipes/simple/pipe_simple_he.dm
index 7051226d245..9e3f2494cd2 100644
--- a/code/ATMOSPHERICS/pipes/simple/pipe_simple_he.dm
+++ b/code/ATMOSPHERICS/pipes/simple/pipe_simple_he.dm
@@ -3,6 +3,8 @@
icon_state = "intact"
pipe_icon = "hepipe"
level = 2
+ plane = GAME_PLANE
+ layer = GAS_PIPE_VISIBLE_LAYER
var/initialize_directions_he
var/surface = 2
@@ -85,6 +87,8 @@
/obj/machinery/atmospherics/pipe/simple/heat_exchanging/hidden
level=1
icon_state="intact-f"
+ plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER
/////////////////////////////////
// JUNCTION
@@ -130,3 +134,5 @@
/obj/machinery/atmospherics/pipe/simple/heat_exchanging/junction/hidden
level=1
icon_state="intact-f"
+ plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER
diff --git a/code/ATMOSPHERICS/pipes/simple/pipe_simple_hidden.dm b/code/ATMOSPHERICS/pipes/simple/pipe_simple_hidden.dm
index 0df4cd3fc64..ea2a32824e8 100644
--- a/code/ATMOSPHERICS/pipes/simple/pipe_simple_hidden.dm
+++ b/code/ATMOSPHERICS/pipes/simple/pipe_simple_hidden.dm
@@ -2,13 +2,17 @@
icon_state = "intact"
level = 1
alpha = 128 //set for the benefit of mapping - this is reset to opaque when the pipe is spawned in game
+ // these are inherited, but it's nice to have them explicit here
+ plane = FLOOR_PLANE
+ layer = GAS_PIPE_HIDDEN_LAYER
/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers
name = "Scrubbers pipe"
desc = "A one meter section of scrubbers pipe"
icon_state = "intact-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -17,7 +21,8 @@
desc = "A one meter section of supply pipe"
icon_state = "intact-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
diff --git a/code/ATMOSPHERICS/pipes/simple/pipe_simple_visible.dm b/code/ATMOSPHERICS/pipes/simple/pipe_simple_visible.dm
index c2708be19eb..963d38fe82d 100644
--- a/code/ATMOSPHERICS/pipes/simple/pipe_simple_visible.dm
+++ b/code/ATMOSPHERICS/pipes/simple/pipe_simple_visible.dm
@@ -1,13 +1,17 @@
/obj/machinery/atmospherics/pipe/simple/visible
icon_state = "intact"
level = 2
+ // these are inherited, but it's nice to have them explicit here
+ plane = GAME_PLANE
+ layer = GAS_PIPE_VISIBLE_LAYER
/obj/machinery/atmospherics/pipe/simple/visible/scrubbers
name = "Scrubbers pipe"
desc = "A one meter section of scrubbers pipe"
icon_state = "intact-scrubbers"
connect_types = list(3)
- layer = 2.38
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SCRUB_OFFSET
+ layer_offset = GAS_PIPE_SCRUB_OFFSET
icon_connect_type = "-scrubbers"
color = PIPE_COLOR_RED
@@ -16,7 +20,8 @@
desc = "A one meter section of supply pipe"
icon_state = "intact-supply"
connect_types = list(2)
- layer = 2.39
+ layer = GAS_PIPE_HIDDEN_LAYER + GAS_PIPE_SUPPLY_OFFSET
+ layer_offset = GAS_PIPE_SUPPLY_OFFSET
icon_connect_type = "-supply"
color = PIPE_COLOR_BLUE
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index c242a61efbf..74434dbea26 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -23,6 +23,8 @@
#define COMPONENT_GLOB_BLOCK_CINEMATIC (1<<0)
/// ingame button pressed (/obj/machinery/button/button)
#define COMSIG_GLOB_BUTTON_PRESSED "!button_pressed"
+/// cable was placed or joined somewhere : (turf)
+#define COMSIG_GLOB_CABLE_UPDATED "!cable_updated"
/// signals from globally accessible objects
@@ -275,7 +277,8 @@
///from base of /datum/mind/proc/transfer_to(mob/living/new_character)
#define COMSIG_MIND_TRANSER_TO "mind_transfer_to"
-
+///called on the mob instead of the mind
+#define COMSIG_BODY_TRANSFER_TO "body_transfer_to"
// /mob signals
///from base of /mob/Login(): ()
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 243cbaadfcb..ffc637b21bc 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -24,6 +24,8 @@
#define ismachinery(A) (istype(A, /obj/machinery))
+#define isapc(A) (istype(A, /obj/machinery/power/apc))
+
#define ismecha(A) (istype(A, /obj/mecha))
#define isvampirecoffin(A) (istype(A, /obj/structure/closet/coffin/vampire))
@@ -96,6 +98,8 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list(
#define isbrain(A) (istype(A, /mob/living/carbon/brain))
+#define ispulsedemon(A) (istype(A, /mob/living/simple_animal/demon/pulse_demon))
+
//Carbon mobs
#define iscarbon(A) (istype(A, /mob/living/carbon))
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index c8692b06073..067da4146ec 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -12,8 +12,20 @@
#define CINEMATIC_LAYER -1
#define BLACKNESS_PLANE 0 //To keep from conflicts with SEE_BLACKNESS internals
-#define SPACE_LAYER 1.8
+#define SPACE_LAYER 1.5
+#define GRASS_UNDER_LAYER 1.6
+/// Which layer turfs appear on by default in the map editor. Should be unique!
+#define MAP_EDITOR_TURF_LAYER 1.6999
+#define PLATING_LAYER 1.7
+#define LATTICE_LAYER 1.701
+#define DISPOSAL_PIPE_LAYER 1.71
+#define GAS_PIPE_HIDDEN_LAYER 1.72
+#define WIRE_LAYER 1.73
+#define WIRE_TERMINAL_LAYER 1.75
+#define ABOVE_PLATING_LAYER 1.76 // generic for /obj/hide
+#define TRAY_SCAN_LAYER_OFFSET 0.5 // place images above TURF_LAYER
//#define TURF_LAYER 2 //For easy recordkeeping; this is a byond define
+#define ABOVE_TRANSPARENT_TURF_LAYER 2.01
#define MID_TURF_LAYER 2.02
#define HIGH_TURF_LAYER 2.03
#define TURF_PLATING_DECAL_LAYER 2.031
@@ -23,16 +35,12 @@
#define BULLET_HOLE_LAYER 2.06
#define ABOVE_NORMAL_TURF_LAYER 2.08
#define ABOVE_ICYOVERLAY_LAYER 2.11
-#define LATTICE_LAYER 2.2
-#define DISPOSAL_PIPE_LAYER 2.3
-#define GAS_PIPE_HIDDEN_LAYER 2.35
-#define WIRE_LAYER 2.4
-#define TRANSPARENT_TURF_LAYER 2.41
-#define WIRE_TERMINAL_LAYER 2.45
-#define GAS_SCRUBBER_LAYER 2.46
+#define GAS_SCRUBBER_OFFSET -0.001
#define GAS_PIPE_VISIBLE_LAYER 2.47
-#define GAS_FILTER_LAYER 2.48
-#define GAS_PUMP_LAYER 2.49
+#define GAS_PIPE_SCRUB_OFFSET 0.001
+#define GAS_PIPE_SUPPLY_OFFSET 0.002
+#define GAS_FILTER_OFFSET 0.003
+#define GAS_PUMP_OFFSET 0.004
#define HOLOPAD_LAYER 2.491
#define CONVEYOR_LAYER 2.495
#define LOW_OBJ_LAYER 2.5
diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm
index 041f0aeba9d..a3e22e97e2a 100644
--- a/code/__DEFINES/math.dm
+++ b/code/__DEFINES/math.dm
@@ -2,6 +2,7 @@
#define PI 3.1415
#define INFINITY 1e31 //closer than enough
+#define SQRT_2 1.41421356237
#define SHORT_REAL_LIMIT 16777216
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index aafd1584fef..96d4fd6b51e 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -74,6 +74,8 @@
#define STATUS_EFFECT_SUMMONEDGHOST /datum/status_effect/cultghost //is a cult ghost: can see dead people, can't manifest more ghosts
+#define STATUS_EFFECT_DELAYED /datum/status_effect/delayed //delayed status effect: gets /datum/callback to call on expire, signal if we want to prevent and duration
+
#define STATUS_EFFECT_CRUSHERMARK /datum/status_effect/crusher_mark //if struck with a proto-kinetic crusher, takes a ton of damage
#define STATUS_EFFECT_SAWBLEED /datum/status_effect/saw_bleed //if the bleed builds up enough, takes a ton of damage
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index cd548ce5883..652f586b3a0 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -764,3 +764,25 @@
/proc/sanitize_filename(text)
var/static/regex/regex = regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g")
return regex.Replace(text, "")
+
+/**
+ * Formats num with an SI prefix.
+ *
+ * Returns a string formatted with a multiple of num and an SI prefix corresponding to an exponent of 10.
+ * Only considers exponents that are multiples of 3 (deca, deci, hecto, and centi are not included).
+ * A unit is not included in the string, the prefix is placed after the number with no spacing added anywhere.
+ * Listing of prefixes: https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes
+ */
+/proc/format_si_suffix(num)
+ if(num == 0)
+ return "[num]"
+
+ var/exponent = round_down(log(10, abs(num)))
+ var/ofthree = exponent / 3
+ if(exponent < 0)
+ ofthree = round(ofthree)
+ else
+ ofthree = round_down(ofthree)
+ if(ofthree == 0)
+ return "[num]"
+ return "[num / (10 ** (ofthree * 3))][GLOB.si_suffixes[round(length(GLOB.si_suffixes) / 2) + ofthree + 1]]"
diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm
index 07efd8b5eaa..86933f39a91 100644
--- a/code/__HELPERS/traits.dm
+++ b/code/__HELPERS/traits.dm
@@ -132,6 +132,8 @@
#define HAS_TRAIT_NOT_FROM(target, trait, source) (target.status_traits ? (target.status_traits[trait] ? (length(target.status_traits[trait] - source) > 0) : FALSE) : FALSE)
+/// Gives a unique trait source for any given datum
+#define UNIQUE_TRAIT_SOURCE(target) "unique_source_[UID(target)]"
/*
Remember to update _globalvars/traits.dm if you're adding/removing/renaming traits.
@@ -155,6 +157,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FAKEDEATH "fakedeath" //Makes the owner appear as dead to most forms of medical examination
#define TRAIT_XENO_HOST "xeno_host" //Tracks whether we're gonna be a baby alien's mummy.
#define TRAIT_GOTTAGOFAST "gottagofast"
+#define TRAIT_SHOCKIMMUNE "shockimmune"
#define TRAIT_GOTTAGONOTSOFAST "gottagonotsofast"
#define TRAIT_CHUNKYFINGERS "chunkyfingers" //means that you can't use weapons with normal trigger guards.
#define TRAIT_FORCE_DOORS "force_doors"
@@ -183,6 +186,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
// unique trait sources
#define CULT_EYES "cult_eyes"
#define CLOCK_HANDS "clock_hands"
+#define PULSEDEMON_TRAIT "pulse_demon"
// Healing traits
/// This mob heals from carp rifts.
@@ -196,3 +200,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
//traits that should be properly converted to genetic mutations one day
#define TRAIT_LASEREYES "laser_eyes"
+
+/// Prevent mobs on the turf from being affected by anything below that turf, such as a pulse demon going under it. Added by a /obj/structure with creates_cover set to TRUE
+#define TRAIT_TURF_COVERED "turf_covered"
+
diff --git a/code/_globalvars/lists/misc.dm b/code/_globalvars/lists/misc.dm
index 3da92272d74..ee8b931cdfb 100644
--- a/code/_globalvars/lists/misc.dm
+++ b/code/_globalvars/lists/misc.dm
@@ -21,6 +21,9 @@ GLOBAL_LIST_INIT(html_colors, list("Alice Blue","Antique White","Aqua","Aquamari
GLOBAL_LIST_INIT(day_names, list("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
GLOBAL_LIST_INIT(month_names, list("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"))
+// keep si_suffixes balanced and with a pivot in the middle!
+GLOBAL_LIST_INIT(si_suffixes, list("y", "z", "a", "f", "p", "n", "u", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"))
+
GLOBAL_LIST_INIT(restricted_camera_networks, list(
"CentComm",
diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm
index ff8b6dc4d0f..4ea584227ea 100644
--- a/code/_onclick/ai.dm
+++ b/code/_onclick/ai.dm
@@ -139,7 +139,8 @@
A.AIAltClick(src)
/mob/living/silicon/ai/MiddleClickOn(atom/A)
A.AIMiddleClick(src)
-
+/mob/living/silicon/ai/MiddleShiftClickOn(atom/A)
+ A.AIMiddleShiftClick(src)
// DEFAULT PROCS TO OVERRIDE
@@ -165,6 +166,9 @@
/atom/proc/AIMiddleClick(mob/living/user)
return
+/atom/proc/AIMiddleShiftClick()
+ return
+
/mob/living/silicon/ai/TurfAdjacent(turf/T)
return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(T) && (get_dist(eyeobj, T) <= 7)) //not further than view distance
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 7ac4ad70bfa..ed7ca0f9a98 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -262,7 +262,6 @@
else
..()
-
/*
Middle shift-click
Makes the mob face the direction of the clicked thing
@@ -345,6 +344,13 @@
else
..()
+/// Use this instead of [/mob/proc/AltClickOn] where you only want turf content listing without additional atom alt-click interaction
+/atom/proc/AltClickNoInteract(mob/user, atom/A)
+ var/turf/T = get_turf(A)
+ if(T && user.TurfAdjacent(T))
+ user.listed_turf = T
+ user.client.statpanel = T.name
+
/atom/proc/AltClick(var/mob/user)
turf_examine(user)
diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm
index 4358892d8e7..43f0623c6ea 100644
--- a/code/_onclick/cyborg.dm
+++ b/code/_onclick/cyborg.dm
@@ -7,7 +7,7 @@
*/
/mob/living/silicon/robot/ClickOn(atom/A, params)
- if(client.click_intercept)
+ if(client?.click_intercept)
client.click_intercept.InterceptClickOn(src, params, A)
return
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index 84e22cb1720..90ebdfabc94 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -27,6 +27,9 @@
/obj/screen/movable/action_button/Click(location,control,params)
var/list/modifiers = params2list(params)
+ if(usr.next_click > world.time)
+ return FALSE
+ usr.changeNext_click(1)
if(modifiers["shift"])
if(locked)
to_chat(usr, "Action button \"[name]\" is locked, unlock it first.")
@@ -38,13 +41,16 @@
locked = !locked
to_chat(usr, "Action button \"[name]\" [locked ? "" : "un"]locked.")
return TRUE
- if(usr.next_click > world.time)
- return
- usr.next_click = world.time + 0.1 SECONDS
+ if(modifiers["alt"])
+ AltClick(usr)
+ return TRUE
linked_action.Trigger()
linked_action.UpdateButtonIcon() //redraw button
return TRUE
+/obj/screen/movable/action_button/AltClick(mob/user)
+ return linked_action.AltTrigger()
+
//Hide/Show Action Buttons ... Button
/obj/screen/movable/action_button/hide_toggle
name = "Hide Buttons"
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 045f90784e8..51d2c91af12 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -68,6 +68,9 @@
return FALSE
return TRUE
+/datum/action/proc/AltTrigger()
+ Trigger()
+
/datum/action/proc/Process()
return
@@ -644,6 +647,12 @@
spell.Click()
return TRUE
+/datum/action/spell_action/AltTrigger()
+ if(target)
+ var/obj/effect/proc_holder/spell/spell = target
+ spell.AltClick(usr)
+ return TRUE
+
/datum/action/spell_action/IsAvailable(message = FALSE)
if(!target)
return FALSE
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 96ad9273325..0192d85d29a 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -190,6 +190,7 @@
new_character.reload_huds()
SEND_SIGNAL(src, COMSIG_MIND_TRANSER_TO, new_character)
+ SEND_SIGNAL(new_character, COMSIG_BODY_TRANSFER_TO)
/datum/mind/proc/store_memory(new_text)
diff --git a/code/datums/spells/lightning.dm b/code/datums/spells/lightning.dm
index d47948fe5f2..4d8b0592fb1 100644
--- a/code/datums/spells/lightning.dm
+++ b/code/datums/spells/lightning.dm
@@ -54,7 +54,7 @@
/obj/effect/proc_holder/spell/charge_up/bounce/lightning/apply_bounce_effect(mob/origin, mob/living/target, energy, mob/user)
- if(NO_SHOCK in target.mutations)
+ if(HAS_TRAIT(target, TRAIT_SHOCKIMMUNE))
return
if(damaging)
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 4bef9459b6c..ff3d2870c72 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -57,3 +57,34 @@
/datum/status_effect/charging
id = "charging"
alert_type = null
+
+/datum/status_effect/delayed
+ id = "delayed_status_effect"
+ status_type = STATUS_EFFECT_MULTIPLE
+ alert_type = null
+ var/prevent_signal = null
+ var/datum/callback/expire_proc = null
+
+/datum/status_effect/delayed/on_creation(mob/living/new_owner, new_duration, datum/callback/new_expire_proc, new_prevent_signal = null)
+ if(!new_duration || !istype(new_expire_proc))
+ qdel(src)
+ return
+ duration = new_duration
+ expire_proc = new_expire_proc
+ . = ..()
+ if(new_prevent_signal)
+ RegisterSignal(owner, new_prevent_signal, PROC_REF(prevent_action))
+ prevent_signal = new_prevent_signal
+
+/datum/status_effect/proc/prevent_action()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/datum/status_effect/delayed/on_remove()
+ if(prevent_signal)
+ UnregisterSignal(owner, prevent_signal)
+ . = ..()
+
+/datum/status_effect/delayed/on_timeout()
+ . = ..()
+ expire_proc.Invoke()
diff --git a/code/game/dna/genes/gene.dm b/code/game/dna/genes/gene.dm
index 1509cd098a4..bfbc189c3a4 100644
--- a/code/game/dna/genes/gene.dm
+++ b/code/game/dna/genes/gene.dm
@@ -105,6 +105,9 @@
// Possible deactivation messages
var/list/deactivation_messages = list()
+ //which traits gene gives
+ var/list/traits_to_add = list()
+
/datum/dna/gene/basic/can_activate(mob/M, flags)
if(flags & MUTCHK_FORCED)
return TRUE
@@ -114,6 +117,8 @@
/datum/dna/gene/basic/activate(mob/M, connected, flags)
..()
M.mutations.Add(mutation)
+ for(var/trait in traits_to_add)
+ ADD_TRAIT(M, trait, "mutation")
if(activation_messages.len)
var/msg = pick(activation_messages)
to_chat(M, "[msg]")
@@ -121,6 +126,8 @@
/datum/dna/gene/basic/deactivate(mob/living/M, connected, flags)
..()
M.mutations.Remove(mutation)
+ for(var/trait in traits_to_add)
+ REMOVE_TRAIT(M, trait, "mutation")
if(deactivation_messages.len)
var/msg = pick(deactivation_messages)
to_chat(M, "[msg]")
diff --git a/code/game/dna/genes/powers.dm b/code/game/dna/genes/powers.dm
index b44c7d36a2b..4bf38edfe92 100644
--- a/code/game/dna/genes/powers.dm
+++ b/code/game/dna/genes/powers.dm
@@ -95,6 +95,7 @@
deactivation_messages = list("Your skin no longer feels dry and unreactive.")
instability = GENE_INSTABILITY_MODERATE
mutation = NO_SHOCK
+ traits_to_add = list(TRAIT_SHOCKIMMUNE)
/datum/dna/gene/basic/noshock/New()
..()
diff --git a/code/game/gamemodes/blob/theblob.dm b/code/game/gamemodes/blob/theblob.dm
index 7e9d54999dd..801e65227bc 100644
--- a/code/game/gamemodes/blob/theblob.dm
+++ b/code/game/gamemodes/blob/theblob.dm
@@ -15,6 +15,7 @@
var/fire_resist = 1 //multiplies burn damage by this
var/atmosblock = FALSE //if the blob blocks atmos and heat spread
var/mob/camera/blob/overmind
+ creates_cover = TRUE
/obj/structure/blob/New(loc)
..()
diff --git a/code/game/gamemodes/miniantags/demons/demon.dm b/code/game/gamemodes/miniantags/demons/demon.dm
index 5255e63708f..69f13302f35 100644
--- a/code/game/gamemodes/miniantags/demons/demon.dm
+++ b/code/game/gamemodes/miniantags/demons/demon.dm
@@ -27,6 +27,7 @@
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
del_on_death = TRUE
+ dirslash_enabled = TRUE
var/vialspawned = FALSE
var/playstyle_string
var/datum/action/innate/demon/whisper/whisper_action
diff --git a/code/game/gamemodes/miniantags/demons/pulse_demon/cross_shock_component.dm b/code/game/gamemodes/miniantags/demons/pulse_demon/cross_shock_component.dm
new file mode 100644
index 00000000000..1b01752d95d
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/pulse_demon/cross_shock_component.dm
@@ -0,0 +1,52 @@
+/datum/component/cross_shock
+ var/shock_damage
+ var/energy_cost
+ var/delay_between_shocks
+ var/requires_cable
+ COOLDOWN_DECLARE(last_shock)
+
+/datum/component/cross_shock/Initialize(_shock_damage, _energy_cost, _delay_between_shocks, _requires_cable = TRUE)
+ if(ismovable(parent))
+ RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_CROSSED_MOVABLE), PROC_REF(do_shock))
+ if(ismob(parent))
+ RegisterSignal(parent, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_organ_removal))
+ else if(isarea(parent))
+ RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(do_shock))
+ else if(isturf(parent))
+ RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(do_shock))
+ else
+ return COMPONENT_INCOMPATIBLE
+
+ shock_damage = _shock_damage
+ energy_cost = _energy_cost
+ delay_between_shocks = _delay_between_shocks
+ requires_cable = _requires_cable
+
+/datum/component/cross_shock/proc/do_shock(datum/source, mob/living/thing_were_gonna_shock)
+ SIGNAL_HANDLER
+ if(!COOLDOWN_FINISHED(src, last_shock))
+ return
+ if(!istype(thing_were_gonna_shock))
+ return
+ if(isliving(parent))
+ var/mob/living/M = parent
+ if(M.stat == DEAD || !M.incapacitated())
+ return
+ if(requires_cable)
+ var/turf/our_turf = get_turf(parent)
+ if(our_turf.transparent_floor || our_turf.intact || HAS_TRAIT(our_turf, TRAIT_TURF_COVERED))
+ return
+ var/obj/structure/cable/our_cable = locate(/obj/structure/cable) in our_turf
+ if(!our_cable || !our_cable.powernet || !our_cable.powernet.avail)
+ return
+ thing_were_gonna_shock.electrocute_act(shock_damage, src)
+ our_cable.add_load(energy_cost)
+ playsound(get_turf(parent), 'sound/effects/eleczap.ogg', 30, TRUE)
+ else
+ thing_were_gonna_shock.electrocute_act(shock_damage, src)
+ playsound(get_turf(parent), 'sound/effects/eleczap.ogg', 30, TRUE)
+ COOLDOWN_START(src, last_shock, delay_between_shocks)
+
+/datum/component/cross_shock/proc/on_organ_removal(datum/source)
+ SIGNAL_HANDLER
+ qdel(src)
diff --git a/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon.dm b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon.dm
new file mode 100644
index 00000000000..75fd6a38068
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon.dm
@@ -0,0 +1,855 @@
+// original implementation: https://ss13.moe/wiki/index.php/Pulse_Demon
+
+#define PULSEDEMON_PLATING_SPARK_CHANCE 20
+#define PULSEDEMON_APC_CHARGE_MULTIPLIER 2
+#define PULSEDEMON_SMES_DRAIN_MULTIPLIER 10
+#define ALERT_CATEGORY_NOPOWER "pulse_nopower"
+#define ALERT_CATEGORY_NOREGEN "pulse_noregen"
+
+#define PULSEDEMON_SOURCE_DRAIN_INVALID (-1)
+
+/mob/living/simple_animal/demon/pulse_demon
+ name = "pulse demon"
+ real_name = "pulse demon"
+ desc = "A strange electrical apparition that lives in wires."
+ gender = NEUTER
+ speak_chance = 20
+
+ damage_coeff = list(BRUTE = 0, BURN = 0, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) // Pulse demons take damage from nothing
+
+ emote_hear = list("vibrates", "sizzles")
+ speak_emote = list("modulates")
+
+ icon = 'icons/mob/animal.dmi'
+ icon_state = "pulsedem"
+ icon_living = "pulsedem"
+ icon_dead = "pulsedem"
+ response_help = "reaches their hand into"
+ response_disarm = "pushes their hand through"
+ response_harm = "punches their fist through"
+ deathmessage = "fizzles out into faint sparks, leaving only a slight trail of smoke..."
+ level = 1
+ plane = FLOOR_PLANE
+ layer = ABOVE_PLATING_LAYER
+
+ maxHealth = 50
+ health = 50
+ speed = -0.5
+ flying = TRUE
+ mob_size = MOB_SIZE_TINY
+ density = FALSE
+ del_on_death = TRUE
+
+ attacktext = "electrocutes"
+ attack_sound = "sparks"
+ a_intent = INTENT_HARM
+ harm_intent_damage = 0
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ pass_flags = PASSDOOR
+ stop_automated_movement = TRUE
+ has_unlimited_silicon_privilege = TRUE
+ // this makes the demon able to speak through holopads, due to the overriden say, PD cannot speak normally regardless
+ universal_speak = TRUE
+ loot = list(/obj/item/organ/internal/heart/demon/pulse)
+
+ /// List of sounds that is picked from when the demon speaks.
+ var/list/speech_sounds = list("sound/voice/pdvoice1.ogg", "sound/voice/pdvoice2.ogg", "sound/voice/pdvoice3.ogg")
+ /// List of sounds that is picked from when the demon dies or is EMP'd.
+ var/list/hurt_sounds = list("sound/voice/pdwail1.ogg", "sound/voice/pdwail2.ogg", "sound/voice/pdwail3.ogg")
+
+ /// Current quantity of power the demon currently holds, spent while purchasing, upgrading or using spells or upgrades. Use adjust_charge to modify this.
+ var/charge = 1000
+ /// Maximum quantity of power the demon can hold at once.
+ var/maxcharge = 1000
+ /// Book keeping for objective win conditions.
+ var/charge_drained = 0
+ /// Controls whether the demon will drain power from sources. Toggled by a spell.
+ var/do_drain = TRUE
+ /// Amount of power (in watts) to drain from power sources every Life tick.
+ var/power_drain_rate = 1000
+ /// Maximum value for power_drain_rate based on upgrades.
+ var/max_drain_rate = 1000
+
+ /// Amount of power (in watts) required to regenerate health.
+ var/power_per_regen = 1000
+ /// Amount of health lost per Life tick when the power requirement was not met.
+ var/health_loss_rate = 5
+ /// Amount of health regenerated per Life tick when the power requirement was met.
+ var/health_regen_rate = 3
+ /// Lock health regeneration while this is not 0, decreases by 1 every Life tick.
+ var/regen_lock = 0
+ /// Tracking to prevent multiple EMPs in the same tick from instakilling a demon.
+ var/emp_debounce = FALSE
+
+ /// Controls whether the demon can move outside of cables. Toggled by a spell.
+ var/can_exit_cable = FALSE
+ /// Speed used while moving inside cables.
+ var/inside_cable_speed = -0.5
+ /// Speed used while moving outside cables. Can be upgraded.
+ var/outside_cable_speed = 5
+
+ /// The time it takes to hijack APCs and cyborgs.
+ var/hijack_time = 30 SECONDS
+
+ /// The color of light the demon emits. The range of the light is proportional to charge.
+ var/glow_color = "#bbbb00"
+
+ /// Area being controlled, should be maintained as long as the demon does not move outside a container (APC, object, robot, bot).
+ var/area/controlling_area
+ /// Inhabited cable, only maintained while on top of the cable.
+ var/obj/structure/cable/current_cable
+ /// Inhabited power source, maintained while inside, or while inside its area if it is an APC.
+ var/obj/machinery/power/current_power
+ /// Inhabited item, only items which can be used in rechargers can be hijacked. Only maintained while inside the item.
+ var/obj/item/current_weapon
+ /// Inhabited cyborg, only maintained while inside the cyborg.
+ var/mob/living/silicon/robot/current_robot
+ /// Inhabited bot, only maintained while inside the bot.
+ var/mob/living/simple_animal/bot/current_bot
+
+ /// Delay tracker for movement inside bots.
+ var/bot_movedelay = 0
+ /// A cyborg that has already been hijacked can be re-entered instantly.
+ var/list/hijacked_robots = list()
+
+ /// Images of cables currently being shown on the client.
+ var/list/cable_images = list()
+ /// Images of APCs currently being shown on the client.
+ var/list/apc_images = list()
+ /// List of all previously hijacked APCs.
+ var/list/hijacked_apcs = list()
+ /// Reference to the APC currently being hijacked.
+ var/obj/machinery/power/apc/apc_being_hijacked
+
+/mob/living/simple_animal/demon/pulse_demon/Initialize(mapload)
+ . = ..()
+ if(!mapload)
+ name += " ([rand(100, 999)])"
+ real_name = name
+
+ remove_from_all_data_huds()
+ ADD_TRAIT(src, TRAIT_AI_UNTRACKABLE, PULSEDEMON_TRAIT)
+ // flags_2 |= RAD_NO_CONTAMINATE_2
+
+ // don't step on me
+ RegisterSignal(src, COMSIG_CROSSED_MOVABLE, PROC_REF(try_cross_shock))
+ RegisterSignal(src, COMSIG_MOVABLE_CROSSED, PROC_REF(try_cross_shock))
+
+ // drop demon onto ground if its loc is a non-turf and gets deleted
+ RegisterSignal(src, COMSIG_PARENT_PREQDELETED, PROC_REF(deleted_handler))
+
+ RegisterSignal(SSdcs, COMSIG_GLOB_CABLE_UPDATED, PROC_REF(cable_updated_handler))
+
+ RegisterSignal(src, COMSIG_BODY_TRANSFER_TO, PROC_REF(make_pulse_antagonist))
+
+ current_power = locate(/obj/machinery/power) in loc
+ // in the case that both current_power and current_cable are null, the pulsedemon will die the next tick
+ if(!current_power)
+ current_cable = locate(/obj/structure/cable) in loc
+ else
+ forceMove(current_power)
+ update_glow()
+ playsound(get_turf(src), 'sound/effects/eleczap.ogg', 30, TRUE)
+ give_spells()
+ whisper_action.button_icon_state = "pulse_whisper"
+ whisper_action.background_icon_state = "bg_pulsedemon"
+
+/mob/living/simple_animal/demon/pulse_demon/proc/deleted_handler(our_demon, force)
+ SIGNAL_HANDLER
+ // assume normal deletion if we're on a turf, otherwise deletion could be inherited from loc
+ if(force || isnull(loc) || isturf(loc))
+ return FALSE
+ // if we did actually die, simple_animal/death will set del_on_death to FALSE before calling qdel
+ if(!del_on_death)
+ return FALSE
+ exit_to_turf()
+ return TRUE
+
+/mob/living/simple_animal/demon/pulse_demon/proc/cable_updated_handler(SSdcs, turf/T)
+ SIGNAL_HANDLER
+ if(cable_images[T])
+ var/list/turf_images = cable_images[T]
+ for(var/image/current_image in turf_images)
+ client?.images -= current_image
+ turf_images.Cut()
+ else
+ cable_images[T] = list()
+
+ for(var/obj/structure/cable/C in T)
+ var/image/cable_image = image(C, C, layer = ABOVE_LIGHTING_LAYER, dir = C.dir)
+ cable_image.plane = ABOVE_LIGHTING_PLANE
+ cable_images[T] += cable_image
+ client?.images += cable_image
+
+/mob/living/simple_animal/demon/pulse_demon/proc/apc_deleted_handler(obj/machinery/power/apc/A, force)
+ SIGNAL_HANDLER
+ hijacked_apcs -= A
+
+/mob/living/simple_animal/demon/pulse_demon/Destroy()
+ cable_images.Cut()
+ apc_images.Cut()
+
+ controlling_area = null
+ current_bot = null
+ current_cable = null
+ current_power = null
+ current_robot = null
+ current_weapon = null
+ apc_being_hijacked = null
+ hijacked_apcs = null
+ hijacked_robots = null
+
+ return ..()
+
+/mob/living/simple_animal/demon/pulse_demon/Login()
+ . = ..()
+ update_cableview()
+
+/mob/living/simple_animal/demon/pulse_demon/proc/make_pulse_antagonist(demon)
+ SIGNAL_HANDLER
+ mind.assigned_role = SPECIAL_ROLE_DEMON
+ mind.special_role = SPECIAL_ROLE_DEMON
+ give_objectives()
+
+/mob/living/simple_animal/demon/pulse_demon/vv_edit_var(var_name, var_value)
+ switch(var_name)
+ if("glow_color")
+ update_glow()
+ if("charge")
+ // automatically adjusts maxcharge to allow the new value
+ adjust_charge(var_value - charge, TRUE)
+ return TRUE
+ return ..()
+
+/mob/living/simple_animal/demon/pulse_demon/forceMove(atom/destination)
+ var/old_location = loc
+ . = ..()
+ current_weapon = null
+ current_robot = null
+ if(current_bot)
+ current_bot.hijacked = FALSE
+ current_bot = null
+ if(istype(old_location, /obj/item/stock_parts/cell))
+ var/obj/item/stock_parts/cell/C = old_location
+ // only set rigged if there are no remaining demons in the cell
+ C.rigged = !(locate(/mob/living/simple_animal/demon/pulse_demon) in old_location)
+ if(istype(loc, /obj/item/stock_parts/cell))
+ var/obj/item/stock_parts/cell/C = loc
+ C.rigged = FALSE
+
+/mob/living/simple_animal/demon/pulse_demon/proc/give_objectives()
+ if(!mind)
+ return
+ mind.wipe_memory()
+ var/list/greeting = list()
+ greeting.Add(span_warningbig("You are a pulse demon."))
+ greeting.Add(span_clock("A being made of pure electrical energy, you travel through the station's wires and infest machinery."))
+ greeting.Add(span_clock("Navigate the station's power cables to find power sources to steal from, and hijack APCs to interact with their connected machines."))
+ greeting.Add(span_clock("If the wire or power source you're connected to runs out of power you'll start losing health and eventually die, but you are otherwise immune to damage."))
+ to_chat(src, span_notice(greeting.Join("
")))
+ var/datum/objective/pulse_demon/infest/infestapc = new
+ var/datum/objective/pulse_demon/drain/drainpower = new
+ var/datum/objective/pulse_demon/tamper/tampermach = new
+ mind.objectives += infestapc
+ mind.objectives += drainpower
+ mind.objectives += tampermach
+ infestapc.owner = mind
+ drainpower.owner = mind
+ tampermach.owner = mind
+ mind.announce_objectives()
+ SSticker.mode.traitors |= mind
+ return
+
+/mob/living/simple_animal/demon/pulse_demon/proc/give_spells()
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/cycle_camera)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/toggle/do_drain(do_drain))
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/toggle/can_exit_cable(can_exit_cable))
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/cablehop)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/emagtamper)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/emp)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/overload)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/remotehijack)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/remotedrain)
+ AddSpell(new /obj/effect/proc_holder/spell/pulse_demon/open_upgrades)
+
+/mob/living/simple_animal/demon/pulse_demon/Stat()
+ . = ..()
+ if(statpanel("Status"))
+ stat(null, "Charge: [format_si_suffix(charge)]W")
+ stat(null, "Maximum Charge: [format_si_suffix(maxcharge)]W")
+ stat(null, "Drained Charge: [format_si_suffix(charge_drained)]W")
+ stat(null, "Hijacked APCs: [length(hijacked_apcs)]")
+ stat(null, "Drain Rate: [format_si_suffix(power_drain_rate)]W")
+ stat(null, "Hijack Time: [hijack_time / 10] seconds")
+
+/mob/living/simple_animal/demon/pulse_demon/dust()
+ return death()
+
+/mob/living/simple_animal/demon/pulse_demon/gib()
+ return death()
+
+/mob/living/simple_animal/demon/pulse_demon/death()
+ var/turf/T = get_turf(src)
+ do_sparks(rand(2, 4), FALSE, src)
+ . = ..()
+
+ var/heavy_radius = min(charge / 50000, 20)
+ var/light_radius = min(charge / 25000, 25)
+ empulse(T, heavy_radius, light_radius)
+ playsound(T, pick(hurt_sounds), 30, TRUE)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/exit_to_turf()
+ var/turf/T = get_turf(src)
+ current_power = null
+ update_controlling_area()
+ current_cable = null
+ forceMove(T)
+ Move(T)
+ if(!current_cable && !current_power)
+ var/obj/effect/proc_holder/spell/pulse_demon/toggle/can_exit_cable/S = locate() in mob_spell_list
+ if(!S.locked && !can_exit_cable)
+ can_exit_cable = TRUE
+ S.do_toggle(can_exit_cable)
+ to_chat(src, span_danger("Your self-sustaining ability has automatically enabled itself to prevent death from having no connection!"))
+
+/mob/living/simple_animal/demon/pulse_demon/proc/update_controlling_area(reset = FALSE)
+ var/area/prev = controlling_area
+ if(reset || current_power == null)
+ controlling_area = null
+ else if(isapc(current_power))
+ var/obj/machinery/power/apc/A = current_power
+ if(A in hijacked_apcs)
+ controlling_area = A.area
+ else
+ controlling_area = null
+
+ if((!prev && !controlling_area) || (prev && controlling_area))
+ return // only update icons when we get or no longer have ANY area
+ for(var/obj/effect/proc_holder/spell/pulse_demon/S in mob_spell_list)
+ if(!S.action || S.locked)
+ continue
+ if(S.requires_area)
+ S.action.UpdateButtonIcon()
+
+// can enter an apc at all?
+/mob/living/simple_animal/demon/pulse_demon/proc/is_valid_apc(obj/machinery/power/apc/A)
+ return istype(A) && !(A.stat & BROKEN) && !A.shorted
+
+/mob/living/simple_animal/demon/pulse_demon/Move(newloc)
+ var/obj/machinery/power/new_power = locate(/obj/machinery/power) in newloc
+ var/obj/structure/cable/new_cable = locate(/obj/structure/cable) in newloc
+
+ if(QDELETED(new_power))
+ new_power = null
+ if(QDELETED(new_cable))
+ new_cable = null
+
+ if(istype(new_power, /obj/machinery/power/terminal))
+ // entering a terminal is kinda useless and any working terminal will have a cable under it
+ new_power = null
+
+ if(isapc(new_power))
+ var/obj/machinery/power/apc/A = new_power
+ if(!is_valid_apc(new_power) || !A.terminal)
+ new_power = null // don't enter an APC without a terminal or a broken APC, etc.
+
+ // there's no electricity in space
+ if(!new_cable && !new_power && (!can_exit_cable || isspaceturf(newloc)))
+ return
+
+ var/moved = ..()
+
+ if(!new_cable && !new_power)
+ if(can_exit_cable && moved)
+ speed = outside_cable_speed
+ else
+ speed = inside_cable_speed
+
+ if(moved)
+ if(!is_under_tile() && prob(PULSEDEMON_PLATING_SPARK_CHANCE))
+ do_sparks(rand(2, 4), FALSE, src)
+
+ current_weapon = null
+ current_robot = null
+ if(current_bot)
+ current_bot.hijacked = FALSE
+ current_bot = null
+
+ /*
+ A few notes about this terrible proc, If you're wondering, I didn't write it but man I do NOT want to touch it
+ 1. A lot of this 100% shouldn't be on move, that's just waiting for something bad to happen
+ 2. Never, EVER directly call a do_after here, it will cause move to sleep which is awful
+ */
+ if(new_power)
+ current_power = new_power
+ current_cable = null
+ forceMove(current_power) // we go inside the machine
+ playsound(src, 'sound/effects/eleczap.ogg', 15, TRUE)
+ do_sparks(rand(2, 4), FALSE, src)
+ if(isapc(current_power))
+ if(current_power in hijacked_apcs)
+ update_controlling_area()
+ else
+ INVOKE_ASYNC(src, PROC_REF(try_hijack_apc), current_power)
+ else if(new_cable)
+ current_cable = new_cable
+ current_power = null
+ update_controlling_area()
+ if(!isturf(loc))
+ loc = get_turf(newloc)
+ if(!moved)
+ forceMove(newloc)
+ else if(moved)
+ current_cable = null
+ current_power = null
+ update_controlling_area()
+
+// signal to replace relaymove where or when? // Never, actually just manage your code instead
+/obj/machinery/power/relaymove(mob/user, dir)
+ if(!ispulsedemon(user))
+ return ..()
+
+ var/mob/living/simple_animal/demon/pulse_demon/demon = user
+ var/turf/T = get_turf(src)
+ var/turf/T2 = get_step(T, dir)
+ if(demon.can_exit_cable || locate(/obj/structure/cable) in T2)
+ playsound(src, 'sound/effects/eleczap.ogg', 15, TRUE)
+ do_sparks(rand(2, 4), FALSE, src)
+ user.forceMove(T)
+ if(isapc(src))
+ demon.update_controlling_area(TRUE)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/adjust_charge(amount, adjust_max = FALSE)
+ if(!amount)
+ return FALSE
+ if(adjust_max)
+ maxcharge = max(maxcharge, charge + amount)
+ var/orig = charge
+ charge = min(maxcharge, charge + amount)
+ var/realdelta = charge - orig
+ if(!realdelta)
+ return FALSE
+ if(realdelta > 0)
+ charge_drained += realdelta
+
+ update_glow()
+ for(var/obj/effect/proc_holder/spell/pulse_demon/S in mob_spell_list)
+ if(!S.action || S.locked || !S.cast_cost)
+ continue
+ var/dist = S.cast_cost - orig
+ // only update icon if the amount is actually enough to change a spell's availability
+ if(dist == 0 || (dist > 0 && realdelta >= dist) || (dist < 0 && realdelta <= dist))
+ S.action.UpdateButtonIcon()
+ return realdelta
+
+// logarithmic scale for glow strength, see table:
+ // 1.5 <= 25k
+ // 2 at 50k
+ // 2.5 at 100k
+ // 3 at 200k
+ // 3.5 at 400k, etc
+/mob/living/simple_animal/demon/pulse_demon/proc/update_glow()
+ var/range = 2 + (log(2, charge + 1) - log(2, 50000)) / 2
+ range = max(range, 1.5)
+ set_light(range, 2, glow_color)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/drain_APC(obj/machinery/power/apc/A, multiplier = 1)
+ if(A.being_hijacked)
+ return PULSEDEMON_SOURCE_DRAIN_INVALID
+ var/amount_to_drain = clamp(A.cell.charge, 0, power_drain_rate * multiplier)
+ A.cell.use(min(amount_to_drain, maxcharge - charge)) // calculated seperately because the apc charge multiplier shouldn't affect the actual consumption
+ return adjust_charge(amount_to_drain * PULSEDEMON_APC_CHARGE_MULTIPLIER)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/drain_SMES(obj/machinery/power/smes/S, multiplier = 1)
+ var/amount_to_drain = clamp(S.charge, 0, power_drain_rate * multiplier * PULSEDEMON_SMES_DRAIN_MULTIPLIER)
+ var/drained = adjust_charge(amount_to_drain)
+ S.charge -= drained
+ return drained
+
+/mob/living/simple_animal/demon/pulse_demon/Life(seconds, times_fired)
+ . = ..()
+
+ var/got_power = FALSE
+ if(current_cable)
+ if(current_cable.avail() >= power_per_regen)
+ current_cable.add_load(power_per_regen)
+ got_power = TRUE
+
+ var/excess = initial(power_per_regen) - power_per_regen
+ if(excess > 0 && current_cable.avail() >= excess && do_drain)
+ adjust_charge(excess)
+ current_cable.add_load(excess)
+ else if(current_power)
+ if(isapc(current_power) && loc == current_power && do_drain)
+ if(drain_APC(current_power) > power_per_regen)
+ got_power = TRUE
+ else if(istype(current_power, /obj/machinery/power/smes) && do_drain)
+ if(drain_SMES(current_power) > power_per_regen)
+ got_power = TRUE
+ // try to take power from the powernet if the APC or SMES is empty (or we're not /really/ in the APC)
+ if(!got_power && current_power.avail() >= power_per_regen)
+ current_power.add_load(power_per_regen)
+ got_power = TRUE
+ else if(!can_exit_cable)
+ death()
+ return
+
+ if(got_power)
+ if(regen_lock <= 0)
+ adjustHealth(-health_regen_rate)
+ clear_alert(ALERT_CATEGORY_NOPOWER)
+ else
+ var/rate = health_loss_rate
+ if(!current_cable && !current_power && can_exit_cable)
+ // 2 * initial_rate - upgrade_level
+ rate += initial(health_loss_rate)
+ adjustHealth(rate)
+ throw_alert(ALERT_CATEGORY_NOPOWER, /obj/screen/alert/pulse_nopower)
+
+ if(regen_lock > 0)
+ if(--regen_lock == 0)
+ clear_alert(ALERT_CATEGORY_NOREGEN)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/gen_speech_name()
+ . = ""
+ for(var/i = 1 to 10)
+ . += pick("!", "@", "#", "$", "%", "^", "&", "*")
+
+/mob/living/simple_animal/demon/pulse_demon/say(message, verb, sanitize = TRUE, ignore_speech_problems = FALSE, ignore_atmospherics = FALSE, ignore_languages = FALSE)
+ if(client && (client.prefs.muted & MUTE_IC))
+ to_chat(src, span_danger("You cannot speak in IC (Muted)."))
+ return FALSE
+
+ if(sanitize)
+ message = trim_strip_html_properly(message)
+
+ if(stat)
+ if(stat == DEAD)
+ return say_dead(message)
+ return FALSE
+
+ if(current_robot)
+ var/turf/T = get_turf(src)
+ log_say("[key_name_admin(src)] (@[T.x], [T.y], [T.z]) made [current_robot]([key_name_admin(current_robot)]) say: [message]")
+ log_admin("[key_name_admin(src)] made [key_name_admin(current_robot)] say: [message]")
+ message_admins(span_notice("[key_name_admin(src)] made [key_name_admin(current_robot)] say: [message]"))
+ // don't sanitize again
+ current_robot.say(message, null, FALSE, ignore_speech_problems, ignore_atmospherics, ignore_languages)
+ return TRUE
+
+ var/message_mode = parse_message_mode(message, "headset")
+
+ if(message_mode)
+ if(message_mode == "headset")
+ message = copytext(message, 2)
+ else
+ message = copytext(message, 3)
+
+ message = trim_left(message)
+
+ var/list/message_pieces = list()
+ if(ignore_languages)
+ message_pieces = message_to_multilingual(message)
+ else
+ message_pieces = parse_languages(message)
+
+ // hivemind languages
+ if(istype(message_pieces, /datum/multilingual_say_piece))
+ var/datum/multilingual_say_piece/S = message_pieces
+ S.speaking.broadcast(src, S.message)
+ return TRUE
+
+ if(!LAZYLEN(message_pieces))
+ . = FALSE
+ CRASH("Message failed to generate pieces. [message] - [json_encode(message_pieces)]")
+
+ create_log(SAY_LOG, "[message_mode ? "([message_mode])" : ""] '[message]'")
+
+ playsound(get_turf(src), pick(speech_sounds), 30, TRUE)
+ if(istype(loc, /obj/item/radio))
+ var/obj/item/radio/R = loc
+ name = gen_speech_name()
+ R.talk_into(src, message_pieces, message_mode, verb)
+ name = real_name
+ return TRUE
+ else if(istype(loc, /obj/machinery/hologram/holopad))
+ var/obj/machinery/hologram/holopad/H = loc
+ name = "[H]"
+ for(var/mob/M in get_mobs_in_view(7, H))
+ M.hear_say(message_pieces, verb, FALSE, src)
+ name = real_name
+ return TRUE
+
+ emote("me", message = "[pick(emote_hear)]")
+ return TRUE
+
+/mob/living/simple_animal/demon/pulse_demon/visible_message(message, self_message, blind_message)
+ // overriden because pulse demon is quite often in non-turf locs, and /mob/visible_message acts differently there
+ for(var/mob/M in get_mobs_in_view(7, src))
+ if(M.see_invisible < invisibility)
+ continue //can't view the invisible
+ var/msg = message
+ if(self_message && M == src)
+ msg = self_message
+ M.show_message(msg, EMOTE_VISUAL, blind_message, EMOTE_SOUND)
+
+/mob/living/simple_animal/demon/pulse_demon/has_internal_radio_channel_access(mob/user, list/req_one_accesses)
+ return has_access(list(), req_one_accesses, get_all_accesses())
+
+/mob/living/simple_animal/demon/pulse_demon/proc/try_hijack_apc(obj/machinery/power/apc/A, remote = FALSE)
+ // one APC per pulse demon, one pulse demon per APC, no duplicate APCs
+ if(!is_valid_apc(A) || (A in hijacked_apcs) || apc_being_hijacked || A.being_hijacked)
+ return FALSE
+
+ to_chat(src, span_notice("You are now attempting to hijack [A], this will take approximately [hijack_time / 10] seconds."))
+ apc_being_hijacked = A
+ A.being_hijacked = TRUE
+ A.update_icon()
+ if(do_after(src, hijack_time, target = A))
+ if(is_valid_apc(A))
+ finish_hijack_apc(A, remote)
+ else
+ to_chat(src, span_warning("Failed to hijack [src]."))
+ apc_being_hijacked = null
+ A.being_hijacked = FALSE
+ A.update_icon()
+
+// Basically this proc gives you more max charge per apc you have hijacked
+// Looks weird but it gets the job done
+/mob/living/simple_animal/demon/pulse_demon/proc/calc_maxcharge(hijacked_apcs)
+ if(!hijacked_apcs) // No APCs hijacked? No extra charge
+ return 1000
+ return 20000 * clamp(hijacked_apcs, 0, 20) + 500000 * clamp(hijacked_apcs - 20, 0, 30) + 1000000 * clamp(hijacked_apcs - 50, 0, 50) + 500000000 * max(0, hijacked_apcs - 100)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/finish_hijack_apc(obj/machinery/power/apc/A, remote = FALSE)
+ var/image/apc_image = image('icons/obj/engines_and_power/power.dmi', A, "apcemag", ABOVE_LIGHTING_LAYER, A.dir)
+ apc_image.plane = ABOVE_LIGHTING_PLANE
+ LAZYADD(apc_images[get_turf(A)], apc_image)
+ client.images += apc_image
+
+ hijacked_apcs += A
+ RegisterSignal(A, COMSIG_PARENT_QDELETING, PROC_REF(apc_deleted_handler))
+ if(!remote)
+ update_controlling_area()
+ maxcharge = calc_maxcharge(length(hijacked_apcs)) + (maxcharge - calc_maxcharge(length(hijacked_apcs) - 1))
+ to_chat(src, span_notice("Hijacking complete! You now control [length(hijacked_apcs)] APCs."))
+
+/mob/living/simple_animal/demon/pulse_demon/proc/try_cross_shock(src, atom/A)
+ SIGNAL_HANDLER
+ if(!isliving(A) || is_under_tile())
+ return
+ var/mob/living/L = A
+ try_shock_mob(L)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/try_shock_mob(mob/living/L, siemens_coeff = 1)
+ var/dealt = 0
+ if(current_cable && current_cable.powernet && current_cable.powernet.avail)
+ // returns used energy, not damage dealt, but ez conversion with /20
+ dealt = electrocute_mob(L, current_cable.powernet, src, siemens_coeff) / 20
+ else if(charge >= 1000)
+ dealt = L.electrocute_act(30, src, siemens_coeff)
+ adjust_charge(-1000)
+ if(dealt > 0)
+ do_sparks(rand(2, 4), FALSE, src)
+ add_attack_logs(src, L, "shocked ([dealt] damage)")
+
+/mob/living/simple_animal/demon/pulse_demon/proc/is_under_tile()
+ var/turf/T = get_turf(src)
+ return T.transparent_floor || T.intact || HAS_TRAIT(T, TRAIT_TURF_COVERED)
+
+// cable (and hijacked APC) view helper
+/mob/living/simple_animal/demon/pulse_demon/proc/update_cableview()
+ if(!client)
+ return
+
+ // clear out old images
+ for(var/image/current_image in cable_images + apc_images)
+ client.images -= current_image
+
+ var/turf/T = get_turf(src)
+
+ // regenerate for all cables on our (or our holder's) z-level
+ cable_images.Cut()
+ for(var/datum/powernet/P in SSmachines.powernets)
+ for(var/obj/structure/cable/C in P.cables)
+ var/turf/cable_turf = get_turf(C)
+ if(T.z != cable_turf.z)
+ break // skip entire powernet if it's off z-level
+
+ var/image/cable_image = image(C, C, layer = ABOVE_LIGHTING_LAYER, dir = C.dir)
+ // good visibility here
+ cable_image.plane = ABOVE_LIGHTING_PLANE
+ LAZYADD(cable_images[cable_turf], cable_image)
+ client.images += cable_image
+
+ // same for hijacked APCs
+ apc_images.Cut()
+ for(var/obj/machinery/power/apc/A in hijacked_apcs)
+ var/turf/apc_turf = get_turf(A)
+ if(T.z != apc_turf.z)
+ continue
+ // parent of image is the APC, not the turf because of how clicking on images works
+ var/image/apc_image = image('icons/obj/engines_and_power/power.dmi', A, "apcemag", ABOVE_LIGHTING_LAYER, A.dir)
+ apc_image.plane = ABOVE_LIGHTING_PLANE
+ LAZYADD(apc_images[apc_turf], apc_image)
+ client.images += apc_image
+
+/mob/living/simple_animal/demon/pulse_demon/emp_act(severity)
+ if(emp_debounce)
+ return
+
+ . = ..()
+ visible_message(span_danger(">[src] [pick("fizzles", "wails", "flails")] in anguish!"))
+ playsound(get_turf(src), pick(hurt_sounds), 30, TRUE)
+ throw_alert(ALERT_CATEGORY_NOREGEN, /obj/screen/alert/pulse_noregen)
+ switch(severity)
+ if(EMP_LIGHT)
+ adjustHealth(round(max(initial(health) / 4, round(maxHealth / 8))))
+ regen_lock = 3
+ if(EMP_HEAVY)
+ adjustHealth(round(max(initial(health) / 3, round(maxHealth / 6))))
+ regen_lock = 5
+ emp_debounce = TRUE
+ addtimer(VARSET_CALLBACK(src, emp_debounce, FALSE), 0.1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
+
+/mob/living/simple_animal/demon/pulse_demon/proc/try_attack_mob(mob/living/L)
+ if(!is_under_tile() && L != src)
+ do_attack_animation(L)
+ try_shock_mob(L)
+
+/mob/living/simple_animal/demon/pulse_demon/UnarmedAttack(atom/A)
+ if(isliving(A))
+ try_attack_mob(A)
+ else if(isitem(A) && !is_under_tile())
+ var/obj/item/O = A
+ var/obj/item/stock_parts/cell/C = O.get_cell()
+ if(C?.charge)
+ C.use(min(C.charge, power_drain_rate))
+ adjust_charge(min(C.charge, power_drain_rate))
+ visible_message(span_notice("[src] touches [O] and drains its power!"), span_notice("You touch [O] and drain it's power!"))
+
+/mob/living/simple_animal/demon/pulse_demon/attack_hand(mob/living/carbon/human/M)
+ if(is_under_tile())
+ to_chat(M, span_danger(">You can't interact with something that's under the floor!"))
+ return
+ switch(M.intent)
+ if(INTENT_HELP)
+ visible_message(span_notice("[M] [response_help] [src]."))
+ if(INTENT_DISARM, INTENT_GRAB)
+ visible_message(span_notice("[M] [response_disarm] [src]."))
+ if(INTENT_HELP)
+ visible_message(span_danger("[M] [response_harm] [src]."))
+ try_attack_mob(M)
+
+/mob/living/simple_animal/demon/pulse_demon/attackby(obj/item/O, mob/living/user)
+ if(is_under_tile())
+ to_chat(user, span_danger("You can't interact with something that's under the floor!"))
+ return
+ var/obj/item/stock_parts/cell/C = O.get_cell()
+ if(C && C.charge)
+ C.use(min(C.charge, power_drain_rate))
+ adjust_charge(min(C.charge, power_drain_rate))
+ to_chat(user, span_warning("You touch [src] with [O] and [src] drains it."))
+ to_chat(src, span_notice("[user] touches you with [O] and you drain its power!"))
+ visible_message(span_notice("[O] goes right through [src]."))
+ try_shock_mob(user, O.siemens_coefficient)
+
+/mob/living/simple_animal/demon/pulse_demon/ex_act()
+ return
+
+/mob/living/simple_animal/demon/pulse_demon/CanPass(atom/movable/mover, turf/target, height)
+ . = ..()
+ if(istype(mover, /obj/item/projectile/ion))
+ return FALSE
+
+/mob/living/simple_animal/demon/pulse_demon/bullet_act(obj/item/projectile/proj)
+ if(istype(proj, /obj/item/projectile/ion))
+ return ..()
+ visible_message(span_warning("[proj] goes right through [src]!"))
+
+/mob/living/simple_animal/demon/pulse_demon/electrocute_act(shock_damage, source, siemens_coeff, safety, override, tesla_shock, illusion, stun)
+ return
+
+/mob/living/simple_animal/demon/pulse_demon/blob_act(obj/structure/blob/B)
+ return // will likely end up dying if the blob cuts its wires anyway
+
+/mob/living/simple_animal/demon/pulse_demon/narsie_act()
+ return // you can't turn electricity into a harvester
+
+/mob/living/simple_animal/demon/pulse_demon/get_access()
+ return get_all_accesses()
+
+/mob/living/simple_animal/demon/pulse_demon/IsAdvancedToolUser()
+ return TRUE // interacting with machines
+
+/mob/living/simple_animal/demon/pulse_demon/can_be_pulled()
+ return FALSE
+
+/mob/living/simple_animal/demon/pulse_demon/can_buckle()
+ return FALSE
+
+/mob/living/simple_animal/demon/pulse_demon/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ return
+
+/mob/living/simple_animal/demon/pulse_demon/experience_pressure_difference()
+ return // no thanks
+
+/mob/living/simple_animal/demon/pulse_demon/singularity_pull()
+ return
+
+/mob/living/simple_animal/demon/pulse_demon/mob_negates_gravity()
+ return TRUE
+
+/mob/living/simple_animal/demon/pulse_demon/mob_has_gravity()
+ return TRUE
+
+/obj/item/organ/internal/heart/demon/pulse
+ name = "perpetual pacemaker"
+ desc = "It still beats furiously, thousands of bright lights shine within it."
+ color = COLOR_YELLOW
+
+/obj/item/organ/internal/heart/demon/pulse/Initialize(mapload)
+ . = ..()
+ set_light(13, 2, "#bbbb00")
+
+/obj/item/organ/internal/heart/demon/pulse/attack_self(mob/living/user)
+ . = ..()
+ user.drop_item_ground()
+ insert(user)
+
+/obj/item/organ/internal/heart/demon/pulse/insert(mob/living/carbon/M, special, dont_remove_slot)
+ . = ..()
+ M.AddComponent(/datum/component/cross_shock, 30, 500, 2 SECONDS)
+ ADD_TRAIT(M, TRAIT_SHOCKIMMUNE, UNIQUE_TRAIT_SOURCE(src))
+ M.set_light(3, 2, "#bbbb00")
+
+/obj/item/organ/internal/heart/demon/pulse/remove(mob/living/carbon/M, special)
+ . = ..()
+ REMOVE_TRAIT(M, TRAIT_SHOCKIMMUNE, UNIQUE_TRAIT_SOURCE(src))
+ M.remove_light()
+
+/obj/item/organ/internal/heart/demon/pulse/on_life()
+ if(!owner)
+ return
+ for(var/obj/item/stock_parts/cell/cell_to_charge in owner.GetAllContents())
+ var/newcharge = min(0.05 * cell_to_charge.maxcharge + cell_to_charge.charge, cell_to_charge.maxcharge)
+ if(cell_to_charge.charge < newcharge)
+ cell_to_charge.charge = newcharge
+ if(isobj(cell_to_charge.loc))
+ var/obj/cell_location = cell_to_charge.loc
+ cell_location.update_icon() //update power meters and such
+ cell_to_charge.update_icon()
+
+/obj/screen/alert/pulse_nopower
+ name = "No Power"
+ desc = "You are not connected to a cable or machine and are losing health!"
+ icon_state = "pd_nopower"
+
+/obj/screen/alert/pulse_noregen
+ name = "Regeneration Stalled"
+ desc = "You've been EMP'd and cannot regenerate health!"
+ icon_state = "pd_noregen"
+
+#undef ALERT_CATEGORY_NOPOWER
+#undef ALERT_CATEGORY_NOREGEN
diff --git a/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_abilities.dm b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_abilities.dm
new file mode 100644
index 00000000000..e0de392c20f
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_abilities.dm
@@ -0,0 +1,488 @@
+#define KW *1000
+#define PULSEDEMON_REMOTE_DRAIN_MULTIPLIER 5
+
+#define PD_UPGRADE_HIJACK_SPEED "Speed"
+#define PD_UPGRADE_DRAIN_SPEED "Absorption"
+#define PD_UPGRADE_HEALTH_LOSS "Endurance"
+#define PD_UPGRADE_HEALTH_REGEN "Recovery"
+#define PD_UPGRADE_MAX_HEALTH "Strength"
+#define PD_UPGRADE_HEALTH_COST "Efficiency"
+#define PD_UPGRADE_MAX_CHARGE "Capacity"
+
+/obj/effect/proc_holder/spell/pulse_demon
+ panel = "Pulse Demon"
+ school = "pulse demon"
+ human_req = FALSE
+ clothes_req = FALSE
+ action_background_icon_state = "bg_pulsedemon"
+ var/locked = TRUE
+ var/unlock_cost = 1 KW
+ var/cast_cost = 1 KW
+ var/upgrade_cost = 1 KW
+ var/requires_area = FALSE
+ base_cooldown = 20 SECONDS
+ level_max = 4
+
+/obj/effect/proc_holder/spell/pulse_demon/New()
+ . = ..()
+ update_info()
+
+/obj/effect/proc_holder/spell/pulse_demon/proc/update_info()
+ if(locked)
+ name = "[initial(name)] (Locked) ([format_si_suffix(unlock_cost)]W)"
+ desc = "[initial(desc)] It costs [format_si_suffix(unlock_cost)]W to unlock."
+ else
+ name = "[initial(name)][cast_cost == 0 ? "" : " ([format_si_suffix(cast_cost)]W)"]"
+ desc = "[initial(desc)][spell_level == level_max ? "" : " It costs [format_si_suffix(upgrade_cost)]W to upgrade."]"
+ action.button.name = name
+ action.desc = desc
+ action.UpdateButtonIcon()
+
+/obj/effect/proc_holder/spell/pulse_demon/can_cast(mob/living/simple_animal/demon/pulse_demon/user, charge_check, show_message)
+ if(!..())
+ return FALSE
+ if(!istype(user))
+ return FALSE
+ if(locked)
+ if(show_message)
+ to_chat(user, span_warning("This ability is locked! Alt-click the button to purchase this ability."))
+ to_chat(user, span_notice("It costs [format_si_suffix(unlock_cost)]W to unlock."))
+ return FALSE
+ if(user.charge < cast_cost)
+ if(show_message)
+ to_chat(user, span_warning("You do not have enough charge to use this ability!"))
+ to_chat(user, span_notice("It costs [format_si_suffix(cast_cost)]W to use."))
+ return FALSE
+ if(requires_area && !user.controlling_area)
+ if(show_message)
+ to_chat(user, span_warning("You need to be controlling an area to use this ability!"))
+ return FALSE
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/cast(list/targets, mob/living/simple_animal/demon/pulse_demon/user)
+ if(!istype(user) || locked || user.charge < cast_cost || !length(targets))
+ return FALSE
+ if(requires_area && !user.controlling_area)
+ return FALSE
+ if(requires_area && user.controlling_area != get_area(targets[1]))
+ to_chat(user, span_warning("You can only use this ability in your controlled area!"))
+ return FALSE
+ if(try_cast_action(user, targets[1]))
+ user.adjust_charge(-cast_cost)
+ return TRUE
+ else
+ revert_cast(user)
+ return FALSE
+
+/obj/effect/proc_holder/spell/pulse_demon/create_new_targeting()
+ return new /datum/spell_targeting/clicked_atom
+
+/obj/effect/proc_holder/spell/pulse_demon/proc/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ return FALSE
+
+// handles purchasing and upgrading abilities
+/obj/effect/proc_holder/spell/pulse_demon/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
+ if(!istype(user))
+ return
+
+ if(locked)
+ if(user.charge >= unlock_cost)
+ user.adjust_charge(-unlock_cost)
+ locked = FALSE
+ to_chat(user, span_notice("You have unlocked [initial(name)]!"))
+
+ if(cast_cost > 0)
+ to_chat(user, span_notice("It costs [format_si_suffix(cast_cost)]W to use once."))
+ if(level_max > 0 && spell_level < level_max)
+ to_chat(user, span_notice("It will cost [format_si_suffix(upgrade_cost)]W to upgrade."))
+
+ update_info()
+ else
+ to_chat(user, span_warning("You cannot afford this ability! It costs [format_si_suffix(unlock_cost)]W to unlock."))
+ else
+ if(spell_level >= level_max)
+ to_chat(user, span_warning("You have already fully upgraded this ability!"))
+ else if(user.charge >= upgrade_cost)
+ user.adjust_charge(-upgrade_cost)
+ spell_level = min(spell_level + 1, level_max)
+ upgrade_cost = round(initial(upgrade_cost) * (1.5 ** spell_level))
+ do_upgrade(user)
+
+ if(spell_level == level_max)
+ to_chat(user, span_notice("You have fully upgraded [initial(name)]!"))
+ else
+ to_chat(user, span_notice("The next upgrade will cost [format_si_suffix(upgrade_cost)]W to unlock."))
+
+ update_info()
+ else
+ to_chat(user, span_warning("You cannot afford to upgrade this ability! It costs [format_si_suffix(upgrade_cost)]W to upgrade."))
+
+/obj/effect/proc_holder/spell/pulse_demon/proc/do_upgrade(mob/living/simple_animal/demon/pulse_demon/user)
+ cooldown_handler.recharge_duration = round(base_cooldown / (1.5 ** spell_level))
+ to_chat(user, span_notice("You have upgraded [initial(name)] to level [spell_level + 1], it now takes [cooldown_handler.recharge_duration / 10] seconds to recharge."))
+
+/obj/effect/proc_holder/spell/pulse_demon/cablehop
+ name = "Cable Hop"
+ desc = "Jump to another cable in view."
+ action_icon_state = "pd_cablehop"
+ unlock_cost = 15 KW
+ cast_cost = 5 KW
+ upgrade_cost = 75 KW
+
+/obj/effect/proc_holder/spell/pulse_demon/cablehop/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ var/turf/O = get_turf(user)
+ var/turf/T = get_turf(target)
+ var/obj/structure/cable/C = locate(/obj/structure/cable) in T
+ if(!istype(C))
+ to_chat(user, span_warning("No cable found!"))
+ return FALSE
+ playsound(T, 'sound/magic/lightningshock.ogg', 50, TRUE)
+ O.Beam(target, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 1 SECONDS)
+ for(var/turf/working in getline(O, T))
+ for(var/mob/living/L in working)
+ if(!electrocute_mob(L, C.powernet, user)) // give a little bit of non-lethal counterplay against insuls
+ L.Jitter(5 SECONDS)
+ L.apply_status_effect(STATUS_EFFECT_DELAYED, 1 SECONDS, CALLBACK(L, TYPE_PROC_REF(/mob/living, Stun), 5 SECONDS))
+ user.forceMove(T)
+ user.Move(T)
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/emagtamper
+ name = "Electromagnetic Tamper"
+ desc = "Unlocks hidden programming in machines. Must be inside a hijacked APC to use."
+ action_icon_state = "pd_emag"
+ unlock_cost = 50 KW
+ cast_cost = 20 KW
+ upgrade_cost = 200 KW
+ requires_area = TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/emagtamper/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ to_chat(user, span_warning("You attempt to tamper with [target]!"))
+ target.emag_act(user)
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/emp
+ name = "Electromagnetic Pulse"
+ desc = "Creates an EMP where you click. Be careful not to use it on yourself!"
+ action_icon_state = "pd_emp"
+ unlock_cost = 50 KW
+ cast_cost = 10 KW
+ upgrade_cost = 200 KW
+ requires_area = TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/emp/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ to_chat(user, span_warning("You attempt to EMP [target]!"))
+ empulse(get_turf(target), 1, 1)
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/overload
+ name = "Overload Machine"
+ desc = "Overloads a machine, causing it to explode."
+ action_icon_state = "pd_overload"
+ unlock_cost = 300 KW
+ cast_cost = 50 KW
+ upgrade_cost = 500 KW
+ requires_area = TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/overload/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ var/obj/machinery/M = target
+ if(!istype(M))
+ to_chat(user, span_warning("That is not a machine."))
+ return FALSE
+ if(M.resistance_flags & NO_MALF_EFFECT)
+ to_chat(user, span_warning("That machine cannot be overloaded."))
+ return FALSE
+ target.audible_message(span_italics(">You hear a loud electrical buzzing sound coming from [target]!"))
+ addtimer(CALLBACK(src, PROC_REF(detonate), M), 5 SECONDS)
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/overload/proc/detonate(obj/machinery/target)
+ if(!QDELETED(target))
+ explosion(get_turf(target), 0, 1, 1, 0)
+ if(!QDELETED(target))
+ qdel(target)
+
+/obj/effect/proc_holder/spell/pulse_demon/remotehijack
+ name = "Remote Hijack"
+ desc = "Remotely hijacks an APC."
+ action_icon_state = "pd_remotehack"
+ unlock_cost = 15 KW
+ cast_cost = 10 KW
+ level_max = 0
+ base_cooldown = 3 SECONDS // you have to wait for the regular hijack time anyway
+
+/obj/effect/proc_holder/spell/pulse_demon/remotehijack/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ var/obj/machinery/power/apc/A = target
+ if(!istype(A))
+ to_chat(user, span_warning("That is not an APC."))
+ return FALSE
+ if(!user.try_hijack_apc(A, TRUE))
+ to_chat(user, span_warning("You cannot hijack that APC right now!"))
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/remotedrain
+ name = "Remote Drain"
+ desc = "Remotely drains a power source."
+ action_icon_state = "pd_remotedrain"
+ unlock_cost = 5 KW
+ cast_cost = 100
+ upgrade_cost = 100 KW
+
+/obj/effect/proc_holder/spell/pulse_demon/remotedrain/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ if(isapc(target))
+ var/drained = user.drain_APC(target, PULSEDEMON_REMOTE_DRAIN_MULTIPLIER)
+ if(drained == PULSEDEMON_SOURCE_DRAIN_INVALID)
+ to_chat(user, span_warning("This APC is being hijacked, you cannot drain from it right now."))
+ else
+ to_chat(user, span_notice("You drain [format_si_suffix(drained)]W from [target]."))
+ else if(istype(target, /obj/machinery/power/smes))
+ var/drained = user.drain_SMES(target, PULSEDEMON_REMOTE_DRAIN_MULTIPLIER)
+ to_chat(user, span_notice("You drain [format_si_suffix(drained)]W from [target]."))
+ else
+ to_chat(user, span_warning("That is not a valid source."))
+ return FALSE
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle
+ base_cooldown = 0
+ cast_cost = 0
+ create_attack_logs = FALSE
+ var/base_message = "see messages you shouldn't!"
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/New(initstate = FALSE)
+ . = ..()
+ do_toggle(initstate, null)
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/create_new_targeting()
+ return new /datum/spell_targeting/self
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/proc/do_toggle(varstate, mob/user)
+ if(action)
+ action.background_icon_state = varstate ? action_background_icon_state : "[action_background_icon_state]_disabled"
+ action.UpdateButtonIcon()
+ if(user)
+ to_chat(user, span_notice("You will [varstate ? "now" : "no longer"] [base_message]"))
+ return varstate
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/do_drain
+ name = "Toggle Draining"
+ desc = "Toggle whether you drain charge from power sources."
+ base_message = "drain charge from power sources."
+ action_icon_state = "pd_toggle_steal"
+ locked = FALSE
+ level_max = 0
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/do_drain/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ user.do_drain = do_toggle(!user.do_drain, user)
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/do_drain/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
+ if(!istype(user))
+ return
+
+ var/amount = text2num(input(user, "Input a value between 1 and [user.max_drain_rate]. 0 will reset it to the maximum.", "Drain Speed Setting"))
+ if(amount == null || amount < 0)
+ to_chat(user, span_warning("Invalid input. Drain speed has not been modified."))
+ return
+
+ if(amount == 0)
+ amount = user.max_drain_rate
+ user.power_drain_rate = amount
+ to_chat(user, span_notice("Drain speed has been set to [format_si_suffix(user.power_drain_rate)]W per second."))
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/can_exit_cable
+ name = "Toggle Self-Sustaining"
+ desc = "Toggle whether you can move outside of cables or power sources."
+ base_message = "move outside of cables."
+ action_icon_state = "pd_toggle_exit"
+ unlock_cost = 100 KW
+ upgrade_cost = 300 KW
+ level_max = 3
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/can_exit_cable/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ if(user.can_exit_cable && !(user.current_cable || user.current_power))
+ to_chat(user, span_warning("Enter a cable or power source first!"))
+ return FALSE
+ user.can_exit_cable = do_toggle(!user.can_exit_cable, user)
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/toggle/can_exit_cable/do_upgrade(mob/living/simple_animal/demon/pulse_demon/user)
+ user.outside_cable_speed = max(initial(user.outside_cable_speed) - spell_level, 1)
+ to_chat(user, span_notice("You have upgraded [initial(name)] to level [spell_level + 1], you will now move faster outside of cables."))
+
+/obj/effect/proc_holder/spell/pulse_demon/cycle_camera
+ name = "Cycle Camera View"
+ desc = "Jump between the cameras in your APC's area. Alt-click to return to the APC."
+ action_icon_state = "pd_camera_view"
+ create_attack_logs = FALSE
+ locked = FALSE
+ cast_cost = 0
+ level_max = 0
+ base_cooldown = 0
+ requires_area = TRUE
+ var/current_camera = 0
+
+/obj/effect/proc_holder/spell/pulse_demon/cycle_camera/create_new_targeting()
+ return new /datum/spell_targeting/self
+
+/obj/effect/proc_holder/spell/pulse_demon/cycle_camera/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
+ if(!istype(user))
+ return
+ current_camera = 0
+
+ if(!isapc(user.current_power))
+ return
+ if(get_area(user.loc) != user.controlling_area)
+ return
+ user.forceMove(user.current_power)
+
+/obj/effect/proc_holder/spell/pulse_demon/cycle_camera/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ if(!length(user.controlling_area.cameras))
+ return FALSE
+
+ if(isapc(user.loc))
+ current_camera = 0
+ else if(istype(user.loc, /obj/machinery/camera))
+ current_camera = (current_camera + 1) % length(user.controlling_area.cameras)
+ if(current_camera == 0)
+ user.forceMove(user.current_power)
+ return TRUE
+
+ if(length(user.controlling_area.cameras) < current_camera)
+ current_camera = 0
+
+ user.forceMove(locateUID(user.controlling_area.cameras[current_camera + 1]))
+ return TRUE
+
+/obj/effect/proc_holder/spell/pulse_demon/open_upgrades
+ name = "Open Upgrade Menu"
+ desc = "Open the upgrades menu. Alt-click for descriptions and costs."
+ action_icon_state = "pd_upgrade"
+ create_attack_logs = FALSE
+ locked = FALSE
+ cast_cost = 0
+ level_max = 0
+ base_cooldown = 0
+ var/static/list/upgrade_icons = list(
+ PD_UPGRADE_HIJACK_SPEED = image(icon = 'icons/obj/engines_and_power/power.dmi', icon_state = "apcemag"),
+ PD_UPGRADE_DRAIN_SPEED = image(icon = 'icons/obj/engines_and_power/power.dmi', icon_state = "ccharger1"),
+ PD_UPGRADE_MAX_HEALTH = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "bluespace_matter_bin"),
+ PD_UPGRADE_HEALTH_REGEN = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "femto_mani"),
+ PD_UPGRADE_HEALTH_LOSS = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "triphasic_scan_module"),
+ PD_UPGRADE_HEALTH_COST = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "quadultra_micro_laser"),
+ PD_UPGRADE_MAX_CHARGE = image(icon = 'icons/obj/stock_parts.dmi', icon_state = "quadratic_capacitor")
+ )
+ var/static/list/upgrade_descs = list(
+ PD_UPGRADE_HIJACK_SPEED = "Decrease the amount of time required to hijack an APC.",
+ PD_UPGRADE_DRAIN_SPEED = "Increase the amount of charge drained from a power source per cycle.",
+ PD_UPGRADE_MAX_HEALTH = "Increase the total amount of health you can have at once.",
+ PD_UPGRADE_HEALTH_REGEN = "Increase the amount of health regenerated when powered per cycle.",
+ PD_UPGRADE_HEALTH_LOSS = "Decrease the amount of health lost when unpowered per cycle.",
+ PD_UPGRADE_HEALTH_COST = "Decrease the amount of power required to regenerate per cycle.",
+ PD_UPGRADE_MAX_CHARGE = "Increase the total amount of charge you can have at once."
+ )
+
+/obj/effect/proc_holder/spell/pulse_demon/open_upgrades/create_new_targeting()
+ return new /datum/spell_targeting/self
+
+/obj/effect/proc_holder/spell/pulse_demon/open_upgrades/proc/calc_cost(mob/living/simple_animal/demon/pulse_demon/user, upgrade)
+ var/cost
+ switch(upgrade)
+ if(PD_UPGRADE_HIJACK_SPEED)
+ if(user.hijack_time <= 1 SECONDS)
+ return -1
+ cost = (100 / (user.hijack_time / (1 SECONDS))) * 20 KW
+ if(PD_UPGRADE_DRAIN_SPEED)
+ if(user.max_drain_rate >= 500 KW)
+ return -1
+ cost = user.max_drain_rate * 15
+ if(PD_UPGRADE_MAX_HEALTH)
+ if(user.maxHealth >= 200)
+ return -1
+ cost = user.maxHealth * 5 KW
+ if(PD_UPGRADE_HEALTH_REGEN)
+ if(user.health_regen_rate >= 100)
+ return -1
+ cost = user.health_regen_rate * 50 KW
+ if(PD_UPGRADE_HEALTH_LOSS)
+ if(user.health_loss_rate <= 1)
+ return -1
+ cost = (100 / user.health_loss_rate) * 20 KW
+ if(PD_UPGRADE_HEALTH_COST)
+ if(user.power_per_regen <= 1)
+ return -1
+ cost = (100 / user.power_per_regen) * 50 KW
+ if(PD_UPGRADE_MAX_CHARGE)
+ cost = user.maxcharge
+ else
+ return -1
+ return round(cost)
+
+/obj/effect/proc_holder/spell/pulse_demon/open_upgrades/proc/get_upgrades(mob/living/simple_animal/demon/pulse_demon/user)
+ var/upgrades = list()
+ for(var/upgrade in upgrade_icons)
+ var/cost = calc_cost(user, upgrade)
+ if(cost == -1)
+ continue
+ upgrades["[upgrade] ([format_si_suffix(cost)]W)"] = upgrade_icons[upgrade]
+ return upgrades
+
+/obj/effect/proc_holder/spell/pulse_demon/open_upgrades/AltClick(mob/living/simple_animal/demon/pulse_demon/user)
+ if(!istype(user))
+ return
+
+ to_chat(user, "Pulse Demon upgrades:")
+ for(var/upgrade in upgrade_descs)
+ var/cost = calc_cost(user, upgrade)
+ to_chat(user, "[upgrade] ([cost == -1 ? "Fully Upgraded" : "[format_si_suffix(cost)]W"]) - [upgrade_descs[upgrade]]")
+
+/obj/effect/proc_holder/spell/pulse_demon/open_upgrades/try_cast_action(mob/living/simple_animal/demon/pulse_demon/user, atom/target)
+ var/upgrades = get_upgrades(user)
+ if(!length(upgrades))
+ to_chat(user, span_warning("You have already fully upgraded everything available!"))
+ return FALSE
+
+ var/raw_choice = show_radial_menu(user, isturf(user.loc) ? user : user.loc, upgrades, radius = 48)
+ if(!raw_choice)
+ return
+ var/choice = splittext(raw_choice, " ")[1]
+
+ var/cost = calc_cost(user, choice)
+ if(cost == -1)
+ return FALSE
+ if(user.charge < cost)
+ to_chat(user, span_warning("You do not have enough charge to purchase this upgrade!"))
+ return FALSE
+
+ user.adjust_charge(-cost)
+ switch(choice)
+ if(PD_UPGRADE_HIJACK_SPEED)
+ user.hijack_time = max(round(user.hijack_time / 1.5), 1 SECONDS)
+ to_chat(user, span_notice("You have upgraded your [choice], it now takes [user.hijack_time / (1 SECONDS)] second\s to hijack APCs."))
+ if(PD_UPGRADE_DRAIN_SPEED)
+ var/old = user.max_drain_rate
+ user.max_drain_rate = min(round(user.max_drain_rate * 1.5), 500 KW)
+ if(user.power_drain_rate == old)
+ user.power_drain_rate = user.max_drain_rate
+ to_chat(user, span_notice("You have upgraded your [choice], you can now drain [format_si_suffix(user.max_drain_rate)]W per cycle."))
+ if(PD_UPGRADE_MAX_HEALTH)
+ user.maxHealth = min(round(user.maxHealth * 1.5), 200)
+ to_chat(user, span_notice("You have upgraded your [choice], your max health is now [user.maxHealth]."))
+ if(PD_UPGRADE_HEALTH_REGEN)
+ user.health_regen_rate = min(round(user.health_regen_rate * 1.5), 100)
+ to_chat(user, span_notice("You have upgraded your [choice], you will now regenerate [user.health_regen_rate] health per cycle when powered."))
+ if(PD_UPGRADE_HEALTH_LOSS)
+ user.health_loss_rate = max(round(user.health_loss_rate / 1.5), 1)
+ to_chat(user, span_notice("You have upgraded your [choice], you will now lose [user.health_loss_rate] health per cycle when unpowered."))
+ if(PD_UPGRADE_HEALTH_COST)
+ user.power_per_regen = max(round(user.power_per_regen / 1.5), 1)
+ to_chat(user, span_notice("You have upgraded your [choice], it now takes [format_si_suffix(user.power_per_regen)]W of power to regenerate health."))
+ to_chat(user, span_notice("Additionally, if you enable draining while on a cable, any excess power that would've been used regenerating will be added to your charge."))
+ if(PD_UPGRADE_MAX_CHARGE)
+ user.maxcharge = round(user.maxcharge * 2)
+ to_chat(user, span_notice("You have upgraded your [choice], you can now store [format_si_suffix(user.maxcharge)]W of charge."))
+ else
+ return FALSE
+ return TRUE
+
+#undef KW
diff --git a/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_event.dm b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_event.dm
new file mode 100644
index 00000000000..df155d29db3
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_event.dm
@@ -0,0 +1,46 @@
+/datum/event/spawn_pulsedemon
+ var/key_of_pulsedemon
+
+/datum/event/spawn_pulsedemon/proc/get_pulsedemon()
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a pulse demon?", ROLE_DEMON, FALSE, source = /mob/living/simple_animal/demon/pulse_demon)
+ if(!length(candidates))
+ message_admins("no candidates were found for the pulse demon event.")
+ kill()
+ return
+
+ var/mob/C = pick(candidates)
+ key_of_pulsedemon = C.key
+
+ if(!key_of_pulsedemon)
+ kill()
+ return
+
+ var/datum/mind/player_mind = new /datum/mind(key_of_pulsedemon)
+ player_mind.active = TRUE
+
+ var/turf/spawn_loc = get_spawn_loc(player_mind.current)
+ var/mob/living/simple_animal/demon/pulse_demon/demon = new(spawn_loc)
+ player_mind.transfer_to(demon)
+ message_admins("[key_name_admin(demon)] has been made into a [initial(demon.name)] by an event.")
+ log_game("[key_name_admin(demon)] was spawned as a [initial(demon.name)] by an event.")
+
+/datum/event/spawn_pulsedemon/proc/get_spawn_loc()
+ var/list/spawn_centers = list()
+ for(var/datum/powernet/P in SSmachines.powernets)
+ for(var/obj/structure/cable/C in P.cables)
+ if(!is_station_level(C.z) || P.avail <= 0)
+ break // skip iterating over this entire powernet, it's not on station or it has zero available power (so it's not suitable)
+
+ var/turf/simulated/floor/F = get_turf(C)
+ // is a floor, not tiled, on station, in maintenance and cable has power?
+ if(istype(F) && (!F.intact && !F.transparent_floor) && istype(get_area(C), /area/maintenance))
+ spawn_centers += F
+ if(!spawn_centers)
+ message_admins("no suitable spawn locations were found for the pulse demon event.")
+ log_debug("no suitable spawn locations were found for the pulse demon event.")
+ kill()
+ return
+ return pick(spawn_centers)
+
+/datum/event/spawn_pulsedemon/start()
+ INVOKE_ASYNC(src, PROC_REF(get_pulsedemon))
diff --git a/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_interactions.dm b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_interactions.dm
new file mode 100644
index 00000000000..0f4669228f1
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_interactions.dm
@@ -0,0 +1,255 @@
+/mob/living/simple_animal/demon/pulse_demon/ClickOn(atom/A, params)
+ if(client?.click_intercept)
+ client.click_intercept.InterceptClickOn(src, params, A)
+ return
+
+ if(next_click > world.time)
+ return
+
+ if(try_modified_click(A, params))
+ return
+
+ // escape out of whatever we've hijacked first
+ if(isapc(A) && !isturf(loc) && (A in hijacked_apcs))
+ A.attack_pulsedemon(src)
+ else if(current_weapon && istype(current_weapon, /obj/item/gun/energy))
+ // ironically, because ion guns override their emp_act, it's perfectly safe to be in one and be emp'd
+ var/obj/item/gun/energy/G = current_weapon
+ // we probably shouldn't be firing from inside a recharger or someone's bag
+ if(iscarbon(G.loc) || isturf(G.loc))
+ G.process_fire(A, src, FALSE)
+ visible_message(span_danger("[G] fires itself at [A]!"), span_danger("You force [G] to fire at [A]!"), span_italics("You hear \a [G.fire_sound_text]!"))
+ changeNext_click(CLICK_CD_RANGE) // I can't actually find what the default gun fire cooldown is, so it's 1 second until someone enlightens me
+ return
+ else if(current_robot)
+ log_admin("[key_name_admin(src)] made [key_name_admin(current_robot)] attack [A]")
+ message_admins(span_notice("[key_name_admin(src)] made [key_name_admin(current_robot)] attack [A]"))
+
+ current_robot.ClickOn(A, params)
+ changeNext_click(0.5 SECONDS)
+ return
+ else if(current_bot)
+ if(A == current_bot)
+ A.attack_ai(src)
+ else
+ current_bot.attack_integrated_pulsedemon(src, A)
+ changeNext_click(0.5 SECONDS)
+ return
+ else if(get_area(A) == controlling_area)
+ A.attack_pulsedemon(src)
+ else
+ ..()
+ changeNext_click(0.1 SECONDS)
+
+// returns TRUE if any [modifier]ClickOn was called
+/mob/living/simple_animal/demon/pulse_demon/proc/try_modified_click(atom/A, params)
+ var/list/modifiers = params2list(params)
+ if(modifiers["middle"])
+ if(modifiers["shift"])
+ MiddleShiftClickOn(A)
+ else
+ MiddleClickOn(A)
+ return TRUE
+ if(modifiers["shift"])
+ ShiftClickOn(A)
+ return TRUE
+ if(modifiers["alt"])
+ AltClickOn(A)
+ return TRUE
+ if(modifiers["ctrl"])
+ CtrlClickOn(A)
+ return TRUE
+ return FALSE
+
+// check area for all of these, then do AI actions
+/mob/living/simple_animal/demon/pulse_demon/MiddleShiftClickOn(atom/A)
+ if(get_area(A) == controlling_area)
+ A.AIMiddleShiftClick(src)
+
+/mob/living/simple_animal/demon/pulse_demon/ShiftClickOn(atom/A)
+ if(get_area(A) == controlling_area)
+ A.AIShiftClick(src)
+ else
+ examinate(A)
+
+/mob/living/simple_animal/demon/pulse_demon/AltClickOn(atom/A)
+ if(get_area(A) == controlling_area)
+ A.AIAltClick(src)
+ else
+ AltClickNoInteract(src, A)
+
+/mob/living/simple_animal/demon/pulse_demon/CtrlClickOn(atom/A)
+ if(get_area(A) == controlling_area)
+ A.AICtrlClick(src)
+
+// for alt-click status tab
+/mob/living/simple_animal/demon/pulse_demon/TurfAdjacent(turf/T)
+ return get_area(T) == controlling_area || ..()
+
+// for overrides in general
+/atom/proc/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ return
+
+/obj/machinery/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ return attack_ai(user)
+
+// ai not allowed to use cams consoles
+/obj/machinery/computer/security/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ return attack_hand(user)
+
+// jump back into our apc
+/obj/machinery/power/apc/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ if(user.loc != src)
+ user.forceMove(src)
+ user.current_power = src
+ user.update_controlling_area()
+ else
+ attack_ai(user)
+
+/mob/living/simple_animal/bot/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ if(user.loc == src)
+ return
+ to_chat(user, span_warning("You are now inside [src]. If it is destroyed, you will be dropped onto the ground, and may die if there is no cable under you."))
+ to_chat(user, span_notice("Leave it by jumping to a hijacked APC."))
+ ejectpai(user)
+ user.forceMove(src)
+ user.current_bot = src
+ hijacked = TRUE
+
+/mob/living/simple_animal/bot/relaymove(mob/user, dir)
+ if(!on)
+ to_chat(user, "[src] isn't turned on!")
+ return
+ if(ispulsedemon(user))
+ var/mob/living/simple_animal/demon/pulse_demon/demon = user
+ if(demon.bot_movedelay <= world.time && dir)
+ Move(get_step(get_turf(src), dir))
+ demon.bot_movedelay = world.time + (BOT_STEP_DELAY * (base_speed - 1)) * ((dir in GLOB.diagonals) ? SQRT_2 : 1)
+
+/obj/machinery/recharger/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ user.forceMove(src)
+ if(!charging)
+ to_chat(user, span_warning("There is no weapon charging. Click again to retry."))
+ return
+ to_chat(user, span_notice("You are now attempting to hijack [src], this will take approximately [user.hijack_time / 10] seconds."))
+ if(!do_after(user, user.hijack_time, FALSE, src))
+ return
+ if(!charging)
+ to_chat(src, span_warning("Failed to hijack [src]"))
+ return
+ to_chat(user, span_notice("You are now inside [charging]. Click on a hijacked APC to return."))
+ user.forceMove(charging)
+ user.current_weapon = charging
+
+/obj/machinery/cell_charger/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ user.forceMove(src)
+ if(!charging)
+ to_chat(user, span_warning("There is no cell charging. Click again to retry."))
+ return
+ to_chat(user, span_notice("You are now attempting to hijack [src], this will take approximately [user.hijack_time / 10] seconds."))
+ if(charging.rigged)
+ to_chat(user, span_notice("You are now inside [charging]. Click on a hijacked APC to return."))
+ user.forceMove(charging)
+ return
+ if(!do_after(user, user.hijack_time, FALSE, src))
+ return
+ if(!charging)
+ to_chat(src, span_warning("Failed to hijack [src]."))
+ return
+ to_chat(user, span_notice("You are now inside [charging]. Click on a hijacked APC to return."))
+ user.forceMove(charging)
+
+/obj/machinery/recharge_station/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ user.forceMove(src)
+ if(!isrobot(occupant))
+ to_chat(user, span_warning("There is no silicon-based occupant inside. Click again to retry."))
+ return
+ to_chat(user, span_notice("You are now attempting to hijack [occupant], this will take approximately [user.hijack_time / 10] seconds."))
+ var/mob/living/silicon/robot/R = occupant
+ if(R in user.hijacked_robots)
+ user.do_hijack_robot(occupant)
+ return
+ to_chat(R, span_userdanger(">ALERT: ELECTRICAL MALEVOLENCE DETECTED, TARGETING SYSTEMS HIJACK IN PROGRESS"))
+ if(!do_after(user, user.hijack_time, FALSE, src))
+ return
+ if(isrobot(occupant))
+ user.do_hijack_robot(occupant)
+ return
+ to_chat(src, span_warning("Failed to hijack [src]."))
+
+/mob/living/simple_animal/demon/pulse_demon/proc/do_hijack_robot(mob/living/silicon/robot/R)
+ to_chat(src, span_notice("You are now inside [R]. Click on a hijacked APC to return."))
+ forceMove(R)
+ current_robot = R
+ if(!(R in hijacked_robots))
+ hijacked_robots += R
+ to_chat(R, span_userdanger(">TARGETING SYSTEMS HIJACKED, REPORT ALL UNWANTED ACTIVITY"))
+
+/obj/machinery/camera/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ if(user.loc != src)
+ user.forceMove(src)
+ to_chat(user, span_notice("You jump towards [src]. Click on a hijacked APC to return."))
+
+// see pulse_demon/say
+/obj/machinery/hologram/holopad/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ if(user.loc != src)
+ user.forceMove(src)
+ to_chat(user, span_notice("You jump towards [src]. You can now communicate via the holopad's speaker. Click on a hijacked APC to return."))
+
+/obj/item/radio/attack_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user)
+ if(user.loc != src)
+ user.forceMove(src)
+ to_chat(user, span_notice("You jump towards [src]. You can now communicate via radio. Click on a hijacked APC to return."))
+ else
+ attack_ai(user)
+
+/mob/living/simple_animal/bot/proc/attack_integrated_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user, atom/A)
+ if(!on)
+ return
+ if(Adjacent(A))
+ UnarmedAttack(A)
+ else
+ RangedAttack(A)
+
+/mob/living/simple_animal/bot/secbot/attack_integrated_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user, atom/A)
+ if(!on)
+ return
+ if(Adjacent(A))
+ UnarmedAttack(A)
+ else if(iscarbon(A))
+ speak("Level 10 infraction alert!")
+ playsound(loc, pick('sound/voice/bcriminal.ogg', 'sound/voice/bjustice.ogg', 'sound/voice/bfreeze.ogg'), 50)
+ visible_message("[src] points at [A.name]!")
+
+/mob/living/simple_animal/bot/floorbot/attack_integrated_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user, atom/A)
+ if(!on)
+ return
+ if(isfloorturf(A) && Adjacent(A))
+ var/turf/simulated/floor/F = A
+ // there was originally a 1% chance to break to lattice, but that doesn't help a pulse demon, so I don't see the point
+ F.break_tile_to_plating()
+ audible_message(span_danger("[src] makes an excited booping sound."))
+
+/mob/living/simple_animal/bot/cleanbot/attack_integrated_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user, atom/A)
+ if(!on)
+ return
+ if(isfloorturf(A) && Adjacent(A))
+ var/turf/simulated/floor/F = A
+ if(prob(50))
+ F.MakeSlippery(TURF_WET_WATER)
+ if(prob(50))
+ visible_message(span_warning("Something flies out of [src]! It seems to be acting oddly."))
+ if(!(locate(/obj/effect/decal/cleanable/blood/gibs) in F))
+ new /obj/effect/decal/cleanable/blood/gibs(F)
+ playsound(F, 'sound/effects/blobattack.ogg', 40, TRUE)
+
+/mob/living/simple_animal/bot/mulebot/attack_integrated_pulsedemon(mob/living/simple_animal/demon/pulse_demon/user, atom/A)
+ if(!on)
+ return
+ if(istype(A) && Adjacent(A) && ismovable(A))
+ to_chat(user, span_notice("You try to load [A] onto [src]."))
+ load(A)
+ return
+ if(load)
+ to_chat(user, span_notice("You unload [load]."))
+ unload(0)
diff --git a/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_objectives.dm b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_objectives.dm
new file mode 100644
index 00000000000..64d9b8af4e0
--- /dev/null
+++ b/code/game/gamemodes/miniantags/demons/pulse_demon/pulse_demon_objectives.dm
@@ -0,0 +1,57 @@
+/datum/objective/pulse_demon/infest
+ name = "Hijack APCs"
+ /// Amount of APCs we need to hijack, can be 15, 20, or 25
+ var/amount = 0
+
+/datum/objective/pulse_demon/infest/New()
+ . = ..()
+ amount = rand(3, 5) * 5
+ explanation_text = "Hijack [amount] APCs."
+
+/datum/objective/pulse_demon/infest/check_completion()
+ if(..())
+ return TRUE
+ var/hijacked = 0
+ for(var/datum/mind/M in get_owners())
+ if(!ispulsedemon(M.current) || QDELETED(M.current))
+ continue
+ var/mob/living/simple_animal/demon/pulse_demon/demon = M.current
+ hijacked += length(demon.hijacked_apcs)
+ return hijacked >= amount
+
+/datum/objective/pulse_demon/drain
+ name = "Drain Power"
+ /// Amount of power we need to drain, ranges from 500 KW to 5 MW
+ var/amount = 0
+
+/datum/objective/pulse_demon/drain/New()
+ . = ..()
+ amount = rand(1, 10) * 500000
+ explanation_text = "Drain [format_si_suffix(amount)]W of power."
+
+/datum/objective/pulse_demon/drain/check_completion()
+ if(..())
+ return TRUE
+ var/drained = 0
+ for(var/datum/mind/M in get_owners())
+ if(!ispulsedemon(M.current) || QDELETED(M.current))
+ continue
+ var/mob/living/simple_animal/demon/pulse_demon/demon = M.current
+ drained += demon.charge_drained
+ return drained >= amount
+
+// Requires 1 APC to be hacked and not destroyed to complete
+/datum/objective/pulse_demon/tamper
+ name = "Tamper Machinery"
+ explanation_text = "Cause mischief amongst the machines in rooms with APCs you've hijacked, and defend yourself from anyone trying to stop you."
+
+/datum/objective/pulse_demon/tamper/check_completion()
+ if(..())
+ return TRUE
+ for(var/datum/mind/M in get_owners())
+ if(!ispulsedemon(M.current) || QDELETED(M.current))
+ continue
+ var/mob/living/simple_animal/demon/pulse_demon/demon = M.current
+ if(!length(demon.hijacked_apcs) || !M.active || demon.stat == DEAD)
+ return FALSE
+ return TRUE
diff --git a/code/game/gamemodes/miniantags/guardian/types/lightning.dm b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
index cf353ba769c..f0ead6c6f42 100644
--- a/code/game/gamemodes/miniantags/guardian/types/lightning.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/lightning.dm
@@ -23,8 +23,8 @@
. = ..()
if(!summoner)
return
- if(!(NO_SHOCK in summoner.mutations))
- summoner.mutations.Add(NO_SHOCK)
+ if(!(HAS_TRAIT(summoner, TRAIT_SHOCKIMMUNE)))
+ ADD_TRAIT(summoner, TRAIT_SHOCKIMMUNE, "guardian")
/mob/living/simple_animal/hostile/guardian/beam/New()
..()
@@ -105,6 +105,6 @@
. = 1
/mob/living/simple_animal/hostile/guardian/beam/death(gibbed)
- if(summoner && (NO_SHOCK in summoner.mutations))
- summoner.mutations.Remove(NO_SHOCK)
+ if(HAS_TRAIT(summoner, TRAIT_SHOCKIMMUNE))
+ REMOVE_TRAIT(summoner, TRAIT_SHOCKIMMUNE, "guardian")
return ..()
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index dedbd45392d..bdc982b9dfa 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -525,6 +525,14 @@
limit = 3
cost = 1 //Unless you blackout the station this ain't going to do much, wizard doesn't get NV, still dies easily to a group of 2 and it doesn't eat bodies.
+/datum/spellbook_entry/item/pulsedemonbottle
+ name = "Living Lightbulb"
+ desc = "A magically sealed lightbulb confining some manner of electricity based creature. Beware, these creatures are indiscriminate in their shocking antics, and you yourself may become a victim."
+ item_path = /obj/item/antag_spawner/pulse_demon
+ category = "Summons"
+ limit = 3
+ cost = 1 //Needs station power to live. Also can kill the wizard trivially in maints (get shock protection).
+
/datum/spellbook_entry/item/mayhembottle
name = "Mayhem in a Bottle"
desc = "A magically infused bottle of blood, the scent of which will drive anyone nearby into a murderous frenzy."
diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm
index 8fcfafb0c40..77dcfb67096 100644
--- a/code/game/machinery/alarm.dm
+++ b/code/game/machinery/alarm.dm
@@ -795,7 +795,7 @@
return TRUE
if(user.can_admin_interact())
return TRUE
- else if(isAI(user) || (isrobot(user) || emagged) && !iscogscarab(user))
+ else if(isAI(user) || (isrobot(user) || emagged || user.has_unlimited_silicon_privilege) && !iscogscarab(user))
return TRUE
else
return !locked
diff --git a/code/game/machinery/atmoalter/meter.dm b/code/game/machinery/atmoalter/meter.dm
index f39e1d51ba5..da0c9fb5a24 100644
--- a/code/game/machinery/atmoalter/meter.dm
+++ b/code/game/machinery/atmoalter/meter.dm
@@ -1,10 +1,11 @@
-/obj/machinery/meter
+/obj/machinery/atmospherics/meter
name = "gas flow meter"
desc = "It measures something."
icon = 'icons/obj/pipes_and_stuff/atmospherics/meter.dmi'
icon_state = "meterX"
- layer = GAS_PUMP_LAYER
+ layer = GAS_PIPE_VISIBLE_LAYER + GAS_PUMP_OFFSET
+ layer_offset = GAS_PUMP_OFFSET
var/obj/machinery/atmospherics/pipe/target = null
anchored = TRUE
@@ -18,19 +19,19 @@
idle_power_usage = 2
active_power_usage = 5
-/obj/machinery/meter/Initialize(mapload)
+/obj/machinery/atmospherics/meter/Initialize(mapload)
. = ..(mapload)
SSair.atmos_machinery += src
target = locate(/obj/machinery/atmospherics/pipe) in loc
if(id && !id_tag)//i'm not dealing with further merge conflicts, fuck it
id_tag = id
-/obj/machinery/meter/Destroy()
+/obj/machinery/atmospherics/meter/Destroy()
SSair.atmos_machinery -= src
target = null
return ..()
-/obj/machinery/meter/process_atmos()
+/obj/machinery/atmospherics/meter/process_atmos()
if(!target)
icon_state = "meterX"
return 0
@@ -75,7 +76,7 @@
)
radio_connection.post_signal(src, signal)
-/obj/machinery/meter/proc/status()
+/obj/machinery/atmospherics/meter/proc/status()
var/t = ""
if(target)
var/datum/gas_mixture/environment = target.return_air()
@@ -87,7 +88,7 @@
t += "The connect error light is blinking."
return t
-/obj/machinery/meter/examine(mob/user)
+/obj/machinery/atmospherics/meter/examine(mob/user)
. = ..()
if(get_dist(user, src) > 3 && !(istype(user, /mob/living/silicon/ai) || istype(user, /mob/dead)))
. += span_boldnotice("You are too far away to read it.")
@@ -104,14 +105,14 @@
else
. += span_warning("The connect error light is blinking.")
-/obj/machinery/meter/Click()
+/obj/machinery/atmospherics/meter/Click()
if(istype(usr, /mob/living/silicon/ai)) // ghosts can call ..() for examine
usr.examinate(src)
return 1
return ..()
-/obj/machinery/meter/wrench_act(mob/user, obj/item/I)
+/obj/machinery/atmospherics/meter/wrench_act(mob/user, obj/item/I)
. = TRUE
playsound(loc, I.usesound, 50, 1)
to_chat(user, span_notice("You begin to unfasten [src]..."))
@@ -122,28 +123,28 @@
"You hear ratchet.")
deconstruct(TRUE)
-/obj/machinery/meter/deconstruct(disassembled = TRUE)
+/obj/machinery/atmospherics/meter/deconstruct(disassembled = TRUE)
if(!(flags & NODECONSTRUCT))
new /obj/item/pipe_meter(loc)
qdel(src)
-/obj/machinery/meter/singularity_pull(S, current_size)
+/obj/machinery/atmospherics/meter/singularity_pull(S, current_size)
..()
if(current_size >= STAGE_FIVE)
deconstruct()
// TURF METER - REPORTS A TILE'S AIR CONTENTS
-/obj/machinery/meter/turf/New()
+/obj/machinery/atmospherics/meter/turf/New()
..()
target = loc
return 1
-/obj/machinery/meter/turf/Initialize()
+/obj/machinery/atmospherics/meter/turf/Initialize()
if(!target)
target = loc
..()
-/obj/machinery/meter/turf/attackby(var/obj/item/W as obj, var/mob/user as mob, params)
+/obj/machinery/atmospherics/meter/turf/attackby(var/obj/item/W as obj, var/mob/user as mob, params)
return
diff --git a/code/game/machinery/computer/robot.dm b/code/game/machinery/computer/robot.dm
index 68fe6cf2436..e253ebc61f2 100644
--- a/code/game/machinery/computer/robot.dm
+++ b/code/game/machinery/computer/robot.dm
@@ -81,6 +81,22 @@
return FALSE
return TRUE
+/obj/machinery/computer/robotics/proc/can_detonate_any(mob/user, telluserwhy = FALSE)
+ if(ispulsedemon(user))
+ if(telluserwhy)
+ to_chat(user, "The console's authentication circuits reject your control!")
+ return FALSE
+ return TRUE
+
+/// Checks if a user can detonate a specific cyborg, does a can_control check first.
+/obj/machinery/computer/robotics/proc/can_detonate(mob/user, mob/living/silicon/robot/R, telluserwhy = FALSE)
+ if(!can_control(user, R, telluserwhy))
+ return FALSE
+ if(!can_detonate_any(user, telluserwhy))
+ return FALSE
+ return TRUE
+
+
/**
* Check if the user is the right kind of entity to be able to hack borgs
@@ -178,6 +194,8 @@
to_chat(usr, span_danger("Access Denied (silicon detected)"))
playsound(src, pick('sound/machines/button.ogg', 'sound/machines/button_alternate.ogg', 'sound/machines/button_meloboom.ogg'), 20)
return
+ if(!can_detonate_any(usr, TRUE))
+ return
if(safety)
to_chat(usr, span_danger("Self-destruct aborted - safety active"))
return
@@ -196,7 +214,7 @@
. = TRUE
if("killbot") // destroys one specific cyborg
var/mob/living/silicon/robot/R = locateUID(params["uid"])
- if(!can_control(usr, R, TRUE))
+ if(!can_detonate(usr, R, TRUE))
return
if(R.mind && R.mind.special_role && R.emagged)
to_chat(R, span_userdanger("Extreme danger! Termination codes detected. Scrambling security codes and automatic AI unlink triggered."))
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 2a4e3aebee2..c30005f7a98 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -796,6 +796,8 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/proc/ai_control_check(mob/user)
if(!issilicon(user))
return TRUE
+ if(ispulsedemon(user))
+ return TRUE
if(emagged || HAS_TRAIT(src, TRAIT_CMAGGED))
to_chat(user, span_warning("Unable to interface: Internal error."))
return FALSE
@@ -813,7 +815,7 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/ui_act(action, params)
if(..())
return
- if(!issilicon(usr) && !usr.can_admin_interact())
+ if(!issilicon(usr) && !usr.can_admin_interact() && !usr.has_unlimited_silicon_privilege)
to_chat(usr, span_warning("Access denied. Only silicons may use this interface."))
return
if(!ai_control_check(usr))
diff --git a/code/game/machinery/pipe/construction.dm b/code/game/machinery/pipe/construction.dm
index ccbd0a8cfc3..b4fe56281d3 100644
--- a/code/game/machinery/pipe/construction.dm
+++ b/code/game/machinery/pipe/construction.dm
@@ -527,7 +527,7 @@
if(!locate(/obj/machinery/atmospherics/pipe, src.loc))
to_chat(user, span_warning("You need to fasten it to a pipe"))
return 1
- var/obj/machinery/meter/meter = new(loc)
+ var/obj/machinery/atmospherics/meter/meter = new(loc)
meter.add_fingerprint(user)
playsound(src.loc, W.usesound, 50, 1)
to_chat(user, span_notice("You have fastened the meter to the pipe."))
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index 62c29263296..7fd72aa7e93 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -72,7 +72,7 @@
for(var/mob/M as mob in src) // makes sure that simple mobs don't get stuck inside a sleeper when they resist out of occupant's grasp
if(M == occupant)
continue
- else
+ else if(!ispulsedemon(M))
M.forceMove(src.loc)
return 1
@@ -108,6 +108,8 @@
return FALSE
/obj/machinery/recharge_station/relaymove(mob/user as mob)
+ if(ispulsedemon(user))
+ return
if(user.stat)
return
src.go_out()
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index af4ec6e9e80..c3e98b9f034 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -1557,7 +1557,7 @@
products = list(/obj/item/clothing/under/rank/chief_engineer = 4,/obj/item/clothing/under/rank/engineer = 4,/obj/item/clothing/shoes/workboots = 4,/obj/item/clothing/head/hardhat = 4,
/obj/item/storage/belt/utility = 4,/obj/item/clothing/glasses/meson = 4,/obj/item/clothing/gloves/color/yellow = 4, /obj/item/screwdriver = 12,
/obj/item/crowbar = 12,/obj/item/wirecutters = 12,/obj/item/multitool = 12,/obj/item/wrench = 12,/obj/item/t_scanner = 12,
- /obj/item/stack/cable_coil/heavyduty = 8, /obj/item/stock_parts/cell = 8, /obj/item/weldingtool = 8,/obj/item/clothing/head/welding = 8,
+ /obj/item/stack/cable_coil = 8, /obj/item/stock_parts/cell = 8, /obj/item/weldingtool = 8,/obj/item/clothing/head/welding = 8,
/obj/item/light/tube = 10,/obj/item/clothing/suit/fire = 4, /obj/item/stock_parts/scanning_module = 5,/obj/item/stock_parts/micro_laser = 5,
/obj/item/stock_parts/matter_bin = 5,/obj/item/stock_parts/manipulator = 5)
refill_canister = /obj/item/vending_refill/engineering
diff --git a/code/game/objects/effects/decals/Cleanable/fuel.dm b/code/game/objects/effects/decals/Cleanable/fuel.dm
index cc728919079..7d0af07d0e3 100644
--- a/code/game/objects/effects/decals/Cleanable/fuel.dm
+++ b/code/game/objects/effects/decals/Cleanable/fuel.dm
@@ -2,7 +2,7 @@
//Liquid fuel is used for things that used to rely on volatile fuels or plasma being contained to a couple tiles.
icon = 'icons/effects/effects.dmi'
icon_state = "fuel"
- layer = LATTICE_LAYER
+ layer = ABOVE_NORMAL_TURF_LAYER
anchored = TRUE
var/amount = 1 //Basically moles.
diff --git a/code/game/objects/effects/effect_system/effects_sparks.dm b/code/game/objects/effects/effect_system/effects_sparks.dm
index e16d7bbfee6..829bf2d77f7 100644
--- a/code/game/objects/effects/effect_system/effects_sparks.dm
+++ b/code/game/objects/effects/effect_system/effects_sparks.dm
@@ -13,7 +13,7 @@
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(n, c, source)
sparks.autocleanup = TRUE
- sparks.start()
+ INVOKE_ASYNC(sparks, TYPE_PROC_REF(/datum/effect_system, start))
/obj/effect/particle_effect/sparks
name = "sparks"
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index ab8ab9a6ddc..941db8c8dc7 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -109,7 +109,10 @@ REAGENT SCANNER
in_turf_object.invisibility = 0
in_turf_object.alpha = 128
in_turf_object.drain_act_protected = TRUE
+ if(in_turf_object.layer < TURF_LAYER)
+ in_turf_object.layer += TRAY_SCAN_LAYER_OFFSET
spawn(pulse_duration)
+ in_turf_object.plane = GAME_PLANE
if(in_turf_object)
var/turf/objects_turf = in_turf_object.loc
if(objects_turf && objects_turf.intact)
diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm
index 3ab0d3ab68f..e4caba1c520 100644
--- a/code/game/objects/structures.dm
+++ b/code/game/objects/structures.dm
@@ -4,6 +4,8 @@
max_integrity = 300
pull_push_speed_modifier = 1.2
var/climbable
+ /// Determines if a structure adds the TRAIT_TURF_COVERED to its turf.
+ var/creates_cover = FALSE
var/mob/living/climber
var/broken = FALSE
/// Amount of SSobj ticks (Roughly 2 seconds) that a extinguished structure has been lit up
@@ -24,6 +26,8 @@
/obj/structure/Initialize(mapload)
if(!armor)
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50)
+ if(creates_cover && isturf(loc))
+ ADD_TRAIT(loc, TRAIT_TURF_COVERED, UNIQUE_TRAIT_SOURCE(src))
return ..()
/obj/structure/Destroy()
@@ -33,10 +37,25 @@
var/turf/T = get_turf(src)
spawn(0)
queue_smooth_neighbors(T)
+ if(creates_cover && isturf(loc))
+ REMOVE_TRAIT(loc, TRAIT_TURF_COVERED, UNIQUE_TRAIT_SOURCE(src))
if(isprocessing)
STOP_PROCESSING(SSobj, src)
return ..()
+/obj/structure/Move()
+ var/atom/old = loc
+ if(!..())
+ return FALSE
+
+ if(creates_cover)
+ if(isturf(old))
+ REMOVE_TRAIT(old, TRAIT_TURF_COVERED, UNIQUE_TRAIT_SOURCE(src))
+ if(isturf(loc))
+ ADD_TRAIT(loc, TRAIT_TURF_COVERED, UNIQUE_TRAIT_SOURCE(src))
+ return TRUE
+
+
/obj/structure/has_prints()
return TRUE
diff --git a/code/game/objects/structures/aliens.dm b/code/game/objects/structures/aliens.dm
index 13e6e6dc84e..7976fac4099 100644
--- a/code/game/objects/structures/aliens.dm
+++ b/code/game/objects/structures/aliens.dm
@@ -323,12 +323,13 @@
desc = "A thick resin surface covers the floor."
anchored = TRUE
density = FALSE
- layer = TURF_LAYER
+ layer = ABOVE_ICYOVERLAY_LAYER
plane = FLOOR_PLANE
icon_state = "weeds"
max_integrity = 15
var/obj/structure/alien/weeds/node/linked_node = null
var/static/list/weedImageCache
+ creates_cover = TRUE
/obj/structure/alien/weeds/New(pos, node)
diff --git a/code/game/objects/structures/safe.dm b/code/game/objects/structures/safe.dm
index 6a5ddebc800..06cd6627178 100644
--- a/code/game/objects/structures/safe.dm
+++ b/code/game/objects/structures/safe.dm
@@ -364,7 +364,8 @@ GLOBAL_LIST_EMPTY(safes)
icon_state = "floorsafe"
density = FALSE
level = 1 //Under the floor
- layer = LOW_OBJ_LAYER
+ plane = FLOOR_PLANE
+ layer = ABOVE_PLATING_LAYER
drill_x_offset = -1
drill_y_offset = 20
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 00d760c46cd..6086619458e 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -26,6 +26,7 @@
integrity_failure = 30
smooth = SMOOTH_TRUE
canSmoothWith = list(/obj/structure/table, /obj/structure/table/reinforced)
+ creates_cover = TRUE
var/frame = /obj/structure/table_frame
var/framestack = /obj/item/stack/rods
var/buildstack = /obj/item/stack/sheet/metal
@@ -368,6 +369,10 @@
T.flip(direction)
update_icon()
+ creates_cover = FALSE
+ if(isturf(loc))
+ REMOVE_TRAIT(loc, TRAIT_TURF_COVERED, UNIQUE_TRAIT_SOURCE(src))
+
return 1
/obj/structure/table/proc/unflip()
@@ -395,6 +400,10 @@
dir = initial(dir)
update_icon()
+ creates_cover = TRUE
+ if(isturf(loc))
+ ADD_TRAIT(loc, TRAIT_TURF_COVERED, UNIQUE_TRAIT_SOURCE(src))
+
return 1
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index 32a6e4e6e7e..ab709cbf816 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -155,10 +155,12 @@ GLOBAL_LIST_INIT(icons_to_ignore_at_floor_init, list("damaged1","damaged2","dama
var/old_plating = icon_plating
var/old_dir = dir
var/old_regular_dir = floor_regular_dir
+ var/old_transparent_floor = transparent_floor
var/turf/simulated/floor/W = ..()
var/obj/machinery/atmospherics/R
+ var/obj/machinery/power/terminal/term
if(keep_icon)
W.icon_regular_floor = old_icon
@@ -166,9 +168,11 @@ GLOBAL_LIST_INIT(icons_to_ignore_at_floor_init, list("damaged1","damaged2","dama
if(W.keep_dir)
W.floor_regular_dir = old_regular_dir
W.dir = old_dir
- if(W.transparent_floor)
+ if(W.transparent_floor != old_transparent_floor)
for(R in W)
R.update_icon()
+ for(term in W)
+ term.update_icon()
for(R in W)
R.update_underlays()
W.update_icon()
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm
index 729ecba5c20..b659905a818 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/simulated/floor/plating.dm
@@ -13,6 +13,7 @@
barefootstep = FOOTSTEP_HARD_BAREFOOT
clawfootstep = FOOTSTEP_HARD_CLAW
heavyfootstep = FOOTSTEP_GENERIC_HEAVY
+ real_layer = PLATING_LAYER
/turf/simulated/floor/plating/Initialize(mapload)
. = ..()
diff --git a/code/game/turfs/simulated/floor/transparent.dm b/code/game/turfs/simulated/floor/transparent.dm
index 4d22307714b..13ff27be9cf 100644
--- a/code/game/turfs/simulated/floor/transparent.dm
+++ b/code/game/turfs/simulated/floor/transparent.dm
@@ -8,7 +8,6 @@
canSmoothWith = list(/turf/simulated/floor/transparent/glass, /turf/simulated/floor/transparent/glass/reinforced, /turf/simulated/floor/transparent/glass/plasma, /turf/simulated/floor/transparent/glass/reinforced/plasma)
light_power = 0.25
light_range = 2
- layer = TRANSPARENT_TURF_LAYER
keep_dir = FALSE
intact = FALSE
transparent_floor = TRUE
diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm
index 123670f72c1..3786a9478d0 100644
--- a/code/game/turfs/simulated/minerals.dm
+++ b/code/game/turfs/simulated/minerals.dm
@@ -174,7 +174,8 @@
canSmoothWith = list(/turf/simulated/mineral, /obj/structure/falsewall/mineral_ancient)
mine_time = 6 SECONDS
color = COLOR_ANCIENT_ROCK
- layer = TURF_LAYER
+ layer = MAP_EDITOR_TURF_LAYER
+ real_layer = TURF_LAYER
should_reset_color = FALSE
mineralAmt = 2
mineralType = /obj/item/stack/ore/glass/basalt/ancient
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 435db857860..784221bcb67 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -8,6 +8,9 @@
var/slowdown = 0 //negative for faster, positive for slower
var/transparent_floor = FALSE //used to check if pipes should be visible under the turf or not
+ var/real_layer = TURF_LAYER
+ layer = MAP_EDITOR_TURF_LAYER
+
///Properties for open tiles (/floor)
/// All the gas vars, on the turf, are meant to be utilized for initializing a gas datum and setting its first gas values; the turf vars are never further modified at runtime; it is never directly used for calculations by the atmospherics system.
var/oxygen = 0
@@ -52,6 +55,10 @@
stack_trace("Warning: [src]([type]) initialized multiple times!")
initialized = TRUE
+ if(layer == MAP_EDITOR_TURF_LAYER)
+ layer = real_layer
+
+
// by default, vis_contents is inherited from the turf that was here before
vis_contents.Cut()
diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm
index 17a2f28fad9..2275678617c 100644
--- a/code/modules/antagonists/_common/antag_spawner.dm
+++ b/code/modules/antagonists/_common/antag_spawner.dm
@@ -303,3 +303,73 @@
to_chat(M, "Objective #[1]: [KillDaWiz.explanation_text]")
to_chat(M, "Objective #[2]: [KillDaCrew.explanation_text]")
M << 'sound/magic/mutate.ogg'
+
+
+///////////Pulse Demon
+
+/obj/item/antag_spawner/pulse_demon
+ name = "living lightbulb"
+ desc = "A magically sealed lightbulb confining some manner of electricity based creature."
+ icon = 'icons/obj/lighting.dmi'
+ icon_state = "lbulb"
+ var/shatter_msg = "You shatter the bulb, no turning back now!"
+ var/veil_msg = "The creature sparks energetically and zips away..."
+ var/objective_verb = "Electrocute"
+ var/mob/living/demon_type = /mob/living/simple_animal/demon/pulse_demon
+
+/obj/item/antag_spawner/pulse_demon/attack_self(mob/user)
+ if(level_blocks_magic(user.z))
+ to_chat(user, "You should probably wait until you reach the station.")
+ return
+
+ var/turf/T = get_turf(src)
+ var/obj/structure/cable/wire = locate() in T
+ if(!wire || wire.avail() <= 0)
+ to_chat(user, "This is not a suitable place, the creature would die here. Find a powered cable to release it onto.")
+ return
+
+ if(used)
+ to_chat(user, "This bulb already has a broken seal.")
+ return
+
+ used = TRUE
+ to_chat(user, "You break the seal on the bulb, waiting for the creature to spark to life...")
+
+ var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a pulse demon summoned by [user.real_name]?", ROLE_DEMON, TRUE, 10 SECONDS, source = demon_type)
+
+ if(!length(candidates))
+ used = FALSE
+ to_chat(user, "The creature does not come to life. Perhaps you should try again later.")
+ return
+
+ var/mob/C = pick(candidates)
+ spawn_antag(C, T, user)
+ to_chat(user, shatter_msg)
+ to_chat(user, veil_msg)
+ playsound(T, 'sound/effects/glassbr1.ogg', 100, TRUE)
+ qdel(src)
+
+/obj/item/antag_spawner/pulse_demon/spawn_antag(client/C, turf/T, mob/user)
+ var/datum/mind/player_mind = new /datum/mind(C.key)
+ player_mind.active = TRUE
+
+ var/mob/living/simple_animal/demon/pulse_demon/demon = new(T)
+ player_mind.transfer_to(demon)
+ player_mind.assigned_role = SPECIAL_ROLE_DEMON
+ player_mind.special_role = SPECIAL_ROLE_DEMON
+ var/i = demon.give_objectives()
+
+ var/datum/objective/assassinate/kill_wiz = new /datum/objective/assassinate
+ kill_wiz.owner = demon.mind
+ kill_wiz.target = user.mind
+ kill_wiz.explanation_text = "[objective_verb] [user.real_name], the one who was foolish enough to free you."
+ demon.mind.objectives += kill_wiz
+
+ var/datum/objective/kill_crew = new /datum/objective
+ kill_crew.owner = demon.mind
+ kill_crew.explanation_text = "[objective_verb] everyone else while you're at it."
+ kill_crew.completed = TRUE
+ demon.mind.objectives += kill_crew
+
+ to_chat(demon, "Objective #[i++]: [kill_wiz.explanation_text]")
+ to_chat(demon, "Objective #[i++]: [kill_crew.explanation_text]")
diff --git a/code/modules/events/event_container.dm b/code/modules/events/event_container.dm
index 1be7a276cc1..e6dce3240bb 100644
--- a/code/modules/events/event_container.dm
+++ b/code/modules/events/event_container.dm
@@ -222,6 +222,7 @@ GLOBAL_LIST_EMPTY(event_last_fired)
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Ревенант", /datum/event/revenant, 150),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Спавн свармеров", /datum/event/spawn_swarmer, 0, is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Спавн морфа", /datum/event/spawn_morph, 40, list(ASSIGNMENT_SECURITY = 10), is_one_shot = TRUE),
+ new /datum/event_meta(EVENT_LEVEL_MODERATE, "Pulse Demon Infiltration", /datum/event/spawn_pulsedemon, 150, list(ASSIGNMENT_ENGINEER = 10), is_one_shot = TRUE),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Вспышка болезни", /datum/event/disease_outbreak, 0, list(ASSIGNMENT_MEDICAL = 150), TRUE),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Хедкрабы", /datum/event/headcrabs, 0, list(ASSIGNMENT_SECURITY = 20)),
new /datum/event_meta(EVENT_LEVEL_MODERATE, "Сбой работы дверей", /datum/event/door_runtime, 50, list(ASSIGNMENT_ENGINEER = 25, ASSIGNMENT_AI = 150), TRUE),
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index ade4459a349..c6496d2ec81 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -154,7 +154,7 @@
SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage)
if(status_flags & GODMODE) //godmode
return FALSE
- if(NO_SHOCK in mutations) //shockproof
+ if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) //shockproof
return FALSE
if(tesla_shock && tesla_ignore)
return FALSE
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 0f9b4e41c97..308c8179a73 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -59,7 +59,7 @@
SEND_SIGNAL(src, COMSIG_LIVING_ELECTROCUTE_ACT, shock_damage)
if(status_flags & GODMODE) //godmode
return FALSE
- if(NO_SHOCK in mutations) //shockproof
+ if(HAS_TRAIT(src, TRAIT_SHOCKIMMUNE)) //shockproof
return FALSE
if(tesla_shock && tesla_ignore)
return FALSE
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index a544706b0c7..8244ae91f15 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -800,6 +800,15 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
//This will mean that removing and replacing a power cell will repair the mount, but I don't care at this point. ~Z
C.brute_damage = 0
C.electronics_damage = 0
+ var/been_hijacked = FALSE
+ for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell)
+ if(!been_hijacked)
+ demon.do_hijack_robot(src)
+ been_hijacked = TRUE
+ else
+ demon.exit_to_turf()
+ if(been_hijacked)
+ cell.rigged = FALSE
diag_hud_set_borgcell()
else if(istype(W, /obj/item/encryptionkey/) && opened)
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index 45a962b698a..ff6a4f2cece 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -50,6 +50,8 @@
var/hacked = FALSE
/// Custom text returned to a silicon upon hacking a bot.
var/text_hack = ""
+ /// Being hijacked by a pulse demon?
+ var/hijacked = FALSE
/// Text shown when resetting a bots hacked status to normal.
var/text_dehack = ""
/// Shown when a silicon tries to reset a bot emagged with the emag item, which cannot be reset.
@@ -159,6 +161,8 @@
return "Autonomous"
else if(!on)
return span_bad("Inactive")
+ else if(hijacked)
+ return "ERROR"
else if(!mode)
return span_good("Idle")
else
@@ -236,6 +240,7 @@
. = ..()
if(!on)
. = FALSE
+
canmove = .
@@ -331,6 +336,9 @@
if(!on)
return
+ if(hijacked)
+ return
+
switch(mode) //High-priority overrides are processed first. Bots can do nothing else while under direct command.
if(BOT_RESPONDING) //Called by the AI.
@@ -404,7 +412,7 @@
to_chat(user, span_warning("A [paicard] is already inserted!"))
else if((allow_pai || card.pai?.syndipai) && !key)
- if(!locked && !open)
+ if(!locked && !open && !hijacked)
if(card.pai && card.pai.mind)
if(!card.pai.ckey || jobban_isbanned(card.pai, ROLE_SENTIENT))
to_chat(user, span_warning("[W] is unable to establish a connection to [src]."))
@@ -861,7 +869,7 @@ Pass the desired type path itself, declaring a temporary var beforehand is not r
if(signal.data["active"] != src)
return
- if(emagged == 2 || remote_disabled) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
+ if(emagged == 2 || remote_disabled || hijacked) //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands. //Emagged bots do not respect anyone's authority! Bots with their remote controls off cannot get commands.
return
if(client)
@@ -1110,9 +1118,11 @@ Pass the desired type path itself, declaring a temporary var beforehand is not r
if(emagged == 2) //An emagged bot cannot be controlled by humans, silicons can if one hacked it.
if(!hacked) //Manually emagged by a human - access denied to all.
return TRUE
- else if(!issilicon(user)) //Bot is hacked, so only silicons are allowed access.
+ else if(!(issilicon(user) || ispulsedemon(user))) //Bot is hacked, so only silicons are allowed access.
return TRUE
- if(locked && !issilicon(user))
+ if(hijacked && !ispulsedemon(user))
+ return FALSE
+ if(locked && !(issilicon(user) || ispulsedemon(user)))
return TRUE
return FALSE
@@ -1230,6 +1240,9 @@ Pass the desired type path itself, declaring a temporary var beforehand is not r
/mob/living/simple_animal/bot/get_access()
+ if(hijacked)
+ return get_all_accesses()
+
. = ..()
if(access_card)
. |= access_card.GetAccess()
diff --git a/code/modules/mob/living/simple_animal/bot/griefsky.dm b/code/modules/mob/living/simple_animal/bot/griefsky.dm
index b18e24d39f4..72d38ef2040 100644
--- a/code/modules/mob/living/simple_animal/bot/griefsky.dm
+++ b/code/modules/mob/living/simple_animal/bot/griefsky.dm
@@ -145,6 +145,9 @@
if(!on)
return
+ if(hijacked)
+ return // is there a good reason this override doesn't call its parent?
+
switch(mode)
if(BOT_IDLE) // idle
icon_state = "[base_icon][on]"
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index 676fc3368c2..802c719e43a 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -443,7 +443,7 @@
if(H.dna.species && H.dna.species.reagent_tag == PROCESS_SYN)
return FALSE
- if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins)
+ if(emagged == 2 || hijacked) //Everyone needs our medicine. (Our medicine is toxins)
return TRUE
if(syndicate_aligned && !("syndicate" in C.faction))
@@ -498,7 +498,7 @@
var/reagent_id
var/beaker_injection //If and what kind of beaker reagent needs to be injected
- if(emagged == 2) //Emagged! Time to poison everybody.
+ if(emagged == 2 || hijacked) //Emagged! Time to poison everybody.
reagent_id = "pancuronium"
else
beaker_injection = assess_beaker_injection(C)
@@ -514,7 +514,7 @@
bot_reset()
return
else
- if(!emagged && check_overdose(patient, reagent_id, injection_amount))
+ if(!emagged && !hijacked && check_overdose(patient, reagent_id, injection_amount))
soft_reset()
return
C.visible_message(span_danger("[src] is trying to inject [patient]!"),
diff --git a/code/modules/mob/living/simple_animal/bot/mulebot.dm b/code/modules/mob/living/simple_animal/bot/mulebot.dm
index 9ac2640f8d8..6e3b6f6450a 100644
--- a/code/modules/mob/living/simple_animal/bot/mulebot.dm
+++ b/code/modules/mob/living/simple_animal/bot/mulebot.dm
@@ -396,7 +396,7 @@
if(istype(AM,/obj/structure/closet/crate))
CRATE = AM
else
- if(!wires.is_cut(WIRE_LOADCHECK))
+ if(!wires.is_cut(WIRE_LOADCHECK) && !hijacked)
buzz(SIGH)
return // if not hacked, only allow crates to be loaded
@@ -472,7 +472,7 @@
// with items dropping as mobs are loaded
for(var/atom/movable/AM in src)
- if(AM == cell || AM == access_card || AM == Radio || AM == bot_core || AM == paicard)
+ if(AM == cell || AM == access_card || AM == Radio || AM == paicard || AM == bot_core || ispulsedemon(AM))
continue
AM.forceMove(loc)
@@ -846,6 +846,8 @@
* Player on mulebot attempted to move.
*/
/mob/living/simple_animal/bot/mulebot/relaymove(mob/user)
+ if(ispulsedemon(user))
+ return ..()
if(user.incapacitated())
return
if(load == user)
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm
index 9df3039c8f7..fc49da4e81f 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/actions.dm
@@ -162,6 +162,7 @@
anchored = 1 // prevents people dragging it
density = 0 // prevents it blocking all movement
max_integrity = 20 // two welders, or one laser shot (15 for the normal spider webs)
+ creates_cover = TRUE
icon_state = "stickyweb1"
var/creator_ckey = null
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index 714122fd12d..a32d5633ad9 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -121,6 +121,9 @@
var/emergency_power_timer
var/emergency_lights = FALSE
+ /// Being hijacked by a pulse demon?
+ var/being_hijacked = FALSE
+
/obj/machinery/power/apc/worn_out
name = "\improper Worn out APC"
keep_preset_name = 1
@@ -394,7 +397,7 @@
update_state |= UPSTATE_OPENED1
if(opened==2)
update_state |= UPSTATE_OPENED2
- else if(emagged || malfai)
+ else if(emagged || malfai || being_hijacked)
update_state |= UPSTATE_BLUESCREEN
else if(panel_open)
update_state |= UPSTATE_WIREEXP
@@ -491,6 +494,15 @@
return
add_fingerprint(user)
cell = W
+
+ for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell)
+ demon.forceMove(src)
+ demon.current_power = src
+ if(!being_hijacked) // first come first serve
+ demon.try_hijack_apc(src)
+ if(being_hijacked)
+ cell.rigged = FALSE // don't blow the demon up
+
user.visible_message(\
"[user.name] has inserted the power cell to [name]!",\
"You insert the power cell.")
@@ -1009,7 +1021,7 @@
/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob)
if(user.can_admin_interact())
return TRUE
- if(isAI(user) || isrobot(user) && !iscogscarab(user))
+ if(isAI(user) || (isrobot(user) || user.has_unlimited_silicon_privilege) && !iscogscarab(user))
return TRUE
else
return !locked
@@ -1017,13 +1029,13 @@
/obj/machinery/power/apc/proc/is_locked(mob/user as mob)
if(user.can_admin_interact())
return FALSE
- if(isAI(user) || isrobot(user) && !iscogscarab(user))
+ if(isAI(user) || (isrobot(user) || user.has_unlimited_silicon_privilege) && !iscogscarab(user))
return FALSE
else
return locked
/obj/machinery/power/apc/ui_act(action, params)
- if(..() || !can_use(usr, TRUE) || (locked && !usr.has_unlimited_silicon_privilege && (action != "toggle_nightshift") && !usr.can_admin_interact()))
+ if(..() || !can_use(usr, TRUE) || (is_locked(usr) && (action != "toggle_nightshift")))
return
. = TRUE
switch(action)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index ffddbe72675..14817af9707 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -703,6 +703,7 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe/cable_restrain
new /obj/item/stack/cable_coil(get_turf(C), 1, TRUE, C.color)
C.deconstruct()
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CABLE_UPDATED, T)
return C
// called when cable_coil is click on an installed obj/cable
@@ -727,46 +728,46 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe/cable_restrain
return
var/dirn = get_dir(C, user)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CABLE_UPDATED, T)
// one end of the clicked cable is pointing towards us
if(C.d1 == dirn || C.d2 == dirn)
if(U.intact || U.transparent_floor) // can't place a cable if the floor is complete
to_chat(user, "You can't lay cable there unless the floor tiles are removed!")
return
- else
- // cable is pointing at us, we're standing on an open tile
- // so create a stub pointing at the clicked cable on our tile
+ // cable is pointing at us, we're standing on an open tile
+ // so create a stub pointing at the clicked cable on our tile
- var/fdirn = turn(dirn, 180) // the opposite direction
+ var/fdirn = turn(dirn, 180) // the opposite direction
- for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already
- if(LC.d1 == fdirn || LC.d2 == fdirn)
- to_chat(user, "There's already a cable at that position!")
- return
+ for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already
+ if(LC.d1 == fdirn || LC.d2 == fdirn)
+ to_chat(user, "There's already a cable at that position!")
+ return
- var/obj/structure/cable/NC = get_new_cable (U)
+ var/obj/structure/cable/NC = get_new_cable (U)
- NC.d1 = 0
- NC.d2 = fdirn
- NC.add_fingerprint(user)
- NC.update_icon()
+ NC.d1 = 0
+ NC.d2 = fdirn
+ NC.add_fingerprint(user)
+ NC.update_icon()
- //create a new powernet with the cable, if needed it will be merged later
- var/datum/powernet/newPN = new()
- newPN.add_cable(NC)
+ //create a new powernet with the cable, if needed it will be merged later
+ var/datum/powernet/newPN = new()
+ newPN.add_cable(NC)
- NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets
- NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets
+ NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets
+ NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets
- if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions
- NC.mergeDiagonalsNetworks(NC.d2)
+ if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions
+ NC.mergeDiagonalsNetworks(NC.d2)
- use(1)
+ use(1)
- if(NC.shock(user, 50))
- if(prob(50)) //fail
- NC.deconstruct()
- return
+ if(NC.shock(user, 50))
+ if(prob(50)) //fail
+ NC.deconstruct()
+ return
// exisiting cable doesn't point at our position, so see if it's a stub
else if(C.d1 == 0)
@@ -815,7 +816,7 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe/cable_restrain
return
C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets.
- return
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CABLE_UPDATED, T)
//////////////////////////////
// Misc.
diff --git a/code/modules/power/cable_heavyduty.dm b/code/modules/power/cable_heavyduty.dm
deleted file mode 100644
index 4ee1874cc09..00000000000
--- a/code/modules/power/cable_heavyduty.dm
+++ /dev/null
@@ -1,28 +0,0 @@
-/obj/item/stack/cable_coil/heavyduty
- name = "heavy cable coil"
- icon = 'icons/obj/engines_and_power/power.dmi'
- icon_state = "wire"
-
-/obj/structure/cable/heavyduty
- icon = 'icons/obj/engines_and_power/power_cond/power_cond_heavy.dmi'
- name = "large power cable"
- desc = "This cable is tough. It cannot be cut with simple hand tools."
- layer = 2.39 //Just below pipes, which are at 2.4
-
-/obj/structure/cable/heavyduty/attackby(obj/item/W, mob/user, params)
-
- var/turf/T = src.loc
- if(T.intact)
- return
-
- if(istype(W, /obj/item/wirecutters))
- to_chat(usr, "These cables are too tough to be cut with those [W.name].")
- return
- else if(istype(W, /obj/item/stack/cable_coil))
- to_chat(usr, "You will need heavier cables to connect to these.")
- return
- else
- ..()
-
-/obj/structure/cable/heavyduty/cable_color(var/colorC)
- return
diff --git a/code/modules/power/terminal.dm b/code/modules/power/terminal.dm
index 8362b921c2f..e2ee72809fb 100644
--- a/code/modules/power/terminal.dm
+++ b/code/modules/power/terminal.dm
@@ -8,6 +8,7 @@
icon_state = "term"
desc = "It's an underfloor wiring terminal for power equipment."
level = 1
+ plane = FLOOR_PLANE
layer = WIRE_TERMINAL_LAYER //a bit above wires
var/obj/machinery/power/master = null
@@ -16,6 +17,7 @@
. = ..()
var/turf/T = get_turf(src)
if(T.transparent_floor)
+ layer = ABOVE_TRANSPARENT_TURF_LAYER
return
if(level == 1)
hide(T.intact)
@@ -26,6 +28,10 @@
master = null
return ..()
+/obj/machinery/power/terminal/update_icon()
+ . = ..()
+ var/turf/T = get_turf(src)
+ layer = T.transparent_floor ? ABOVE_TRANSPARENT_TURF_LAYER : WIRE_TERMINAL_LAYER
/obj/machinery/power/terminal/hide(i)
if(i)
diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm
index 320f39a1a7a..65f28bd9474 100644
--- a/code/modules/tgui/states/default.dm
+++ b/code/modules/tgui/states/default.dm
@@ -47,6 +47,17 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
/mob/living/simple_animal/revenant/default_can_use_topic(src_object)
return STATUS_UPDATE
+/mob/living/simple_animal/demon/pulse_demon/default_can_use_topic(src_object)
+ . = shared_ui_interaction(src_object)
+ if(. < STATUS_INTERACTIVE)
+ return
+
+ // anything in its APC's area
+ if(get_area(src_object) == controlling_area)
+ return STATUS_INTERACTIVE
+ return STATUS_CLOSE
+
+
/mob/living/simple_animal/default_can_use_topic(src_object)
. = shared_ui_interaction(src_object)
if(. > STATUS_CLOSE)
diff --git a/code/modules/tgui/states/physical.dm b/code/modules/tgui/states/physical.dm
index c663cf4cd5d..71ea1a418ac 100644
--- a/code/modules/tgui/states/physical.dm
+++ b/code/modules/tgui/states/physical.dm
@@ -17,6 +17,9 @@ GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new)
/mob/living/simple_animal/revenant/physical_can_use_topic(src_object)
return STATUS_UPDATE
+/mob/living/simple_animal/demon/pulse_demon/physical_can_use_topic(src_object)
+ return STATUS_UPDATE
+
/mob/living/physical_can_use_topic(src_object)
return shared_living_ui_distance(src_object)
diff --git a/code/modules/w_examine/descriptions/atmospherics.dm b/code/modules/w_examine/descriptions/atmospherics.dm
index fb723336d85..60a9e301ef7 100644
--- a/code/modules/w_examine/descriptions/atmospherics.dm
+++ b/code/modules/w_examine/descriptions/atmospherics.dm
@@ -148,7 +148,7 @@
replaced by using a screwdriver, and then adding a new cell. A tank of gas can also be attached to the scrubber. "
//Meters
-/obj/machinery/meter
+/obj/machinery/atmospherics/meter
description_info = "Measures the volume and temperature of the pipe under the meter."
//Pipe dispensers
diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi
index 16ef17d20ce..cf22c56b001 100644
Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 6853c25994d..25ac77c5725 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 06918df7513..cfd601e665c 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/paradise.dme b/paradise.dme
index 9beef7d0fcb..291a78f92d4 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -714,6 +714,12 @@
#include "code\game\gamemodes\miniantags\demons\demon.dm"
#include "code\game\gamemodes\miniantags\demons\shadow_demon\shadow_demon.dm"
#include "code\game\gamemodes\miniantags\demons\slaughter_demon\slaughter_demon.dm"
+#include "code\game\gamemodes\miniantags\demons\pulse_demon\cross_shock_component.dm"
+#include "code\game\gamemodes\miniantags\demons\pulse_demon\pulse_demon.dm"
+#include "code\game\gamemodes\miniantags\demons\pulse_demon\pulse_demon_abilities.dm"
+#include "code\game\gamemodes\miniantags\demons\pulse_demon\pulse_demon_event.dm"
+#include "code\game\gamemodes\miniantags\demons\pulse_demon\pulse_demon_interactions.dm"
+#include "code\game\gamemodes\miniantags\demons\pulse_demon\pulse_demon_objectives.dm"
#include "code\game\gamemodes\miniantags\guardian\guardian.dm"
#include "code\game\gamemodes\miniantags\guardian\host_actions.dm"
#include "code\game\gamemodes\miniantags\guardian\types\assassin.dm"
@@ -2497,7 +2503,6 @@
#include "code\modules\persistence\persistence.dm"
#include "code\modules\power\apc.dm"
#include "code\modules\power\cable.dm"
-#include "code\modules\power\cable_heavyduty.dm"
#include "code\modules\power\cable_logic.dm"
#include "code\modules\power\cell.dm"
#include "code\modules\power\generator.dm"
diff --git a/sound/voice/pdvoice1.ogg b/sound/voice/pdvoice1.ogg
new file mode 100644
index 00000000000..64b884620e7
Binary files /dev/null and b/sound/voice/pdvoice1.ogg differ
diff --git a/sound/voice/pdvoice2.ogg b/sound/voice/pdvoice2.ogg
new file mode 100644
index 00000000000..317f90d4a13
Binary files /dev/null and b/sound/voice/pdvoice2.ogg differ
diff --git a/sound/voice/pdvoice3.ogg b/sound/voice/pdvoice3.ogg
new file mode 100644
index 00000000000..c4e3d0e41d1
Binary files /dev/null and b/sound/voice/pdvoice3.ogg differ
diff --git a/sound/voice/pdwail1.ogg b/sound/voice/pdwail1.ogg
new file mode 100644
index 00000000000..5ef0de17506
Binary files /dev/null and b/sound/voice/pdwail1.ogg differ
diff --git a/sound/voice/pdwail2.ogg b/sound/voice/pdwail2.ogg
new file mode 100644
index 00000000000..2ec3e4ed204
Binary files /dev/null and b/sound/voice/pdwail2.ogg differ
diff --git a/sound/voice/pdwail3.ogg b/sound/voice/pdwail3.ogg
new file mode 100644
index 00000000000..11cd81343b7
Binary files /dev/null and b/sound/voice/pdwail3.ogg differ