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