diff --git a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
index 6aa9707c72d..b3e6cb1a190 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
@@ -1093,7 +1093,7 @@
/turf/open/floor/wood,
/area/ruin/powered/beach)
"IL" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/turf/open/floor/plating,
diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_seed_vault.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_seed_vault.dmm
index 220b617cd33..6eb8b1a6b11 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_surface_seed_vault.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_seed_vault.dmm
@@ -621,7 +621,7 @@
/turf/closed/wall/r_wall,
/area/lavaland/surface/outdoors)
"ca" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 4
},
/turf/open/floor/iron/freezer,
diff --git a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm
index 9cffc15c8fc..389522ca8fb 100644
--- a/_maps/RandomRuins/SpaceRuins/caravanambush.dmm
+++ b/_maps/RandomRuins/SpaceRuins/caravanambush.dmm
@@ -1038,7 +1038,7 @@
/turf/open/floor/iron/dark/airless,
/area/shuttle/caravan/freighter3)
"jH" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 4
},
/obj/effect/turf_decal/stripes/line{
diff --git a/_maps/RandomRuins/SpaceRuins/forgottenship.dmm b/_maps/RandomRuins/SpaceRuins/forgottenship.dmm
index 0d9d1032084..09658a8b869 100644
--- a/_maps/RandomRuins/SpaceRuins/forgottenship.dmm
+++ b/_maps/RandomRuins/SpaceRuins/forgottenship.dmm
@@ -965,7 +965,7 @@
/turf/open/floor/mineral/plastitanium/red,
/area/ruin/space/has_grav/syndicate_forgotten_ship)
"cM" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/turf/open/floor/mineral/plastitanium/red,
diff --git a/_maps/RandomRuins/SpaceRuins/hellfactory.dmm b/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
index cb6722cceab..f5e0c1cb064 100644
--- a/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
+++ b/_maps/RandomRuins/SpaceRuins/hellfactory.dmm
@@ -68,7 +68,7 @@
/turf/open/floor/iron/grimy,
/area/ruin/space/has_grav/hellfactoryoffice)
"ak" = (
-/obj/machinery/atmospherics/components/unary/tank/oxygen{
+/obj/machinery/atmospherics/components/tank/oxygen{
dir = 8;
gas_type = /datum/gas/water_vapor;
initialize_directions = 8
diff --git a/_maps/RandomRuins/SpaceRuins/listeningstation.dmm b/_maps/RandomRuins/SpaceRuins/listeningstation.dmm
index c3d1c60a68a..9371c5207a4 100644
--- a/_maps/RandomRuins/SpaceRuins/listeningstation.dmm
+++ b/_maps/RandomRuins/SpaceRuins/listeningstation.dmm
@@ -1076,7 +1076,7 @@
/turf/open/floor/plating,
/area/ruin/space/has_grav/listeningstation)
"bH" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/obj/effect/turf_decal/bot,
diff --git a/_maps/RandomRuins/SpaceRuins/oldstation.dmm b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
index b21c65051e0..bc8740a333a 100644
--- a/_maps/RandomRuins/SpaceRuins/oldstation.dmm
+++ b/_maps/RandomRuins/SpaceRuins/oldstation.dmm
@@ -4231,7 +4231,7 @@
/area/ruin/space/has_grav/ancientstation/atmo)
"kC" = (
/obj/effect/decal/cleanable/dirt,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/turf/open/floor/plating,
diff --git a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm
index a84ac244e3a..0598e323dca 100644
--- a/_maps/RandomRuins/SpaceRuins/spacehotel.dmm
+++ b/_maps/RandomRuins/SpaceRuins/spacehotel.dmm
@@ -3570,14 +3570,14 @@
/turf/open/floor/iron/freezer,
/area/ruin/space/has_grav/hotel/pool)
"lp" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/turf/open/floor/plating,
/area/ruin/space/has_grav/hotel/power)
"lq" = (
/obj/machinery/light/small/directional/south,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/turf/open/floor/plating,
diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm
index 5f3ef9142cd..98302f84847 100644
--- a/_maps/map_files/Deltastation/DeltaStation2.dmm
+++ b/_maps/map_files/Deltastation/DeltaStation2.dmm
@@ -20495,7 +20495,7 @@
/area/science/xenobiology)
"cNb" = (
/obj/effect/turf_decal/bot,
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/iron,
/area/science/xenobiology)
"cNc" = (
@@ -34389,7 +34389,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/obj/machinery/firealarm/directional/west,
diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
index 381999523ef..e7074daef94 100644
--- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm
+++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm
@@ -2805,7 +2805,7 @@
/turf/open/floor/iron,
/area/construction/mining/aux_base)
"alF" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/plating,
/area/maintenance/port/fore)
"alG" = (
@@ -13986,7 +13986,7 @@
dir = 1;
layer = 2.9
},
-/obj/machinery/atmospherics/components/unary/tank/toxins,
+/obj/machinery/atmospherics/components/tank/toxins,
/turf/open/floor/iron,
/area/maintenance/disposal/incinerator)
"bCq" = (
@@ -17313,7 +17313,7 @@
/obj/effect/turf_decal/stripes/line{
dir = 5
},
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/turf/open/floor/plating,
@@ -19014,7 +19014,7 @@
/turf/open/floor/plating,
/area/ai_monitored/turret_protected/aisat/atmos)
"cui" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/plating,
/area/ai_monitored/turret_protected/aisat/atmos)
"cuj" = (
@@ -27590,7 +27590,7 @@
/turf/open/floor/iron,
/area/commons/cryopods)
"gSR" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/effect/turf_decal/delivery,
/turf/open/floor/iron/dark,
/area/maintenance/starboard/aft)
@@ -40953,7 +40953,7 @@
/turf/open/floor/iron/white,
/area/medical/medbay)
"oCz" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/effect/turf_decal/delivery,
/turf/open/floor/iron/dark,
/area/maintenance/port)
diff --git a/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm b/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm
index 86e7a5ea053..00512e4609a 100644
--- a/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm
+++ b/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm
@@ -719,7 +719,7 @@
/turf/open/floor/plating,
/area/mine/living_quarters)
"dr" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/turf/open/floor/plating,
diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm
index 0a5d0c9189d..ed7cd7429cd 100644
--- a/_maps/map_files/KiloStation/KiloStation.dmm
+++ b/_maps/map_files/KiloStation/KiloStation.dmm
@@ -2936,7 +2936,7 @@
/turf/open/floor/plating/airless,
/area/space/nearstation)
"ajj" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/obj/effect/turf_decal/tile/neutral{
@@ -55599,7 +55599,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/effect/turf_decal/delivery,
/turf/open/floor/iron/dark,
/area/ai_monitored/turret_protected/aisat/atmos)
@@ -74625,7 +74625,7 @@
/turf/open/floor/engine,
/area/engineering/supermatter/room)
"rFl" = (
-/obj/machinery/atmospherics/components/unary/tank/toxins{
+/obj/machinery/atmospherics/components/tank/toxins{
dir = 4
},
/obj/effect/turf_decal/delivery,
diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm
index e26f36b913c..4c2ba0b66e9 100644
--- a/_maps/map_files/MetaStation/MetaStation.dmm
+++ b/_maps/map_files/MetaStation/MetaStation.dmm
@@ -19526,7 +19526,7 @@
/turf/open/floor/engine,
/area/science/mixing/chamber)
"eoW" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/turf/open/floor/plating,
@@ -29173,7 +29173,7 @@
/obj/effect/turf_decal/tile/green{
dir = 1
},
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/iron/white,
/area/medical/virology)
"hFg" = (
@@ -44351,7 +44351,7 @@
"mKO" = (
/obj/effect/decal/cleanable/dirt,
/obj/effect/turf_decal/delivery,
-/obj/machinery/atmospherics/components/unary/tank/toxins{
+/obj/machinery/atmospherics/components/tank/toxins{
dir = 4
},
/turf/open/floor/iron,
diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm
index f849e4a2bb3..75de8850d8c 100644
--- a/_maps/map_files/Mining/Lavaland.dmm
+++ b/_maps/map_files/Mining/Lavaland.dmm
@@ -1033,7 +1033,7 @@
/turf/open/floor/plating,
/area/mine/living_quarters)
"dr" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/turf/open/floor/plating,
diff --git a/_maps/map_files/debug/multiz.dmm b/_maps/map_files/debug/multiz.dmm
index 60fab950172..5934d8c5b4d 100644
--- a/_maps/map_files/debug/multiz.dmm
+++ b/_maps/map_files/debug/multiz.dmm
@@ -47,7 +47,7 @@
/turf/open/floor/plating,
/area/engineering/atmos)
"am" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/plating,
/area/engineering/atmos)
"an" = (
diff --git a/_maps/map_files/debug/runtimestation.dmm b/_maps/map_files/debug/runtimestation.dmm
index 18cdbd6c397..066fffbf7e2 100644
--- a/_maps/map_files/debug/runtimestation.dmm
+++ b/_maps/map_files/debug/runtimestation.dmm
@@ -41,7 +41,7 @@
/turf/open/floor/iron,
/area/engineering/main)
"am" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/plating,
/area/engineering/atmos)
"an" = (
@@ -1107,7 +1107,7 @@
/turf/open/floor/iron,
/area/security/brig)
"dS" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/machinery/camera/autoname,
/turf/open/floor/plating,
/area/engineering/atmos)
@@ -2651,7 +2651,7 @@
/turf/open/floor/plating,
/area/engineering/atmos)
"XU" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/machinery/light/directional/north,
/turf/open/floor/plating,
/area/engineering/atmos)
diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm
index f2f4e17236c..0491227ff45 100644
--- a/_maps/map_files/generic/CentCom.dmm
+++ b/_maps/map_files/generic/CentCom.dmm
@@ -5801,7 +5801,7 @@
dir = 1
},
/obj/effect/decal/cleanable/dirt,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/obj/machinery/firealarm/directional/south,
diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm
index 758ee7e340c..a8f5126fe96 100644
--- a/_maps/map_files/tramstation/tramstation.dmm
+++ b/_maps/map_files/tramstation/tramstation.dmm
@@ -61583,7 +61583,7 @@
/turf/open/floor/plating,
/area/maintenance/central/secondary)
"tMg" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 4
},
/turf/open/floor/plating,
diff --git a/_maps/shuttles/emergency_monastery.dmm b/_maps/shuttles/emergency_monastery.dmm
index 9a792802784..925e610f087 100644
--- a/_maps/shuttles/emergency_monastery.dmm
+++ b/_maps/shuttles/emergency_monastery.dmm
@@ -388,7 +388,7 @@
/turf/open/floor/iron/dark,
/area/shuttle/escape)
"dR" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/turf/open/floor/plating,
@@ -3719,7 +3719,7 @@
/obj/effect/turf_decal/tile/neutral{
dir = 8
},
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/iron/dark,
/area/shuttle/escape)
"Rd" = (
diff --git a/_maps/shuttles/infiltrator_advanced.dmm b/_maps/shuttles/infiltrator_advanced.dmm
index 9f1f824d760..9aee53bffca 100644
--- a/_maps/shuttles/infiltrator_advanced.dmm
+++ b/_maps/shuttles/infiltrator_advanced.dmm
@@ -1965,7 +1965,7 @@
/turf/open/floor/mineral/plastitanium,
/area/shuttle/syndicate/armory)
"eb" = (
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 8
},
/obj/effect/turf_decal/bot,
diff --git a/_maps/shuttles/pirate_default.dmm b/_maps/shuttles/pirate_default.dmm
index bdbc0ec0d8d..3a9695a20d6 100644
--- a/_maps/shuttles/pirate_default.dmm
+++ b/_maps/shuttles/pirate_default.dmm
@@ -968,7 +968,7 @@
/turf/open/floor/pod/dark,
/area/shuttle/pirate)
"km" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/effect/turf_decal/bot,
/obj/machinery/firealarm/directional/north,
/obj/effect/decal/cleanable/dirt,
diff --git a/_maps/shuttles/pirate_silverscale.dmm b/_maps/shuttles/pirate_silverscale.dmm
index 5f5b0ef19f3..dc387cfcab6 100644
--- a/_maps/shuttles/pirate_silverscale.dmm
+++ b/_maps/shuttles/pirate_silverscale.dmm
@@ -661,7 +661,7 @@
/turf/open/floor/pod/dark,
/area/shuttle/pirate)
"QI" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/pod/dark,
/area/shuttle/pirate)
"QO" = (
diff --git a/_maps/shuttles/ruin_caravan_victim.dmm b/_maps/shuttles/ruin_caravan_victim.dmm
index 04cecafc22d..576993771f1 100644
--- a/_maps/shuttles/ruin_caravan_victim.dmm
+++ b/_maps/shuttles/ruin_caravan_victim.dmm
@@ -486,7 +486,7 @@
/area/shuttle/caravan/freighter1)
"zd" = (
/obj/effect/turf_decal/bot,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 4
},
/obj/effect/decal/cleanable/dirt,
diff --git a/_maps/shuttles/ruin_pirate_cutter.dmm b/_maps/shuttles/ruin_pirate_cutter.dmm
index 1b35635b151..b9bd78ba0de 100644
--- a/_maps/shuttles/ruin_pirate_cutter.dmm
+++ b/_maps/shuttles/ruin_pirate_cutter.dmm
@@ -771,7 +771,7 @@
/turf/open/floor/plating,
/area/shuttle/caravan/pirate)
"Gh" = (
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/effect/turf_decal/bot,
/turf/open/floor/plating,
/area/shuttle/caravan/pirate)
diff --git a/_maps/shuttles/whiteship_box.dmm b/_maps/shuttles/whiteship_box.dmm
index 51d2f39ca8b..4728d36e876 100644
--- a/_maps/shuttles/whiteship_box.dmm
+++ b/_maps/shuttles/whiteship_box.dmm
@@ -767,7 +767,7 @@
/area/shuttle/abandoned/engine)
"bq" = (
/obj/effect/decal/cleanable/dirt/dust,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/obj/effect/turf_decal/bot,
diff --git a/_maps/shuttles/whiteship_delta.dmm b/_maps/shuttles/whiteship_delta.dmm
index 57536a79d54..00a886c4beb 100644
--- a/_maps/shuttles/whiteship_delta.dmm
+++ b/_maps/shuttles/whiteship_delta.dmm
@@ -984,7 +984,7 @@
dir = 1
},
/obj/effect/decal/cleanable/dirt/dust,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/turf/open/floor/plating,
diff --git a/_maps/shuttles/whiteship_kilo.dmm b/_maps/shuttles/whiteship_kilo.dmm
index bf25d783750..ef4649f54cf 100644
--- a/_maps/shuttles/whiteship_kilo.dmm
+++ b/_maps/shuttles/whiteship_kilo.dmm
@@ -425,7 +425,7 @@
"aN" = (
/obj/machinery/light/small/built/directional/north,
/obj/effect/decal/cleanable/dirt/dust,
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/obj/effect/turf_decal/bot,
/obj/effect/turf_decal/stripes/line{
dir = 1
diff --git a/_maps/shuttles/whiteship_meta.dmm b/_maps/shuttles/whiteship_meta.dmm
index 46ea40fa84b..84e15ae058e 100644
--- a/_maps/shuttles/whiteship_meta.dmm
+++ b/_maps/shuttles/whiteship_meta.dmm
@@ -685,7 +685,7 @@
/obj/effect/decal/cleanable/dirt/dust,
/obj/effect/decal/cleanable/dirt/dust,
/obj/effect/decal/cleanable/dirt/dust,
-/obj/machinery/atmospherics/components/unary/tank/air{
+/obj/machinery/atmospherics/components/tank/air{
dir = 1
},
/obj/effect/turf_decal/bot,
diff --git a/_maps/shuttles/whiteship_tram.dmm b/_maps/shuttles/whiteship_tram.dmm
index c06a29e03f1..127b179181a 100644
--- a/_maps/shuttles/whiteship_tram.dmm
+++ b/_maps/shuttles/whiteship_tram.dmm
@@ -46,7 +46,7 @@
/area/shuttle/abandoned/engine)
"ai" = (
/obj/effect/decal/cleanable/dirt,
-/obj/machinery/atmospherics/components/unary/tank/air,
+/obj/machinery/atmospherics/components/tank/air,
/turf/open/floor/mineral/titanium/tiled/yellow,
/area/shuttle/abandoned/engine)
"aj" = (
diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm
index 0e50c808fb9..bf9dba1f7d2 100644
--- a/code/__DEFINES/construction.dm
+++ b/code/__DEFINES/construction.dm
@@ -58,6 +58,10 @@
#define FLOODLIGHT_NEEDS_LIGHTS 1
#define FLOODLIGHT_NEEDS_SECURING 2
+// Stationary gas tanks
+#define TANK_FRAME 0
+#define TANK_PLATING_UNSECURED 1
+
//other construction-related things
//windows affected by Nar'Sie turn this color.
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 3c54e10d098..3162a671da2 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -1357,3 +1357,11 @@
/// Called in /obj/structure/moneybot/add_money(). (to_add)
#define COMSIG_MONEYBOT_ADD_MONEY "moneybot_add_money"
+
+// Merger datum signals
+/// Called on the object being added to a merger group: (datum/merger/new_merger)
+#define COMSIG_MERGER_ADDING "comsig_merger_adding"
+/// Called on the object being removed from a merger group: (datum/merger/old_merger)
+#define COMSIG_MERGER_REMOVING "comsig_merger_removing"
+/// Called on the merger after finishing a refresh: (list/leaving_members, list/joining_members)
+#define COMSIG_MERGER_REFRESH_COMPLETE "comsig_merger_refresh_complete"
diff --git a/code/__DEFINES/icon_smoothing.dm b/code/__DEFINES/icon_smoothing.dm
index 286a920a557..80abc7ae3bf 100644
--- a/code/__DEFINES/icon_smoothing.dm
+++ b/code/__DEFINES/icon_smoothing.dm
@@ -165,10 +165,13 @@ DEFINE_BITFIELD(smoothing_flags, list(
#define SMOOTH_GROUP_CLEANABLE_DIRT S_OBJ(67) ///obj/effect/decal/cleanable/dirt
#define SMOOTH_GROUP_INDUSTRIAL_LIFT S_OBJ(70) ///obj/structure/industrial_lift
-//SKYRAT EDIT ADDITION
-#define SMOOTH_GROUP_SHUTTERS S_OBJ(71)
-#define SMOOTH_GROUP_WATER S_OBJ(72) ///obj/effect/abstract/liquid_turf
+#define SMOOTH_GROUP_GAS_TANK S_OBJ(71)
+
+//SKYRAT EDIT ADDITION
+#define SMOOTH_GROUP_SHUTTERS S_OBJ(72)
+
+#define SMOOTH_GROUP_WATER S_OBJ(73) ///obj/effect/abstract/liquid_turf
#define MAX_S_OBJ SMOOTH_GROUP_WATER //Always match this value with the one above it.
//SKYRAT EDIT END
diff --git a/code/__HELPERS/chat.dm b/code/__HELPERS/chat.dm
index 27e400f262a..230dd03361e 100644
--- a/code/__HELPERS/chat.dm
+++ b/code/__HELPERS/chat.dm
@@ -72,3 +72,6 @@ In TGS3 it will always be sent to all connected designated game chats.
if(!embed_links)
message = GLOB.has_discord_embeddable_links.Replace(replacetext(message, "`", ""), " ```$1``` ")
world.TgsTargetedChatBroadcast("[category] | [message]", TRUE)
+
+/// Handles text formatting for item use hints in examine text
+#define EXAMINE_HINT(text) ("" + text + "")
diff --git a/code/datums/components/cracked.dm b/code/datums/components/cracked.dm
new file mode 100644
index 00000000000..79b5b653442
--- /dev/null
+++ b/code/datums/components/cracked.dm
@@ -0,0 +1,74 @@
+/// Tracks damage to add or remove crack overlays, when none are needed this components is qdeleted
+/datum/component/cracked
+ dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
+ var/list/icon/crack_icons
+ var/crack_integrity
+ var/list/applied_cracks = list()
+
+/datum/component/cracked/Initialize(list/crack_icons, crack_integrity)
+ . = ..()
+ if(!isobj(parent))
+ return COMPONENT_INCOMPATIBLE
+ src.crack_icons = crack_icons
+ src.crack_integrity = crack_integrity
+
+/datum/component/cracked/Destroy(force, silent)
+ RemoveCracks(parent, length(applied_cracks))
+ return ..()
+
+/datum/component/cracked/RegisterWithParent()
+ . = ..()
+ RegisterSignal(parent, COMSIG_OBJ_INTEGRITY_CHANGED, .proc/IntegrityChanged)
+ var/obj/master = parent
+ var/integrity = master.get_integrity()
+ IntegrityChanged(parent, integrity, integrity)
+
+/datum/component/cracked/UnregisterFromParent()
+ . = ..()
+ UnregisterSignal(parent, COMSIG_OBJ_INTEGRITY_CHANGED)
+
+/datum/component/cracked/proc/IntegrityChanged(obj/source, old_value, new_value)
+ SIGNAL_HANDLER
+ var/cracked_max_integrity = source.max_integrity * crack_integrity
+ if(new_value >= cracked_max_integrity)
+ qdel(src)
+ return
+
+ var/current_percent = 1 - (new_value / cracked_max_integrity)
+ var/cracks = CEILING(10 * current_percent, 1) // 1 crack per 10% integrity lost
+ var/current_cracks = length(applied_cracks)
+ if(!islist(source.filters))
+ if(isnull(source.filters))
+ source.filters = list()
+ else
+ source.filters = list(source.filters)
+
+ if(current_cracks == cracks)
+ return
+
+ if(current_cracks < cracks)
+ AddCracks(source, cracks - current_cracks)
+ else
+ RemoveCracks(source, current_cracks - cracks)
+
+/datum/component/cracked/proc/AddCracks(obj/source, count)
+ for(var/i in 1 to count)
+ var/rand_x = rand(0, 20) - 10
+ var/rand_y = rand(0, 20) - 10
+ var/icon/new_crack_icon = pick(crack_icons)
+ var/list/new_filter_data = alpha_mask_filter(icon=new_crack_icon, x=rand_x, y=rand_y, flags=MASK_INVERSE)
+ var/name
+ for(var/k in 1 to 100)
+ name = "crack[rand(0, 999)]"
+ if(!applied_cracks[name])
+ break
+ if(applied_cracks[name])
+ CRASH("No unique name could be found after 100 iterations.")
+ applied_cracks[name] = TRUE
+ source.add_filter(name, 1, new_filter_data)
+
+/datum/component/cracked/proc/RemoveCracks(obj/source, count)
+ for(var/i in 1 to count)
+ var/removed_filter = pick(applied_cracks)
+ applied_cracks -= removed_filter
+ source.remove_filter(removed_filter)
diff --git a/code/datums/elements/crackable.dm b/code/datums/elements/crackable.dm
new file mode 100644
index 00000000000..88d972bc9a8
--- /dev/null
+++ b/code/datums/elements/crackable.dm
@@ -0,0 +1,27 @@
+/// Adds crack overlays to an object when integrity gets low
+/datum/element/crackable
+ element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
+ id_arg_index = 2
+ var/list/icon/crack_icons
+ /// The level at which the object starts showing cracks, 1 being at full health and 0.5 being at half health
+ var/crack_integrity = 1
+
+/datum/element/crackable/Attach(datum/target, icon/crack_icon, list/crack_states, crack_integrity)
+ . = ..()
+ if(!isobj(target))
+ return ELEMENT_INCOMPATIBLE
+ src.crack_integrity = crack_integrity || src.crack_integrity
+ if(!crack_icons) // This is the first attachment and we need to do first time setup
+ crack_icons = list()
+ for(var/state in crack_states)
+ for(var/i in 1 to 36)
+ var/icon/new_crack_icon = icon(crack_icon, state)
+ new_crack_icon.Turn(i * 10)
+ crack_icons += new_crack_icon
+ RegisterSignal(target, COMSIG_OBJ_INTEGRITY_CHANGED, .proc/IntegrityChanged)
+
+/datum/element/crackable/proc/IntegrityChanged(obj/source, old_value, new_value)
+ SIGNAL_HANDLER
+ if(new_value >= source.max_integrity * crack_integrity)
+ return
+ source.AddComponent(/datum/component/cracked, crack_icons, crack_integrity)
diff --git a/code/datums/greyscale/config_types/greyscale_configs.dm b/code/datums/greyscale/config_types/greyscale_configs.dm
index 7d652ba1702..7a70c906729 100644
--- a/code/datums/greyscale/config_types/greyscale_configs.dm
+++ b/code/datums/greyscale/config_types/greyscale_configs.dm
@@ -28,6 +28,11 @@
icon_file = 'icons/obj/atmospherics/prototype_canister.dmi'
json_config = 'code/datums/greyscale/json_configs/canister_proto.json'
+/datum/greyscale_config/stationary_canister
+ name = "Stationary Canister"
+ icon_file = 'icons/obj/atmospherics/stationary_canisters.dmi'
+ json_config = 'code/datums/greyscale/json_configs/smooth_canister_stationary.json'
+
/datum/greyscale_config/carp
name = "Space Carp"
icon_file = 'icons/mob/carp.dmi'
diff --git a/code/datums/greyscale/json_configs/smooth_canister_stationary.json b/code/datums/greyscale/json_configs/smooth_canister_stationary.json
new file mode 100644
index 00000000000..df01ce1e6d2
--- /dev/null
+++ b/code/datums/greyscale/json_configs/smooth_canister_stationary.json
@@ -0,0 +1,186 @@
+{
+ "smooth": [
+ {
+ "type": "icon_state",
+ "icon_state": "smooth",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "window-fg": [
+ {
+ "type": "icon_state",
+ "icon_state": "window-fg",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "window-bg": [
+ {
+ "type": "icon_state",
+ "icon_state": "window-bg",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "1-i": [
+ {
+ "type": "icon_state",
+ "icon_state": "1-i",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "2-i": [
+ {
+ "type": "icon_state",
+ "icon_state": "2-i",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "3-i": [
+ {
+ "type": "icon_state",
+ "icon_state": "3-i",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "4-i": [
+ {
+ "type": "icon_state",
+ "icon_state": "4-i",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "1-n": [
+ {
+ "type": "icon_state",
+ "icon_state": "1-n",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "2-n": [
+ {
+ "type": "icon_state",
+ "icon_state": "2-n",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "3-s": [
+ {
+ "type": "icon_state",
+ "icon_state": "3-s",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "4-s": [
+ {
+ "type": "icon_state",
+ "icon_state": "4-s",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "1-w": [
+ {
+ "type": "icon_state",
+ "icon_state": "1-w",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "2-e": [
+ {
+ "type": "icon_state",
+ "icon_state": "2-e",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "3-w": [
+ {
+ "type": "icon_state",
+ "icon_state": "3-w",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "4-e": [
+ {
+ "type": "icon_state",
+ "icon_state": "4-e",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "1-nw": [
+ {
+ "type": "icon_state",
+ "icon_state": "1-nw",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "2-ne": [
+ {
+ "type": "icon_state",
+ "icon_state": "2-ne",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "3-sw": [
+ {
+ "type": "icon_state",
+ "icon_state": "3-sw",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "4-se": [
+ {
+ "type": "icon_state",
+ "icon_state": "4-se",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "1-f": [
+ {
+ "type": "icon_state",
+ "icon_state": "1-f",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "2-f": [
+ {
+ "type": "icon_state",
+ "icon_state": "2-f",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "3-f": [
+ {
+ "type": "icon_state",
+ "icon_state": "3-f",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ],
+ "4-f": [
+ {
+ "type": "icon_state",
+ "icon_state": "4-f",
+ "blend_mode": "overlay",
+ "color_ids": [ 1 ]
+ }
+ ]
+}
diff --git a/code/datums/mergers/_merger.dm b/code/datums/mergers/_merger.dm
new file mode 100644
index 00000000000..886117e8fa9
--- /dev/null
+++ b/code/datums/mergers/_merger.dm
@@ -0,0 +1,132 @@
+#define MERGERS_DEBUG FALSE
+
+/// A datum that tracks a type or types of objects in a cluster
+/datum/merger
+ /// The unique ID for this merger datum, adjacent merg groups with the same id will combine
+ var/id
+ /// The types allowed to be in this merge group
+ var/list/merged_typecache
+ /// Optional proc to call on potential members, return true to allow merge
+ var/attempt_merge_proc
+
+ /// The arbitrary "owner" member of the merge group
+ var/atom/origin
+ /// A list of all members in the group
+ var/list/members = list()
+
+#if MERGERS_DEBUG
+ var/debug_color
+#endif
+
+ /// Signals in members to trigger a refresh
+ var/static/list/refresh_signals = list(
+ COMSIG_PARENT_QDELETING,
+ COMSIG_MOVABLE_MOVED,
+ )
+
+/datum/merger/New(id, list/merged_typecache, atom/origin, attempt_merge_proc)
+#if MERGERS_DEBUG
+ debug_color = rgb(rand(0, 255), rand(0, 255), rand(0, 255))
+#endif
+ src.id = id
+ src.merged_typecache = merged_typecache
+ src.origin = origin
+ src.attempt_merge_proc = attempt_merge_proc
+ Refresh()
+
+/datum/merger/Destroy(force, ...)
+ for(var/atom/thing as anything in members)
+ RemoveMember(thing)
+ return ..()
+
+/datum/merger/proc/RemoveMember(atom/thing, clean=TRUE)
+ SEND_SIGNAL(thing, COMSIG_MERGER_REMOVING, src)
+ UnregisterSignal(thing, refresh_signals)
+ if(!thing.mergers)
+ return
+ thing.mergers -= id
+ if(clean && !length(thing.mergers))
+ thing.mergers = null
+ members -= thing
+ if(origin == thing && length(members))
+ origin = pick(members)
+
+/datum/merger/proc/AddMember(atom/thing, connected_dir)
+ SEND_SIGNAL(thing, COMSIG_MERGER_ADDING, src)
+ RegisterSignal(thing, refresh_signals, .proc/QueueRefresh)
+ if(!thing.mergers)
+ thing.mergers = list()
+ else if(thing.mergers[id])
+ var/datum/merger/other_merger = thing.mergers[id]
+ other_merger.RemoveMember(thing)
+ if(!thing.mergers)
+ thing.mergers = list()
+
+ thing.mergers[id] = src
+ members[thing] = connected_dir
+ if(!origin)
+ origin = thing
+
+#if MERGERS_DEBUG
+ thing.add_atom_colour(debug_color, ADMIN_COLOUR_PRIORITY)
+ if(SSatoms.initialized != INITIALIZATION_INNEW_MAPLOAD)
+ sleep(1 SECONDS)
+#endif
+
+/datum/merger/proc/QueueRefresh()
+ SIGNAL_HANDLER
+ addtimer(CALLBACK(src, .proc/Refresh), 1, TIMER_UNIQUE)
+
+/datum/merger/proc/Refresh()
+ var/list/tips = list()
+ tips[origin] = NORTH|EAST|SOUTH|WEST
+ var/list/checked_turfs = list()
+ var/list/new_members = list()
+ new_members[origin] = NONE
+ while(length(tips))
+ var/atom/focus = tips[length(tips)]
+ var/dirs_to_check = tips[focus]
+ tips.len--
+ for(var/dir in GLOB.cardinals)
+ if(!(dirs_to_check & dir))
+ continue
+ var/turf/location = get_step(focus, dir)
+ if(!location || checked_turfs[location])
+ continue
+ checked_turfs[location] = TRUE
+ for(var/i in location)
+ var/atom/movable/thing = i
+ if(!merged_typecache[thing.type])
+ continue
+ if(attempt_merge_proc && !call(thing, attempt_merge_proc)(src, new_members))
+ continue
+ if(thing.mergers && thing.mergers[id] != src)
+ var/datum/merger/existing = thing.mergers[id]
+ qdel(src)
+ existing.Refresh()
+ return
+ new_members[focus] |= dir // This is not a list, value of the members list is a bitfield of dirs
+ var/next_dirs = turn(dir, 180)
+ new_members[thing] |= next_dirs
+ next_dirs = ~next_dirs
+ var/existing = tips[thing] || (NORTH|EAST|SOUTH|WEST)
+ tips[thing] = existing & next_dirs
+
+ var/list/leaving_members = members - new_members
+ for(var/atom/thing as anything in leaving_members)
+ RemoveMember(thing)
+
+ var/list/joining_members = new_members - members
+ for(var/atom/thing as anything in joining_members)
+ AddMember(thing, joining_members[thing])
+
+ // They may not need a full update but the connected dirs could change
+ for(var/atom/thing as anything in new_members)
+ members[thing] = new_members[thing]
+
+ SEND_SIGNAL(src, COMSIG_MERGER_REFRESH_COMPLETE, leaving_members, joining_members)
+
+ if(!length(members))
+ qdel(src)
+
+#undef MERGERS_DEBUG
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 050062834dd..1eb18d956a8 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -83,7 +83,7 @@
///The custom materials this atom is made of, used by a lot of things like furniture, walls, and floors (if I finish the functionality, that is.)
///The list referenced by this var can be shared by multiple objects and should not be directly modified. Instead, use [set_custom_materials][/atom/proc/set_custom_materials].
- var/list/custom_materials
+ var/list/datum/material/custom_materials
///Bitfield for how the atom handles materials.
var/material_flags = NONE
///Modifier that raises/lowers the effect of the amount of a material, prevents small and easy to get items from being death machines.
@@ -133,6 +133,9 @@
///A string of hex format colors to be used by greyscale sprites, ex: "#0054aa#badcff"
var/greyscale_colors
+ ///Holds merger groups currently active on the atom. Do not access directly, use GetMergeGroup() instead.
+ var/list/datum/merger/mergers
+
///Icon-smoothing behavior.
var/smoothing_flags = NONE
///What directions this is currently smoothing with. IMPORTANT: This uses the smoothing direction flags as defined in icon_smoothing.dm, instead of the BYOND flags.
@@ -748,6 +751,10 @@
SHOULD_CALL_PARENT(TRUE)
if(greyscale_colors && greyscale_config)
icon = SSgreyscale.GetColoredIconByType(greyscale_config, greyscale_colors)
+ if(!smoothing_flags) // This is a bitfield but we're just checking that some sort of smoothing is happening
+ return
+ update_atom_colour()
+ QUEUE_SMOOTH(src)
/**
* An atom we are buckled or is contained within us has tried to move
@@ -2071,3 +2078,14 @@
usr.hud_used.screentip_text.maptext = ""
else
usr.hud_used.screentip_text.maptext = MAPTEXT("[name]")
+
+/// Gets a merger datum representing the connected blob of objects in the allowed_types argument
+/atom/proc/GetMergeGroup(id, list/allowed_types)
+ RETURN_TYPE(/datum/merger)
+ var/datum/merger/candidate
+ if(mergers)
+ candidate = mergers[id]
+ if(!candidate)
+ new /datum/merger(id, allowed_types, src)
+ candidate = mergers[id]
+ return candidate
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 9a6767b5b22..866e4df27fd 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -174,12 +174,13 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
* Plasteel
*/
GLOBAL_LIST_INIT(plasteel_recipes, list ( \
- new/datum/stack_recipe("AI core", /obj/structure/ai_core, 4, time = 50, one_per_turf = TRUE), \
- new/datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 10, time = 50), \
- null, \
+ new/datum/stack_recipe("AI core", /obj/structure/ai_core, 4, time = 50, one_per_turf = TRUE),
+ new/datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 10, time = 50),
+ new/datum/stack_recipe("Large Gas Tank", /obj/structure/tank_frame, 4, time=1 SECONDS, one_per_turf=TRUE),
+ null,
new /datum/stack_recipe_list("airlock assemblies", list( \
- new/datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, 4, time = 50, one_per_turf = 1, on_floor = 1), \
- new/datum/stack_recipe("vault door assembly", /obj/structure/door_assembly/door_assembly_vault, 6, time = 50, one_per_turf = 1, on_floor = 1), \
+ new/datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, 4, time = 50, one_per_turf = 1, on_floor = 1),
+ new/datum/stack_recipe("vault door assembly", /obj/structure/door_assembly/door_assembly_vault, 6, time = 50, one_per_turf = 1, on_floor = 1),
)), \
))
diff --git a/code/game/objects/obj_defense.dm b/code/game/objects/obj_defense.dm
index 619321e1274..f1479ddc04d 100644
--- a/code/game/objects/obj_defense.dm
+++ b/code/game/objects/obj_defense.dm
@@ -41,11 +41,12 @@
/// Handles the integrity of an object changing. This must be called instead of changing integrity directly.
/obj/proc/update_integrity(new_value)
SHOULD_NOT_OVERRIDE(TRUE)
+ var/old_value = obj_integrity
new_value = max(0, new_value)
if(obj_integrity == new_value)
return
obj_integrity = new_value
- SEND_SIGNAL(src, COMSIG_OBJ_INTEGRITY_CHANGED)
+ SEND_SIGNAL(src, COMSIG_OBJ_INTEGRITY_CHANGED, old_value, new_value)
/// This mostly exists to keep obj_integrity private. Might be useful in the future.
/obj/proc/get_integrity()
diff --git a/code/modules/admin/greyscale_modify_menu.dm b/code/modules/admin/greyscale_modify_menu.dm
index 0ccc5c4bc54..5f33ceec206 100644
--- a/code/modules/admin/greyscale_modify_menu.dm
+++ b/code/modules/admin/greyscale_modify_menu.dm
@@ -127,12 +127,16 @@
if("recolor")
var/index = text2num(params["color_index"])
- split_colors[index] = lowertext(params["new_color"])
- queue_refresh()
+ var/new_color = lowertext(params["new_color"])
+ if(split_colors[index] != new_color)
+ split_colors[index] = new_color
+ queue_refresh()
if("recolor_from_string")
- ReadColorsFromString(lowertext(params["color_string"]))
- queue_refresh()
+ var/full_color_string = lowertext(params["color_string"])
+ if(full_color_string != split_colors.Join())
+ ReadColorsFromString(full_color_string)
+ queue_refresh()
if("pick_color")
var/group = params["color_index"]
diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
index 4b25fd19efa..3df7d399fd6 100644
--- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
+++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm
@@ -25,7 +25,7 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
/obj/machinery/atmospherics/pipe/heat_exchanging/junction,
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold,
/obj/machinery/atmospherics/pipe/heat_exchanging/manifold4w,
- /obj/machinery/atmospherics/components/unary/tank,
+ /obj/machinery/atmospherics/components/tank,
/obj/machinery/atmospherics/components/unary/portables_connector,
/obj/machinery/atmospherics/components/unary/passive_vent,
/obj/machinery/atmospherics/components/unary/heat_exchanger,
diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm
index 88988491cc9..a0089338244 100644
--- a/code/modules/atmospherics/machinery/atmosmachinery.dm
+++ b/code/modules/atmospherics/machinery/atmosmachinery.dm
@@ -135,13 +135,16 @@
var/list/node_connects = list()
node_connects.len = device_type
+ var/init_directions = GetInitDirections()
for(var/i in 1 to device_type)
- for(var/D in GLOB.cardinals)
- if(D & GetInitDirections())
- if(D in node_connects)
- continue
- node_connects[i] = D
- break
+ for(var/direction in GLOB.cardinals)
+ if(!(direction & init_directions))
+ continue
+ if(direction in node_connects)
+ continue
+ node_connects[i] = direction
+ break
+
return node_connects
/**
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/valve.dm b/code/modules/atmospherics/machinery/components/binary_devices/valve.dm
index ad2fd531cb6..4651447d833 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/valve.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/valve.dm
@@ -14,6 +14,7 @@ It's like a regular ol' straight pipe, but you can turn it on and off.
pipe_flags = PIPING_CARDINAL_AUTONORMALIZE | PIPING_BRIDGE
construction_type = /obj/item/pipe/binary
pipe_state = "mvalve"
+ custom_reconcilation = TRUE
///Type of valve (manual or digital), used to set the icon of the component in update_icon_nopipes()
var/valve_type = MANUAL_VALVE
///Bool to stop interactions while the opening/closing animation is going
@@ -47,6 +48,15 @@ It's like a regular ol' straight pipe, but you can turn it on and off.
vent_movement &= ~VENTCRAWL_ALLOWED
+// This is what handles the actual functionality of combining 2 pipenets when the valve is open
+// Basically when a pipenet updates it will consider both sides to be the same for the purpose of the gas update
+/obj/machinery/atmospherics/components/binary/valve/returnPipenetsForReconcilation(datum/pipeline/requester)
+ . = ..()
+ if(!on)
+ return
+ . += parents[1]
+ . += parents[2]
+
/obj/machinery/atmospherics/components/binary/valve/interact(mob/user)
add_fingerprint(usr)
if(switching)
diff --git a/code/modules/atmospherics/machinery/components/components_base.dm b/code/modules/atmospherics/machinery/components/components_base.dm
index 153f49b5099..d63353ad7a5 100644
--- a/code/modules/atmospherics/machinery/components/components_base.dm
+++ b/code/modules/atmospherics/machinery/components/components_base.dm
@@ -13,6 +13,8 @@
var/list/datum/pipeline/parents
///Stores the component gas mixture
var/list/datum/gas_mixture/airs
+ ///Handles whether the custom reconcilation handling should be used
+ var/custom_reconcilation = FALSE
/obj/machinery/atmospherics/components/New()
parents = new(device_type)
@@ -21,6 +23,8 @@
..()
for(var/i in 1 to device_type)
+ if(airs[i])
+ continue
var/datum/gas_mixture/A = new
A.volume = 200
airs[i] = A
@@ -193,6 +197,8 @@
* This way gases won't get stuck
*/
/obj/machinery/atmospherics/components/proc/update_parents()
+ if(!SSair.initialized)
+ return
for(var/i in 1 to device_type)
var/datum/pipeline/parent = parents[i]
if(!parent)
@@ -206,6 +212,16 @@
for(var/i in 1 to device_type)
. += returnPipenet(nodes[i])
+/// When this machine is in a pipenet that is reconciling airs, this proc can add pipelines to the calculation.
+/// Can be either a list of pipenets or a single pipenet.
+/obj/machinery/atmospherics/components/proc/returnPipenetsForReconcilation(datum/pipeline/requester)
+ return list()
+
+/// When this machine is in a pipenet that is reconciling airs, this proc can add airs to the calculation.
+/// Can be either a list of airs or a single air mix.
+/obj/machinery/atmospherics/components/proc/returnAirsForReconcilation(datum/pipeline/requester)
+ return list()
+
// UI Stuff
/obj/machinery/atmospherics/components/ui_status(mob/user)
diff --git a/code/modules/atmospherics/machinery/components/tank.dm b/code/modules/atmospherics/machinery/components/tank.dm
new file mode 100644
index 00000000000..bb3f9f5119a
--- /dev/null
+++ b/code/modules/atmospherics/machinery/components/tank.dm
@@ -0,0 +1,545 @@
+/obj/machinery/atmospherics/components/tank
+ icon = 'icons/obj/atmospherics/stationary_canisters.dmi'
+ icon_state = "smooth"
+
+ name = "pressure tank"
+ desc = "A large vessel containing pressurized gas."
+
+ max_integrity = 800
+ integrity_failure = 0.2
+ density = TRUE
+ layer = ABOVE_WINDOW_LAYER
+
+ custom_materials = list(/datum/material/alloy/plasteel=4000, /datum/material/iron=20000)
+ material_flags = MATERIAL_GREYSCALE | MATERIAL_ADD_PREFIX | MATERIAL_AFFECT_STATISTICS
+
+ pipe_flags = PIPING_ONE_PER_TURF
+ device_type = QUATERNARY
+ initialize_directions = NONE
+ custom_reconcilation = TRUE
+
+ smoothing_flags = SMOOTH_CORNERS | SMOOTH_OBJ
+ smoothing_groups = list(SMOOTH_GROUP_GAS_TANK)
+ canSmoothWith = list(SMOOTH_GROUP_GAS_TANK)
+ appearance_flags = KEEP_TOGETHER
+
+ greyscale_config = /datum/greyscale_config/stationary_canister
+ greyscale_colors = "#ffffff"
+
+ var/image/window
+
+ /// The volume of the gas mixture
+ var/volume = 2500 //in liters
+ /// The max pressure of the gas mixture before damaging the tank
+ var/max_pressure = 20000
+ /// The typepath of the gas this tank should be filled with.
+ var/gas_type = null
+
+ var/datum/gas_mixture/air_contents
+
+ /// The sounds that play when the tank is breaking from overpressure
+ var/static/list/breaking_sounds = list(
+ 'sound/effects/structure_stress/pop1.ogg',
+ 'sound/effects/structure_stress/pop2.ogg',
+ 'sound/effects/structure_stress/pop3.ogg',
+ )
+
+ /// Shared images for the knob overlay representing a side of the tank that is open to connections
+ var/static/list/knob_overlays
+
+ /// Number of crack states to fill the list with. This exists because I'm lazy and didn't want to keeping adding more things manually to the below list.
+ var/crack_states_count = 10
+ /// The icon states for the cracks in the tank dmi
+ var/static/list/crack_states
+
+ /// The merger id used to create/get the merger group in charge of handling tanks that share an internal gas storage
+ var/merger_id = "stationary_tanks"
+ /// The typecache of types which are allowed to merge internal storage
+ var/static/list/merger_typecache
+
+/obj/machinery/atmospherics/components/tank/Initialize()
+ . = ..()
+
+ if(!knob_overlays)
+ knob_overlays = list()
+ for(var/dir in GLOB.cardinals)
+ knob_overlays["[dir]"] = image('icons/obj/atmospherics/stationary_canisters.dmi', icon_state="knob", dir=dir, layer=FLOAT_LAYER)
+
+ if(!crack_states)
+ crack_states = list()
+ for(var/i in 1 to crack_states_count)
+ crack_states += "crack[i]"
+
+ if(!merger_typecache)
+ merger_typecache = typecacheof(/obj/machinery/atmospherics/components/tank)
+
+ AddComponent(/datum/component/gas_leaker, leak_rate=0.05)
+ AddElement(/datum/element/volatile_gas_storage)
+ AddElement(/datum/element/crackable, 'icons/obj/atmospherics/stationary_canisters.dmi', crack_states)
+
+ RegisterSignal(src, COMSIG_MERGER_ADDING, .proc/MergerAdding)
+ RegisterSignal(src, COMSIG_MERGER_REMOVING, .proc/MergerRemoving)
+ RegisterSignal(src, COMSIG_ATOM_SMOOTHED_ICON, .proc/Smoothed)
+
+ air_contents = new
+ air_contents.temperature = T20C
+ air_contents.volume = volume
+ RefreshPressureLimit()
+
+ if(gas_type)
+ FillToPressure(gas_type)
+
+ setPipingLayer(piping_layer)
+ QUEUE_SMOOTH(src)
+ QUEUE_SMOOTH_NEIGHBORS(src)
+
+ return INITIALIZE_HINT_LATELOAD
+
+// We late initialize here so all stationary tanks have time to set up their
+// initial gas mixes and signal registrations.
+/obj/machinery/atmospherics/components/tank/LateInitialize()
+ . = ..()
+ GetMergeGroup(merger_id, merger_typecache)
+
+/obj/machinery/atmospherics/components/tank/Destroy()
+ QUEUE_SMOOTH_NEIGHBORS(src)
+ return ..()
+
+/obj/machinery/atmospherics/components/tank/examine(mob/user, thats)
+ . = ..()
+ var/wrench_hint = EXAMINE_HINT("wrench")
+ if(!initialize_directions)
+ . += "A pipe port can be opened with a [wrench_hint]."
+ else
+ . += "The pipe port can be moved or closed with a [wrench_hint]."
+
+/obj/machinery/atmospherics/components/tank/set_custom_materials(list/materials, multiplier)
+ . = ..()
+ RefreshPressureLimit()
+
+/// Recalculates pressure based on the current max integrity compared to original
+/obj/machinery/atmospherics/components/tank/proc/RefreshPressureLimit()
+ var/max_pressure_multiplier = max_integrity / initial(max_integrity)
+ max_pressure = max_pressure_multiplier * initial(max_pressure)
+
+/// Fills the tank to the maximum safe pressure.
+/// Safety margin is a multiplier for the cap for the purpose of this proc so it doesn't have to be filled completely.
+/obj/machinery/atmospherics/components/tank/proc/FillToPressure(gastype, safety_margin=0.5)
+ var/pressure_limit = max_pressure * safety_margin
+
+ var/moles_to_add = (pressure_limit * air_contents.volume) / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
+ air_contents.assert_gas(gastype)
+ air_contents.gases[gastype][MOLES] += moles_to_add
+ air_contents.archive()
+ update_parents()
+
+/obj/machinery/atmospherics/components/tank/process_atmos()
+ if(air_contents.react(src))
+ update_parents()
+
+ if(air_contents.return_pressure() > max_pressure)
+ take_damage(0.1, BRUTE, sound_effect=FALSE)
+ if(prob(40))
+ playsound(src, pick(breaking_sounds), 30, vary=TRUE)
+
+ RefreshWindow()
+
+///////////////////////////////////////////////////////////////////
+// Pipenet stuff
+
+/obj/machinery/atmospherics/components/tank/returnAirsForReconcilation(datum/pipeline/requester)
+ . = ..()
+ if(!air_contents)
+ return
+ . += air_contents
+
+/obj/machinery/atmospherics/components/tank/returnPipenetsForReconcilation(datum/pipeline/requester)
+ . = ..()
+ var/datum/merger/merge_group = GetMergeGroup(merger_id, merger_typecache)
+ for(var/obj/machinery/atmospherics/components/tank/tank as anything in merge_group.members)
+ . += tank.parents
+
+/obj/machinery/atmospherics/components/tank/proc/ToggleSidePort(new_dir)
+ if(initialize_directions & new_dir)
+ initialize_directions &= ~new_dir
+ else
+ initialize_directions |= new_dir
+
+ for(var/i in 1 to length(nodes))
+ var/obj/machinery/atmospherics/components/node = nodes[i]
+ if(!node)
+ continue
+ if(src in node.nodes)
+ node.disconnect(src)
+ nodes[i] = null
+ if(parents[i])
+ nullifyPipenet(parents[i])
+
+ atmosinit()
+
+ for(var/obj/machinery/atmospherics/components/node as anything in nodes)
+ if(!node)
+ continue
+ node.atmosinit()
+ node.addMember(src)
+ SSair.add_to_rebuild_queue(src)
+
+ update_parents()
+
+///////////////////////////////////////////////////////////////////
+// Merger handling
+
+/obj/machinery/atmospherics/components/tank/proc/MergerAdding(obj/machinery/atmospherics/components/tank/us, datum/merger/new_merger)
+ SIGNAL_HANDLER
+ if(new_merger.id != merger_id)
+ return
+ RegisterSignal(new_merger, COMSIG_MERGER_REFRESH_COMPLETE, .proc/MergerRefreshComplete)
+
+/obj/machinery/atmospherics/components/tank/proc/MergerRemoving(obj/machinery/atmospherics/components/tank/us, datum/merger/old_merger)
+ SIGNAL_HANDLER
+ if(old_merger.id != merger_id)
+ return
+ UnregisterSignal(old_merger, COMSIG_MERGER_REFRESH_COMPLETE)
+
+/// Handles the combined gas tank for the entire merger group, only the origin tank actualy runs this.
+/obj/machinery/atmospherics/components/tank/proc/MergerRefreshComplete(datum/merger/merger, list/leaving_members, list/joining_members)
+ SIGNAL_HANDLER
+ if(merger.origin != src)
+ return
+ var/shares = length(merger.members) + length(leaving_members) - length(joining_members)
+ for(var/obj/machinery/atmospherics/components/tank/leaver as anything in leaving_members)
+ var/datum/gas_mixture/gas_share = air_contents.remove_ratio(1/shares--)
+ air_contents.volume -= leaver.volume
+ leaver.air_contents = gas_share
+ leaver.update_appearance()
+
+ for(var/obj/machinery/atmospherics/components/tank/joiner as anything in joining_members)
+ var/datum/gas_mixture/joiner_share = joiner.air_contents
+ if(joiner_share)
+ air_contents.merge(joiner_share)
+ joiner.air_contents = air_contents
+ air_contents.volume += joiner.volume
+ joiner.update_appearance()
+
+ for(var/dir in GLOB.cardinals)
+ if(dir & initialize_directions & merger.members[src])
+ ToggleSidePort(dir)
+
+ update_parents()
+
+///////////////////////////////////////////////////////////////////
+// Appearance stuff
+
+/obj/machinery/atmospherics/components/tank/proc/Smoothed()
+ SIGNAL_HANDLER
+ RefreshWindow()
+
+/obj/machinery/atmospherics/components/tank/update_appearance()
+ . = ..()
+ RefreshWindow()
+
+/obj/machinery/atmospherics/components/tank/update_overlays()
+ . = ..()
+ if(!initialize_directions)
+ return
+ for(var/dir in GLOB.cardinals)
+ if(initialize_directions & dir)
+ . += knob_overlays["[dir]"]
+
+/obj/machinery/atmospherics/components/tank/update_greyscale()
+ . = ..()
+ RefreshWindow()
+
+/obj/machinery/atmospherics/components/tank/proc/RefreshWindow()
+ cut_overlay(window)
+
+ if(!air_contents)
+ window = null
+ return
+
+ window = image(icon, icon_state="window-bg", layer=FLOAT_LAYER)
+
+ var/list/new_underlays = list()
+ for(var/obj/effect/overlay/gas/gas as anything in air_contents.return_visuals())
+ var/image/new_underlay = image(gas.icon, icon_state=gas.icon_state, layer=FLOAT_LAYER)
+ new_underlay.filters = alpha_mask_filter(icon=icon(icon, icon_state="window-bg"))
+ new_underlays += new_underlay
+
+ var/image/foreground = image(icon, icon_state="window-fg", layer=FLOAT_LAYER)
+ foreground.underlays = new_underlays
+ window.overlays = list(foreground)
+
+ add_overlay(window)
+
+///////////////////////////////////////////////////////////////////
+// Tool interactions
+
+/obj/machinery/atmospherics/components/tank/wrench_act(mob/living/user, obj/item/item)
+ . = TRUE
+ var/new_dir = get_dir(src, user)
+
+ if(new_dir in GLOB.diagonals)
+ return
+
+ item.play_tool_sound(src, 10)
+ if(!item.use_tool(src, user, 3 SECONDS))
+ return
+
+ ToggleSidePort(new_dir)
+
+ item.play_tool_sound(src, 50)
+
+/obj/machinery/atmospherics/components/tank/welder_act(mob/living/user, obj/item/tool)
+ . = ..()
+ . = TRUE
+ if(obj_integrity >= max_integrity)
+ return
+ if(!tool.tool_start_check(user, amount=0))
+ return
+ to_chat(user, "You begin to repair the cracks in the gas tank...")
+ var/repair_amount = max_integrity / 10
+ do
+ if(!tool.use_tool(src, user, 2.5 SECONDS, volume=40))
+ return
+ while(repair_damage(repair_amount))
+ to_chat(user, "The gas tank has been fully repaired and all cracks sealed.")
+
+/obj/machinery/atmospherics/components/tank/welder_act_secondary(mob/living/user, obj/item/tool)
+ . = ..()
+ . = TRUE
+ to_chat(user, "You begin cutting open the gas tank...")
+ var/turf/current_location = get_turf(src)
+ var/datum/gas_mixture/airmix = current_location.return_air()
+
+ var/time_taken = 4 SECONDS
+
+ if(air_contents.return_pressure() > airmix.return_pressure())
+ time_taken *= 2
+ to_chat(user, "The tank seems to be pressurized, are you sure this is a good idea?")
+
+ if(!tool.use_tool(src, user, time_taken, volume=60))
+ return
+
+ deconstruct(disassembled=TRUE)
+ to_chat(user, "You finish cutting open the sealed gas tank, revealing the innards.")
+
+/obj/machinery/atmospherics/components/tank/deconstruct(disassembled)
+ var/turf/location = drop_location()
+ . = ..()
+ if(!disassembled)
+ return
+ var/obj/structure/tank_frame/frame = new(location)
+ frame.construction_state = TANK_PLATING_UNSECURED
+ frame.material_end_product = custom_materials[2].type
+ frame.update_appearance()
+
+///////////////////////////////////////////////////////////////////
+// Gas tank variants
+
+/obj/machinery/atmospherics/components/tank/air
+ name = "pressure tank (Air)"
+
+/obj/machinery/atmospherics/components/tank/air/Initialize()
+ . = ..()
+ FillToPressure(/datum/gas/oxygen, safety_margin=0.1)
+ FillToPressure(/datum/gas/nitrogen, safety_margin=0.5)
+
+/obj/machinery/atmospherics/components/tank/carbon_dioxide
+ gas_type = /datum/gas/carbon_dioxide
+
+/obj/machinery/atmospherics/components/tank/toxins
+ gas_type = /datum/gas/plasma
+
+/obj/machinery/atmospherics/components/tank/nitrogen
+ gas_type = /datum/gas/nitrogen
+
+/obj/machinery/atmospherics/components/tank/oxygen
+ gas_type = /datum/gas/oxygen
+
+/obj/machinery/atmospherics/components/tank/nitrous
+ gas_type = /datum/gas/nitrous_oxide
+
+/obj/machinery/atmospherics/components/tank/bz
+ gas_type = /datum/gas/bz
+
+/obj/machinery/atmospherics/components/tank/freon
+ gas_type = /datum/gas/freon
+
+/obj/machinery/atmospherics/components/tank/halon
+ gas_type = /datum/gas/halon
+
+/obj/machinery/atmospherics/components/tank/healium
+ gas_type = /datum/gas/healium
+
+/obj/machinery/atmospherics/components/tank/hydrogen
+ gas_type = /datum/gas/hydrogen
+
+/obj/machinery/atmospherics/components/tank/hypernoblium
+ gas_type = /datum/gas/hypernoblium
+
+/obj/machinery/atmospherics/components/tank/miasma
+ gas_type = /datum/gas/miasma
+
+/obj/machinery/atmospherics/components/tank/nitryl
+ gas_type = /datum/gas/nitryl
+
+/obj/machinery/atmospherics/components/tank/pluoxium
+ gas_type = /datum/gas/pluoxium
+
+/obj/machinery/atmospherics/components/tank/proto_nitrate
+ gas_type = /datum/gas/proto_nitrate
+
+/obj/machinery/atmospherics/components/tank/stimulum
+ gas_type = /datum/gas/stimulum
+
+/obj/machinery/atmospherics/components/tank/tritium
+ gas_type = /datum/gas/tritium
+
+/obj/machinery/atmospherics/components/tank/water_vapor
+ gas_type = /datum/gas/water_vapor
+
+/obj/machinery/atmospherics/components/tank/zauker
+ gas_type = /datum/gas/zauker
+
+/obj/machinery/atmospherics/components/tank/helium
+ gas_type = /datum/gas/helium
+
+/obj/machinery/atmospherics/components/tank/antinoblium
+ gas_type = /datum/gas/antinoblium
+
+///////////////////////////////////////////////////////////////////
+// Tank Frame Structure
+
+/obj/structure/tank_frame
+ icon = 'icons/obj/atmospherics/stationary_canisters.dmi'
+ icon_state = "frame"
+ anchored = FALSE
+ density = TRUE
+ custom_materials = list(/datum/material/alloy/plasteel=4000)
+ var/construction_state = TANK_FRAME
+ var/datum/material/material_end_product
+
+/obj/structure/tank_frame/examine(mob/user)
+ . = ..()
+ var/wrenched_hint = EXAMINE_HINT("wrenched")
+
+ if(!anchored)
+ . += "[src] has not been [wrenched_hint] to the floor yet."
+ else
+ . += "[src] is [wrenched_hint] to the floor."
+
+ switch(construction_state)
+ if(TANK_FRAME)
+ var/screwed_hint = EXAMINE_HINT("screwed")
+ var/plating_hint = EXAMINE_HINT("metal plating")
+ . += "[src] is [screwed_hint] together and now just needs some [plating_hint]."
+ if(TANK_PLATING_UNSECURED)
+ var/crowbar_hint = EXAMINE_HINT("crowbar")
+ var/welder_hint = EXAMINE_HINT("welder")
+ . += "The plating has been firmly attached and would need a [crowbar_hint] to detach, but still needs to be sealed by a [welder_hint]."
+
+/obj/structure/tank_frame/deconstruct(disassembled)
+ if(disassembled)
+ for(var/datum/material/mat as anything in custom_materials)
+ new mat.sheet_type(drop_location())
+ return ..()
+
+/obj/structure/tank_frame/update_icon(updates)
+ . = ..()
+ switch(construction_state)
+ if(TANK_FRAME)
+ icon_state = "frame"
+ if(TANK_PLATING_UNSECURED)
+ icon_state = "plated_frame"
+
+/obj/structure/tank_frame/attackby(obj/item/item, mob/living/user, params)
+ if(construction_state == TANK_FRAME && istype(item, /obj/item/stack) && add_plating(user, item))
+ return
+ return ..()
+
+/obj/structure/tank_frame/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ return default_unfasten_wrench(user, tool, 0.5 SECONDS)
+
+/obj/structure/tank_frame/screwdriver_act_secondary(mob/living/user, obj/item/tool)
+ . = ..()
+ if(construction_state != TANK_FRAME)
+ return
+ . = TRUE
+ to_chat(user, "You begin taking apart [src].")
+ if(!tool.use_tool(src, user, 1 SECONDS))
+ return
+ deconstruct(TRUE)
+ to_chat(user, "[src] has been taken apart.")
+
+/obj/structure/tank_frame/proc/add_plating(mob/living/user, obj/item/stack/stack)
+ . = FALSE
+ var/datum/material/stack_mat = GET_MATERIAL_REF(stack.material_type)
+ if(!(MAT_CATEGORY_RIGID in stack_mat.categories))
+ to_chat(user, "This material doesn't seem rigid enough to hold the shape of a tank...")
+ return
+
+ . = TRUE
+ to_chat(user, "You begin adding [stack] to [src]...")
+ if(!stack.use_tool(src, user, 3 SECONDS))
+ return
+ if(!stack.use(20))
+ var/amount_more
+ switch(stack.amount)
+ if(0) // Wat?
+ amount_more = "any at all"
+ if(1 to 4)
+ amount_more = "a lot more"
+ if(5 to 9)
+ amount_more = "about four times as much"
+ if(10 to 15)
+ amount_more = "about twice as much"
+ if(16 to 20)
+ amount_more = "just a bit more"
+ else
+ amount_more = "an indeterminate amount more"
+ to_chat(user, "You don't have enough [stack] to add all the plating. Maybe [amount_more].")
+ return
+
+ material_end_product = stack_mat
+ construction_state = TANK_PLATING_UNSECURED
+ update_appearance()
+ to_chat(user, "You finish attaching [stack] to [src].")
+
+/obj/structure/tank_frame/crowbar_act_secondary(mob/living/user, obj/item/tool)
+ . = ..()
+ if(construction_state != TANK_PLATING_UNSECURED)
+ return
+ . = TRUE
+ to_chat(user, "You start prying off the outer plating...")
+ if(!tool.use_tool(src, user, 2 SECONDS))
+ return
+ construction_state = TANK_FRAME
+ new material_end_product.sheet_type(drop_location(), 20)
+ material_end_product = null
+ update_appearance()
+
+/obj/structure/tank_frame/welder_act(mob/living/user, obj/item/tool)
+ . = ..()
+ if(construction_state != TANK_PLATING_UNSECURED)
+ return
+ . = TRUE
+ if(!anchored)
+ to_chat(user, "You need to wrench [src] to the floor before finishing.")
+ return
+ if(!tool.tool_start_check(user, amount=0))
+ return
+ to_chat(user, "You begin sealing the outer plating with the welder...")
+ if(!tool.use_tool(src, user, 2 SECONDS, volume=60))
+ return
+
+ var/turf/build_location = drop_location()
+ if(!isturf(build_location))
+ return
+ var/obj/machinery/atmospherics/components/tank/new_tank = new(build_location)
+ var/list/new_custom_materials = list()
+ new_custom_materials[/datum/material/alloy/plasteel] = 4000
+ new_custom_materials[material_end_product] = 20000
+ new_tank.set_custom_materials(new_custom_materials)
+ to_chat(user, "[new_tank] has been sealed and is ready to accept gases.")
+ qdel(src)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
index d4673b5a659..7165f0602ed 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/portables_connector.dm
@@ -13,6 +13,7 @@
pipe_flags = PIPING_ONE_PER_TURF
pipe_state = "connector"
+ custom_reconcilation = TRUE
var/obj/machinery/portable_atmospherics/connected_device
@@ -38,6 +39,12 @@
return
update_parents()
+/obj/machinery/atmospherics/components/unary/portables_connector/returnAirsForReconcilation(datum/pipeline/requester)
+ . = ..()
+ if(!connected_device)
+ return
+ . += connected_device.return_air()
+
/obj/machinery/atmospherics/components/unary/portables_connector/can_unwrench(mob/user)
. = ..()
if(. && connected_device)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm b/code/modules/atmospherics/machinery/components/unary_devices/tank.dm
deleted file mode 100644
index 7bc46e1433a..00000000000
--- a/code/modules/atmospherics/machinery/components/unary_devices/tank.dm
+++ /dev/null
@@ -1,115 +0,0 @@
-#define AIR_CONTENTS ((25*ONE_ATMOSPHERE)*(air_contents.volume)/(R_IDEAL_GAS_EQUATION*air_contents.temperature))
-/obj/machinery/atmospherics/components/unary/tank
- icon = 'icons/obj/atmospherics/pipes/pressure_tank.dmi'
- icon_state = "generic"
-
- name = "pressure tank"
- desc = "A large vessel containing pressurized gas."
-
- max_integrity = 800
- density = TRUE
- layer = ABOVE_WINDOW_LAYER
- pipe_flags = PIPING_ONE_PER_TURF
-
- var/volume = 10000 //in liters
- /// The typepath of the gas this tank should be filled with.
- var/gas_type = null
-
-/obj/machinery/atmospherics/components/unary/tank/New()
- ..()
- var/datum/gas_mixture/air_contents = airs[1]
- air_contents.volume = volume
- air_contents.temperature = T20C
- if(gas_type)
- air_contents.assert_gas(gas_type)
- air_contents.gases[gas_type][MOLES] = AIR_CONTENTS
- name = "[name] ([air_contents.gases[gas_type][GAS_META][META_GAS_NAME]])"
- setPipingLayer(piping_layer)
-
-
-/obj/machinery/atmospherics/components/unary/tank/air
- icon_state = "grey"
- name = "pressure tank (Air)"
-
-/obj/machinery/atmospherics/components/unary/tank/air/New()
- ..()
- var/datum/gas_mixture/air_contents = airs[1]
- air_contents.assert_gases(/datum/gas/oxygen, /datum/gas/nitrogen)
- air_contents.gases[/datum/gas/oxygen][MOLES] = AIR_CONTENTS * 0.2
- air_contents.gases[/datum/gas/nitrogen][MOLES] = AIR_CONTENTS * 0.8
-
-/obj/machinery/atmospherics/components/unary/tank/carbon_dioxide
- gas_type = /datum/gas/carbon_dioxide
-
-/obj/machinery/atmospherics/components/unary/tank/toxins
- icon_state = "orange"
- gas_type = /datum/gas/plasma
-
-/obj/machinery/atmospherics/components/unary/tank/nitrogen
- icon_state = "red"
- gas_type = /datum/gas/nitrogen
-
-/obj/machinery/atmospherics/components/unary/tank/oxygen
- icon_state = "blue"
- gas_type = /datum/gas/oxygen
-
-/obj/machinery/atmospherics/components/unary/tank/nitrous
- icon_state = "red_white"
- gas_type = /datum/gas/nitrous_oxide
-
-/obj/machinery/atmospherics/components/unary/tank/bz
- gas_type = /datum/gas/bz
-
-/obj/machinery/atmospherics/components/unary/tank/freon
- icon_state = "blue"
- gas_type = /datum/gas/freon
-
-/obj/machinery/atmospherics/components/unary/tank/halon
- icon_state = "blue"
- gas_type = /datum/gas/halon
-
-/obj/machinery/atmospherics/components/unary/tank/healium
- icon_state = "red"
- gas_type = /datum/gas/healium
-
-/obj/machinery/atmospherics/components/unary/tank/hydrogen
- icon_state = "grey"
- gas_type = /datum/gas/hydrogen
-
-/obj/machinery/atmospherics/components/unary/tank/hypernoblium
- icon_state = "blue"
- gas_type = /datum/gas/hypernoblium
-
-/obj/machinery/atmospherics/components/unary/tank/miasma
- gas_type = /datum/gas/miasma
-
-/obj/machinery/atmospherics/components/unary/tank/nitryl
- gas_type = /datum/gas/nitryl
-
-/obj/machinery/atmospherics/components/unary/tank/pluoxium
- icon_state = "blue"
- gas_type = /datum/gas/pluoxium
-
-/obj/machinery/atmospherics/components/unary/tank/proto_nitrate
- icon_state = "red"
- gas_type = /datum/gas/proto_nitrate
-
-/obj/machinery/atmospherics/components/unary/tank/stimulum
- icon_state = "red"
- gas_type = /datum/gas/stimulum
-
-/obj/machinery/atmospherics/components/unary/tank/tritium
- gas_type = /datum/gas/tritium
-
-/obj/machinery/atmospherics/components/unary/tank/water_vapor
- icon_state = "grey"
- gas_type = /datum/gas/water_vapor
-
-/obj/machinery/atmospherics/components/unary/tank/zauker
- gas_type = /datum/gas/zauker
-
-/obj/machinery/atmospherics/components/unary/tank/helium
- gas_type = /datum/gas/helium
-
-/obj/machinery/atmospherics/components/unary/tank/antinoblium
- gas_type = /datum/gas/antinoblium
diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm
index 06cd46f306e..73aa1c4bbc1 100644
--- a/code/modules/atmospherics/machinery/datum_pipeline.dm
+++ b/code/modules/atmospherics/machinery/datum_pipeline.dm
@@ -234,6 +234,7 @@
stack_trace("[src] has one or more null gas mixtures, which may cause bugs. Null mixtures will not be considered in reconcile_air().")
return removeNullsFromList(.)
+/// Called when the pipenet needs to update and mix together all the air mixes
/datum/pipeline/proc/reconcile_air()
var/list/datum/gas_mixture/gas_mixture_list = list()
var/list/datum/pipeline/pipeline_list = list()
@@ -245,16 +246,11 @@
continue
gas_mixture_list += pipeline.other_airs
gas_mixture_list += pipeline.air
- for(var/atmosmch in pipeline.other_atmosmch)
- if (istype(atmosmch, /obj/machinery/atmospherics/components/binary/valve))
- var/obj/machinery/atmospherics/components/binary/valve/considered_valve = atmosmch
- if(considered_valve.on)
- pipeline_list |= considered_valve.parents[1]
- pipeline_list |= considered_valve.parents[2]
- else if (istype(atmosmch, /obj/machinery/atmospherics/components/unary/portables_connector))
- var/obj/machinery/atmospherics/components/unary/portables_connector/considered_connector = atmosmch
- if(considered_connector.connected_device)
- gas_mixture_list += considered_connector.connected_device.return_air()
+ for(var/obj/machinery/atmospherics/components/atmosmch as anything in pipeline.other_atmosmch)
+ if(!atmosmch.custom_reconcilation)
+ continue
+ pipeline_list |= atmosmch.returnPipenetsForReconcilation(src)
+ gas_mixture_list |= atmosmch.returnAirsForReconcilation(src)
var/total_thermal_energy = 0
var/total_heat_capacity = 0
@@ -262,8 +258,7 @@
var/list/total_gases = total_gas_mixture.gases
- for(var/mixture in gas_mixture_list)
- var/datum/gas_mixture/gas_mixture = mixture
+ for(var/datum/gas_mixture/gas_mixture as anything in gas_mixture_list)
total_gas_mixture.volume += gas_mixture.volume
// This is sort of a combined merge + heat_capacity calculation
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index 35d1c9de866..7683fc9ab61 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -435,7 +435,7 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister())
window = image(icon, icon_state="window-base", layer=FLOAT_LAYER)
var/list/window_overlays = list()
for(var/visual in air_contents.return_visuals())
- var/image/new_visual = image(visual, layer=FLOAT_PLANE)
+ var/image/new_visual = image(visual, layer=FLOAT_LAYER)
new_visual.filters = alpha_filter
window_overlays += new_visual
window.overlays = window_overlays
diff --git a/icons/obj/atmospherics/pipes/pressure_tank.dmi b/icons/obj/atmospherics/pipes/pressure_tank.dmi
deleted file mode 100644
index 95103015cfd..00000000000
Binary files a/icons/obj/atmospherics/pipes/pressure_tank.dmi and /dev/null differ
diff --git a/icons/obj/atmospherics/stationary_canisters.dmi b/icons/obj/atmospherics/stationary_canisters.dmi
new file mode 100644
index 00000000000..4f87a50845b
Binary files /dev/null and b/icons/obj/atmospherics/stationary_canisters.dmi differ
diff --git a/sound/effects/structure_stress/pop1.ogg b/sound/effects/structure_stress/pop1.ogg
new file mode 100644
index 00000000000..e5c3cc95f7b
Binary files /dev/null and b/sound/effects/structure_stress/pop1.ogg differ
diff --git a/sound/effects/structure_stress/pop2.ogg b/sound/effects/structure_stress/pop2.ogg
new file mode 100644
index 00000000000..2165a55e903
Binary files /dev/null and b/sound/effects/structure_stress/pop2.ogg differ
diff --git a/sound/effects/structure_stress/pop3.ogg b/sound/effects/structure_stress/pop3.ogg
new file mode 100644
index 00000000000..e441ab15441
Binary files /dev/null and b/sound/effects/structure_stress/pop3.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 3767e461ada..dce2327ca33 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -534,6 +534,7 @@
#include "code\datums\components\chasm.dm"
#include "code\datums\components\codeword_hearing.dm"
#include "code\datums\components\construction.dm"
+#include "code\datums\components\cracked.dm"
#include "code\datums\components\creamed.dm"
#include "code\datums\components\curse_of_hunger.dm"
#include "code\datums\components\customizable_reagent_holder.dm"
@@ -730,6 +731,7 @@
#include "code\datums\elements\cleaning.dm"
#include "code\datums\elements\climbable.dm"
#include "code\datums\elements\connect_loc.dm"
+#include "code\datums\elements\crackable.dm"
#include "code\datums\elements\curse_announcement.dm"
#include "code\datums\elements\cursed.dm"
#include "code\datums\elements\deferred_aquarium_content.dm"
@@ -835,6 +837,7 @@
#include "code\datums\materials\hauntium.dm"
#include "code\datums\materials\meat.dm"
#include "code\datums\materials\pizza.dm"
+#include "code\datums\mergers\_merger.dm"
#include "code\datums\mocking\client.dm"
#include "code\datums\mood_events\_mood_event.dm"
#include "code\datums\mood_events\beauty_events.dm"
@@ -1882,6 +1885,7 @@
#include "code\modules\atmospherics\machinery\bluespace_vendor.dm"
#include "code\modules\atmospherics\machinery\datum_pipeline.dm"
#include "code\modules\atmospherics\machinery\components\components_base.dm"
+#include "code\modules\atmospherics\machinery\components\tank.dm"
#include "code\modules\atmospherics\machinery\components\binary_devices\binary_devices.dm"
#include "code\modules\atmospherics\machinery\components\binary_devices\circulator.dm"
#include "code\modules\atmospherics\machinery\components\binary_devices\dp_vent_pump.dm"
@@ -1910,7 +1914,6 @@
#include "code\modules\atmospherics\machinery\components\unary_devices\outlet_injector.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\passive_vent.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\portables_connector.dm"
-#include "code\modules\atmospherics\machinery\components\unary_devices\tank.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\unary_devices.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\vent_pump.dm"
#include "code\modules\atmospherics\machinery\components\unary_devices\vent_scrubber.dm"