diff --git a/citadel.dme b/citadel.dme
index 9ba994101ae..303ee5f6c75 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -117,11 +117,20 @@
#include "code\__DEFINES\atmospherics\atmospheres.dm"
#include "code\__DEFINES\atmospherics\constants.dm"
#include "code\__DEFINES\atmospherics\environmental.dm"
-#include "code\__DEFINES\atmospherics\flags.dm"
#include "code\__DEFINES\atmospherics\gas_strings.dm"
#include "code\__DEFINES\atmospherics\gasmixtures.dm"
#include "code\__DEFINES\atmospherics\helpers.dm"
+#include "code\__DEFINES\atmospherics\machinery.dm"
#include "code\__DEFINES\atmospherics\pipes.dm"
+#include "code\__DEFINES\atmospherics\gases\flags.dm"
+#include "code\__DEFINES\atmospherics\gases\generation.dm"
+#include "code\__DEFINES\atmospherics\gases\groups.dm"
+#include "code\__DEFINES\atmospherics\gases\ids.dm"
+#include "code\__DEFINES\atmospherics\gases\reagents.dm"
+#include "code\__DEFINES\atmospherics\gases\visuals.dm"
+#include "code\__DEFINES\atmospherics\machinery\alarm.dm"
+#include "code\__DEFINES\atmospherics\machinery\scrubber.dm"
+#include "code\__DEFINES\atmospherics\machinery\vent.dm"
#include "code\__DEFINES\cargo\supply.dm"
#include "code\__DEFINES\client\player_flags.dm"
#include "code\__DEFINES\client\playtime.dm"
@@ -1114,10 +1123,10 @@
#include "code\game\legacy_html_uis\menus.dm"
#include "code\game\machinery\_frame.dm"
#include "code\game\machinery\_machinery.dm"
+#include "code\game\machinery\_machinery_construction.dm"
#include "code\game\machinery\_machinery_power.dm"
#include "code\game\machinery\adv_med.dm"
#include "code\game\machinery\ai_slipper.dm"
-#include "code\game\machinery\air_alarm.dm"
#include "code\game\machinery\atm_ret_field.dm"
#include "code\game\machinery\atmo_control.dm"
#include "code\game\machinery\beacon.dm"
@@ -1180,12 +1189,6 @@
#include "code\game\machinery\wall_frames.dm"
#include "code\game\machinery\washing_machine.dm"
#include "code\game\machinery\wishgranter.dm"
-#include "code\game\machinery\atmoalter\area_atmos_computer.dm"
-#include "code\game\machinery\atmoalter\canister.dm"
-#include "code\game\machinery\atmoalter\meter.dm"
-#include "code\game\machinery\atmoalter\portable_atmospherics.dm"
-#include "code\game\machinery\atmoalter\pump.dm"
-#include "code\game\machinery\atmoalter\scrubber.dm"
#include "code\game\machinery\camera\camera.dm"
#include "code\game\machinery\camera\camera_assembly.dm"
#include "code\game\machinery\camera\motion.dm"
@@ -1536,7 +1539,6 @@
#include "code\game\objects\items\devices\paicard.dm"
#include "code\game\objects\items\devices\pipe_painter.dm"
#include "code\game\objects\items\devices\powersink.dm"
-#include "code\game\objects\items\devices\scanners.dm"
#include "code\game\objects\items\devices\scanners_vr.dm"
#include "code\game\objects\items\devices\spy_bug.dm"
#include "code\game\objects\items\devices\suit_cooling.dm"
@@ -1580,6 +1582,12 @@
#include "code\game\objects\items\robot\robot_items.dm"
#include "code\game\objects\items\robot\robot_parts.dm"
#include "code\game\objects\items\robot\robot_upgrades.dm"
+#include "code\game\objects\items\scanners\atmos.dm"
+#include "code\game\objects\items\scanners\health.dm"
+#include "code\game\objects\items\scanners\plant.dm"
+#include "code\game\objects\items\scanners\reagent.dm"
+#include "code\game\objects\items\scanners\slime.dm"
+#include "code\game\objects\items\scanners\spectrometer.dm"
#include "code\game\objects\items\stacks\fifty_spawner.dm"
#include "code\game\objects\items\stacks\marker_beacons.dm"
#include "code\game\objects\items\stacks\matter_synth.dm"
@@ -2142,7 +2150,7 @@
#include "code\modules\asset_cache\assets\debug\fucky_wucky.dm"
#include "code\modules\asset_cache\transports\asset_transport.dm"
#include "code\modules\asset_cache\transports\webroot_transport.dm"
-#include "code\modules\atmospherics\atmosphere\_atmosphere.dm"
+#include "code\modules\atmospherics\atmosphere\atmosphere.dm"
#include "code\modules\atmospherics\atmosphere\planet.dm"
#include "code\modules\atmospherics\environmental\atom.dm"
#include "code\modules\atmospherics\environmental\public.dm"
@@ -2160,18 +2168,26 @@
#include "code\modules\atmospherics\environmental\zas\turf.dm"
#include "code\modules\atmospherics\environmental\zas\variable_settings.dm"
#include "code\modules\atmospherics\environmental\zas\zone.dm"
+#include "code\modules\atmospherics\gasmixtures\flow.dm"
+#include "code\modules\atmospherics\gasmixtures\gas_data.dm"
+#include "code\modules\atmospherics\gasmixtures\gas_generation.dm"
#include "code\modules\atmospherics\gasmixtures\gas_mixture.dm"
#include "code\modules\atmospherics\gasmixtures\gas_types.dm"
+#include "code\modules\atmospherics\gasmixtures\gas_types_hardcoded.dm"
#include "code\modules\atmospherics\gasmixtures\immutable_mixtures.dm"
#include "code\modules\atmospherics\gasmixtures\reactions.dm"
#include "code\modules\atmospherics\gasmixtures\transfer_helpers.dm"
+#include "code\modules\atmospherics\gasmixtures\gas_types\core.dm"
#include "code\modules\atmospherics\machinery\_atmos_setup.dm"
#include "code\modules\atmospherics\machinery\_atmospherics_helpers.dm"
+#include "code\modules\atmospherics\machinery\air_alarm.dm"
#include "code\modules\atmospherics\machinery\analyzer_hooks.dm"
#include "code\modules\atmospherics\machinery\datum_pipe_network.dm"
#include "code\modules\atmospherics\machinery\datum_pipeline.dm"
#include "code\modules\atmospherics\machinery\machinery.dm"
#include "code\modules\atmospherics\machinery\mainspipe.dm"
+#include "code\modules\atmospherics\machinery\meter.dm"
+#include "code\modules\atmospherics\machinery\components\component.dm"
#include "code\modules\atmospherics\machinery\components\binary_devices\algae_generator_vr.dm"
#include "code\modules\atmospherics\machinery\components\binary_devices\binary_atmos_base.dm"
#include "code\modules\atmospherics\machinery\components\binary_devices\circulator.dm"
@@ -2190,6 +2206,7 @@
#include "code\modules\atmospherics\machinery\components\omni_devices\omni_base.dm"
#include "code\modules\atmospherics\machinery\components\trinary_devices\filter.dm"
#include "code\modules\atmospherics\machinery\components\trinary_devices\mixer.dm"
+#include "code\modules\atmospherics\machinery\components\trinary_devices\molar_filter.dm"
#include "code\modules\atmospherics\machinery\components\trinary_devices\trinary_base.dm"
#include "code\modules\atmospherics\machinery\components\trinary_devices\tvalve.dm"
#include "code\modules\atmospherics\machinery\components\unary\cold_sink.dm"
@@ -2199,7 +2216,6 @@
#include "code\modules\atmospherics\machinery\components\unary\outlet_injector.dm"
#include "code\modules\atmospherics\machinery\components\unary\oxygen_generator.dm"
#include "code\modules\atmospherics\machinery\components\unary\portables_connector.dm"
-#include "code\modules\atmospherics\machinery\components\unary\retro_pump_and_scrubber.dm"
#include "code\modules\atmospherics\machinery\components\unary\unary_base.dm"
#include "code\modules\atmospherics\machinery\components\unary\vent_pump.dm"
#include "code\modules\atmospherics\machinery\components\unary\vent_scrubber.dm"
@@ -2212,6 +2228,12 @@
#include "code\modules\atmospherics\machinery\pipes\tank.dm"
#include "code\modules\atmospherics\machinery\pipes\universal.dm"
#include "code\modules\atmospherics\machinery\pipes\vent.dm"
+#include "code\modules\atmospherics\machinery\portable\area_atmos_computer.dm"
+#include "code\modules\atmospherics\machinery\portable\canister.dm"
+#include "code\modules\atmospherics\machinery\portable\portable_atmospherics.dm"
+#include "code\modules\atmospherics\machinery\portable\powered.dm"
+#include "code\modules\atmospherics\machinery\portable\pump.dm"
+#include "code\modules\atmospherics\machinery\portable\scrubber.dm"
#include "code\modules\automata\__automata.dm"
#include "code\modules\automata\_wave.dm"
#include "code\modules\automata\explosion.dm"
diff --git a/code/__DEFINES/atmospherics/constants.dm b/code/__DEFINES/atmospherics/constants.dm
index 1633b1de39d..f2b2a2c7a5d 100644
--- a/code/__DEFINES/atmospherics/constants.dm
+++ b/code/__DEFINES/atmospherics/constants.dm
@@ -1,11 +1,3 @@
-// Gas moles
-/// Moles in a tile after which gases are visible.
-#define MOLES_GAS_VISIBLE 0.25
-/// moles_visible * FACTOR_GAS_VISIBLE_MAX = Moles after which gas is at maximum visibility.
-#define FACTOR_GAS_VISIBLE_MAX 20
-/// Mole step for alpha updates. This means alpha can update at 0.25, 0.5, 0.75, and so on.
-#define MOLES_GAS_VISIBLE_STEP 0.25
-
/// Volume, in liters, of a single tile. Y'KNOW WHY THIS IS A CONSTANT? WELL FOR ONE, initial_gas_mix IS MOLES, NOT PERCENTAGES OR PRESSURES. IF YOU TOUCH THIS, YOU BREAK *EVERYTHING*. DON'T TOUCH THIS.
/// Exceptions can be made if you're a big boy who knows how this clusterfuck of a ZAS/LINDA hybrid works and have good reason to touch this.
#define CELL_VOLUME 2500
diff --git a/code/__DEFINES/atmospherics/flags.dm b/code/__DEFINES/atmospherics/flags.dm
deleted file mode 100644
index ee5e62a362a..00000000000
--- a/code/__DEFINES/atmospherics/flags.dm
+++ /dev/null
@@ -1,13 +0,0 @@
-// /datum/gas/gas_flags flags
-/// Is this gas a viable combustion fuel? Ported from XGM variable oxidizer-oxidant combustion system.
-#define GAS_FLAG_FUEL (1<<0)
-/// Is this gas a viable combustion oxidizer? Ported from XGM variable oxidizer-oxidant combustion system.
-#define GAS_FLAG_OXIDIZER (1<<1)
-/// Is this gas able to contaminate things like clothing? ZAS phoron contaminent memes.
-#define GAS_FLAG_CONTAMINANT (1<<2)
-/// Is this gas a valid R-UST fusion fuel? Has NOTHING to do with tg fusion!
-#define GAS_FLAG_FUSION_FUEL (1<<3)
-
-// /turf/var/air_flags flags
-/// If this is there, the turf will apply graphics even if air status isn't AIR_STATUS_NORMAL. Otherwise, the turf will have no gas graphics, ever.
-#define AIR_FLAG_FORCE_VISUAL (1<<0)
diff --git a/code/__DEFINES/atmospherics/gases/flags.dm b/code/__DEFINES/atmospherics/gases/flags.dm
new file mode 100644
index 00000000000..a9588f4f451
--- /dev/null
+++ b/code/__DEFINES/atmospherics/gases/flags.dm
@@ -0,0 +1,29 @@
+//! flags
+// /datum/gas/gas_flags flags
+/// Is this gas a viable combustion fuel? Ported from XGM variable oxidizer-oxidant combustion system.
+#define GAS_FLAG_FUEL (1<<0)
+/// Is this gas a viable combustion oxidizer? Ported from XGM variable oxidizer-oxidant combustion system.
+#define GAS_FLAG_OXIDIZER (1<<1)
+/// Is this gas able to contaminate things like clothing? ZAS phoron contaminent memes.
+#define GAS_FLAG_CONTAMINANT (1<<2)
+/// Is this gas a valid R-UST fusion fuel? Has NOTHING to do with tg fusion!
+#define GAS_FLAG_FUSION_FUEL (1<<3)
+/// gas is unknown/alien
+#define GAS_FLAG_UNKNOWN (1<<4)
+/// gas is core gas, always render & on main filter options. implies [GAS_FLAG_FILTERABLE].
+#define GAS_FLAG_CORE (1<<5)
+/// gas is common enough to be filtered by machinery without spectrometry/grouping
+#define GAS_FLAG_FILTERABLE (1<<6)
+/// gas is highly dangerous, mostly used to know when to *loudly* log as opposed to just log
+#define GAS_FLAG_DANGEROUS (1<<7)
+
+DEFINE_BITFIELD(gas_flags, list(
+ BITFIELD(GAS_FLAG_FUEL),
+ BITFIELD(GAS_FLAG_OXIDIZER),
+ BITFIELD(GAS_FLAG_CONTAMINANT),
+ BITFIELD(GAS_FLAG_FUSION_FUEL),
+ BITFIELD(GAS_FLAG_UNKNOWN),
+ BITFIELD(GAS_FLAG_CORE),
+ BITFIELD(GAS_FLAG_FILTERABLE),
+ BITFIELD(GAS_FLAG_DANGEROUS),
+))
diff --git a/code/__DEFINES/atmospherics/gases/generation.dm b/code/__DEFINES/atmospherics/gases/generation.dm
new file mode 100644
index 00000000000..3b378114e39
--- /dev/null
+++ b/code/__DEFINES/atmospherics/gases/generation.dm
@@ -0,0 +1,21 @@
+//* Collisions
+
+/// collision threshold for molar mass on generated gasses
+#define GAS_COLLISION_THRESHOLD_MOLAR_MASS 0.09
+/// factor used because rand() doesn't do decimals.
+#define GAS_COLLISION_FACTOR_MOLAR_MASS (1 / GAS_COLLISION_THRESHOLD_MOLAR_MASS)
+
+//* Danger flags
+
+/// allow oxidizer
+#define PROCEDURAL_GAS_ALLOW_OXIDIZER (1<<0)
+/// allow fuel
+#define PROCEDURAL_GAS_ALLOW_FUEL (1<<1)
+/// can be both oxidizer and fuel. !!DANGEROUS!!
+#define PROCEDURAL_GAS_ALLOW_UNARY_BURNMIX (1<<2)
+
+DEFINE_BITFIELD(procedural_gas_flags, list(
+ BITFIELD(PROCEDURAL_GAS_ALLOW_OXIDIZER),
+ BITFIELD(PROCEDURAL_GAS_ALLOW_FUEL),
+ BITFIELD(PROCEDURAL_GAS_ALLOW_UNARY_BURNMIX),
+))
diff --git a/code/__DEFINES/atmospherics/gases/groups.dm b/code/__DEFINES/atmospherics/gases/groups.dm
new file mode 100644
index 00000000000..697398b08b2
--- /dev/null
+++ b/code/__DEFINES/atmospherics/gases/groups.dm
@@ -0,0 +1,33 @@
+//! groups
+/// Should always and only include gasses that are directly selectable on interfaces.
+/// Anything not in here should be in one or more of the other groups.
+/// Anything in this group should have the CORE and FILTERABLE flag
+/// This should not be a lot of gases! Air alarms, amongst other things, genreate lists for these gases.
+#define GAS_GROUP_CORE (1<<0)
+/// Should include all non-core gasses.
+/// Shows up on misc lists.
+#define GAS_GROUP_OTHER (1<<1)
+/// Unknown / generated gases
+#define GAS_GROUP_UNKNOWN (1<<2)
+/// chemicals
+#define GAS_GROUP_REAGENT (1<<3)
+
+/// Total gas groups
+#define GAS_GROUP_COUNT 4
+
+DEFINE_BITFIELD(gas_groups, list(
+ BITFIELD(GAS_GROUP_CORE),
+ BITFIELD(GAS_GROUP_OTHER),
+ BITFIELD(GAS_GROUP_UNKNOWN),
+ BITFIELD(GAS_GROUP_REAGENT),
+))
+
+GLOBAL_REAL_LIST(gas_group_names) = list(
+ "Core",
+ "Other",
+ "Unknown",
+ "Reagents",
+)
+
+#define GAS_GROUPS_FILTERABLE (GAS_GROUP_UNKNOWN | GAS_GROUP_REAGENT | GAS_GROUP_OTHER)
+#define GAS_GROUPS_ALL (GAS_GROUP_CORE | GAS_GROUP_UNKNOWN | GAS_GROUP_OTHER | GAS_GROUP_REAGENT)
diff --git a/code/__DEFINES/atmospherics/gases/ids.dm b/code/__DEFINES/atmospherics/gases/ids.dm
new file mode 100644
index 00000000000..6e710b4f8da
--- /dev/null
+++ b/code/__DEFINES/atmospherics/gases/ids.dm
@@ -0,0 +1,49 @@
+/**
+ * !! THESE PREFIXES ARE RESERVED. !!
+ * !! No touchies. !!
+ * !! They're used for randomgen !!
+ * !! and we don't want collisions.!!
+ * ?? These can be changed a will, ??
+ * ?? as they will probably never ??
+ * ?? be directly mapped in. ??
+ */
+/// prefix reserved for dynamic chemgas later
+#define GAS_ID_PREFIX_REAGENTS "r_"
+/// random generated gas
+#define GAS_ID_PREFIX_PROCGEN "x_"
+
+/**
+ * !! Hardcoded Gas IDs !!
+ * ?? Naming convention: ??
+ * ?? Use chemical formula if based off ??
+ * ?? of a real gas, otherwise make ??
+ * ?? something that doesn't collide ??
+ * ?? with the dynamic prefixes that ??
+ * ?? makes sense like "fuel", "phoron". ??
+ * !! These may not be changed at will. !!
+ * !! Maps can contain them sometimes, !!
+ * !! so changing them will break things !!
+ */
+#define GAS_ID_OXYGEN "o2"
+#define GAS_ID_NITROGEN "n2"
+#define GAS_ID_CARBON_DIOXIDE "co2"
+#define GAS_ID_PHORON "phoron"
+#define GAS_ID_VOLATILE_FUEL "fuel"
+#define GAS_ID_NITROUS_OXIDE "n2o"
+#define GAS_ID_HELIUM "he"
+#define GAS_ID_CARBON_MONOXIDE "co"
+#define GAS_ID_METHYL_BROMIDE "ch3br"
+#define GAS_ID_NITROGEN_DIOXIDE "no2"
+#define GAS_ID_NITRIC_OXIDE "no"
+#define GAS_ID_METHANE "ch4"
+#define GAS_ID_ARGON "ar"
+#define GAS_ID_KRYPTON "kr"
+#define GAS_ID_NEON "ne"
+#define GAS_ID_AMMONIA "nh3"
+#define GAS_ID_XENON "xe"
+#define GAS_ID_CHLORINE "cl2"
+#define GAS_ID_SULFUR_DIOXIDE "so2"
+#define GAS_ID_HYDROGEN "hydrogen"
+#define GAS_ID_DEUTERIUM "deuterium"
+#define GAS_ID_TRITIUM "tritium"
+#define GAS_ID_VIMUR "vimur"
diff --git a/code/__DEFINES/atmospherics/gases/reagents.dm b/code/__DEFINES/atmospherics/gases/reagents.dm
new file mode 100644
index 00000000000..2b6b0574f0b
--- /dev/null
+++ b/code/__DEFINES/atmospherics/gases/reagents.dm
@@ -0,0 +1,6 @@
+//! reagent list indices
+#define GAS_REAGENT_LIST_ID 1
+#define GAS_REAGENT_LIST_AMOUNT 2
+#define GAS_REAGENT_LIST_THRESHOLD 3
+#define GAS_REAGENT_LIST_FACTOR 4
+#define GAS_REAGENT_LIST_MAX 5
diff --git a/code/__DEFINES/atmospherics/gases/visuals.dm b/code/__DEFINES/atmospherics/gases/visuals.dm
new file mode 100644
index 00000000000..fe19270ddb4
--- /dev/null
+++ b/code/__DEFINES/atmospherics/gases/visuals.dm
@@ -0,0 +1,8 @@
+//! visuals list defines
+#define GAS_VISUAL_INDEX_STATE 1
+#define GAS_VISUAL_INDEX_THRESHOLD 2
+#define GAS_VISUAL_INDEX_FACTOR 3
+
+//! caching system
+/// levels of alpha to make
+#define GAS_VISUAL_STEP_MAX 20
diff --git a/code/__DEFINES/atmospherics/helpers.dm b/code/__DEFINES/atmospherics/helpers.dm
index 5b015180a43..4ca34abec2d 100644
--- a/code/__DEFINES/atmospherics/helpers.dm
+++ b/code/__DEFINES/atmospherics/helpers.dm
@@ -1,7 +1,7 @@
//! gasmixtures
#define THERMAL_ENERGY(gas) (gas.temperature * gas.heat_capacity())
/// Atmospherics quantization define.
-#define QUANTIZE(variable) (round(variable,0.0000001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that
+#define QUANTIZE(variable) (round(variable,0.00001))/*I feel the need to document what happens here. Basically this is used to catch most rounding errors, however it's previous value made it so that
once gases got hot enough, most procedures wouldnt occur due to the fact that the mole counts would get rounded away. Thus, we lowered it a few orders of magnititude */
/// Writes the total moles of cached gases gaslist to out_var. Use this to spare a proccall in performance critical areas.
diff --git a/code/__DEFINES/atmospherics/machinery.dm b/code/__DEFINES/atmospherics/machinery.dm
new file mode 100644
index 00000000000..f15b3053b36
--- /dev/null
+++ b/code/__DEFINES/atmospherics/machinery.dm
@@ -0,0 +1,64 @@
+//* Balancing - Efficiency *//
+
+/// Global power efficiency of pumps - enforced by transfer helpers, not the machine!
+#define ATMOS_ABSTRACT_PUMP_EFFICIENCY 2.5
+/// Global power efficiency of scrubbing - enforced by transfer helpers, not the machine!
+#define ATMOS_ABSTRACT_SCRUB_EFFICIENCY 2.5
+/// Global power efficiency of filtering - enforced by transfer helpers, not the machine!
+#define ATMOS_ABSTRACT_FILTER_EFFICIENCY 2.5
+
+//* /obj/machinery/atmospherics/component atmos_component_ui_flags
+
+/// allow toggling power
+#define ATMOS_COMPONENT_UI_TOGGLE_POWER (1<<0)
+/// allow setting power. implies SEE_POWER.
+#define ATMOS_COMPONENT_UI_SET_POWER (1<<1)
+/// render power usage
+#define ATMOS_COMPONENT_UI_SEE_POWER (1<<2)
+
+DEFINE_BITFIELD(atmos_component_ui_flags, list(
+ BITFIELD(ATMOS_COMPONENT_UI_TOGGLE_POWER),
+ BITFIELD(ATMOS_COMPONENT_UI_SET_POWER),
+ BITFIELD(ATMOS_COMPONENT_UI_SEE_POWER),
+))
+
+//* /obj/machinery/portable_atmospherics atmos_portable_ui_flags
+
+/// view flow
+#define ATMOS_PORTABLE_UI_SEE_FLOW (1<<0)
+/// toggle on/off
+#define ATMOS_PORTABLE_UI_TOGGLE_POWER (1<<1)
+/// adjust flow. implies SEE_FLOW.
+#define ATMOS_PORTABLE_UI_SET_FLOW (1<<2)
+/// adjust power. implies SEE_POWER.
+#define ATMOS_PORTABLE_UI_SET_POWER (1<<3)
+/// see power
+#define ATMOS_PORTABLE_UI_SEE_POWER (1<<4)
+
+DEFINE_BITFIELD(atmos_portable_ui_flags, list(
+ BITFIELD(ATMOS_PORTABLE_UI_SEE_FLOW),
+ BITFIELD(ATMOS_PORTABLE_UI_TOGGLE_POWER),
+ BITFIELD(ATMOS_PORTABLE_UI_SET_FLOW),
+ BITFIELD(ATMOS_PORTABLE_UI_SET_POWER),
+ BITFIELD(ATMOS_PORTABLE_UI_SEE_POWER),
+))
+
+//! legacy
+
+// Will not bother pumping or filtering if the gas source as fewer than this amount of moles, to help with performance.
+#define MINIMUM_MOLES_TO_PUMP 0.00001
+#define MINIMUM_MOLES_TO_FILTER 0.00001
+#define MINIMUM_MOLES_TO_SCRUB 0.00001
+// fire sparking
+#define MINIMUM_MOLES_TO_SPARK 0.015
+
+// The flow rate/effectiveness of various atmos devices is limited by their internal volume,
+// so for many atmos devices these will control maximum flow rates in L/s.
+/// Liters.
+#define ATMOS_DEFAULT_VOLUME_PUMP 200
+/// L.
+#define ATMOS_DEFAULT_VOLUME_FILTER 200
+/// L.
+#define ATMOS_DEFAULT_VOLUME_MIXER 200
+/// L.
+#define ATMOS_DEFAULT_VOLUME_PIPE 70
diff --git a/code/__DEFINES/atmospherics/machinery/alarm.dm b/code/__DEFINES/atmospherics/machinery/alarm.dm
new file mode 100644
index 00000000000..6edd4ef3d2f
--- /dev/null
+++ b/code/__DEFINES/atmospherics/machinery/alarm.dm
@@ -0,0 +1,38 @@
+//* air alarm mode
+/// Everything off
+#define AIR_ALARM_MODE_OFF "off"
+/// vents on, scrubbers scrub
+#define AIR_ALARM_MODE_SCRUB "scrub"
+/// vents on, scrubbers siphon
+#define AIR_ALARM_MODE_REPLACE "replace"
+/// vents off, scrubbers siphon
+#define AIR_ALARM_MODE_SIPHON "siphon"
+/// siphon and then switch to scrub
+#define AIR_ALARM_MODE_CYCLE "cycle"
+/// panic siphon - rapidly siphon all air out of room
+#define AIR_ALARM_MODE_PANIC "panic"
+/// contaminated - high power scrub
+#define AIR_ALARM_MODE_CONTAMINATED "contaminated"
+/// vents on, scrubbers off
+#define AIR_ALARM_MODE_FILL "fill"
+
+//* air alarm danger state
+#define AIR_ALARM_RAISE_OKAY 0
+#define AIR_ALARM_RAISE_WARNING 1
+#define AIR_ALARM_RAISE_DANGER 2
+
+//* air alarm TLV operatoins
+#define AIR_ALARM_TLV_DANGER_LOW 1
+#define AIR_ALARM_TLV_WARNING_LOW 2
+#define AIR_ALARM_TLV_WARNING_HIGH 3
+#define AIR_ALARM_TLV_DANGER_HIGH 4
+
+#define AIR_ALARM_TLV_INDEX_MIN 1
+#define AIR_ALARM_TLV_INDEX_MAX 4
+
+#define AIR_ALARM_MAKE_TLV(mindanger, minwarning, maxwarning, maxdanger) list(mindanger, minwarning, maxwarning, maxdanger)
+#define AIR_ALARM_TEST_TLV(val, list) ( \
+ ((val < list[AIR_ALARM_TLV_DANGER_LOW]) || (val > list[AIR_ALARM_TLV_DANGER_HIGH]))? \
+ AIR_ALARM_RAISE_DANGER : ((val < list[AIR_ALARM_TLV_WARNING_LOW] || val > list[AIR_ALARM_TLV_WARNING_HIGH])? \
+ AIR_ALARM_RAISE_WARNING : AIR_ALARM_RAISE_OKAY) \
+ )
diff --git a/code/__DEFINES/atmospherics/machinery/scrubber.dm b/code/__DEFINES/atmospherics/machinery/scrubber.dm
new file mode 100644
index 00000000000..de83d9a536a
--- /dev/null
+++ b/code/__DEFINES/atmospherics/machinery/scrubber.dm
@@ -0,0 +1,5 @@
+//* Scrubber default enums - these must be on SSair!
+#define SCRUBBER_DEFAULT_STATION "station"
+
+/// max ids scrubbers can scrub at once
+#define SCRUBBER_MAX_GAS_IDS 10
diff --git a/code/__DEFINES/atmospherics/machinery/vent.dm b/code/__DEFINES/atmospherics/machinery/vent.dm
new file mode 100644
index 00000000000..94a98a6597e
--- /dev/null
+++ b/code/__DEFINES/atmospherics/machinery/vent.dm
@@ -0,0 +1,13 @@
+/// check to make sure external gas pressure doesn't drop below (pump in)
+/// or rise above (pump out) a certain pressure
+#define ATMOS_VENT_CHECK_EXTERNAL (1<<0)
+/// check to make sure internal gas pressure doesn't drop below (pump out)
+/// or rise above (pump in) a certain pressure
+#define ATMOS_VENT_CHECK_INTERNAL (1<<1)
+/// all bits in check bitfield
+#define ATMOS_VENT_CHECKS (ATMOS_VENT_CHECK_EXTERNAL | ATMOS_VENT_CHECK_INTERNAL)
+
+#define ATMOS_VENT_DIRECTION_SIPHON 0
+#define ATMOS_VENT_DIRECTION_RELEASE 1
+
+#define ATMOS_VENT_MAX_PRESSURE_LIMIT (ONE_ATMOSPHERE * 500)
diff --git a/code/__DEFINES/machinery.dm b/code/__DEFINES/machinery.dm
index 97c31383a26..87eea72002c 100644
--- a/code/__DEFINES/machinery.dm
+++ b/code/__DEFINES/machinery.dm
@@ -135,35 +135,6 @@ var/list/restricted_camera_networks = list(NETWORK_ERT,NETWORK_MERCENARY,"Secret
#define STAGE_FIVE 9
#define STAGE_SUPER 11
-/**
- *! Atmospherics Machinery.
- */
-/// L/s. This can be used to balance how fast a room is siphoned. Anything higher than CELL_VOLUME has no effect.
-#define MAX_SIPHON_FLOWRATE 2500
-/// L/s. Max flow rate when scrubbing from a turf.
-#define MAX_SCRUBBER_FLOWRATE 200
-// These balance how easy or hard it is to create huge pressure gradients with pumps and filters.
-// Lower values means it takes longer to create large pressures differences.
-// Has no effect on pumping gasses from high pressure to low, only from low to high.
-#define ATMOS_PUMP_EFFICIENCY 2.5
-#define ATMOS_FILTER_EFFICIENCY 2.5
-
-// Will not bother pumping or filtering if the gas source as fewer than this amount of moles, to help with performance.
-#define MINIMUM_MOLES_TO_PUMP 0.01
-#define MINIMUM_MOLES_TO_FILTER 0.01
-// fire sparking
-#define MINIMUM_MOLES_TO_SPARK 0.015
-
-// The flow rate/effectiveness of various atmos devices is limited by their internal volume,
-// so for many atmos devices these will control maximum flow rates in L/s.
-/// Liters.
-#define ATMOS_DEFAULT_VOLUME_PUMP 200
-/// L.
-#define ATMOS_DEFAULT_VOLUME_FILTER 200
-/// L.
-#define ATMOS_DEFAULT_VOLUME_MIXER 200
-/// L.
-#define ATMOS_DEFAULT_VOLUME_PIPE 70
// These are used by supermatter and supermatter monitor program, mostly for UI updating purposes. Higher should always be worse!
/// Unknown status, shouldn't happen but just in case.
#define SUPERMATTER_ERROR -1
diff --git a/code/__DEFINES/procs/do_after.dm b/code/__DEFINES/procs/do_after.dm
index 24d4fd21fd5..e517c8c53c2 100644
--- a/code/__DEFINES/procs/do_after.dm
+++ b/code/__DEFINES/procs/do_after.dm
@@ -54,3 +54,5 @@
#define INTERACTING_FOR_RESIST "resist"
/// Climbing a structure
#define INTERACTING_FOR_CLIMB "climb"
+/// Construct/deconstruct
+#define INTERACTING_FOR_CONSTRUCTION "construction"
diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm
index 4d121bd82ed..749a21bd200 100644
--- a/code/__HELPERS/unsorted.dm
+++ b/code/__HELPERS/unsorted.dm
@@ -1080,7 +1080,7 @@
* TODO - Fix this ancient list of wall items. Preferably make it dynamically populated. ~Leshana
*/
var/list/WALLITEMS = list(
- /obj/machinery/power/apc, /obj/machinery/alarm, /obj/item/radio/intercom, /obj/structure/frame,
+ /obj/machinery/power/apc, /obj/machinery/air_alarm, /obj/item/radio/intercom, /obj/structure/frame,
/obj/structure/extinguisher_cabinet, /obj/structure/reagent_dispensers/peppertank,
/obj/machinery/status_display, /obj/machinery/requests_console, /obj/machinery/light_switch, /obj/structure/sign,
/obj/machinery/newscaster, /obj/machinery/firealarm, /obj/structure/noticeboard, /obj/machinery/button/remote,
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index 8caf16342e5..871fb553600 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -19,6 +19,10 @@ SUBSYSTEM_DEF(air)
var/list/generated_atmospheres
/// cached lists of unpacked gas-strings
var/list/cached_strings
+ /// scrubber defaults
+ var/list/scrubber_defaults = list(
+ SCRUBBER_DEFAULT_STATION = list(/datum/gas/carbon_dioxide, /datum/gas/phoron),
+ )
var/cost_turfs = 0
var/cost_edges = 0
@@ -57,7 +61,20 @@ SUBSYSTEM_DEF(air)
return ..() + " [msg.Join()]"
/datum/controller/subsystem/air/PreInit(recovering)
- air_master = src
+ // resolve scrubber defaults
+ for(var/key in scrubber_defaults)
+ var/list/values = scrubber_defaults[key]
+ if(isnull(values))
+ scrubber_defaults -= key
+ stack_trace("invalid default [key] in scrubber_defaults")
+ continue
+ for(var/i in 1 to length(values))
+ if(ispath(values[i]))
+ var/datum/gas/casted = values[i]
+ if(!ispath(casted, /datum/gas))
+ stack_trace("invalid path [casted] in scrubber_defaults")
+ casted = /datum/gas/oxygen
+ values[i] = initial(casted.id)
/datum/controller/subsystem/air/Preload(recovering)
cached_strings = list()
@@ -132,7 +149,7 @@ SUBSYSTEM_DEF(air)
INTERNAL_PROCESS_STEP(SSAIR_HOTSPOTS, FALSE, process_active_hotspots, cost_hotspots, SSAIR_ZONES)
INTERNAL_PROCESS_STEP(SSAIR_ZONES, FALSE, process_zones_to_update, cost_zones, SSAIR_DONE)
- // Okay, we're done! Woo! Got thru a whole air_master cycle!
+ // Okay, we're done! Woo! Got thru a whole SSair cycle!
if(LAZYLEN(currentrun) != 0)
stack_trace("Currentrun not empty after processing cycle when it should be. [english_list(currentrun.Copy(1, min(currentrun.len, 5)))]")
currentrun = null
@@ -320,11 +337,14 @@ SUBSYSTEM_DEF(air)
if(initial(A.abstract_type) == T)
continue
A = new T
+ // register normal id
generated_atmospheres[A.id] = A
+ // register type too - for faster lookup
+ generated_atmospheres[A.type] = A
/**
* parses a gas string
- * returns list(gas list, temp)
+ * returns list(gas list, temp); gas list is cached, make sure to copy it!!!
*
* @params
* - gas_string - gas string
@@ -353,17 +373,11 @@ SUBSYSTEM_DEF(air)
/datum/controller/subsystem/air/proc/unpack_gas_string(gas_string)
var/list/built = new /list(2)
var/list/unpacked = params2list(gas_string)
- var/list/gases = list()
built[2] = text2num(unpacked["TEMP"]) // null allowed
unpacked -= "TEMP"
- // convert id to path
- // todo: remove when we convert gas to ids and not paths why did we ever make it paths aough
- for(var/i in 1 to length(unpacked))
- var/id = unpacked[i]
- var/amount = text2num(unpacked[id])
- var/path = GLOB.meta_gas_id_lookup[id]
- gases[path] = amount
- built[1] = gases
+ for(var/id in unpacked)
+ unpacked[id] = text2num(unpacked[id])
+ built[1] = unpacked
return built
#undef SSAIR_TURFS
diff --git a/code/controllers/subsystem/plants.dm b/code/controllers/subsystem/plants.dm
index a4c5c5bba92..403eb196781 100644
--- a/code/controllers/subsystem/plants.dm
+++ b/code/controllers/subsystem/plants.dm
@@ -111,8 +111,8 @@ SUBSYSTEM_DEF(plants)
if(survive_on_station)
if(seed.consume_gasses)
- seed.consume_gasses[/datum/gas/phoron] = null
- seed.consume_gasses[/datum/gas/carbon_dioxide] = null
+ seed.consume_gasses[GAS_ID_PHORON] = null
+ seed.consume_gasses[GAS_ID_CARBON_DIOXIDE] = null
if(seed.chems && !isnull(seed.chems["pacid"]))
seed.chems["pacid"] = null //Eating through the hull will make these plants completely inviable, albeit very dangerous.
seed.chems -= null //Setting to null does not actually remove the entry, which is weird.
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index 03ab864a634..2d9c11c27ee 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -79,9 +79,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
if(!holder)
return
var/list/options = list()
- options["MC"] = Master
- options["Failsafe"] = Failsafe
- options["Configuration"] = config
+ options["MC"] = global.Master
+ options["Failsafe"] = global.Failsafe
+ options["Global Variables"] = global.GLOB
+ options["Configuration"] = global.config
+ options["Gas Data"] = global.gas_data
options["Legacy Configuration"] = config_legacy
for(var/i in Master.subsystems)
var/datum/controller/subsystem/S = i
@@ -97,7 +99,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
options[strtype] = S
//Goon PS stuff, and other yet-to-be-subsystem things.
- options["LEGACY: air_master"] = air_master
options["LEGACY: radio_controller"] = radio_controller
options["LEGACY: paiController"] = paiController
options["LEGACY: GLOB.cameranet"] = GLOB.cameranet
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index 11138052b04..bcf6f22c232 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -397,7 +397,7 @@
reqs = list(/obj/item/stock_parts/subspace/amplifier = 1,
/obj/item/stack/cable_coil = 15,
/obj/item/radio = 1,
- /obj/item/analyzer = 1)
+ /obj/item/atmos_analyzer = 1)
subcategory = CAT_MISCELLANEOUS
category = CAT_MISCELLANEOUS
diff --git a/code/datums/wires/alarm.dm b/code/datums/wires/alarm.dm
index d90463e2c59..a84752f0482 100644
--- a/code/datums/wires/alarm.dm
+++ b/code/datums/wires/alarm.dm
@@ -1,5 +1,5 @@
/datum/wires/alarm
- holder_type = /obj/machinery/alarm
+ holder_type = /obj/machinery/air_alarm
wire_count = 5
proper_name = "Air alarm"
@@ -12,20 +12,20 @@
/datum/wires/alarm/interactable(mob/user)
- var/obj/machinery/alarm/A = holder
+ var/obj/machinery/air_alarm/A = holder
if(A.panel_open)
return TRUE
return FALSE
/datum/wires/alarm/get_status()
- var/obj/machinery/alarm/A = holder
+ var/obj/machinery/air_alarm/A = holder
. = ..()
. += "The Air Alarm is [A.locked ? "locked." : "unlocked."]"
. += "The Air Alarm is [(A.shorted || (A.machine_stat & (NOPOWER|BROKEN))) ? "offline." : "working properly!"]"
. += "The 'AI control allowed' light is [A.aidisabled ? "off" : "on"]."
/datum/wires/alarm/on_cut(wire, mend)
- var/obj/machinery/alarm/A = holder
+ var/obj/machinery/air_alarm/A = holder
switch(wire)
if(WIRE_IDSCAN)
if(!mend)
@@ -51,7 +51,7 @@
..()
/datum/wires/alarm/on_pulse(wire)
- var/obj/machinery/alarm/A = holder
+ var/obj/machinery/air_alarm/A = holder
switch(wire)
if(WIRE_IDSCAN)
A.locked = !A.locked
diff --git a/code/game/area/area.dm b/code/game/area/area.dm
index 17cfdbec1ca..d0329eb5e68 100644
--- a/code/game/area/area.dm
+++ b/code/game/area/area.dm
@@ -33,6 +33,14 @@
//? tracking lists for machinery
/// holopads - lazyinit'd
var/list/obj/machinery/holopad/holopads
+ /// vents
+ var/list/obj/machinery/atmospherics/component/unary/vent_pump/vent_pumps
+ /// next vent id
+ var/vent_pump_next = 1
+ /// scrubbers
+ var/list/obj/machinery/atmospherics/component/unary/vent_scrubber/vent_scrubbers
+ /// next scrubber id
+ var/vent_scrubber_next = 1
//? unsorted
var/fire = null
@@ -261,14 +269,14 @@
if (danger_level == 0)
atmosphere_alarm.clearAlarm(src, alarm_source)
else
- var/obj/machinery/alarm/atmosalarm = alarm_source //maybe other things can trigger these, who knows
+ var/obj/machinery/air_alarm/atmosalarm = alarm_source //maybe other things can trigger these, who knows
if(istype(atmosalarm))
atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level, hidden = atmosalarm.alarms_hidden)
else
atmosphere_alarm.triggerAlarm(src, alarm_source, severity = danger_level)
//Check all the alarms before lowering atmosalm. Raising is perfectly fine.
- for (var/obj/machinery/alarm/AA as anything in GLOB.air_alarms)
+ for (var/obj/machinery/air_alarm/AA as anything in GLOB.air_alarms)
if(AA.loc?.loc != src)
continue
if (!(AA.machine_stat & (NOPOWER|BROKEN)) && !AA.shorted && AA.report_danger_level)
@@ -280,7 +288,7 @@
if (danger_level < 1 || danger_level >= 2)
firedoors_update()
- for (var/obj/machinery/alarm/AA in src)
+ for (var/obj/machinery/air_alarm/AA in src)
AA.update_icon()
return 1
@@ -672,3 +680,49 @@ var/list/ghostteleportlocs = list()
ghostteleportlocs = tim_sort(ghostteleportlocs, GLOBAL_PROC_REF(cmp_text_asc), TRUE)
return 1
+
+//* Atmospherics
+
+/area/proc/register_scrubber(obj/machinery/atmospherics/component/unary/vent_scrubber/instance)
+ LAZYADD(vent_scrubbers, instance)
+ instance.name = "\improper [name] Vent Scrubber #[vent_scrubber_next++]"
+
+/area/proc/unregister_scrubber(obj/machinery/atmospherics/component/unary/vent_scrubber/instance)
+ LAZYREMOVE(vent_scrubbers, instance)
+ instance.name = "\improper Vent Scrubber"
+
+/area/proc/register_vent(obj/machinery/atmospherics/component/unary/vent_pump/instance)
+ LAZYADD(vent_pumps, instance)
+ instance.name = "\improper [name] Vent Pump #[vent_pump_next++]"
+
+/area/proc/unregister_vent(obj/machinery/atmospherics/component/unary/vent_pump/instance)
+ LAZYREMOVE(vent_pumps, instance)
+ instance.name = "\improper Vent Pump"
+
+// todo: this should unregister first, probably, incase anyone makes registration non-repeatable
+
+/area/proc/reregister_atmos_machinery()
+ reregister_atmos_vents()
+ reregister_atmos_scrubbers()
+
+/area/proc/reregister_atmos_vents()
+ vent_pump_next = 0
+ var/list/old = vent_pumps.Copy()
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/instance in old)
+ register_vent(instance)
+
+/area/proc/reregister_atmos_scrubbers()
+ vent_scrubber_next = 0
+ var/list/old = vent_scrubbers.Copy()
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/instance in old)
+ register_scrubber(instance)
+
+/area/proc/vent_pump_by_id(id)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in vent_pumps)
+ if(pump.id_tag == id)
+ return pump
+
+/area/proc/vent_scrubber_by_id(id)
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in vent_scrubbers)
+ if(scrubber.id_tag == id)
+ return scrubber
diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm
index fd167f19fc3..a40b745449c 100644
--- a/code/game/gamemodes/objective.dm
+++ b/code/game/gamemodes/objective.dm
@@ -510,7 +510,7 @@ var/global/list/all_objectives = list()
for(var/obj/item/I in all_items) //Check for phoron tanks
if(istype(I, steal_target))
- found_amount += (target_name=="28 moles of phoron (full tank)" ? (I:air_contents:gas[/datum/gas/phoron]) : (I:amount))
+ found_amount += (target_name=="28 moles of phoron (full tank)" ? (I:air_contents:gas[GAS_ID_PHORON]) : (I:amount))
return found_amount>=target_amount
if("50 coins (in bag)")
diff --git a/code/game/gamemodes/technomancer/spells/oxygenate.dm b/code/game/gamemodes/technomancer/spells/oxygenate.dm
index 416bbedfd50..3b79fb9846f 100644
--- a/code/game/gamemodes/technomancer/spells/oxygenate.dm
+++ b/code/game/gamemodes/technomancer/spells/oxygenate.dm
@@ -27,7 +27,7 @@
else if(isturf(hit_atom))
var/turf/T = hit_atom
if(pay_energy(1500))
- T.assume_gas(/datum/gas/oxygen, 200)
- T.assume_gas(/datum/gas/nitrogen, 800)
+ T.assume_gas(GAS_ID_OXYGEN, 200)
+ T.assume_gas(GAS_ID_NITROGEN, 800)
playsound(src.loc, 'sound/effects/spray.ogg', 50, 1, -3)
adjust_instability(10)
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index 2e7021f1042..f3d9a4160ee 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -108,6 +108,8 @@
// todo: anchored / unanchored should be replaced by movement force someday, how to handle that?
//* Construction / Deconstruction
+ /// allow default part replacement. null for disallowed, number for time.
+ var/default_part_replacement = 0
/// Can be constructed / deconstructed by players by default. null for off, number for time needed. Panel must be open.
// todo: proc for allow / disallow, refactor
var/default_deconstruct
@@ -116,8 +118,12 @@
/// Can be anchored / unanchored by players without deconstructing by default with a wrench. null for off, number for time needed.
// todo: proc for allow / disallow, refactor, unify with can_be_unanchored
var/default_unanchor
- /// allow default part replacement. null for disallowed, number for time.
- var/default_part_replacement = 0
+ /// tool used for deconstruction
+ var/tool_deconstruct = TOOL_CROWBAR
+ /// tool used for panel open
+ var/tool_panel = TOOL_SCREWDRIVER
+ /// tool used for unanchor
+ var/tool_unanchor = TOOL_WRENCH
/// default icon state overlay for panel open
var/panel_icon_state
/// is the maintenance panel open?
@@ -195,50 +201,6 @@
qdel(A)
return ..()
-/obj/machinery/screwdriver_act(obj/item/I, mob/user, flags, hint)
- if(!isnull(default_panel))
- default_deconstruction_screwdriver(user, I)
- return TRUE
- return ..()
-
-/obj/machinery/crowbar_act(obj/item/I, mob/user, flags, hint)
- if(!isnull(default_deconstruct))
- default_deconstruction_crowbar(user, I)
- return TRUE
- return ..()
-
-/obj/machinery/wrench_act(obj/item/I, mob/user, flags, hint)
- if(!isnull(default_unanchor))
- default_unfasten_wrench(user, I, default_unanchor)
- return TRUE
- return ..()
-
-/obj/machinery/dynamic_tool_functions(obj/item/I, mob/user)
- . = ..()
- if(!isnull(default_unanchor))
- COERCE_OPTIONS_LIST_IN(.[TOOL_WRENCH])
- .[TOOL_WRENCH] += anchored? "unfasten" : "fasten"
- if(!isnull(default_deconstruct) && panel_open)
- COERCE_OPTIONS_LIST_IN(.[TOOL_CROWBAR])
- .[TOOL_CROWBAR] += "deconstruct"
- if(!isnull(default_panel))
- COERCE_OPTIONS_LIST_IN(.[TOOL_SCREWDRIVER])
- .[TOOL_SCREWDRIVER] += panel_open? "close panel" : "open panel"
-
-/obj/machinery/dynamic_tool_image(function, hint)
- . = ..()
- switch(hint)
- if("unfasten")
- return dyntool_image_backward(TOOL_WRENCH)
- if("fasten")
- return dyntool_image_forward(TOOL_WRENCH)
- if("deconstruct")
- return dyntool_image_backward(TOOL_CROWBAR)
- if("open panel")
- return dyntool_image_backward(TOOL_SCREWDRIVER)
- if("close panel")
- return dyntool_image_forward(TOOL_SCREWDRIVER)
-
/obj/machinery/process(delta_time)//If you dont use process or power why are you here
return PROCESS_KILL
@@ -267,6 +229,10 @@
if(panel_open && panel_icon_state)
. += panel_icon_state
+/obj/machinery/proc/set_panel_open(panel_opened)
+ panel_open = panel_opened
+ update_appearance()
+
/obj/machinery/legacy_ex_act(severity)
switch(severity)
if(1.0)
@@ -472,6 +438,12 @@
RefreshParts()
return 1
+// todo: refactor
+/obj/machinery/set_anchored(anchorvalue)
+ . = ..()
+ power_change()
+ update_appearance()
+
// Default behavior for wrenching down machines. Supports both delay and instant modes.
/obj/machinery/proc/default_unfasten_wrench(var/mob/user, var/obj/item/W, var/time = 0)
if(!W.is_wrench())
@@ -494,8 +466,6 @@
return TRUE
/obj/machinery/proc/default_deconstruction_crowbar(var/mob/user, var/obj/item/C)
-
-
if(!C.is_crowbar())
return 0
if(!panel_open)
@@ -549,8 +519,9 @@
playsound(src.loc, 'sound/items/Crowbar.ogg', 50, 1)
drop_products(ATOM_DECONSTRUCT_DISASSEMBLED)
on_deconstruction()
- // If it doesn't have a circuit board, don't create a frame. Return a smack instead. BONK!
+ // If it doesn't have a circuit board, don't create a frame, instead just break.
if(!circuit)
+ qdel(src)
return 0
var/obj/structure/frame/A = new /obj/structure/frame(src.loc)
var/obj/item/circuitboard/M = circuit
diff --git a/code/game/machinery/_machinery_construction.dm b/code/game/machinery/_machinery_construction.dm
new file mode 100644
index 00000000000..b2e8dc5afae
--- /dev/null
+++ b/code/game/machinery/_machinery_construction.dm
@@ -0,0 +1,103 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/obj/machinery/dynamic_tool_functions(obj/item/I, mob/user)
+ . = list()
+ if(tool_deconstruct && !isnull(default_deconstruct) && panel_open)
+ LAZYADD(.[tool_panel], "deconstruct")
+ if(tool_unanchor && !isnull(default_unanchor))
+ LAZYADD(.[tool_panel], anchored? "unanchor" : "anchor")
+ if(tool_panel && !isnull(default_panel))
+ LAZYADD(.[tool_panel], panel_open? "close panel" : "open panel")
+ return merge_double_lazy_assoc_list(., ..())
+
+/obj/machinery/dynamic_tool_image(function, hint)
+ switch(hint)
+ if("anchor", "close panel")
+ return dyntool_image_backward(function)
+ if("unanchor", "open panel", "deconstruct")
+ return dyntool_image_backward(function)
+ return ..()
+
+/obj/machinery/tool_act(obj/item/I, mob/user, function, flags, hint)
+ if(INTERACTING_WITH_FOR(user, src, INTERACTING_FOR_CONSTRUCTION))
+ return CLICKCHAIN_DO_NOT_PROPAGATE
+ START_INTERACTING_WITH(user, src, INTERACTING_FOR_CONSTRUCTION)
+ if(function == tool_deconstruct && !isnull(default_deconstruct))
+ if(default_deconstruction_dismantle(I, user, flags = flags))
+ . = CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
+ . = CLICKCHAIN_DO_NOT_PROPAGATE
+ else if(function == tool_unanchor && !isnull(default_unanchor))
+ if(default_deconstruction_anchor(I, user, flags = flags))
+ . = CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
+ . = CLICKCHAIN_DO_NOT_PROPAGATE
+ else if(function == tool_panel && !isnull(default_panel))
+ if(default_deconstruction_panel(I, user, flags = flags))
+ . = CLICKCHAIN_DID_SOMETHING | CLICKCHAIN_DO_NOT_PROPAGATE
+ . = CLICKCHAIN_DO_NOT_PROPAGATE
+ STOP_INTERACTING_WITH(user, src,INTERACTING_FOR_CONSTRUCTION)
+ if(isnull(.))
+ return ..()
+
+// todo: better verb/message support
+/obj/machinery/proc/default_deconstruction_panel(obj/item/tool, mob/user, speed_mult = 1, flags)
+ var/needed_time = default_panel * speed_mult * (isnull(tool)? 1 : tool.tool_speed)
+ if(needed_time)
+ user.visible_action_feedback(
+ target = src,
+ soft_range = MESSAGE_RANGE_CONSTRUCTION,
+ visible_soft = SPAN_WARNING("[user] starts to [panel_open? "close" : "open"] [src]'s maintenance panel."),
+ audible_soft = SPAN_WARNING("You hear something being (un)fastened."),
+ )
+ if(!use_tool(tool_panel, tool, user, flags, needed_time))
+ return FALSE
+ user.visible_action_feedback(
+ target = src,
+ soft_range = MESSAGE_RANGE_CONSTRUCTION,
+ visible_soft = SPAN_WARNING("[user] [panel_open? "closes" : "opens"] [src]'s maintenance panel."),
+ audible_soft = SPAN_WARNING("You hear something being (un)fastened."),
+ )
+ set_panel_open(!panel_open)
+ return TRUE
+
+// todo: better verb/message support
+/obj/machinery/proc/default_deconstruction_dismantle(obj/item/tool, mob/user, speed_mult = 1, flags)
+ var/needed_time = default_deconstruct * speed_mult * (isnull(tool)? 1 : tool.tool_speed)
+ if(needed_time)
+ user.visible_action_feedback(
+ target = src,
+ soft_range = MESSAGE_RANGE_CONSTRUCTION,
+ visible_soft = SPAN_WARNING("[user] starts to dismantle [src]."),
+ audible_soft = SPAN_WARNING("You hear a series of small parts being removed from something."),
+ )
+ if(!use_tool(tool_deconstruct, tool, user, flags, needed_time))
+ return FALSE
+ user.visible_action_feedback(
+ target = src,
+ soft_range = MESSAGE_RANGE_CONSTRUCTION,
+ visible_soft = SPAN_WARNING("[user] dismantles [src]."),
+ audible_soft = SPAN_WARNING("You hear something getting dismantled."),
+ )
+ dismantle()
+ return TRUE
+
+// todo: better verb/message support
+/obj/machinery/proc/default_deconstruction_anchor(obj/item/tool, mob/user, speed_mult = 1, flags)
+ var/needed_time = default_unanchor * speed_mult * (isnull(tool)? 1 : tool.tool_speed)
+ if(needed_time)
+ user.visible_action_feedback(
+ target = src,
+ soft_range = MESSAGE_RANGE_CONSTRUCTION,
+ visible_soft = SPAN_WARNING("[user] starts to [anchored? "unbolt" : "bolt"] [src] [anchored? "from" : "to"] the floor."),
+ audible_soft = SPAN_WARNING("You hear something heavy being (un)fastened."),
+ )
+ if(!use_tool(tool_unanchor, tool, user, flags, needed_time))
+ return FALSE
+ user.visible_action_feedback(
+ target = src,
+ soft_range = MESSAGE_RANGE_CONSTRUCTION,
+ visible_soft = SPAN_WARNING("[user] [anchored? "bolts" : "unbolts"] [src] [anchored? "to" : "from"] from the floor."),
+ audible_soft = SPAN_WARNING("You hear something heavy being (un)fastened."),
+ )
+ set_anchored(!anchored)
+ return TRUE
diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm
deleted file mode 100644
index 0f238880a4f..00000000000
--- a/code/game/machinery/air_alarm.dm
+++ /dev/null
@@ -1,865 +0,0 @@
-GLOBAL_LIST_EMPTY(air_alarms)
-
-#define DECLARE_TLV_VALUES var/red_min; var/yel_min; var/yel_max; var/red_max; var/tlv_comparitor;
-#define LOAD_TLV_VALUES(x, y) red_min = x[1]; yel_min = x[2]; yel_max = x[3]; red_max = x[4]; tlv_comparitor = y;
-#define TEST_TLV_VALUES (((tlv_comparitor >= red_max && red_max > 0) || tlv_comparitor <= red_min) ? 2 : ((tlv_comparitor >= yel_max && yel_max > 0) || tlv_comparitor <= yel_min) ? 1 : 0)
-
-#define AALARM_MODE_SCRUBBING 1
-///like scrubbing, but faster.
-#define AALARM_MODE_REPLACEMENT 2
-///constantly sucks all air
-#define AALARM_MODE_PANIC 3
-///sucks off all air, then refill and switches to scrubbing
-#define AALARM_MODE_CYCLE 4
-///emergency fill
-#define AALARM_MODE_FILL 5
-///Shuts it all down.
-#define AALARM_MODE_OFF 6
-#define AALARM_SCREEN_MAIN 1
-#define AALARM_SCREEN_VENT 2
-#define AALARM_SCREEN_SCRUB 3
-#define AALARM_SCREEN_MODE 4
-#define AALARM_SCREEN_SENSORS 5
-
-#define AALARM_REPORT_TIMEOUT 100
-
-#define MAX_TEMPERATURE 90
-#define MIN_TEMPERATURE -40
-
-//all air alarms in area are connected via magic
-/area
- var/obj/machinery/alarm/master_air_alarm
- var/list/air_vent_names = list()
- var/list/air_scrub_names = list()
- var/list/air_vent_info = list()
- var/list/air_scrub_info = list()
-
-/obj/machinery/alarm
- name = "alarm"
- desc = "Used to control various station atmospheric systems. The light indicates the current air status of the area."
- icon = 'icons/obj/monitors.dmi'
- icon_state = "alarm0"
- plane = TURF_PLANE
- layer = ABOVE_TURF_LAYER
- anchored = TRUE
- use_power = USE_POWER_IDLE
- idle_power_usage = 80
- active_power_usage = 1000 //For heating/cooling rooms. 1000 joules equates to about 1 degree every 2 seconds for a single tile of air.
- power_channel = ENVIRON
- req_one_access = list(ACCESS_ENGINEERING_ATMOS, ACCESS_ENGINEERING_ENGINE)
- clicksound = "button"
- clickvol = 30
- //blocks_emissive = NONE
- light_power = 0.25
- var/alarm_id = null
- ///Whether to use automatic breach detection or not
- var/breach_detection = TRUE
- var/frequency = 1439
- //var/skipprocess = 0 //Experimenting
- var/alarm_frequency = 1437
- var/remote_control = FALSE
- var/rcon_setting = RCON_AUTO
- var/rcon_time = 0
- var/locked = TRUE
- ///If it's been screwdrivered open.
- panel_open = FALSE
- var/aidisabled = FALSE
- var/shorted = FALSE
- circuit = /obj/item/circuitboard/airalarm
- //armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, FIRE = 90, ACID = 30)
-
- var/datum/wires/alarm/wires
-
- var/mode = AALARM_MODE_SCRUBBING
- var/screen = AALARM_SCREEN_MAIN
- var/area_uid
- var/area/alarm_area
-
- var/target_temperature = T20C
- var/regulating_temperature = 0
-
- var/datum/radio_frequency/radio_connection
-
- /// Keys are things like temperature and certain gasses. Values are lists, which contain, in order:
- /// red warning minimum value, yellow warning minimum value, yellow warning maximum value, red warning maximum value
- var/list/TLV = list()
- var/list/trace_gas = list("nitrous_oxide", "volatile_fuel") //list of other gases that this air alarm is able to detect
-
- var/danger_level = 0
- var/pressure_dangerlevel = 0
-
- var/report_danger_level = 1
- ///If the alarms from this machine are visible on consoles
- var/alarms_hidden = FALSE
-
-/obj/machinery/alarm/nobreach
- breach_detection = 0
-
-/obj/machinery/alarm/monitor
- report_danger_level = 0
- breach_detection = 0
-
-/obj/machinery/alarm/alarms_hidden
- alarms_hidden = TRUE
-
-/obj/machinery/alarm/angled
-// icon = 'icons/obj/wall_machines_angled.dmi'
-
-/obj/machinery/alarm/angled/hidden
- alarms_hidden = TRUE
-
-/obj/machinery/alarm/angled/offset_airalarm()
- pixel_x = (dir & 3) ? 0 : (dir == 4 ? -21 : 21)
- pixel_y = (dir & 3) ? (dir == 1 ? -18 : 20) : 0
-
-/obj/machinery/alarm/server/Initialize(mapload)
- . = ..()
- req_access = list(ACCESS_SCIENCE_RD, ACCESS_ENGINEERING_ATMOS, ACCESS_ENGINEERING_ENGINE)
- TLV[/datum/gas/oxygen] = list(16, 19, 135, 140) // Partial pressure, kpa
- TLV[/datum/gas/carbon_dioxide] = list(-1.0, -1.0, 5, 10) // Partial pressure, kpa
- TLV[/datum/gas/phoron] = list(-1.0, -1.0, 0, 0.5) // Partial pressure, kpa
- TLV["other"] = list(-1.0, -1.0, 0.5, 1.0) // Partial pressure, kpa
- TLV["pressure"] = list(ONE_ATMOSPHERE * 0.80, ONE_ATMOSPHERE * 0.90, ONE_ATMOSPHERE * 1.10, ONE_ATMOSPHERE * 1.20) /* kpa */
- TLV["temperature"] = list(T0C - 26, T0C, T0C + 40, T0C + 66) // K
- setDir(dir)
-
-/obj/machinery/alarm/Initialize(mapload)
- . = ..()
- GLOB.air_alarms += src
- if(!pixel_x && !pixel_y)
- offset_airalarm()
- first_run()
-
-/obj/machinery/alarm/Destroy()
- GLOB.air_alarms -= src
- unregister_radio(src, frequency)
- qdel(wires)
- wires = null
- if(alarm_area && alarm_area.master_air_alarm == src)
- alarm_area.master_air_alarm = null
- elect_master(exclude_self = TRUE)
- return ..()
-
-/obj/machinery/alarm/proc/offset_airalarm()
- pixel_x = (dir & 3) ? 0 : (dir == 4 ? -26 : 26)
- pixel_y = (dir & 3) ? (dir == 1 ? -26 : 26) : 0
-
-/obj/machinery/alarm/proc/first_run()
- alarm_area = get_area(src)
- area_uid = "\ref[alarm_area]"
- if(name == "alarm")
- name = "[alarm_area.name] Air Alarm"
-
- if(!wires)
- wires = new(src)
-
- // breathable air according to human/Life()
- TLV["oxygen"] = list(16, 19, 135, 140) // Partial pressure, kpa
- TLV["nitrogen"] = list(0, 0, 135, 140) // Partial pressure, kpa
- TLV["carbon dioxide"] = list(-1.0, -1.0, 5, 10) // Partial pressure, kpa
- TLV["phoron"] = list(-1.0, -1.0, 0, 0.5) // Partial pressure, kpa
- TLV["other"] = list(-1.0, -1.0, 0.5, 1.0) // Partial pressure, kpa
- TLV["pressure"] = list(ONE_ATMOSPHERE * 0.80, ONE_ATMOSPHERE * 0.90, ONE_ATMOSPHERE * 1.10, ONE_ATMOSPHERE * 1.20) /* kpa */
- TLV["temperature"] = list(T0C - 26, T0C, T0C + 40, T0C + 66) // K
-
- update_icon()
-
-/obj/machinery/alarm/Initialize(mapload)
- . = ..()
- set_frequency(frequency)
- if(!master_is_operating())
- elect_master()
-
-/obj/machinery/alarm/process(delta_time)
- if((machine_stat & (NOPOWER|BROKEN)) || shorted)
- return
-
- var/turf/simulated/location = src.loc
- if(!istype(location)) return//returns if loc is not simulated
-
- var/datum/gas_mixture/environment = location.return_air()
-
- //Handle temperature adjustment here.
- handle_heating_cooling(environment)
-
- var/old_level = danger_level
- var/old_pressurelevel = pressure_dangerlevel
- danger_level = overall_danger_level(environment)
-
- if(old_level != danger_level)
- apply_danger_level(danger_level)
-
- if(old_pressurelevel != pressure_dangerlevel)
- if(breach_detected())
- mode = AALARM_MODE_OFF
- apply_mode()
-
- if(mode == AALARM_MODE_CYCLE && environment.return_pressure() < ONE_ATMOSPHERE * 0.05)
- mode = AALARM_MODE_FILL
- apply_mode()
-
- //atmos computer remote control stuff
- switch(rcon_setting)
- if(RCON_NO)
- remote_control = 0
- if(RCON_AUTO)
- if(danger_level == 2)
- remote_control = 1
- else
- remote_control = 0
- if(RCON_YES)
- remote_control = 1
-
- return
-
-/obj/machinery/alarm/proc/handle_heating_cooling(var/datum/gas_mixture/environment)
- DECLARE_TLV_VALUES
- LOAD_TLV_VALUES(TLV["temperature"], target_temperature)
- if(!regulating_temperature)
- //check for when we should start adjusting temperature
- if(!TEST_TLV_VALUES && abs(environment.temperature - target_temperature) > 2.0 && environment.return_pressure() >= 1)
- update_use_power(USE_POWER_ACTIVE)
- regulating_temperature = 1
- audible_message("\The [src] clicks as it starts [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\
- "You hear a click and a faint electronic hum.")
- playsound(src, 'sound/machines/click.ogg', 50, 1)
- else
- //check for when we should stop adjusting temperature
- if(TEST_TLV_VALUES || abs(environment.temperature - target_temperature) <= 0.5 || environment.return_pressure() < 1)
- update_use_power(USE_POWER_IDLE)
- regulating_temperature = 0
- audible_message("\The [src] clicks quietly as it stops [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\
- "You hear a click as a faint electronic humming stops.")
- playsound(src, 'sound/machines/click.ogg', 50, 1)
-
- if(regulating_temperature)
- if(target_temperature > T0C + MAX_TEMPERATURE)
- target_temperature = T0C + MAX_TEMPERATURE
-
- if(target_temperature < T0C + MIN_TEMPERATURE)
- target_temperature = T0C + MIN_TEMPERATURE
-
- var/datum/gas_mixture/gas
- gas = environment.remove(0.25 * environment.total_moles)
- if(gas)
-
- if(gas.temperature <= target_temperature) //gas heating
- var/energy_used = min(gas.get_thermal_energy_change(target_temperature) , active_power_usage)
-
- gas.adjust_thermal_energy(energy_used)
- //use_power(energy_used, ENVIRON) //handle by update_use_power instead
- else //gas cooling
- var/heat_transfer = min(abs(gas.get_thermal_energy_change(target_temperature)), active_power_usage)
-
- //Assume the heat is being pumped into the hull which is fixed at 20 C
- //none of this is really proper thermodynamics but whatever
-
- var/cop = gas.temperature / T20C //coefficient of performance -> power used = heat_transfer/cop
-
- heat_transfer = min(heat_transfer, cop * active_power_usage) //this ensures that we don't use more than active_power_usage amount of power
-
- heat_transfer = -gas.adjust_thermal_energy(-heat_transfer) //get the actual heat transfer
-
- //use_power(heat_transfer / cop, ENVIRON) //handle by update_use_power instead
-
- environment.merge(gas)
-
-/obj/machinery/alarm/proc/overall_danger_level(var/datum/gas_mixture/environment)
- var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature/environment.volume
- var/environment_pressure = environment.return_pressure()
-
- var/other_moles = 0
- for(var/g in trace_gas)
- other_moles += environment.gas[g] //this is only going to be used in a partial pressure calc, so we don't need to worry about group_multiplier here.
-
- DECLARE_TLV_VALUES
- LOAD_TLV_VALUES(TLV["pressure"], environment_pressure)
- pressure_dangerlevel = TEST_TLV_VALUES // not local because it's used in process()
- LOAD_TLV_VALUES(TLV["oxygen"], environment.gas[/datum/gas/oxygen]*partial_pressure)
- var/oxygen_dangerlevel = TEST_TLV_VALUES
- LOAD_TLV_VALUES(TLV["carbon dioxide"], environment.gas[/datum/gas/carbon_dioxide]*partial_pressure)
- var/co2_dangerlevel = TEST_TLV_VALUES
- LOAD_TLV_VALUES(TLV["phoron"], environment.gas[/datum/gas/phoron]*partial_pressure)
- var/phoron_dangerlevel = TEST_TLV_VALUES
- LOAD_TLV_VALUES(TLV["temperature"], environment.temperature)
- var/temperature_dangerlevel = TEST_TLV_VALUES
- LOAD_TLV_VALUES(TLV["other"], other_moles*partial_pressure)
- var/other_dangerlevel = TEST_TLV_VALUES
-
- return max(
- pressure_dangerlevel,
- oxygen_dangerlevel,
- co2_dangerlevel,
- phoron_dangerlevel,
- other_dangerlevel,
- temperature_dangerlevel
- )
-
-/// Returns whether this air alarm thinks there is a breach, given the sensors that are available to it.
-/obj/machinery/alarm/proc/breach_detected()
- var/turf/simulated/location = src.loc
-
- if(!istype(location))
- return FALSE
-
- if(!breach_detection)
- return FALSE
-
- var/datum/gas_mixture/environment = location.return_air()
- var/environment_pressure = environment.return_pressure()
- var/pressure_levels = TLV["pressure"]
-
- if(environment_pressure <= pressure_levels[1]) // Low pressures
- if(!(mode == AALARM_MODE_PANIC || mode == AALARM_MODE_CYCLE))
- return TRUE
- return FALSE
-
-/obj/machinery/alarm/proc/master_is_operating()
- return alarm_area && alarm_area.master_air_alarm && !(alarm_area.master_air_alarm.machine_stat & (NOPOWER | BROKEN))
-
-/obj/machinery/alarm/proc/elect_master(exclude_self = FALSE)
- for(var/obj/machinery/alarm/AA in alarm_area)
- if(exclude_self && AA == src)
- continue
- if(!(AA.machine_stat & (NOPOWER|BROKEN)))
- alarm_area.master_air_alarm = AA
- return TRUE
- return FALSE
-
-/obj/machinery/alarm/setDir(ndir)
- . = ..()
- base_pixel_x = 0
- base_pixel_y = 0
- var/turf/T = get_step(get_turf(src), turn(dir, 180))
- if(istype(T) && T.density)
- switch(dir)
- if(NORTH)
- base_pixel_y = -21
- if(SOUTH)
- base_pixel_y = 21
- if(WEST)
- base_pixel_x = 21
- if(EAST)
- base_pixel_x = -21
- reset_pixel_offsets()
-
-/obj/machinery/alarm/update_icon()
- cut_overlays()
-
- if(panel_open)
- icon_state = "alarmx"
- set_light(0)
- //set_light_on(FALSE)
- return
- if((machine_stat & (NOPOWER|BROKEN)) || shorted)
- icon_state = "alarmp"
- set_light(0)
- //set_light_on(FALSE)
- return
-
- var/icon_level = danger_level
- if(alarm_area?.atmosalm)
- icon_level = max(icon_level, 1) //if there's an atmos alarm but everything is okay locally, no need to go past yellow
-
- var/new_color = null
- switch(icon_level)
- if(0)
- icon_state = "alarm0"
- //add_overlay(mutable_appearance(icon, "alarm_ov0"))
- //add_overlay(emissive_appearance(icon, "alarm_ov0"))
- new_color = "#03A728"
- if(1)
- icon_state = "alarm2" //yes, alarm2 is yellow alarm
- //add_overlay(mutable_appearance(icon, "alarm_ov2"))
- //add_overlay(emissive_appearance(icon, "alarm_ov2"))
- new_color = "#EC8B2F"
- if(2)
- icon_state = "alarm1"
- //add_overlay(mutable_appearance(icon, "alarm_ov1"))
- //add_overlay(emissive_appearance(icon, "alarm_ov1"))
- new_color = "#DA0205"
-
- set_light(l_range = 2, l_power = 0.25, l_color = new_color)
- //set_light_on(TRUE)
-
-/obj/machinery/alarm/receive_signal(datum/signal/signal)
- if(machine_stat & (NOPOWER|BROKEN))
- return
- if(alarm_area.master_air_alarm != src)
- if(master_is_operating())
- return
- elect_master()
- if(alarm_area.master_air_alarm != src)
- return
- if(!signal || signal.encryption)
- return
- var/id_tag = signal.data["tag"]
- if(!id_tag)
- return
- if(signal.data["area"] != area_uid)
- return
- if(signal.data["sigtype"] != "status")
- return
-
- var/dev_type = signal.data["device"]
- if(!(id_tag in alarm_area.air_scrub_names) && !(id_tag in alarm_area.air_vent_names))
- register_env_machine(id_tag, dev_type)
- if(dev_type == "AScr")
- alarm_area.air_scrub_info[id_tag] = signal.data
- else if(dev_type == "AVP")
- alarm_area.air_vent_info[id_tag] = signal.data
- SStgui.update_uis(src)
-
-/obj/machinery/alarm/proc/register_env_machine(var/m_id, var/device_type)
- var/new_name
- if(device_type == "AVP")
- new_name = "[alarm_area.name] Vent Pump #[alarm_area.air_vent_names.len+1]"
- alarm_area.air_vent_names[m_id] = new_name
- else if(device_type == "AScr")
- new_name = "[alarm_area.name] Air Scrubber #[alarm_area.air_scrub_names.len+1]"
- alarm_area.air_scrub_names[m_id] = new_name
- else
- return
- spawn(10)
- send_signal(m_id, list("init" = new_name))
-
-/obj/machinery/alarm/proc/refresh_all()
- for(var/id_tag in alarm_area.air_vent_names)
- var/list/I = alarm_area.air_vent_info[id_tag]
- if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
- continue
- send_signal(id_tag, list("status"))
- for(var/id_tag in alarm_area.air_scrub_names)
- var/list/I = alarm_area.air_scrub_info[id_tag]
- if(I && I["timestamp"] + AALARM_REPORT_TIMEOUT / 2 > world.time)
- continue
- send_signal(id_tag, list("status"))
-
-/obj/machinery/alarm/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
- frequency = new_frequency
- radio_connection = radio_controller.add_object(src, frequency, RADIO_TO_AIRALARM)
-
-/obj/machinery/alarm/proc/send_signal(var/target, var/list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise
- if(!radio_connection)
- return 0
-
- var/datum/signal/signal = new
- signal.transmission_method = TRANSMISSION_RADIO //radio signal
- signal.source = src
-
- signal.data = command
- signal.data["tag"] = target
- signal.data["sigtype"] = "command"
-
- radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM)
-// TO_WORLD("Signal [command] Broadcasted to [target]")
-
- return 1
-
-/obj/machinery/alarm/proc/apply_mode()
- //propagate mode to other air alarms in the area
- //TODO: make it so that players can choose between applying the new mode to the room they are in (related area) vs the entire alarm area
- for(var/obj/machinery/alarm/AA in alarm_area)
- AA.mode = mode
-
- switch(mode)
- if(AALARM_MODE_SCRUBBING)
- for(var/device_id in alarm_area.air_scrub_names)
- send_signal(device_id, list("power"= 1, "co2_scrub"= 1, "scrubbing"= 1, "panic_siphon"= 0))
- for(var/device_id in alarm_area.air_vent_names)
- send_signal(device_id, list("power"= 1, "checks"= "default", "set_external_pressure"= "default"))
-
- if(AALARM_MODE_PANIC, AALARM_MODE_CYCLE)
- for(var/device_id in alarm_area.air_scrub_names)
- send_signal(device_id, list("power"= 1, "panic_siphon"= 1))
- for(var/device_id in alarm_area.air_vent_names)
- send_signal(device_id, list("power"= 0))
-
- if(AALARM_MODE_REPLACEMENT)
- for(var/device_id in alarm_area.air_scrub_names)
- send_signal(device_id, list("power"= 1, "panic_siphon"= 1))
- for(var/device_id in alarm_area.air_vent_names)
- send_signal(device_id, list("power"= 1, "checks"= "default", "set_external_pressure"= "default"))
-
- if(AALARM_MODE_FILL)
- for(var/device_id in alarm_area.air_scrub_names)
- send_signal(device_id, list("power"= 0))
- for(var/device_id in alarm_area.air_vent_names)
- send_signal(device_id, list("power"= 1, "checks"= "default", "set_external_pressure"= "default"))
-
- if(AALARM_MODE_OFF)
- for(var/device_id in alarm_area.air_scrub_names)
- send_signal(device_id, list("power"= 0))
- for(var/device_id in alarm_area.air_vent_names)
- send_signal(device_id, list("power"= 0))
-
-/obj/machinery/alarm/proc/apply_danger_level(var/new_danger_level)
- if(report_danger_level && alarm_area.atmosalert(new_danger_level, src))
- post_alert(new_danger_level)
-
- update_icon()
-
-/obj/machinery/alarm/proc/post_alert(alert_level)
- var/datum/radio_frequency/frequency = radio_controller.return_frequency(alarm_frequency)
- if(!frequency)
- return
-
- var/datum/signal/alert_signal = new
- alert_signal.source = src
- alert_signal.transmission_method = TRANSMISSION_RADIO
- alert_signal.data["zone"] = alarm_area.name
- alert_signal.data["type"] = "Atmospheric"
-
- if(alert_level==2)
- alert_signal.data["alert"] = "severe"
- else if(alert_level==1)
- alert_signal.data["alert"] = "minor"
- else if(alert_level==0)
- alert_signal.data["alert"] = "clear"
-
- frequency.post_signal(src, alert_signal)
-
-/obj/machinery/alarm/attack_ai(mob/user)
- ui_interact(user)
-
-/obj/machinery/alarm/attack_hand(mob/user, list/params)
- . = ..()
- if(.)
- return
- return interact(user)
-
-/obj/machinery/alarm/interact(mob/user)
- ui_interact(user)
- wires.Interact(user)
-
-/obj/machinery/alarm/ui_status(mob/user)
- if(isAI(user) && aidisabled)
- to_chat(user, "AI control has been disabled.")
- else if(!shorted)
- return ..()
- return UI_CLOSE
-
-/obj/machinery/alarm/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, datum/ui_state/state)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "AirAlarm", name, parent_ui)
- if(state)
- ui.set_state(state)
- ui.open()
-
-/obj/machinery/alarm/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
- var/list/data = list(
- "locked" = locked,
- "siliconUser" = issilicon(user),
- "remoteUser" = !!ui.parent_ui,
- "danger_level" = danger_level,
- "target_temperature" = "[target_temperature - T0C]C",
- "rcon" = rcon_setting,
- )
-
- var/area/A = get_area(src)
- data["atmos_alarm"] = A?.atmosalm
- data["fire_alarm"] = A?.fire
-
- var/turf/T = get_turf(src)
- var/datum/gas_mixture/environment = T.return_air()
-
- var/list/list/environment_data = list()
- data["environment_data"] = environment_data
-
- DECLARE_TLV_VALUES
-
- var/pressure = environment.return_pressure()
- LOAD_TLV_VALUES(TLV["pressure"], pressure)
- environment_data.Add(list(list(
- "name" = "Pressure",
- "value" = pressure,
- "unit" = "kPa",
- "danger_level" = TEST_TLV_VALUES
- )))
-
- var/temperature = environment.temperature
- LOAD_TLV_VALUES(TLV["temperature"], temperature)
- environment_data.Add(list(list(
- "name" = "Temperature",
- "value" = temperature,
- "unit" = "K ([round(temperature - T0C, 0.1)]C)",
- "danger_level" = TEST_TLV_VALUES
- )))
-
- var/total_moles = environment.total_moles
- var/partial_pressure = R_IDEAL_GAS_EQUATION * environment.temperature / environment.volume
- for(var/gas_id in environment.gas)
- if(!(gas_id in TLV))
- continue
- LOAD_TLV_VALUES(TLV[gas_id], environment.gas[gas_id] * partial_pressure)
- environment_data.Add(list(list(
- "name" = gas_id,
- "value" = environment.gas[gas_id] / total_moles * 100,
- "unit" = "%",
- "danger_level" = TEST_TLV_VALUES
- )))
-
- if(!locked || issilicon(user) || data["remoteUser"])
- var/list/list/vents = list()
- data["vents"] = vents
- for(var/id_tag in A.air_vent_names)
- var/long_name = A.air_vent_names[id_tag]
- var/list/info = A.air_vent_info[id_tag]
- if(!info)
- continue
- vents.Add(list(list(
- "id_tag" = id_tag,
- "long_name" = sanitize(long_name),
- "power" = info["power"],
- "checks" = info["checks"],
- "excheck" = info["checks"]&1,
- "incheck" = info["checks"]&2,
- "direction" = info["direction"],
- "external" = info["external"],
- "internal" = info["internal"],
- "extdefault"= (info["external"] == ONE_ATMOSPHERE),
- "intdefault"= (info["internal"] == 0),
- )))
-
-
- var/list/list/scrubbers = list()
- data["scrubbers"] = scrubbers
- for(var/id_tag in alarm_area.air_scrub_names)
- var/long_name = alarm_area.air_scrub_names[id_tag]
- var/list/info = alarm_area.air_scrub_info[id_tag]
- if(!info)
- continue
- scrubbers += list(list(
- "id_tag" = id_tag,
- "long_name" = sanitize(long_name),
- "power" = info["power"],
- "scrubbing" = info["scrubbing"],
- "panic" = info["panic"],
- "filters" = list(
- list("name" = "Oxygen", "command" = "o2_scrub", "val" = info["filter_o2"]),
- list("name" = "Nitrogen", "command" = "n2_scrub", "val" = info["filter_n2"]),
- list("name" = "Carbon Dioxide", "command" = "co2_scrub","val" = info["filter_co2"]),
- list("name" = "Toxin" , "command" = "tox_scrub","val" = info["filter_phoron"]),
- list("name" = "Nitrous Oxide", "command" = "n2o_scrub","val" = info["filter_n2o"]),
- list("name" = "Fuel", "command" = "fuel_scrub","val" = info["filter_fuel"])
- )
- ))
- data["scrubbers"] = scrubbers
-
- data["mode"] = mode
-
- var/list/list/modes = list()
- data["modes"] = modes
- modes[++modes.len] = list("name" = "Filtering - Scrubs out contaminants", "mode" = AALARM_MODE_SCRUBBING, "selected" = mode == AALARM_MODE_SCRUBBING, "danger" = 0)
- modes[++modes.len] = list("name" = "Replace Air - Siphons out air while replacing", "mode" = AALARM_MODE_REPLACEMENT, "selected" = mode == AALARM_MODE_REPLACEMENT, "danger" = 0)
- modes[++modes.len] = list("name" = "Panic - Siphons air out of the room", "mode" = AALARM_MODE_PANIC, "selected" = mode == AALARM_MODE_PANIC, "danger" = 1)
- modes[++modes.len] = list("name" = "Cycle - Siphons air before replacing", "mode" = AALARM_MODE_CYCLE, "selected" = mode == AALARM_MODE_CYCLE, "danger" = 1)
- modes[++modes.len] = list("name" = "Fill - Shuts off scrubbers and opens vents", "mode" = AALARM_MODE_FILL, "selected" = mode == AALARM_MODE_FILL, "danger" = 0)
- modes[++modes.len] = list("name" = "Off - Shuts off vents and scrubbers", "mode" = AALARM_MODE_OFF, "selected" = mode == AALARM_MODE_OFF, "danger" = 0)
-
- var/list/selected
- var/list/thresholds = list()
-
- var/list/gas_names = list("oxygen", "carbon dioxide", "phoron", "other")
- for(var/g in gas_names)
- thresholds[++thresholds.len] = list("name" = g, "settings" = list())
- selected = TLV[g]
- for(var/i = 1, i <= 4, i++)
- thresholds[thresholds.len]["settings"] += list(list("env" = g, "val" = i, "selected" = selected[i]))
-
- selected = TLV["pressure"]
- thresholds[++thresholds.len] = list("name" = "Pressure", "settings" = list())
- for(var/i = 1, i <= 4, i++)
- thresholds[thresholds.len]["settings"] += list(list("env" = "pressure", "val" = i, "selected" = selected[i]))
-
- selected = TLV["temperature"]
- thresholds[++thresholds.len] = list("name" = "Temperature", "settings" = list())
- for(var/i = 1, i <= 4, i++)
- thresholds[thresholds.len]["settings"] += list(list("env" = "temperature", "val" = i, "selected" = selected[i]))
-
- data["thresholds"] = thresholds
- return data
-
-/obj/machinery/alarm/ui_act(action, params, datum/tgui/ui)
- if(..())
- return TRUE
-
- if(action == "rcon")
- var/attempted_rcon_setting = text2num(params["rcon"])
-
- switch(attempted_rcon_setting)
- if(RCON_NO)
- rcon_setting = RCON_NO
- if(RCON_AUTO)
- rcon_setting = RCON_AUTO
- if(RCON_YES)
- rcon_setting = RCON_YES
- return TRUE
-
- if(action == "temperature")
- var/list/selected = TLV["temperature"]
- var/max_temperature = min(selected[3] - T0C, MAX_TEMPERATURE)
- var/min_temperature = max(selected[2] - T0C, MIN_TEMPERATURE)
- var/input_temperature = tgui_input_number(usr, "What temperature would you like the system to mantain? (Capped between [min_temperature] and [max_temperature]C)", "Thermostat Controls", (target_temperature - T0C), max_temperature, min_temperature)
- if(isnum(input_temperature))
- if(input_temperature > max_temperature || input_temperature < min_temperature)
- to_chat(usr, "Temperature must be between [min_temperature]C and [max_temperature]C")
- else
- target_temperature = input_temperature + T0C
- return TRUE
-
- // Account for remote users here.
- // Yes, this is kinda snowflaky; however, I would argue it would be far more snowflakey
- // to include "custom hrefs" and all the other bullshit that nano states have just for the
- // like, two UIs, that want remote access to other UIs.
- if((locked && !issilicon(usr) && !istype(ui.state, /datum/ui_state/air_alarm_remote)) || (issilicon(usr) && aidisabled))
- return
-
- var/device_id = params["id_tag"]
- switch(action)
- if("lock")
- if(issilicon(usr) && !wires.is_cut(WIRE_IDSCAN))
- locked = !locked
- . = TRUE
- if( "power",
- "o2_scrub",
- "n2_scrub",
- "co2_scrub",
- "tox_scrub",
- "n2o_scrub",
- "fuel_scrub",
- "panic_siphon",
- "scrubbing",
- "direction")
- send_signal(device_id, list("[action]" = text2num(params["val"])), usr)
- . = TRUE
- if("excheck")
- send_signal(device_id, list("checks" = text2num(params["val"])^1), usr)
- . = TRUE
- if("incheck")
- send_signal(device_id, list("checks" = text2num(params["val"])^2), usr)
- . = TRUE
- if("set_external_pressure", "set_internal_pressure")
- var/target = params["value"]
- if(!isnull(target))
- send_signal(device_id, list("[action]" = target), usr)
- . = TRUE
- if("reset_external_pressure")
- send_signal(device_id, list("reset_external_pressure"), usr)
- . = TRUE
- if("reset_internal_pressure")
- send_signal(device_id, list("reset_internal_pressure"), usr)
- . = TRUE
- if("threshold")
- var/env = params["env"]
-
- var/name = params["var"]
- var/value = input(usr, "New [name] for [env]:", name, TLV[env][name]) as num|null
- if(!isnull(value) && !..())
- if(value < 0)
- TLV[env][name] = -1
- else
- TLV[env][name] = round(value, 0.01)
- clamp_tlv_values(env, name)
- // investigate_log(" treshold value for [env]:[name] was set to [value] by [key_name(usr)]",INVESTIGATE_ATMOS)
- . = TRUE
- if("mode")
- mode = text2num(params["mode"])
- // investigate_log("was turned to [get_mode_name(mode)] mode by [key_name(usr)]",INVESTIGATE_ATMOS)
- apply_mode(usr)
- . = TRUE
- if("alarm")
- if(alarm_area.atmosalert(2, src))
- apply_danger_level(2)
- . = TRUE
- if("reset")
- atmos_reset()
- . = TRUE
- update_icon()
-
-// This big ol' mess just ensures that TLV always makes sense. If you set the max value below the min value,
-// it'll automatically update all the other values to keep it sane.
-/obj/machinery/alarm/proc/clamp_tlv_values(env, changed_threshold)
- var/list/selected = TLV[env]
- switch(changed_threshold)
- if(1)
- if(selected[1] > selected[2])
- selected[2] = selected[1]
- if(selected[1] > selected[3])
- selected[3] = selected[1]
- if(selected[1] > selected[4])
- selected[4] = selected[1]
- if(2)
- if(selected[1] > selected[2])
- selected[1] = selected[2]
- if(selected[2] > selected[3])
- selected[3] = selected[2]
- if(selected[2] > selected[4])
- selected[4] = selected[2]
- if(3)
- if(selected[1] > selected[3])
- selected[1] = selected[3]
- if(selected[2] > selected[3])
- selected[2] = selected[3]
- if(selected[3] > selected[4])
- selected[4] = selected[3]
- if(4)
- if(selected[1] > selected[4])
- selected[1] = selected[4]
- if(selected[2] > selected[4])
- selected[2] = selected[4]
- if(selected[3] > selected[4])
- selected[3] = selected[4]
-
-
-
-
-/obj/machinery/alarm/proc/atmos_reset()
- if(alarm_area.atmosalert(0, src))
- apply_danger_level(0)
- update_icon()
-
-/obj/machinery/alarm/attackby(obj/item/W as obj, mob/user as mob)
- add_fingerprint(user)
- if(alarm_deconstruction_screwdriver(user, W))
- return
- if(alarm_deconstruction_wirecutters(user, W))
- return
-
- if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))// trying to unlock the interface with an ID card
- togglelock()
- return ..()
-
-/obj/machinery/alarm/verb/togglelock(mob/user as mob)
- if(machine_stat & (NOPOWER|BROKEN))
- to_chat(user, "It does nothing.")
- return
- else
- if(allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
- locked = !locked
- to_chat(user, SPAN_NOTICE("You [locked ? "lock" : "unlock"] the Air Alarm interface."))
- else
- to_chat(user, SPAN_BOLDWARNING("Access denied."))
- return
-
-/obj/machinery/alarm/AltClick()
- ..()
- togglelock()
-
-/obj/machinery/alarm/power_change()
- ..()
- spawn(rand(0,15))
- update_icon()
-/obj/machinery/alarm/freezer
- target_temperature = T0C - 13.15 // Chilly freezer room
-
-/obj/machinery/alarm/freezer/first_run()
- . = ..()
-
- TLV["temperature"] = list(T0C - 40, T0C - 20, T0C + 40, T0C + 66) // K, Lower Temperature for Freezer Air Alarms (This is because TLV is hardcoded to be generated on first_run, and therefore the only way to modify this without changing TLV generation)
-
-#undef LOAD_TLV_VALUES
-#undef TEST_TLV_VALUES
-#undef DECLARE_TLV_VALUES
diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm
index d6231dd100b..8a232b6d316 100644
--- a/code/game/machinery/atmo_control.dm
+++ b/code/game/machinery/atmo_control.dm
@@ -41,13 +41,13 @@
var/total_moles = air_sample.total_moles
if(total_moles > 0)
if(output&4)
- signal.data["oxygen"] = round(100*air_sample.gas[/datum/gas/oxygen]/total_moles,0.1)
+ signal.data["oxygen"] = round(100*air_sample.gas[GAS_ID_OXYGEN]/total_moles,0.1)
if(output&8)
- signal.data["phoron"] = round(100*air_sample.gas[/datum/gas/phoron]/total_moles,0.1)
+ signal.data["phoron"] = round(100*air_sample.gas[GAS_ID_PHORON]/total_moles,0.1)
if(output&16)
- signal.data["nitrogen"] = round(100*air_sample.gas[/datum/gas/nitrogen]/total_moles,0.1)
+ signal.data["nitrogen"] = round(100*air_sample.gas[GAS_ID_NITROGEN]/total_moles,0.1)
if(output&32)
- signal.data["carbon_dioxide"] = round(100*air_sample.gas[/datum/gas/carbon_dioxide]/total_moles,0.1)
+ signal.data["carbon_dioxide"] = round(100*air_sample.gas[GAS_ID_CARBON_DIOXIDE]/total_moles,0.1)
else
signal.data["oxygen"] = 0
signal.data["phoron"] = 0
diff --git a/code/game/machinery/atmoalter/scrubber.dm b/code/game/machinery/atmoalter/scrubber.dm
deleted file mode 100644
index 16dd175f9c6..00000000000
--- a/code/game/machinery/atmoalter/scrubber.dm
+++ /dev/null
@@ -1,279 +0,0 @@
-/obj/machinery/portable_atmospherics/powered/scrubber
- name = "Portable Air Scrubber"
-
- icon = 'icons/obj/atmos.dmi'
- icon_state = "pscrubber:0"
- density = 1
- w_class = ITEMSIZE_NORMAL
-
- var/on = 0
- var/volume_rate = 800
-
- volume = 750
-
- power_rating = 7500 //7500 W ~ 10 HP
- power_losses = 150
-
- var/minrate = 0
- var/maxrate = 10 * ONE_ATMOSPHERE
-
- var/list/scrubbing_gas = list(/datum/gas/carbon_dioxide, /datum/gas/volatile_fuel, /datum/gas/phoron, /datum/gas/nitrous_oxide,
- /datum/gas/helium, /datum/gas/carbon_monoxide, /datum/gas/methyl_bromide, /datum/gas/nitrodioxide, /datum/gas/nitricoxide,
- /datum/gas/methane, /datum/gas/argon, /datum/gas/krypton, /datum/gas/neon, /datum/gas/ammonia, /datum/gas/xenon, /datum/gas/chlorine,
- /datum/gas/sulfur_dioxide, /datum/gas/hydrogen)
-
-/obj/machinery/portable_atmospherics/powered/scrubber/Initialize(mapload)
- . = ..()
- for(var/i in scrubbing_gas)
- if(!ispath(i))
- scrubbing_gas -= i
- var/path = gas_id2path(i)
- if(!path)
- stack_trace("Invalid gas id [i]")
- else
- scrubbing_gas += path
- cell = new/obj/item/cell/apc(src)
-
-/obj/machinery/portable_atmospherics/powered/scrubber/emp_act(severity)
- if(machine_stat & (BROKEN|NOPOWER))
- ..(severity)
- return
-
- if(prob(50/severity))
- on = !on
- update_icon()
-
- ..(severity)
-
-/obj/machinery/portable_atmospherics/powered/scrubber/update_icon_state()
- . = ..()
- if(on && cell && cell.charge)
- icon_state = "pscrubber:1"
- else
- icon_state = "pscrubber:0"
-
-/obj/machinery/portable_atmospherics/powered/scrubber/update_overlays()
- . = ..()
- if(holding)
- . += "scrubber-open"
-
- if(connected_port)
- . += "scrubber-connector"
-
-/obj/machinery/portable_atmospherics/powered/scrubber/process(delta_time)
- ..()
-
- var/power_draw = -1
-
- if(on && cell && cell.charge)
- var/datum/gas_mixture/environment
- if(holding)
- environment = holding.air_contents
- else
- environment = loc.return_air()
-
- var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles
-
- power_draw = scrub_gas(src, scrubbing_gas, environment, air_contents, transfer_moles, power_rating)
-
- if (power_draw < 0)
- last_flow_rate = 0
- last_power_draw = 0
- else
- power_draw = max(power_draw, power_losses)
- cell.use_scaled(DYNAMIC_W_TO_CELL_UNITS(power_draw, 1))
- last_power_draw = power_draw
-
- update_connected_network()
-
- //ran out of charge
- if (!cell.charge)
- power_change()
- update_icon()
-
- //src.update_icon()
- src.updateDialog()
-
-/obj/machinery/portable_atmospherics/powered/scrubber/return_air()
- return air_contents
-
-/obj/machinery/portable_atmospherics/powered/scrubber/attack_ai(var/mob/user)
- src.add_hiddenprint(user)
- return src.attack_hand(user)
-
-/obj/machinery/portable_atmospherics/powered/scrubber/attack_ghost(var/mob/user)
- . = ..()
- return src.attack_hand(user)
-
-/obj/machinery/portable_atmospherics/powered/scrubber/attack_hand(mob/user, list/params)
- ui_interact(user)
-
-/obj/machinery/portable_atmospherics/powered/scrubber/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "PortableScrubber", name)
- ui.open()
-
-/obj/machinery/portable_atmospherics/powered/scrubber/ui_data(mob/user)
- var/list/data = list()
-
- data["on"] = on ? 1 : 0
- data["connected"] = connected_port ? 1 : 0
- data["pressure"] = round(air_contents.return_pressure() > 0 ? air_contents.return_pressure() : 0)
- data["rate"] = round(volume_rate)
- data["minrate"] = round(minrate)
- data["maxrate"] = round(maxrate)
- data["powerDraw"] = round(last_power_draw)
- data["cellCharge"] = cell ? cell.charge : 0
- data["cellMaxCharge"] = cell ? cell.maxcharge : 1
-
- if(holding)
- data["holding"] = list()
- data["holding"]["name"] = holding.name
- data["holding"]["pressure"] = round(holding.air_contents.return_pressure() > 0 ? holding.air_contents.return_pressure() : 0)
- else
- data["holding"] = null
-
- return data
-
-/obj/machinery/portable_atmospherics/powered/scrubber/ui_act(action, params)
- if(..())
- return TRUE
-
- switch(action)
- if("power")
- on = !on
- . = TRUE
- if("eject")
- if(holding)
- holding.loc = loc
- holding = null
- . = TRUE
- if("volume_adj")
- volume_rate = clamp(text2num(params["vol"]), minrate, maxrate)
- . = TRUE
-
- update_icon()
-
-//Huge scrubber
-/obj/machinery/portable_atmospherics/powered/scrubber/huge
- name = "Huge Air Scrubber"
- icon = 'icons/obj/atmos_vr.dmi'
- icon_state = "scrubber:0"
- anchored = TRUE
- volume = 500000
- volume_rate = 7000
-
- use_power = USE_POWER_IDLE
- idle_power_usage = 50 //internal circuitry, friction losses and stuff
- active_power_usage = 1000 // Blowers running
- power_rating = 100000 //100 kW ~ 135 HP
-
- var/global/gid = 1
- var/id = 0
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/Initialize(mapload)
- . = ..()
- cell = null
-
- id = gid
- gid++
-
- name = "[name] (ID [id])"
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/attack_hand(mob/user, list/params)
- to_chat(user, "You can't directly interact with this machine. Use the scrubber control console.")
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/update_icon()
- cut_overlays()
-
- if(on && !(machine_stat & (NOPOWER|BROKEN)))
- icon_state = "scrubber:1"
- else
- icon_state = "scrubber:0"
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/power_change()
- var/old_stat = machine_stat
- ..()
- if (old_stat != machine_stat)
- update_icon()
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/process(delta_time)
- if(!anchored || (machine_stat & (NOPOWER|BROKEN)))
- on = 0
- last_flow_rate = 0
- last_power_draw = 0
- update_icon()
- var/new_use_power = 1 + on
- if(new_use_power != use_power)
- update_use_power(new_use_power)
- if(!on)
- return
-
- var/power_draw = -1
-
- var/datum/gas_mixture/environment = loc.return_air()
-
- var/transfer_moles = min(1, volume_rate/environment.volume)*environment.total_moles
-
- power_draw = scrub_gas(src, scrubbing_gas, environment, air_contents, transfer_moles, active_power_usage)
-
- if (power_draw < 0)
- last_flow_rate = 0
- last_power_draw = 0
- else
- use_power(power_draw)
- update_connected_network()
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/attackby(var/obj/item/I as obj, var/mob/user as mob)
- if(I.is_wrench())
- if(on)
- to_chat(user, "Turn \the [src] off first!")
- return
-
- anchored = !anchored
- playsound(src.loc, I.tool_sound, 50, 1)
- to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].")
-
- return
-
- //doesn't use power cells
- if(istype(I, /obj/item/cell))
- return
- if(I.is_screwdriver())
- return
-
- //doesn't hold tanks
- if(istype(I, /obj/item/tank))
- return
-
- ..()
-
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary
- name = "Stationary Air Scrubber"
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/attackby(var/obj/item/I as obj, var/mob/user as mob)
- if(I.is_wrench())
- to_chat(user, "The bolts are too tight for you to unscrew!")
- return
-
- ..()
-
-// Tether tram air scrubbers for keeping arrivals clean - they work even with no area power
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/tram
- name = "\improper Tram Air Scrubber"
- icon_state = "scrubber:1"
- on = TRUE
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/tram/powered()
- return TRUE // Always be powered
-
-// Triumph shuttle air scrubbers for keeping arrivals clean - they work even with no area power
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/shuttle
- name = "\improper Shuttle Air Scrubber"
- icon_state = "scrubber:1"
- on = TRUE
-
-/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/shuttle/powered()
- return TRUE // Always be powered
diff --git a/code/game/machinery/bomb_tester.dm b/code/game/machinery/bomb_tester.dm
index 48d68ca5326..4d042530955 100644
--- a/code/game/machinery/bomb_tester.dm
+++ b/code/game/machinery/bomb_tester.dm
@@ -370,7 +370,7 @@
if(G.total_moles)
results += " Temperature: [round(G.temperature-T0C)]°C"
for(var/mix in G.gas)
- results += " [GLOB.meta_gas_names[mix]]: [round((G.gas[mix] / G.total_moles) * 100)]%"
+ results += " [global.gas_data.names[mix]]: [round((G.gas[mix] / G.total_moles) * 100)]%"
return results
diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm
index 153bf06d024..3a54262c390 100644
--- a/code/game/machinery/computer/atmos_alert.dm
+++ b/code/game/machinery/computer/atmos_alert.dm
@@ -67,7 +67,7 @@ var/global/list/minor_air_alarms = list()
var/datum/alarm/alarm = locate(params["ref"]) in atmosphere_alarm.alarms
if(alarm)
for(var/datum/alarm_source/alarm_source in alarm.sources)
- var/obj/machinery/alarm/air_alarm = alarm_source.source
+ var/obj/machinery/air_alarm/air_alarm = alarm_source.source
if(istype(air_alarm))
// I have to leave a note here:
// Once upon a time, this called air_alarm.Topic() with a custom topic state
diff --git a/code/game/machinery/cryo.dm b/code/game/machinery/cryo.dm
index bea8fe774fe..b574db38987 100644
--- a/code/game/machinery/cryo.dm
+++ b/code/game/machinery/cryo.dm
@@ -9,7 +9,6 @@
layer = UNDER_JUNK_LAYER
interaction_flags_machine = INTERACT_MACHINE_OFFLINE | INTERACT_MACHINE_ALLOW_SILICON
- var/on = FALSE
use_power = USE_POWER_IDLE
idle_power_usage = 20
active_power_usage = 200
@@ -25,8 +24,6 @@
var/image/fluid
/obj/machinery/atmospherics/component/unary/cryo_cell/Initialize(mapload)
- . = ..()
-
icon = 'icons/obj/medical/cryogenics_split.dmi'
icon_state = "base"
initialize_directions = dir
@@ -43,6 +40,10 @@
fluid.layer = MOB_LAYER+0.1 //Below glass, above mob
add_overlay(tank)
+
+ . = ..()
+
+ // todo: duped, components update icon on init right?
update_icon()
/obj/machinery/atmospherics/component/unary/cryo_cell/Destroy()
@@ -205,8 +206,6 @@
qdel(grab)
put_mob(M)
- return
-
/obj/machinery/atmospherics/component/unary/cryo_cell/MouseDroppedOnLegacy(mob/target, mob/user) //Allows borgs to put people into cryo without external assistance
if(user.stat || user.lying || !Adjacent(user) || !target.Adjacent(user)|| !ishuman(target))
return
@@ -233,7 +232,7 @@
if(occupant.bodytemperature < T0C)
occupant.afflict_sleeping(20 * max(5, (1/occupant.bodytemperature)*2000))
occupant.afflict_unconscious(20 * max(5, (1/occupant.bodytemperature)*3000))
- if(air_contents.gas[/datum/gas/oxygen] > 2)
+ if(air_contents.gas[GAS_ID_OXYGEN] > 2)
if(occupant.getOxyLoss()) occupant.adjustOxyLoss(-1)
else
occupant.adjustOxyLoss(-1)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index c29ec0c2b6c..fbb196b55be 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -319,7 +319,7 @@ GLOBAL_REAL_VAR(airlock_typecache) = typecacheof(list(
/obj/machinery/door/airlock/phoron/proc/PhoronBurn(temperature)
for(var/turf/simulated/floor/target_tile in range(2,loc))
- target_tile.assume_gas(/datum/gas/phoron, 35, 400+T0C)
+ target_tile.assume_gas(GAS_ID_PHORON, 35, 400+T0C)
spawn (0) target_tile.hotspot_expose(temperature, 400)
for(var/turf/simulated/wall/W in range(3,src))
W.burn((temperature/4))//Added so that you can't set off a massive chain reaction with a small flame
diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm
index d6b3e404015..6a9520fe006 100644
--- a/code/game/machinery/doors/firedoor.dm
+++ b/code/game/machinery/doors/firedoor.dm
@@ -550,15 +550,15 @@ GLOBAL_LIST_INIT(firelock_align_types, typecacheof(list(
update_nearby_tiles(need_rebuild)
- if(!air_master) return 0
+ if(!SSair) return 0
var/turf/simulated/source = loc
var/turf/simulated/destination = get_step(source,dir)
update_heat_protection(loc)
- if(istype(source)) air_master.tiles_to_update += source
- if(istype(destination)) air_master.tiles_to_update += destination
+ if(istype(source)) SSair.tiles_to_update += source
+ if(istype(destination)) SSair.tiles_to_update += destination
return 1
*/
diff --git a/code/game/machinery/doors/phoronlocks.dm b/code/game/machinery/doors/phoronlocks.dm
index 134fb916d9c..28400e95cb2 100644
--- a/code/game/machinery/doors/phoronlocks.dm
+++ b/code/game/machinery/doors/phoronlocks.dm
@@ -23,7 +23,7 @@
if(on)
var/datum/gas_mixture/air_sample = return_air()
var/pressure = round(air_sample.return_pressure(), 0.1)
- var/phoron = (/datum/gas/phoron in air_sample.gas) ? round(air_sample.gas[/datum/gas/phoron], 0.1) : 0
+ var/phoron = (GAS_ID_PHORON in air_sample.gas) ? round(air_sample.gas[GAS_ID_PHORON], 0.1) : 0
if(abs(pressure - previousPressure) > 0.1 || previousPressure == null || abs(phoron - previousPhoron) > 0.1 || previousPhoron == null)
var/datum/signal/signal = new
@@ -92,8 +92,8 @@
return 1
/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/phoronlock //Special scrubber with bonus inbuilt heater
- volume_rate = 40000
active_power_usage = 2000
+ efficiency_multiplier = 4
var/target_temp = T20C
var/heating_power = 150000
@@ -113,11 +113,6 @@
removed.adjust_thermal_energy(clamp(heat_transfer,-heating_power,heating_power))
env.merge(removed)
- var/transfer_moles = min(1, volume_rate/env.volume)*env.total_moles
- for(var/i=1 to 3) //Scrubs 4 times as fast
- scrub_gas(src, scrubbing_gas, env, air_contents, transfer_moles, active_power_usage)
-
-
//
// PHORON LOCK CONTROLLER
//
diff --git a/code/game/machinery/pipe/pipe_recipes.dm b/code/game/machinery/pipe/pipe_recipes.dm
index 59399a2e206..8556ba5e2d9 100644
--- a/code/game/machinery/pipe/pipe_recipes.dm
+++ b/code/game/machinery/pipe/pipe_recipes.dm
@@ -30,7 +30,8 @@ GLOBAL_LIST_INIT(atmos_pipe_recipes, list(
new /datum/pipe_info/pipe("Heat Pump", /obj/machinery/atmospherics/component/binary/heat_pump, TRUE),
//new /datum/pipe_info/pipe("Automatic Shutoff Valve",/obj/machinery/atmospherics/valve/shutoff, TRUE),
new /datum/pipe_info/pipe("Scrubber", /obj/machinery/atmospherics/component/unary/vent_scrubber, TRUE),
- new /datum/pipe_info/pipe("Gas Filter", /obj/machinery/atmospherics/component/trinary/atmos_filter, TRUE),
+ new /datum/pipe_info/pipe("Gas Filter", /obj/machinery/atmospherics/component/trinary/filter, TRUE),
+ new /datum/pipe_info/pipe("Gas Molar Filter", /obj/machinery/atmospherics/component/trinary/molar_filter, TRUE),
new /datum/pipe_info/pipe("Gas Mixer", /obj/machinery/atmospherics/component/trinary/mixer, TRUE),
new /datum/pipe_info/pipe("Gas Mixer 'T'", /obj/machinery/atmospherics/component/trinary/mixer/t_mixer, TRUE),
new /datum/pipe_info/pipe("Omni Gas Mixer", /obj/machinery/atmospherics/component/quaternary/mixer, TRUE),
diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm
index 972f5dd7edc..7383b5c06e9 100644
--- a/code/game/machinery/spaceheater.dm
+++ b/code/game/machinery/spaceheater.dm
@@ -327,7 +327,7 @@
env.merge(removed)
var/turf/T = get_turf(src)
new /obj/effect/debris/cleanable/liquid_fuel(T, 5)
- T.assume_gas(/datum/gas/volatile_fuel, 5, T20C)
+ T.assume_gas(GAS_ID_VOLATILE_FUEL, 5, T20C)
T.hotspot_expose(700,400)
var/datum/effect_system/spark_spread/s = new
s.set_up(5, 0, T)
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index 0df4c120f5f..9f60d0376cb 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -1287,7 +1287,7 @@
/obj/item/weldingtool = 3,
/obj/item/tool/wirecutters = 5,
/obj/item/tool/wrench = 5,
- /obj/item/analyzer = 5,
+ /obj/item/atmos_analyzer = 5,
/obj/item/t_scanner = 5,
/obj/item/tool/screwdriver = 5,
/obj/item/flashlight/glowstick = 3,
diff --git a/code/game/mecha/equipment/tools/generator.dm b/code/game/mecha/equipment/tools/generator.dm
index 389d945d75b..3bb5fe69bca 100644
--- a/code/game/mecha/equipment/tools/generator.dm
+++ b/code/game/mecha/equipment/tools/generator.dm
@@ -96,11 +96,11 @@
return
var/datum/gas_mixture/GM = new
if(prob(10))
- T.assume_gas(/datum/gas/phoron, 100, 1500+T0C)
+ T.assume_gas(GAS_ID_PHORON, 100, 1500+T0C)
T.visible_message("The [src] suddenly disgorges a cloud of heated phoron.")
destroy()
else
- T.assume_gas(/datum/gas/phoron, 5, istype(T) ? T.air.temperature : T20C)
+ T.assume_gas(GAS_ID_PHORON, 5, istype(T) ? T.air.temperature : T20C)
T.visible_message("The [src] suddenly disgorges a cloud of phoron.")
T.assume_air(GM)
return
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 90f9ff9c4d6..04dbf514158 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -408,7 +408,7 @@
cabin_air = new
cabin_air.temperature = T20C
cabin_air.volume = 200
- cabin_air.adjust_multi(/datum/gas/oxygen, O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature), /datum/gas/nitrogen, N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature))
+ cabin_air.adjust_multi(GAS_ID_OXYGEN, O2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature), GAS_ID_NITROGEN, N2STANDARD*cabin_air.volume/(R_IDEAL_GAS_EQUATION*cabin_air.temperature))
return cabin_air
/obj/mecha/proc/add_radio()
diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm
index f4868b547b2..88e0f990487 100644
--- a/code/game/objects/effects/mines.dm
+++ b/code/game/objects/effects/mines.dm
@@ -108,7 +108,7 @@
triggered = 1
for (var/turf/simulated/floor/target in range(1,src))
if(!target.blocks_air)
- target.assume_gas(/datum/gas/chlorine, 30)
+ target.assume_gas(GAS_ID_CHLORINE, 30)
visible_message("\The [src.name] detonates!")
spawn(0)
qdel(src)
@@ -120,7 +120,7 @@
triggered = 1
for (var/turf/simulated/floor/target in range(1,src))
if(!target.blocks_air)
- target.assume_gas(/datum/gas/nitrous_oxide, 30)
+ target.assume_gas(GAS_ID_NITROUS_OXIDE, 30)
visible_message("\The [src.name] detonates!")
spawn(0)
qdel(src)
@@ -132,7 +132,7 @@
triggered = 1
for (var/turf/simulated/floor/target in range(1,src))
if(!target.blocks_air)
- target.assume_gas(/datum/gas/phoron, 30)
+ target.assume_gas(GAS_ID_PHORON, 30)
target.hotspot_expose(1000, CELL_VOLUME)
visible_message("\The [src.name] detonates!")
spawn(0)
diff --git a/code/game/objects/effects/spawners/bombspawner.dm b/code/game/objects/effects/spawners/bombspawner.dm
index 212c00f13b8..4fde689a64f 100644
--- a/code/game/objects/effects/spawners/bombspawner.dm
+++ b/code/game/objects/effects/spawners/bombspawner.dm
@@ -173,14 +173,14 @@
OT.master = V
PT.valve_welded = 1
- PT.air_contents.gas[/datum/gas/phoron] = phoron_amt
- PT.air_contents.gas[/datum/gas/carbon_dioxide] = carbon_amt
+ PT.air_contents.gas[GAS_ID_PHORON] = phoron_amt
+ PT.air_contents.gas[GAS_ID_CARBON_DIOXIDE] = carbon_amt
PT.air_contents.total_moles = phoron_amt + carbon_amt
PT.air_contents.temperature = PHORON_MINIMUM_BURN_TEMPERATURE+1
PT.air_contents.update_values()
OT.valve_welded = 1
- OT.air_contents.gas[/datum/gas/oxygen] = oxygen_amt
+ OT.air_contents.gas[GAS_ID_OXYGEN] = oxygen_amt
OT.air_contents.total_moles = oxygen_amt
OT.air_contents.temperature = PHORON_MINIMUM_BURN_TEMPERATURE+1
OT.air_contents.update_values()
diff --git a/code/game/objects/effects/zone_divider.dm b/code/game/objects/effects/zone_divider.dm
index a179ef2c265..946b6cf8933 100644
--- a/code/game/objects/effects/zone_divider.dm
+++ b/code/game/objects/effects/zone_divider.dm
@@ -12,7 +12,7 @@
// Special case to prevent us from being part of a zone during the first air master tick.
// We must merge ourselves into a zone on next tick. This will cause a bit of lag on
// startup, but it can't really be helped you know?
- if(air_master.current_cycle == 0)
+ if(SSair.current_cycle == 0)
if(!isturf(loc))
return
SSticker.OnRoundstart(CALLBACK(loc, TYPE_PROC_REF(/turf, queue_zone_update)))
diff --git a/code/game/objects/items/blueprints.dm b/code/game/objects/items/blueprints.dm
index 9f8a9b958a9..2051c966690 100644
--- a/code/game/objects/items/blueprints.dm
+++ b/code/game/objects/items/blueprints.dm
@@ -223,7 +223,7 @@
if (!oldtitle) // or replacetext goes to infinite loop
return
- for(var/obj/machinery/alarm/M in A)
+ for(var/obj/machinery/air_alarm/M in A)
M.name = replacetext(M.name,oldtitle,title)
for(var/obj/machinery/power/apc/M in A)
M.name = replacetext(M.name,oldtitle,title)
diff --git a/code/game/objects/items/circuitboards/frame.dm b/code/game/objects/items/circuitboards/frame.dm
index 7d31d2ebb93..62e8713c407 100644
--- a/code/game/objects/items/circuitboards/frame.dm
+++ b/code/game/objects/items/circuitboards/frame.dm
@@ -52,7 +52,7 @@
/obj/item/circuitboard/airalarm
name = T_BOARD("air alarm")
- build_path = /obj/machinery/alarm
+ build_path = /obj/machinery/air_alarm
board_type = new /datum/frame/frame_types/air_alarm
materials = list(MAT_STEEL = 50, MAT_GLASS = 50)
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index c73f5e2ea55..e643bfba9b5 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -1572,10 +1572,10 @@ GLOBAL_LIST_EMPTY(PDAs)
var/pressure = environment.return_pressure()
var/total_moles = environment.total_moles
if (total_moles)
- var/o2_level = environment.gas[/datum/gas/oxygen]/total_moles
- var/n2_level = environment.gas[/datum/gas/nitrogen]/total_moles
- var/co2_level = environment.gas[/datum/gas/carbon_dioxide]/total_moles
- var/phoron_level = environment.gas[/datum/gas/phoron]/total_moles
+ var/o2_level = environment.gas[GAS_ID_OXYGEN]/total_moles
+ var/n2_level = environment.gas[GAS_ID_NITROGEN]/total_moles
+ var/co2_level = environment.gas[GAS_ID_CARBON_DIOXIDE]/total_moles
+ var/phoron_level = environment.gas[GAS_ID_PHORON]/total_moles
var/unknown_level = 1-(o2_level+n2_level+co2_level+phoron_level)
// entry is what the element is describing
diff --git a/code/game/objects/items/devices/communicator/helper.dm b/code/game/objects/items/devices/communicator/helper.dm
index b0508f8b2f2..ca06ce4b36b 100644
--- a/code/game/objects/items/devices/communicator/helper.dm
+++ b/code/game/objects/items/devices/communicator/helper.dm
@@ -6,10 +6,10 @@
var/pressure = environment.return_pressure()
var/total_moles = environment.total_moles
if (total_moles)
- var/o2_level = environment.gas[/datum/gas/oxygen]/total_moles
- var/n2_level = environment.gas[/datum/gas/nitrogen]/total_moles
- var/co2_level = environment.gas[/datum/gas/carbon_dioxide]/total_moles
- var/phoron_level = environment.gas[/datum/gas/phoron]/total_moles
+ var/o2_level = environment.gas[GAS_ID_OXYGEN]/total_moles
+ var/n2_level = environment.gas[GAS_ID_NITROGEN]/total_moles
+ var/co2_level = environment.gas[GAS_ID_CARBON_DIOXIDE]/total_moles
+ var/phoron_level = environment.gas[GAS_ID_PHORON]/total_moles
var/unknown_level = 1-(o2_level+n2_level+co2_level+phoron_level)
// Label is what the entry is describing
diff --git a/code/game/objects/items/scanners/atmos.dm b/code/game/objects/items/scanners/atmos.dm
new file mode 100644
index 00000000000..a36d8978f62
--- /dev/null
+++ b/code/game/objects/items/scanners/atmos.dm
@@ -0,0 +1,60 @@
+/obj/item/atmos_analyzer
+ name = "analyzer"
+ desc = "A hand-held environmental scanner which reports current gas levels."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "atmos"
+ item_state = "analyzer"
+ w_class = ITEMSIZE_SMALL
+ slot_flags = SLOT_BELT
+ throw_force = 5
+ throw_speed = 4
+ throw_range = 20
+ materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
+ origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
+
+/obj/item/atmos_analyzer/apidean
+ name = "\improper Apidean analyzer"
+ desc = "This analyzer has a strange, soft exterior and seems to faintly breathe."
+ icon_state = "apidae-analyzer"
+
+/obj/item/atmos_analyzer/longrange
+ name = "long-range analyzer"
+ desc = "A hand-held environmental scanner which reports current gas levels. This one uses bluespace technology."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "atmos"
+ item_state = "analyzer"
+ w_class = ITEMSIZE_SMALL
+ slot_flags = SLOT_BELT
+ throw_force = 5
+ throw_speed = 4
+ throw_range = 20
+ materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
+ origin_tech = list(TECH_MAGNET = 3, TECH_ENGINEERING = 3)
+
+/obj/item/atmos_analyzer/atmosanalyze(var/mob/user)
+ var/air = user.return_air()
+ if (!air)
+ return
+
+ return atmosanalyzer_scan(src, air, user)
+
+/obj/item/atmos_analyzer/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return
+
+ analyze_gases(src, user)
+ return
+
+/obj/item/atmos_analyzer/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY)
+ if(istype(target, /obj/item/tank)) // don't double post what atmosanalyzer_scan returns
+ return
+ analyze_gases(target, user)
+ return
+
+/obj/item/atmos_analyzer/longrange/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(istype(target, /obj/item/tank)) // don't double post what atmosanalyzer_scan returns
+ return
+ analyze_gases(target, user)
+ return
diff --git a/code/modules/xenobio2/tools/xeno_trait_scanner.dm b/code/game/objects/items/scanners/biology.dm
similarity index 94%
rename from code/modules/xenobio2/tools/xeno_trait_scanner.dm
rename to code/game/objects/items/scanners/biology.dm
index d1fbfd80461..1baffbd71d8 100644
--- a/code/modules/xenobio2/tools/xeno_trait_scanner.dm
+++ b/code/game/objects/items/scanners/biology.dm
@@ -1,10 +1,10 @@
-/obj/item/analyzer/xeno_analyzer
+/obj/item/biology_analyzer
name = "exotic biological analyzer"
desc = "A device to investigate the genetic data of a biological target."
var/form_title
var/last_data
-/obj/item/analyzer/xeno_analyzer/proc/print_report_verb()
+/obj/item/biology_analyzer/proc/print_report_verb()
set name = "Print Plant Report"
set category = "Object"
set src = usr
@@ -13,13 +13,13 @@
return
print_report(usr)
-/obj/item/analyzer/xeno_analyzer/Topic(href, href_list)
+/obj/item/biology_analyzer/Topic(href, href_list)
if(..())
return
if(href_list["print"])
print_report(usr)
-/obj/item/analyzer/xeno_analyzer/proc/print_report(var/mob/living/user)
+/obj/item/biology_analyzer/proc/print_report(var/mob/living/user)
if(!last_data)
to_chat(user, "There is no scan data to print.")
return
@@ -31,14 +31,14 @@
user.visible_message("\The [src] spits out a piece of paper.")
return
-/obj/item/analyzer/xeno_analyzer/attack_self(mob/user)
+/obj/item/biology_analyzer/attack_self(mob/user)
. = ..()
if(.)
return
print_report(user)
return 0
-/obj/item/analyzer/xeno_analyzer/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+/obj/item/biology_analyzer/afterattack(atom/target, mob/user, clickchain_flags, list/params)
if(!flag) return
var/datum/xeno/traits/trait_info
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/scanners/health.dm
similarity index 65%
rename from code/game/objects/items/devices/scanners.dm
rename to code/game/objects/items/scanners/health.dm
index 7cd3bb4c9ac..2a5c6f53109 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/scanners/health.dm
@@ -1,16 +1,3 @@
-/// Past this many seconds, defib is useless.
-#define DEFIB_TIME_LIMIT (10 MINUTES)
-/*
-CONTAINS:
-T-RAY
-DETECTIVE SCANNER
-HEALTH ANALYZER
-GAS ANALYZER - Analyzes atmosphere, container
-MASS SPECTROMETER
-REAGENT SCANNER
-HALOGEN COUNTER - Radcount on mobs
-*/
-
/obj/item/healthanalyzer
name = "health analyzer"
desc = "A hand-held body scanner able to distinguish vital signs of the subject."
@@ -91,8 +78,7 @@ HALOGEN COUNTER - Radcount on mobs
if(M.timeofdeath && (M.stat == DEAD || (M.status_flags & STATUS_FAKEDEATH)))
dat += SPAN_NOTICE("\nTime of Death: [worldtime2stationtime(M.timeofdeath)]")
var/tdelta = round(world.time - M.timeofdeath)
- if(tdelta < (DEFIB_TIME_LIMIT * 10))
- dat += SPAN_BOLDNOTICE("\nSubject died [DisplayTimeText(tdelta)] ago - resuscitation may be possible!")
+ dat += SPAN_BOLDNOTICE("\nSubject died [DisplayTimeText(tdelta)] ago - resuscitation may be possible!")
if(istype(M, /mob/living/carbon/human) && mode == 1)
var/mob/living/carbon/human/H = M
var/list/damaged = H.get_damaged_organs(1,1)
@@ -334,236 +320,3 @@ HALOGEN COUNTER - Radcount on mobs
advscan = 3
origin_tech = list(TECH_MAGNET = 7, TECH_BIO = 8)
icon_state = "health3"
-
-/obj/item/analyzer
- name = "analyzer"
- desc = "A hand-held environmental scanner which reports current gas levels."
- icon = 'icons/obj/device.dmi'
- icon_state = "atmos"
- item_state = "analyzer"
- w_class = ITEMSIZE_SMALL
- slot_flags = SLOT_BELT
- throw_force = 5
- throw_speed = 4
- throw_range = 20
-
- materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
-
- origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1)
-
-/obj/item/analyzer/apidean
- name = "\improper Apidean analyzer"
- desc = "This analyzer has a strange, soft exterior and seems to faintly breathe."
- icon_state = "apidae-analyzer"
-
-/obj/item/analyzer/longrange
- name = "long-range analyzer"
- desc = "A hand-held environmental scanner which reports current gas levels. This one uses bluespace technology."
- icon = 'icons/obj/device.dmi'
- icon_state = "atmos"
- item_state = "analyzer"
- w_class = ITEMSIZE_SMALL
- slot_flags = SLOT_BELT
- throw_force = 5
- throw_speed = 4
- throw_range = 20
- materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
- origin_tech = list(TECH_MAGNET = 3, TECH_ENGINEERING = 3)
-
-/obj/item/analyzer/atmosanalyze(var/mob/user)
- var/air = user.return_air()
- if (!air)
- return
-
- return atmosanalyzer_scan(src, air, user)
-
-/obj/item/analyzer/attack_self(mob/user)
- . = ..()
- if(.)
- return
- if (user.stat)
- return
- if (!(ishuman(user) || SSticker) && SSticker.mode.name != "monkey")
- to_chat(usr, "You don't have the dexterity to do this!")
- return
-
- analyze_gases(src, user)
- return
-
-/obj/item/analyzer/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY)
- if(istype(target, /obj/item/tank)) // don't double post what atmosanalyzer_scan returns
- return
- analyze_gases(target, user)
- return
-
-/obj/item/analyzer/longrange/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(istype(target, /obj/item/tank)) // don't double post what atmosanalyzer_scan returns
- return
- analyze_gases(target, user)
- return
-
-/obj/item/mass_spectrometer
- name = "mass spectrometer"
- desc = "A hand-held mass spectrometer which identifies trace chemicals in a blood sample."
- icon = 'icons/obj/device.dmi'
- icon_state = "spectrometer"
- w_class = ITEMSIZE_SMALL
- atom_flags = OPENCONTAINER
- slot_flags = SLOT_BELT
- throw_force = 5
- throw_speed = 4
- throw_range = 20
-
- materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
-
- origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
- var/details = 0
- var/recent_fail = 0
-
-/obj/item/mass_spectrometer/Initialize(mapload)
- . = ..()
- var/datum/reagents/R = new/datum/reagents(5)
- reagents = R
- R.my_atom = src
-
-/obj/item/mass_spectrometer/on_reagent_change()
- if(reagents.total_volume)
- icon_state = initial(icon_state) + "_s"
- else
- icon_state = initial(icon_state)
-
-/obj/item/mass_spectrometer/attack_self(mob/user)
- . = ..()
- if(.)
- return
- if (user.stat)
- return
- if (!(ishuman(user) || SSticker) && SSticker.mode.name != "monkey")
- to_chat(user, "You don't have the dexterity to do this!")
- return
- if(reagents.total_volume)
- var/list/blood_traces = list()
- for(var/datum/reagent/R in reagents.reagent_list)
- if(R.id != "blood")
- reagents.clear_reagents()
- to_chat(user, "The sample was contaminated! Please insert another sample")
- return
- else
- blood_traces = params2list(R.data["trace_chem"])
- break
- var/dat = "Trace Chemicals Found: "
- for(var/R in blood_traces)
- if(details)
- dat += "[R] ([blood_traces[R]] units) "
- else
- dat += "[R] "
- to_chat(user, "[dat]")
- reagents.clear_reagents()
- return
-
-/obj/item/mass_spectrometer/adv
- name = "advanced mass spectrometer"
- icon_state = "adv_spectrometer"
- details = 1
- origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2)
-
-/obj/item/reagent_scanner
- name = "reagent scanner"
- desc = "A hand-held reagent scanner which identifies chemical agents."
- icon = 'icons/obj/device.dmi'
- icon_state = "spectrometer"
- item_state = "analyzer"
- w_class = ITEMSIZE_SMALL
- slot_flags = SLOT_BELT
- throw_force = 5
- throw_speed = 4
- throw_range = 20
- materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
-
- origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
- var/details = 0
- var/recent_fail = 0
-
-/obj/item/reagent_scanner/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) || user.stat || !istype(target))
- return
- if(!istype(user))
- return
-
- if(!isnull(target.reagents))
- if(!(target.atom_flags & OPENCONTAINER)) // The idea is that the scanner has to touch the reagents somehow. This is done to prevent cheesing unidentified autoinjectors.
- to_chat(user, SPAN_WARNING( "\The [target] is sealed, and cannot be scanned by \the [src] until unsealed."))
- return
-
- var/dat = ""
- if(target.reagents.reagent_list.len > 0)
- var/one_percent = target.reagents.total_volume / 100
- for (var/datum/reagent/R in target.reagents.reagent_list)
- dat += "\n \t " + SPAN_NOTICE("[R][details ? ": [R.volume / one_percent]%" : ""]")
- if(dat)
- to_chat(user, SPAN_NOTICE("Chemicals found: [dat]"))
- else
- to_chat(user, SPAN_NOTICE("No active chemical agents found in [target]."))
- else
- to_chat(user, SPAN_NOTICE("No significant chemical agents found in [target]."))
-
- return
-
-/obj/item/reagent_scanner/apidean
- name = "\improper Apidean taster"
- desc = "This reagent scanner appears to be an artificially created lifeform. Often used by Apidaen guards to test food and drinks during diplomatic meetings."
- icon_state = "apidae-reagent"
-
-/obj/item/reagent_scanner/adv
- name = "advanced reagent scanner"
- icon_state = "adv_spectrometer"
- details = 1
- origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2)
-
-/obj/item/slime_scanner
- name = "slime scanner"
- icon_state = "xenobio"
- item_state = "xenobio"
- icon = 'icons/obj/device.dmi'
- origin_tech = list(TECH_BIO = 1)
- w_class = ITEMSIZE_SMALL
- throw_force = 0
- throw_speed = 3
- throw_range = 7
- materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
-
-/obj/item/slime_scanner/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
- . = CLICKCHAIN_DO_NOT_PROPAGATE
- if(!istype(target, /mob/living/simple_mob/slime/xenobio))
- to_chat(user, "This device can only scan lab-grown slimes!")
- return
- var/mob/living/simple_mob/slime/xenobio/S = target
- user.show_message("Slime scan results: [S.slime_color] [S.is_adult ? "adult" : "baby"] slime Health: [S.health] Mutation Probability: [S.mutation_chance]")
-
- var/list/mutations = list()
- for(var/potential_color in S.slime_mutation)
- var/mob/living/simple_mob/slime/xenobio/slime = potential_color
- mutations.Add(initial(slime.slime_color))
- user.show_message("Potental to mutate into [english_list(mutations)] colors. Extract potential: [S.cores] Nutrition: [S.nutrition]/[S.get_max_nutrition()]")
-
- if (S.nutrition < S.get_starve_nutrition())
- user.show_message("Warning: Subject is starving!")
- else if (S.nutrition < S.get_hunger_nutrition())
- user.show_message("Warning: Subject is hungry.")
- user.show_message("Electric change strength: [S.power_charge]")
-
- if(S.has_AI())
- var/datum/ai_holder/simple_mob/xenobio_slime/AI = S.ai_holder
- if(AI.resentment)
- user.show_message("Warning: Subject is harboring resentment.")
- if(AI.rabid)
- user.show_message("Subject is enraged and extremely dangerous!")
- if(S.harmless)
- user.show_message("Subject has been pacified.")
- if(S.unity)
- user.show_message("Subject is friendly to other slime colors.")
-
- user.show_message("Growth progress: [S.amount_grown]/10")
-
-#undef DEFIB_TIME_LIMIT
diff --git a/code/game/objects/items/scanners/plant.dm b/code/game/objects/items/scanners/plant.dm
new file mode 100644
index 00000000000..31eb5b14caf
--- /dev/null
+++ b/code/game/objects/items/scanners/plant.dm
@@ -0,0 +1,146 @@
+/obj/item/plant_analyzer
+ name = "plant analyzer"
+ icon = 'icons/obj/device.dmi'
+ icon_state = "hydro"
+ item_state = "analyzer"
+ var/datum/seed/last_seed
+ var/list/last_reagents
+
+/obj/item/plant_analyzer/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return
+ ui_interact(user)
+
+/obj/item/plant_analyzer/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "PlantAnalyzer", name)
+ ui.open()
+
+/obj/item/plant_analyzer/ui_state(mob/user, datum/tgui_module/module)
+ return GLOB.inventory_state
+
+/obj/item/plant_analyzer/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ var/list/data = ..()
+
+ var/datum/seed/grown_seed = locate(last_seed)
+ if(!istype(grown_seed))
+ return list("no_seed" = TRUE)
+
+ data["no_seed"] = FALSE
+ data["seed"] = grown_seed.get_tgui_analyzer_data(user)
+ data["reagents"] = last_reagents
+
+ return data
+
+/obj/item/plant_analyzer/ui_act(action, list/params, datum/tgui/ui)
+ if(..())
+ return TRUE
+
+ switch(action)
+ if("print")
+ print_report(usr)
+ return TRUE
+ if("close")
+ last_seed = null
+ last_reagents = null
+ return TRUE
+
+/obj/item/plant_analyzer/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
+ return
+
+ var/datum/seed/grown_seed
+ var/datum/reagents/grown_reagents
+ if(istype(target,/obj/structure/table))
+ return ..()
+ else if(istype(target,/obj/item/reagent_containers/food/snacks/grown))
+
+ var/obj/item/reagent_containers/food/snacks/grown/G = target
+ grown_seed = SSplants.seeds[G.plantname]
+ grown_reagents = G.reagents
+
+ else if(istype(target,/obj/item/grown))
+
+ var/obj/item/grown/G = target
+ grown_seed = SSplants.seeds[G.plantname]
+ grown_reagents = G.reagents
+
+ else if(istype(target,/obj/item/seeds))
+
+ var/obj/item/seeds/S = target
+ grown_seed = S.seed
+
+ else if(istype(target,/obj/machinery/portable_atmospherics/hydroponics))
+
+ var/obj/machinery/portable_atmospherics/hydroponics/H = target
+ if(H.frozen == 1)
+ to_chat(user, "Disable the cryogenic freezing first!")
+ return
+ grown_seed = H.seed
+ grown_reagents = H.reagents
+
+ if(!grown_seed)
+ to_chat(user, "[src] can tell you nothing about \the [target].")
+ return
+
+ last_seed = REF(grown_seed)
+
+ user.visible_message("[user] runs the scanner over \the [target].")
+
+ last_reagents = list()
+ if(grown_reagents && grown_reagents.reagent_list && grown_reagents.reagent_list.len)
+ for(var/datum/reagent/R in grown_reagents.reagent_list)
+ last_reagents.Add(list(list(
+ "name" = R.name,
+ "volume" = grown_reagents.get_reagent_amount(R.id),
+ )))
+
+ ui_interact(user)
+
+/obj/item/plant_analyzer/proc/print_report_verb()
+ set name = "Print Plant Report"
+ set category = "Object"
+ set src = usr
+
+ if(usr.stat || usr.restrained() || usr.lying)
+ return
+ print_report(usr)
+
+/obj/item/plant_analyzer/proc/print_report(var/mob/living/user)
+ var/datum/seed/grown_seed = locate(last_seed)
+ if(!istype(grown_seed))
+ to_chat(user, SPAN_WARNING("There is no scan data to print."))
+ return
+
+ var/form_title = "[grown_seed.seed_name] (#[grown_seed.uid])"
+ var/dat = "
Plant data for [form_title]
"
+ dat += "
General Data
"
+ dat += "
"
+ dat += "
Endurance
[grown_seed.get_trait(TRAIT_ENDURANCE)]
"
+ dat += "
Yield
[grown_seed.get_trait(TRAIT_YIELD)]
"
+ dat += "
Maturation time
[grown_seed.get_trait(TRAIT_MATURATION)]
"
+ dat += "
Production time
[grown_seed.get_trait(TRAIT_PRODUCTION)]
"
+ dat += "
Potency
[grown_seed.get_trait(TRAIT_POTENCY)]
"
+ dat += "
"
+
+ if(LAZYLEN(last_reagents))
+ dat += "
Reagent Data
"
+ dat += " This sample contains: "
+ for(var/i in 1 to LAZYLEN(last_reagents))
+ dat += " - [last_reagents[i]["name"]], [last_reagents[i]["volume"]] unit(s)"
+
+ dat += "
Other Data
"
+
+ var/list/ui_data = grown_seed.get_tgui_analyzer_data()
+
+ dat += jointext(ui_data["trait_info"], " \n")
+
+ var/obj/item/paper/P = new /obj/item/paper(get_turf(src))
+ P.name = "paper - [form_title]"
+ P.info = "[dat]"
+ if(istype(user,/mob/living/carbon/human))
+ user.put_in_hands(P)
+ user.visible_message("\The [src] spits out a piece of paper.")
+ return
diff --git a/code/game/objects/items/scanners/reagent.dm b/code/game/objects/items/scanners/reagent.dm
new file mode 100644
index 00000000000..d7cc88fd368
--- /dev/null
+++ b/code/game/objects/items/scanners/reagent.dm
@@ -0,0 +1,52 @@
+/obj/item/reagent_scanner
+ name = "reagent scanner"
+ desc = "A hand-held reagent scanner which identifies chemical agents."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "spectrometer"
+ item_state = "analyzer"
+ w_class = ITEMSIZE_SMALL
+ slot_flags = SLOT_BELT
+ throw_force = 5
+ throw_speed = 4
+ throw_range = 20
+ materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
+
+ origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
+ var/details = 0
+ var/recent_fail = 0
+
+/obj/item/reagent_scanner/afterattack(atom/target, mob/user, clickchain_flags, list/params)
+ if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) || user.stat || !istype(target))
+ return
+ if(!istype(user))
+ return
+
+ if(!isnull(target.reagents))
+ if(!(target.atom_flags & OPENCONTAINER)) // The idea is that the scanner has to touch the reagents somehow. This is done to prevent cheesing unidentified autoinjectors.
+ to_chat(user, SPAN_WARNING( "\The [target] is sealed, and cannot be scanned by \the [src] until unsealed."))
+ return
+
+ var/dat = ""
+ if(target.reagents.reagent_list.len > 0)
+ var/one_percent = target.reagents.total_volume / 100
+ for (var/datum/reagent/R in target.reagents.reagent_list)
+ dat += "\n \t " + SPAN_NOTICE("[R][details ? ": [R.volume / one_percent]%" : ""]")
+ if(dat)
+ to_chat(user, SPAN_NOTICE("Chemicals found: [dat]"))
+ else
+ to_chat(user, SPAN_NOTICE("No active chemical agents found in [target]."))
+ else
+ to_chat(user, SPAN_NOTICE("No significant chemical agents found in [target]."))
+
+ return
+
+/obj/item/reagent_scanner/apidean
+ name = "\improper Apidean taster"
+ desc = "This reagent scanner appears to be an artificially created lifeform. Often used by Apidaen guards to test food and drinks during diplomatic meetings."
+ icon_state = "apidae-reagent"
+
+/obj/item/reagent_scanner/adv
+ name = "advanced reagent scanner"
+ icon_state = "adv_spectrometer"
+ details = 1
+ origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2)
diff --git a/code/game/objects/items/scanners/slime.dm b/code/game/objects/items/scanners/slime.dm
new file mode 100644
index 00000000000..a7e992c5d77
--- /dev/null
+++ b/code/game/objects/items/scanners/slime.dm
@@ -0,0 +1,44 @@
+/obj/item/slime_scanner
+ name = "slime scanner"
+ icon_state = "xenobio"
+ item_state = "xenobio"
+ icon = 'icons/obj/device.dmi'
+ origin_tech = list(TECH_BIO = 1)
+ w_class = ITEMSIZE_SMALL
+ throw_force = 0
+ throw_speed = 3
+ throw_range = 7
+ materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
+
+/obj/item/slime_scanner/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent)
+ . = CLICKCHAIN_DO_NOT_PROPAGATE
+ if(!istype(target, /mob/living/simple_mob/slime/xenobio))
+ to_chat(user, "This device can only scan lab-grown slimes!")
+ return
+ var/mob/living/simple_mob/slime/xenobio/S = target
+ user.show_message("Slime scan results: [S.slime_color] [S.is_adult ? "adult" : "baby"] slime Health: [S.health] Mutation Probability: [S.mutation_chance]")
+
+ var/list/mutations = list()
+ for(var/potential_color in S.slime_mutation)
+ var/mob/living/simple_mob/slime/xenobio/slime = potential_color
+ mutations.Add(initial(slime.slime_color))
+ user.show_message("Potental to mutate into [english_list(mutations)] colors. Extract potential: [S.cores] Nutrition: [S.nutrition]/[S.get_max_nutrition()]")
+
+ if (S.nutrition < S.get_starve_nutrition())
+ user.show_message("Warning: Subject is starving!")
+ else if (S.nutrition < S.get_hunger_nutrition())
+ user.show_message("Warning: Subject is hungry.")
+ user.show_message("Electric change strength: [S.power_charge]")
+
+ if(S.has_AI())
+ var/datum/ai_holder/simple_mob/xenobio_slime/AI = S.ai_holder
+ if(AI.resentment)
+ user.show_message("Warning: Subject is harboring resentment.")
+ if(AI.rabid)
+ user.show_message("Subject is enraged and extremely dangerous!")
+ if(S.harmless)
+ user.show_message("Subject has been pacified.")
+ if(S.unity)
+ user.show_message("Subject is friendly to other slime colors.")
+
+ user.show_message("Growth progress: [S.amount_grown]/10")
diff --git a/code/game/objects/items/scanners/spectrometer.dm b/code/game/objects/items/scanners/spectrometer.dm
new file mode 100644
index 00000000000..a68230336b4
--- /dev/null
+++ b/code/game/objects/items/scanners/spectrometer.dm
@@ -0,0 +1,64 @@
+/obj/item/mass_spectrometer
+ name = "mass spectrometer"
+ desc = "A hand-held mass spectrometer which identifies trace chemicals in a blood sample."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "spectrometer"
+ w_class = ITEMSIZE_SMALL
+ atom_flags = OPENCONTAINER
+ slot_flags = SLOT_BELT
+ throw_force = 5
+ throw_speed = 4
+ throw_range = 20
+
+ materials = list(MAT_STEEL = 30, MAT_GLASS = 20)
+
+ origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2)
+ var/details = 0
+ var/recent_fail = 0
+
+/obj/item/mass_spectrometer/Initialize(mapload)
+ . = ..()
+ var/datum/reagents/R = new/datum/reagents(5)
+ reagents = R
+ R.my_atom = src
+
+/obj/item/mass_spectrometer/on_reagent_change()
+ if(reagents.total_volume)
+ icon_state = initial(icon_state) + "_s"
+ else
+ icon_state = initial(icon_state)
+
+/obj/item/mass_spectrometer/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return
+ if (user.stat)
+ return
+ if (!(ishuman(user) || SSticker) && SSticker.mode.name != "monkey")
+ to_chat(user, "You don't have the dexterity to do this!")
+ return
+ if(reagents.total_volume)
+ var/list/blood_traces = list()
+ for(var/datum/reagent/R in reagents.reagent_list)
+ if(R.id != "blood")
+ reagents.clear_reagents()
+ to_chat(user, "The sample was contaminated! Please insert another sample")
+ return
+ else
+ blood_traces = params2list(R.data["trace_chem"])
+ break
+ var/dat = "Trace Chemicals Found: "
+ for(var/R in blood_traces)
+ if(details)
+ dat += "[R] ([blood_traces[R]] units) "
+ else
+ dat += "[R] "
+ to_chat(user, "[dat]")
+ reagents.clear_reagents()
+ return
+
+/obj/item/mass_spectrometer/adv
+ name = "advanced mass spectrometer"
+ icon_state = "adv_spectrometer"
+ details = 1
+ origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2)
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 4f117583b35..797e5003d41 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -60,7 +60,7 @@
/obj/item/cell/device,
/obj/item/stack/cable_coil,
/obj/item/t_scanner,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/clothing/glasses,
/obj/item/clothing/gloves,
/obj/item/pda,
@@ -70,7 +70,7 @@
/obj/item/robotanalyzer,
/obj/item/material/minihoe,
/obj/item/material/knife/machete/hatchet,
- /obj/item/analyzer/plant_analyzer,
+ /obj/item/plant_analyzer,
/obj/item/extinguisher/mini,
/obj/item/duct_tape_roll,
/obj/item/switchtool,
@@ -111,7 +111,7 @@
/obj/item/multitool,
/obj/item/stack/cable_coil/random_belt,
/obj/item/extinguisher/mini,
- /obj/item/analyzer/longrange
+ /obj/item/atmos_analyzer/longrange
)
/obj/item/storage/belt/medical
@@ -254,7 +254,7 @@
/obj/item/multitool,
/obj/item/stack/cable_coil,
/obj/item/t_scanner,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/flashlight,
/obj/item/cell/device,
/obj/item/cell/device/weapon,
diff --git a/code/game/objects/items/storage/toolbox.dm b/code/game/objects/items/storage/toolbox.dm
index 67ee29af7b3..0d454fc3708 100644
--- a/code/game/objects/items/storage/toolbox.dm
+++ b/code/game/objects/items/storage/toolbox.dm
@@ -41,7 +41,7 @@
/obj/item/tool/wrench,
/obj/item/weldingtool,
/obj/item/tool/crowbar,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/tool/wirecutters
)
@@ -89,7 +89,7 @@
/obj/item/tool/crowbar/power,
/obj/item/multitool,
/obj/item/stack/cable_coil/random_belt,
- /obj/item/analyzer
+ /obj/item/atmos_analyzer
)
/obj/item/storage/toolbox/gold_fake // used in crafting
diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm
index b6c572d7eb8..f5a809aa4f8 100644
--- a/code/game/objects/items/weapons/flamethrower.dm
+++ b/code/game/objects/items/weapons/flamethrower.dm
@@ -115,8 +115,8 @@
update_icon()
return
- if(istype(W, /obj/item/analyzer))
- var/obj/item/analyzer/A = W
+ if(istype(W, /obj/item/atmos_analyzer))
+ var/obj/item/atmos_analyzer/A = W
A.analyze_gases(src, user)
return
..()
@@ -147,7 +147,7 @@
usr.set_machine(src)
if(href_list["light"])
if(!ptank) return
- if(ptank.air_contents.gas[/datum/gas/phoron] < 1) return
+ if(ptank.air_contents.gas[GAS_ID_PHORON] < 1) return
if(!status) return
lit = !lit
if(lit)
@@ -196,8 +196,8 @@
//Transfer 5% of current tank air contents to turf
var/datum/gas_mixture/air_transfer = ptank.air_contents.remove_ratio(0.02*(throw_amount/100))
//air_transfer.toxins = air_transfer.toxins * 5 // This is me not comprehending the air system. I realize this is stupid and I could probably make it work without fucking it up like this, but there you have it. -- TLE
- new/obj/effect/debris/cleanable/liquid_fuel/flamethrower_fuel(target,air_transfer.gas[/datum/gas/phoron],get_dir(loc,target))
- air_transfer.gas[/datum/gas/phoron] = 0
+ new/obj/effect/debris/cleanable/liquid_fuel/flamethrower_fuel(target,air_transfer.gas[GAS_ID_PHORON],get_dir(loc,target))
+ air_transfer.gas[GAS_ID_PHORON] = 0
target.assume_air(air_transfer)
//Burn it based on transfered gas
//target.hotspot_expose(part4.air_contents.temperature*2,300)
diff --git a/code/game/objects/items/weapons/tanks/jetpack.dm b/code/game/objects/items/weapons/tanks/jetpack.dm
index c4148c3e00e..30b0f1485c5 100644
--- a/code/game/objects/items/weapons/tanks/jetpack.dm
+++ b/code/game/objects/items/weapons/tanks/jetpack.dm
@@ -67,7 +67,7 @@
var/datum/gas_mixture/G = air_contents.remove(num)
- var/allgases = G.gas[/datum/gas/carbon_dioxide] + G.gas[/datum/gas/nitrogen] + G.gas[/datum/gas/oxygen] + G.gas[/datum/gas/phoron]
+ var/allgases = G.gas[GAS_ID_CARBON_DIOXIDE] + G.gas[GAS_ID_NITROGEN] + G.gas[GAS_ID_OXYGEN] + G.gas[GAS_ID_PHORON]
if(allgases >= 0.005)
return 1
@@ -85,7 +85,7 @@
/obj/item/tank/jetpack/void/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_OXYGEN, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/jetpack/oxygen
name = "jetpack (oxygen)"
@@ -95,7 +95,7 @@
/obj/item/tank/jetpack/oxygen/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_OXYGEN, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/jetpack/carbondioxide
name = "jetpack (carbon dioxide)"
@@ -106,7 +106,7 @@
/obj/item/tank/jetpack/carbondioxide/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/carbon_dioxide, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_CARBON_DIOXIDE, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/jetpack/hardsuit
name = "jetpack"
@@ -129,7 +129,7 @@
var/datum/gas_mixture/G = pressure_vessel.air_contents.remove(num)
- var/allgases = G.gas[/datum/gas/carbon_dioxide] + G.gas[/datum/gas/nitrogen] + G.gas[/datum/gas/oxygen] + G.gas[/datum/gas/phoron]
+ var/allgases = G.gas[GAS_ID_CARBON_DIOXIDE] + G.gas[GAS_ID_NITROGEN] + G.gas[GAS_ID_OXYGEN] + G.gas[GAS_ID_PHORON]
if(allgases >= 0.005)
return 1
qdel(G)
diff --git a/code/game/objects/items/weapons/tanks/tank_types.dm b/code/game/objects/items/weapons/tanks/tank_types.dm
index 441465c7d4a..f99bcf9611a 100644
--- a/code/game/objects/items/weapons/tanks/tank_types.dm
+++ b/code/game/objects/items/weapons/tanks/tank_types.dm
@@ -21,11 +21,11 @@
/obj/item/tank/oxygen/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_OXYGEN, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/oxygen/examine(mob/user, dist)
. = ..()
- if(air_contents.gas[/datum/gas/oxygen] < 10)
+ if(air_contents.gas[GAS_ID_OXYGEN] < 10)
. += "The meter on the [src] indicates you are almost out of oxygen!"
/obj/item/tank/oxygen/yellow
@@ -50,8 +50,8 @@
/obj/item/tank/anesthetic/Initialize(mapload)
. = ..()
- air_contents.gas[/datum/gas/oxygen] = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD
- air_contents.gas[/datum/gas/nitrous_oxide] = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD
+ air_contents.gas[GAS_ID_OXYGEN] = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD
+ air_contents.gas[GAS_ID_NITROUS_OXIDE] = (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD
air_contents.update_values()
/*
@@ -65,14 +65,14 @@
/obj/item/tank/air/examine(mob/user, dist)
. = ..()
- if(air_contents.gas[/datum/gas/oxygen] < 1 && loc==user)
+ if(air_contents.gas[GAS_ID_OXYGEN] < 1 && loc==user)
. += "The meter on the [src.name] indicates you are almost out of air!"
//user.playsound('sound/effects/alert.ogg')
/obj/item/tank/air/Initialize(mapload)
. = ..()
- src.air_contents.adjust_multi(/datum/gas/oxygen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD, /datum/gas/nitrogen, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
+ src.air_contents.adjust_multi(GAS_ID_OXYGEN, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * O2STANDARD, GAS_ID_NITROGEN, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C) * N2STANDARD)
/*
* Phoron
@@ -87,7 +87,7 @@
/obj/item/tank/phoron/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/phoron, (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_PHORON, (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/vox //Can't be a child of phoron or the gas amount gets screwey.
name = "phoron tank"
@@ -101,7 +101,7 @@
/obj/item/tank/vox/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/phoron, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_PHORON, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/phoron/pressurized
name = "fuel can"
@@ -111,7 +111,7 @@
/obj/item/tank/phoron/pressurized/Initialize(mapload)
. = ..()
adjust_scale(0.8)
- air_contents.adjust_gas(/datum/gas/phoron, (7*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_PHORON, (7*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/phoron/double
name = "double emergency phoron tank"
@@ -125,7 +125,7 @@
/obj/item/tank/emergency/phoron/double/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/phoron, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_PHORON, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/*
* Emergency Oxygen
@@ -153,11 +153,11 @@
/obj/item/tank/emergency/oxygen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/oxygen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_OXYGEN, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/oxygen/examine(mob/user, dist)
. = ..()
- if(air_contents.gas[/datum/gas/oxygen] < 0.2 && loc==user)
+ if(air_contents.gas[GAS_ID_OXYGEN] < 0.2 && loc==user)
. += "The meter on the [src.name] indicates you are almost out of air!"
//user.playsound('sound/effects/alert.ogg')
@@ -186,7 +186,7 @@
/obj/item/tank/stasis/oxygen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/oxygen, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_OXYGEN, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/nitrogen
name = "emergency nitrogen tank"
@@ -198,7 +198,7 @@
/obj/item/tank/emergency/nitrogen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/nitrogen, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_NITROGEN, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/nitrogen/double
name = "double emergency nitrogen tank"
@@ -219,7 +219,7 @@
/obj/item/tank/emergency/phoron/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/phoron, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_PHORON, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/phoron/double
name = "double emergency phoron tank"
@@ -241,10 +241,10 @@
/obj/item/tank/nitrogen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/nitrogen, (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_NITROGEN, (3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/nitrogen/examine(mob/user, dist)
- if(air_contents.gas[/datum/gas/nitrogen] < 10)
+ if(air_contents.gas[GAS_ID_NITROGEN] < 10)
. += "The meter on \the [src] indicates you are almost out of nitrogen!"
/obj/item/tank/stasis/nitro_cryo // Synthmorph bags need to have initial pressure within safe bounds for human atmospheric pressure, but low temperature to stop unwanted degredation.
@@ -256,7 +256,7 @@
/obj/item/tank/stasis/nitro_cryo/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas_temp(/datum/gas/nitrogen, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*TN60C), TN60C)
+ src.air_contents.adjust_gas_temp(GAS_ID_NITROGEN, (3*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*TN60C), TN60C)
//co2
/obj/item/tank/carbon_dioxide
@@ -270,7 +270,7 @@
/obj/item/tank/carbon_dioxide/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/carbon_dioxide, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ air_contents.adjust_gas(GAS_ID_CARBON_DIOXIDE, (6*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/carbon_dioxide
name = "emergency CO2 tank"
@@ -283,9 +283,9 @@
/obj/item/tank/emergency/carbon_dioxide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/carbon_dioxide, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
+ src.air_contents.adjust_gas(GAS_ID_CARBON_DIOXIDE, (10*ONE_ATMOSPHERE)*volume/(R_IDEAL_GAS_EQUATION*T20C))
/obj/item/tank/emergency/carbon_dioxide/examine(mob/user, dist)
- if(air_contents.gas[/datum/gas/carbon_dioxide] < 0.2 && loc==user)
+ if(air_contents.gas[GAS_ID_CARBON_DIOXIDE] < 0.2 && loc==user)
. += "The meter on the [src.name] indicates you are almost out of air!"
// user.playsound('sound/effects/alert.ogg')
diff --git a/code/game/objects/items/weapons/tanks/tanks.dm b/code/game/objects/items/weapons/tanks/tanks.dm
index 792dd885b53..7b8e2af04db 100644
--- a/code/game/objects/items/weapons/tanks/tanks.dm
+++ b/code/game/objects/items/weapons/tanks/tanks.dm
@@ -115,8 +115,8 @@ var/list/global/tank_gauge_cache = list()
if (istype(loc, /obj/item/assembly))
icon = loc
- if ((istype(W, /obj/item/analyzer)) && get_dist(user, src) <= 1)
- var/obj/item/analyzer/A = W
+ if ((istype(W, /obj/item/atmos_analyzer)) && get_dist(user, src) <= 1)
+ var/obj/item/atmos_analyzer/A = W
A.analyze_gases(src, user)
else if (istype(W,/obj/item/latexballon))
var/obj/item/latexballon/LB = W
@@ -360,6 +360,16 @@ var/list/global/tank_gauge_cache = list()
update_gauge()
check_status()
+/**
+ * Encodes data for AtmosTank in tgui/interfaces/common/Atmos.tsx
+ */
+/obj/item/tank/proc/tgui_tank_data()
+ return list(
+ "name" = name,
+ "pressure" = air_contents?.return_pressure(),
+ "volume" = air_contents.volume,
+ "pressureLimit" = TANK_IDEAL_PRESSURE,
+ )
/obj/item/tank/proc/add_bomb_overlay()
cut_overlays()
@@ -373,7 +383,6 @@ var/list/global/tank_gauge_cache = list()
overlays_to_add += test
add_overlay(overlays_to_add)
-
/obj/item/tank/proc/update_gauge()
var/gauge_pressure = 0
if(air_contents)
@@ -393,7 +402,6 @@ var/list/global/tank_gauge_cache = list()
tank_gauge_cache[indicator] = image(icon, indicator)
add_overlay(tank_gauge_cache[indicator])
-
/obj/item/tank/proc/check_status()
//Handle exploding, leaking, and rupturing of the tank
@@ -401,8 +409,6 @@ var/list/global/tank_gauge_cache = list()
return 0
var/pressure = air_contents.return_pressure()
-
-
if(pressure > TANK_FRAGMENT_PRESSURE)
if(integrity <= 7)
if(!istype(src.loc,/obj/item/transfer_valve))
@@ -444,15 +450,11 @@ var/list/global/tank_gauge_cache = list()
var/obj/item/transfer_valve/TTV = loc
TTV.remove_tank(src)
qdel(TTV)
-
-
if(src)
qdel(src)
-
else
integrity -=7
-
else if(pressure > TANK_RUPTURE_PRESSURE)
#ifdef FIREDBG
log_debug(SPAN_DEBUGWARNING("[x],[y] tank is rupturing: [pressure] kPa, integrity [integrity]"))
@@ -480,8 +482,6 @@ var/list/global/tank_gauge_cache = list()
if(istype(loc, /obj/item/transfer_valve))
var/obj/item/transfer_valve/TTV = loc
TTV.remove_tank(src)
-
-
qdel(src)
else
@@ -491,7 +491,6 @@ var/list/global/tank_gauge_cache = list()
else
integrity-= 5
-
else if(pressure > TANK_LEAK_PRESSURE || air_contents.temperature - T0C > failure_temp)
if((integrity <= 17 || src.leaking) && !valve_welded)
@@ -527,20 +526,6 @@ var/list/global/tank_gauge_cache = list()
if(integrity == max_integrity)
leaking = 0
-/////////////////////////////////
-///Prewelded tanks
-/////////////////////////////////
-
-/obj/item/tank/phoron/welded
- valve_welded = 1
-/obj/item/tank/oxygen/welded
- valve_welded = 1
-
-
-/////////////////////////////////
-///Onetankbombs (added as actual items)
-/////////////////////////////////
-
/obj/item/tank/proc/onetankbomb(fill = 1)
var/phoron_amt = 4 + rand(4)
var/oxygen_amt = 6 + rand(8)
@@ -553,8 +538,8 @@ var/list/global/tank_gauge_cache = list()
oxygen_amt = 4.5
- air_contents.gas[/datum/gas/phoron] = phoron_amt
- air_contents.gas[/datum/gas/oxygen] = oxygen_amt
+ air_contents.gas[GAS_ID_PHORON] = phoron_amt
+ air_contents.gas[GAS_ID_OXYGEN] = oxygen_amt
air_contents.update_values()
valve_welded = 1
air_contents.temperature = PHORON_MINIMUM_BURN_TEMPERATURE-1
@@ -569,31 +554,35 @@ var/list/global/tank_gauge_cache = list()
add_overlay("bomb_assembly")
+/obj/item/tank/oxygen/welded
+ valve_welded = TRUE
+
+/obj/item/tank/phoron/welded
+ valve_welded = TRUE
/obj/item/tank/phoron/onetankbomb/Initialize(mapload)
. = ..()
- src.onetankbomb()
+ onetankbomb()
/obj/item/tank/oxygen/onetankbomb/Initialize(mapload)
. = ..()
- src.onetankbomb()
-
+ onetankbomb()
/obj/item/tank/phoron/onetankbomb/full/Initialize(mapload)
. = ..()
- src.onetankbomb(2)
+ onetankbomb(2)
/obj/item/tank/oxygen/onetankbomb/full/Initialize(mapload)
. = ..()
- src.onetankbomb(2)
+ onetankbomb(2)
/obj/item/tank/phoron/onetankbomb/small/Initialize(mapload)
. = ..()
- src.onetankbomb(0)
+ onetankbomb(0)
/obj/item/tank/oxygen/onetankbomb/small/Initialize(mapload)
. = ..()
- src.onetankbomb(0)
+ onetankbomb(0)
/////////////////////////////////
///Pulled from rewritten bomb.dm
diff --git a/code/game/objects/mob_spawner.dm b/code/game/objects/mob_spawner.dm
index a995fc9782b..a54ea4b6467 100644
--- a/code/game/objects/mob_spawner.dm
+++ b/code/game/objects/mob_spawner.dm
@@ -311,14 +311,14 @@ It also makes it so a ghost wont know where all the goodies/mobs are.
my_mob.maxbodytemp = env.temperature * 1.2
var/list/gaslist = env.gas
- my_mob.min_oxy = gaslist[/datum/gas/oxygen] * 0.8
- my_mob.min_tox = gaslist[/datum/gas/phoron] * 0.8
- my_mob.min_n2 = gaslist[/datum/gas/nitrogen] * 0.8
- my_mob.min_co2 = gaslist[/datum/gas/carbon_dioxide] * 0.8
- my_mob.max_oxy = gaslist[/datum/gas/oxygen] * 1.2
- my_mob.max_tox = gaslist[/datum/gas/phoron] * 1.2
- my_mob.max_n2 = gaslist[/datum/gas/nitrogen] * 1.2
- my_mob.max_co2 = gaslist[/datum/gas/carbon_dioxide] * 1.2
+ my_mob.min_oxy = gaslist[GAS_ID_OXYGEN] * 0.8
+ my_mob.min_tox = gaslist[GAS_ID_PHORON] * 0.8
+ my_mob.min_n2 = gaslist[GAS_ID_NITROGEN] * 0.8
+ my_mob.min_co2 = gaslist[GAS_ID_CARBON_DIOXIDE] * 0.8
+ my_mob.max_oxy = gaslist[GAS_ID_OXYGEN] * 1.2
+ my_mob.max_tox = gaslist[GAS_ID_PHORON] * 1.2
+ my_mob.max_n2 = gaslist[GAS_ID_NITROGEN] * 1.2
+ my_mob.max_co2 = gaslist[GAS_ID_CARBON_DIOXIDE] * 1.2
return
else
STOP_PROCESSING(SSobj, src)
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index 623b05d1d00..5368abb6e74 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -284,7 +284,10 @@
return FALSE
if(INTERACTING_WITH_FOR(climber, src, INTERACTING_FOR_CLIMB))
return FALSE
- climber.visible_action_feedback(SPAN_WARNING("[climber] starts climbing onto \the [src]!"), src, MESSAGE_RANGE_COMBAT_LOUD)
+ climber.visible_action_feedback(
+ target = src,
+ visible_hard = SPAN_WARNING("[climber] starts climbing onto \the [src]!"),
+ hard_range = MESSAGE_RANGE_COMBAT_LOUD)
START_INTERACTING_WITH(climber, src, INTERACTING_FOR_CLIMB)
LAZYDISTINCTADD(climbing, climber)
. = do_after(climber, climb_delay * delay_mod, src, mobility_flags = MOBILITY_CAN_MOVE | MOBILITY_CAN_STAND | MOBILITY_IS_STANDING)
@@ -383,6 +386,14 @@
H.update_health()
*/
+//* Hiding / Underfloor
+
+/obj/proc/is_hidden_underfloor()
+ return FALSE
+
+/obj/proc/should_hide_underfloor()
+ return FALSE
+
//? Materials
/obj/get_materials()
diff --git a/code/game/objects/random/maintenance.dm b/code/game/objects/random/maintenance.dm
index 9d61e47a88a..653ccb086ff 100644
--- a/code/game/objects/random/maintenance.dm
+++ b/code/game/objects/random/maintenance.dm
@@ -253,7 +253,7 @@ something, make sure it's not in one of the other lists.*/
/obj/random/maintenance/research/item_to_spawn()
return pick(prob(320);/obj/random/maintenance/clean,
- prob(3);/obj/item/analyzer/plant_analyzer,
+ prob(3);/obj/item/plant_analyzer,
prob(1);/obj/item/flash/synthetic,
prob(2);/obj/item/bucket_sensor,
prob(1);/obj/item/cell/device/weapon,
diff --git a/code/game/objects/random/misc.dm b/code/game/objects/random/misc.dm
index df25ba1d495..209bdb33033 100644
--- a/code/game/objects/random/misc.dm
+++ b/code/game/objects/random/misc.dm
@@ -66,7 +66,7 @@
/obj/random/technology_scanner/item_to_spawn()
return pick(prob(5);/obj/item/t_scanner,
prob(2);/obj/item/radio,
- prob(5);/obj/item/analyzer)
+ prob(5);/obj/item/atmos_analyzer)
/obj/random/powercell
name = "random powercell"
diff --git a/code/game/objects/structures/bonfire.dm b/code/game/objects/structures/bonfire.dm
index 7e7348d8d3c..57c26df5a4d 100644
--- a/code/game/objects/structures/bonfire.dm
+++ b/code/game/objects/structures/bonfire.dm
@@ -164,7 +164,7 @@
/obj/structure/bonfire/proc/check_oxygen()
var/datum/gas_mixture/G = loc.return_air()
- if(G.gas[/datum/gas/oxygen] < 1)
+ if(G.gas[GAS_ID_OXYGEN] < 1)
return FALSE
return TRUE
@@ -343,7 +343,7 @@
/obj/structure/fireplace/proc/check_oxygen()
var/datum/gas_mixture/G = loc.return_air()
- if(G.gas[/datum/gas/oxygen] < 1)
+ if(G.gas[GAS_ID_OXYGEN] < 1)
return FALSE
return TRUE
diff --git a/code/game/objects/structures/crates_lockers/closets/coffin.dm b/code/game/objects/structures/crates_lockers/closets/coffin.dm
index 137afd18b4d..9c61d1531ef 100644
--- a/code/game/objects/structures/crates_lockers/closets/coffin.dm
+++ b/code/game/objects/structures/crates_lockers/closets/coffin.dm
@@ -159,7 +159,7 @@
return PROJECTILE_CONTINUE // It's a hole in the ground, doesn't usually stop or even care about bullets
/obj/structure/closet/grave/return_air_for_internal_lifeform(var/mob/living/L)
- var/gasid = /datum/gas/carbon_dioxide
+ var/gasid = GAS_ID_CARBON_DIOXIDE
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.species && H.species.exhale_type)
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
index 2f2e133086a..c9b9b6f2215 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/cargo.dm
@@ -74,7 +74,7 @@
/obj/item/clothing/under/rank/miner,
/obj/item/clothing/gloves/black,
/obj/item/clothing/shoes/black,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/storage/bag/ore,
/obj/item/flashlight/lantern,
/obj/item/shovel,
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm
index c790c32d9df..bb399f300e2 100644
--- a/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/hydroponics.dm
@@ -7,7 +7,7 @@
/obj/item/storage/bag/plants,
/obj/item/clothing/under/rank/hydroponics,
/obj/item/clothing/under/rank/hydroponics/skirt_pleated,
- /obj/item/analyzer/plant_analyzer,
+ /obj/item/plant_analyzer,
/obj/item/radio/headset/headset_service,
/obj/item/clothing/head/greenbandana,
/obj/item/material/minihoe,
diff --git a/code/game/objects/structures/loot_piles.dm b/code/game/objects/structures/loot_piles.dm
index a658e7da47f..f54e21d8868 100644
--- a/code/game/objects/structures/loot_piles.dm
+++ b/code/game/objects/structures/loot_piles.dm
@@ -386,7 +386,7 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
/obj/item/circuitboard/rcon_console,
/obj/item/smes_coil,
/obj/item/cartridge/engineering,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/healthanalyzer,
/obj/item/robotanalyzer,
/obj/item/lightreplacer,
diff --git a/code/game/objects/structures/mineral_bath.dm b/code/game/objects/structures/mineral_bath.dm
index d144f338c42..bf572a66f10 100644
--- a/code/game/objects/structures/mineral_bath.dm
+++ b/code/game/objects/structures/mineral_bath.dm
@@ -14,7 +14,7 @@
/obj/structure/adherent_bath/return_air()
var/datum/gas_mixture/venus = new(CELL_VOLUME)
- venus.adjust_multi(/datum/gas/nitrogen, MOLES_N2STANDARD, /datum/gas/oxygen, MOLES_O2STANDARD)
+ venus.adjust_multi(GAS_ID_NITROGEN, MOLES_N2STANDARD, GAS_ID_OXYGEN, MOLES_O2STANDARD)
venus.temperature = 490
return venus
diff --git a/code/game/objects/structures/transit_tubes.dm b/code/game/objects/structures/transit_tubes.dm
index f224e710a44..e7998b36dfd 100644
--- a/code/game/objects/structures/transit_tubes.dm
+++ b/code/game/objects/structures/transit_tubes.dm
@@ -83,7 +83,7 @@
/obj/structure/transit_tube_pod/Initialize(mapload)
. = ..()
- air_contents.adjust_multi(/datum/gas/oxygen, MOLES_O2STANDARD * 2, /datum/gas/nitrogen, MOLES_N2STANDARD)
+ air_contents.adjust_multi(GAS_ID_OXYGEN, MOLES_O2STANDARD * 2, GAS_ID_NITROGEN, MOLES_N2STANDARD)
air_contents.temperature = T20C
// Give auto tubes time to align before trying to start moving
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index cf5c8f936b3..f78a8630925 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -189,7 +189,7 @@
soundloop.stop()
/obj/machinery/shower/attackby(obj/item/I as obj, mob/user as mob)
- if(I.type == /obj/item/analyzer)
+ if(I.type == /obj/item/atmos_analyzer)
to_chat(user, "The water temperature seems to be [watertemp].")
if(I.is_wrench())
var/newtemp = input(user, "What setting would you like to set the temperature valve to?", "Water Temperature Valve") in temperature_settings
diff --git a/code/game/turfs/simulated/floors/water.dm b/code/game/turfs/simulated/floors/water.dm
index 7a1d800b084..1b816d2ea43 100644
--- a/code/game/turfs/simulated/floors/water.dm
+++ b/code/game/turfs/simulated/floors/water.dm
@@ -52,11 +52,11 @@
var/datum/gas_mixture/water_breath = new()
var/datum/gas_mixture/above_air = return_air()
var/amount = 300
- water_breath.adjust_gas(/datum/gas/oxygen, amount) // Assuming water breathes just extract the oxygen directly from the water.
+ water_breath.adjust_gas(GAS_ID_OXYGEN, amount) // Assuming water breathes just extract the oxygen directly from the water.
water_breath.temperature = above_air.temperature
return return_air()
else
- var/gasid = /datum/gas/carbon_dioxide
+ var/gasid = GAS_ID_CARBON_DIOXIDE
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.species && H.species.exhale_type)
@@ -241,11 +241,11 @@ var/list/shoreline_icon_cache = list()
var/datum/gas_mixture/water_breath = new()
var/datum/gas_mixture/above_air = return_air()
var/amount = 300
- water_breath.adjust_gas(/datum/gas/oxygen, amount) // Assuming water breathes just extract the oxygen directly from the water.
+ water_breath.adjust_gas(GAS_ID_OXYGEN, amount) // Assuming water breathes just extract the oxygen directly from the water.
water_breath.temperature = above_air.temperature
return water_breath
else
- var/gasid = /datum/gas/carbon_dioxide
+ var/gasid = GAS_ID_CARBON_DIOXIDE
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.species && H.species.exhale_type)
@@ -351,11 +351,11 @@ var/list/shoreline_icon_cache = list()
var/datum/gas_mixture/water_breath = new()
var/datum/gas_mixture/above_air = return_air()
var/amount = 300
- water_breath.adjust_gas(/datum/gas/oxygen, amount) // Assuming water breathes just extract the oxygen directly from the water.
+ water_breath.adjust_gas(GAS_ID_OXYGEN, amount) // Assuming water breathes just extract the oxygen directly from the water.
water_breath.temperature = above_air.temperature
return water_breath
else
- var/gasid = /datum/gas/carbon_dioxide
+ var/gasid = GAS_ID_CARBON_DIOXIDE
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.species && H.species.exhale_type)
diff --git a/code/game/turfs/simulated/wall_attacks.dm b/code/game/turfs/simulated/wall_attacks.dm
index 53bd8b60c8b..9ef269692f1 100644
--- a/code/game/turfs/simulated/wall_attacks.dm
+++ b/code/game/turfs/simulated/wall_attacks.dm
@@ -47,12 +47,12 @@
// "turf in loc" on a turf
// you for real?
/*
- if(!air_master)
+ if(!SSair)
return
for(var/turf/simulated/turf in loc)
update_thermal(turf)
- air_master.mark_for_update(turf)
+ SSair.mark_for_update(turf)
*/
/turf/simulated/wall/proc/update_thermal(var/turf/simulated/source)
diff --git a/code/modules/admin/callproc/callproc.dm b/code/modules/admin/callproc/callproc.dm
index 54c792c1863..4fd283abc8c 100644
--- a/code/modules/admin/callproc/callproc.dm
+++ b/code/modules/admin/callproc/callproc.dm
@@ -185,6 +185,8 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
retval += named_args
return retval
+// todo: rework all of vv and proccall yet again because this is shit formatting
+
/client/proc/get_callproc_returnval(returnval,procname)
. = ""
if(islist(returnval))
@@ -195,7 +197,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
if(istext(assoc_check) && (returnedlist[assoc_check] != null))
. += "[procname] returned an associative list:"
for(var/key in returnedlist)
- . += "\n[key] = [returnedlist[key]]"
+ . += "\n[key] = [islist(returnedlist[key])? json_encode(returnedlist[key]) : returnedlist[key]]"
else
. += "[procname] returned a list:"
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 3e8227a231f..27fe22e0f1c 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -377,7 +377,7 @@
if(A && !(A.type in areas_with_APC))
areas_with_APC.Add(A.type)
- for(var/obj/machinery/alarm/alarm in GLOB.machines)
+ for(var/obj/machinery/air_alarm/alarm in GLOB.machines)
var/area/A = get_area(alarm)
if(A && !(A.type in areas_with_air_alarm))
areas_with_air_alarm.Add(A.type)
@@ -512,7 +512,7 @@
if(!Rad.P)
var/obj/item/tank/phoron/Phoron = new/obj/item/tank/phoron(Rad)
/// supercooled so we don't just maxcap the engine lol
- Phoron.air_contents.adjust_gas_temp(/datum/gas/phoron, 350, 25)
+ Phoron.air_contents.adjust_gas_temp(GAS_ID_PHORON, 350, 25)
Phoron.forceMove(Rad)
Rad.P = Phoron
@@ -550,7 +550,7 @@
var/obj/item/tank/phoron/Phoron = new/obj/item/tank/phoron(Rad)
- Phoron.air_contents.gas[/datum/gas/phoron] = 29.1154 //This is a full tank if you filled it from a canister
+ Phoron.air_contents.gas[GAS_ID_PHORON] = 29.1154 //This is a full tank if you filled it from a canister
Rad.P = Phoron
Phoron.loc = Rad
@@ -563,7 +563,7 @@
var/obj/machinery/atmospherics/component/binary/pump/Pump = M
if(Pump.name == "Engine Feed" && response == "Setup Completely")
found_the_pump = 1
- Pump.air2.gas[/datum/gas/nitrogen] = 3750 //The contents of 2 canisters.
+ Pump.air2.gas[GAS_ID_NITROGEN] = 3750 //The contents of 2 canisters.
Pump.air2.temperature = 50
Pump.air2.update_values()
Pump.update_use_power(USE_POWER_IDLE)
@@ -591,7 +591,7 @@
if(!found_the_pump && response == "Setup Completely")
to_chat(src, "Unable to locate air supply to fill up with coolant, adding some coolant around the supermatter")
var/turf/simulated/T = SM.loc
- T.zone.air.gas[/datum/gas/nitrogen] += 450
+ T.zone.air.gas[GAS_ID_NITROGEN] += 450
T.zone.air.temperature = 50
T.zone.air.update_values()
diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm
index ec9e9c69f57..60deacb97fe 100644
--- a/code/modules/admin/verbs/diagnostics.dm
+++ b/code/modules/admin/verbs/diagnostics.dm
@@ -2,12 +2,12 @@
set category = "Debug"
set name = "Show Air Report"
- if(!air_master)
- alert(usr,"air_master not found.","Air Report")
+ if(!SSair)
+ alert(usr,"SSair not found.","Air Report")
return
- var/active_groups = air_master.active_zones
- var/inactive_groups = air_master.zones.len - active_groups
+ var/active_groups = SSair.active_zones
+ var/inactive_groups = SSair.zones.len - active_groups
var/hotspots = 0
for(var/atom/movable/fire/hotspot in world)
@@ -15,7 +15,7 @@
var/active_on_main_station = 0
var/inactive_on_main_station = 0
- for(var/datum/zas_zone/zone in air_master.zones)
+ for(var/datum/zas_zone/zone in SSair.zones)
var/turf/simulated/turf = locate() in zone.contents
if(turf && (turf.z in (LEGACY_MAP_DATUM).station_levels))
if(zone.needs_update)
@@ -25,8 +25,8 @@
var/output = {"AIR SYSTEMS REPORTGeneral Processing Data
- Cycle: [air_master.current_cycle]
- Groups: [air_master.zones.len]
+ Cycle: [SSair.current_cycle]
+ Groups: [SSair.zones.len]
---- Active: [active_groups]
---- Inactive: [inactive_groups]
---- Active on station: [active_on_main_station]
@@ -36,7 +36,7 @@
Hotspot Processing: [hotspots]
Geometry Processing Data
- Tile Update: [air_master.tiles_to_update.len]
+ Tile Update: [SSair.tiles_to_update.len]
"}
usr << browse(output,"window=airreport")
@@ -127,11 +127,11 @@
to_chat(src, "Only administrators may use this command.")
return
- if(!air_master)
+ if(!SSair)
to_chat(usr, "Cannot find air_system")
return
var/datum/air_group/dead_groups = list()
- for(var/datum/air_group/group in air_master.air_groups)
+ for(var/datum/air_group/group in SSair.air_groups)
if (!group.group_processing)
dead_groups += group
var/datum/air_group/dest_group = pick(dead_groups)
@@ -149,7 +149,7 @@
to_chat(src, "Only administrators may use this command.")
return
- if(!air_master)
+ if(!SSair)
to_chat(usr, "Cannot find air_system")
return
diff --git a/code/modules/asset_cache/assets/legacy_nanoui.dm b/code/modules/asset_cache/assets/legacy_nanoui.dm
index b3207b1ea63..029e468fac4 100644
--- a/code/modules/asset_cache/assets/legacy_nanoui.dm
+++ b/code/modules/asset_cache/assets/legacy_nanoui.dm
@@ -6,7 +6,6 @@
"nano/css/",
"nano/images/",
"nano/images/status_icons/",
- "nano/images/modular_computers/",
"nano/templates/",
"nano/js/",
"nano/js/libraries/",
diff --git a/code/modules/atmospherics/atmosphere/_atmosphere.dm b/code/modules/atmospherics/atmosphere/_atmosphere.dm
deleted file mode 100644
index abcbc650df5..00000000000
--- a/code/modules/atmospherics/atmosphere/_atmosphere.dm
+++ /dev/null
@@ -1,100 +0,0 @@
-/datum/atmosphere
- /// Don't initialize abstract datums.
- abstract_type = /datum/atmosphere
-
- /// Gas string. Do not modify directly. Generated by [generate_gas_string()]
- var/gas_string
- /// Unique ID. MUST be different for every atmosphere! Defaults to typepath.
- var/id
- // todo: allow id to be set but still allow typepath lookup
-
- /// A list of gases to always have. Associative gas datum typepath:mols
- var/list/base_gases
- /// A list of allowed gases. Associative gas datum typepath:mols. Is randomized on generation.
- var/list/normal_gases
- /// A list of allowed gases that can only be picked once. Associative gas datum typepath:mols. Is randomized on generation.
- var/list/restricted_gases
- /// Chance per iteration to take from restricted gases
- var/restricted_chance = 10
-
- /// Pressure to fill base_gases to, if it isn't there. For lazy coders who don't want to pv=nrt.
- var/base_target_pressure
- /// Minimum pressure in kPa this atmosphere can be
- var/minimum_pressure
- /// Maximum pressure in kPa this atmosphere can be
- var/maximum_pressure
-
- /// Minimum temperature this atmosphere can be
- var/minimum_temp
- /// Maximum temperature this atmosphere can be
- var/maximum_temp
-
-/datum/atmosphere/New()
- if(!id)
- id = type
- generate_gas_string()
-
-/datum/atmosphere/proc/generate_gas_string()
- var/target_pressure = max(base_target_pressure, rand(minimum_pressure, maximum_pressure))
- var/pressure_scalar = target_pressure / maximum_pressure
-
- // First let's set up the gasmix and base gases for this template
- // We make the string from a gasmix in this proc because gases need to calculate their pressure
- var/datum/gas_mixture/gasmix = new
- gasmix.volume = CELL_VOLUME
- var/list/gaslist = gasmix.gas
- gasmix.temperature = rand(minimum_temp, maximum_temp)
- for(var/gaspath in base_gases)
- gaslist[gaspath] = base_gases[gaspath]
-
- // Make sure base gases are at target pressure if it isn't already
- if(gasmix.return_pressure() < base_target_pressure)
- // yeah you screwed up, redo the whole thing.
- // screw lazy coders
- var/moles = (base_target_pressure * CELL_VOLUME) / (R_IDEAL_GAS_EQUATION * gasmix.temperature)
- gaslist.Cut()
- var/total_moles_base
- TOTAL_MOLES(base_gases, total_moles_base)
- for(var/i in base_gases)
- var/amount = base_gases[i]
- var/ratio = amount / total_moles_base
- var/actual = moles * ratio
- gaslist[i] = actual
-
- // Now let the random choices begin
- if(length(normal_gases) && length(restricted_gases))
- var/datum/gas/gastype
- var/amount
- var/safety = 254
- while(gasmix.return_pressure() < target_pressure)
- if(!safety--)
- stack_trace("[type] ran out of safety in its while loop in generate_gas_string. Something has gone horribly wrong!")
- break
- if(!prob(restricted_chance))
- gastype = pick(normal_gases)
- amount = normal_gases[gastype]
- else
- gastype = pick(restricted_gases)
- amount = restricted_gases[gastype]
- if(gaslist[gastype])
- continue
-
- amount *= rand(50, 200) / 100 // Randomly modifes the amount from half to double the base for some variety
- amount *= pressure_scalar // If we pick a really small target pressure we want roughly the same mix but less of it all
- amount = CEILING(amount, 0.01)
-
- gaslist[gastype] += amount
-
- if(gastype)
- // That last one put us over the limit, remove some of it
- while(gasmix.return_pressure() > target_pressure)
- gaslist[gastype] -= gaslist[gastype] * 0.01
- gaslist[gastype] = FLOOR(gaslist[gastype], 0.01)
- GAS_GARBAGE_COLLECT(gasmix.gas)
-
- // Now finally lets make that string
- var/list/gas_string_builder = list()
- for(var/i in gaslist)
- gas_string_builder += "[GLOB.meta_gas_ids[i]]=[gaslist[i]]"
- gas_string_builder += "TEMP=[gasmix.temperature]"
- gas_string = gas_string_builder.Join(";")
diff --git a/code/modules/atmospherics/atmosphere/atmosphere.dm b/code/modules/atmospherics/atmosphere/atmosphere.dm
new file mode 100644
index 00000000000..6949fcec349
--- /dev/null
+++ b/code/modules/atmospherics/atmosphere/atmosphere.dm
@@ -0,0 +1,117 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/**
+ * system for both
+ * 1. bundling together gas string defines so they're not hardcoded gas strings
+ * 2. pregenerating random atmosphers
+ *
+ * generation process:
+ * 0. target pressure and temperature are determined
+ * 1. base is expanded to base_pressure
+ * 2. procedurally generate gases with random vars
+ * if there's no random, use base gases.
+ */
+/datum/atmosphere
+ /// don't initialize abstract datums.
+ abstract_type = /datum/atmosphere
+ /// generated gas string. do not modify.
+ var/gas_string
+ /// unique id. defaults to typepath.
+ var/id
+
+ /// target volume, incase we want to generate for something that isn't a tile, this lets you set it.
+ var/volume = CELL_VOLUME
+
+ var/pressure_gaussian = FALSE
+ var/pressure_center
+ var/pressure_deviation
+ var/pressure_low
+ var/pressure_high
+
+ var/temperature_gaussian = FALSE
+ var/temperature_center
+ var/temperature_deviation
+ var/temperature_low
+ var/temperature_high
+
+ /// base gases. gas id or typepath to ratio.
+ var/list/base = list()
+ /// target pressure to fill base to; if null, and base isn't empty, we fill the whole thing!
+ var/base_pressure
+
+ // todo: random_markov and have key = list(other key = probability)
+ /// key: gasid, typepath, or random_procedural key to probability.
+ var/list/random = list()
+ /// nominal random steps: the target moles per step is determined by this. actual steps may vary.
+ /// higher = more uniform, lower = more rough
+ /// 0 = single pick
+ var/random_nominal_steps = 10
+ /// random procedural gases to throw into the mix.
+ /// key = typepath.
+ /// this will override gas lookup, so be careful not to use a real gasid.
+ var/list/random_procedural = list()
+
+/datum/atmosphere/New()
+ if(!id)
+ id = "[type]"
+ generate()
+
+/datum/atmosphere/proc/generate()
+ // generate wanted pressure/temperature
+ var/target_pressure = pressure_gaussian? gaussian(pressure_center, pressure_deviation) : (rand(pressure_low * 100, pressure_high * 100) * 0.01)
+ var/target_temperature = temperature_gaussian? gaussian(temperature_center, temperature_deviation) : (rand(temperature_low * 100, temperature_high * 100) * 0.01)
+
+ // init tracking
+ var/list/target_gases = list()
+ var/total_moles = 0
+ var/target_moles = (target_pressure * volume) / (R_IDEAL_GAS_EQUATION * target_temperature)
+
+ // generate base
+ var/total_base = 0
+ for(var/i in 1 to length(base))
+ var/what = base[i]
+ var/ratio = base[what]
+ if(ispath(what, /datum/gas))
+ var/datum/gas/casted = what
+ what = initial(casted.id)
+ base[i] = what
+ base[what] = ratio
+ total_base += ratio
+
+ var/moles_base = ((isnull(base_pressure)? (length(base)? target_pressure : 0) : base_pressure) * volume) / (R_IDEAL_GAS_EQUATION * target_temperature)
+ var/ratio_base = moles_base / total_base
+ if(ratio_base)
+ for(var/what in base)
+ var/amount = ratio_base * base[what]
+ total_moles += amount
+ target_gases[what] = amount
+
+ // check if we have enough
+ if(total_moles >= target_moles)
+ gas_string = get_gas_string(target_gases, target_temperature)
+ return
+
+ // generate rest
+ var/moles_per_step = (target_moles - total_moles) / random_nominal_steps
+ // safety
+ for(var/i in 1 to 200)
+ if(total_moles >= target_moles)
+ break
+ var/left = target_moles - total_moles
+ var/moles_this_step = min(rand(75, 125) * 0.01 * moles_per_step, left)
+ // todo: markov chain support by just having pickweight on key = list
+ var/key = pickweight(random)
+ if(random_procedural[key])
+ if(ispath(random_procedural[key]))
+ var/datum/procedural_gas/generated = new random_procedural[key]
+ generated.instance()
+ generated.generate()
+ generated.register()
+ random_procedural[key] = generated
+ key = random_procedural[key]
+ target_gases[key] += moles_this_step
+ total_moles += moles_this_step
+
+ gas_string = get_gas_string(target_gases, target_temperature)
+
diff --git a/code/modules/atmospherics/environmental/public.dm b/code/modules/atmospherics/environmental/public.dm
index ec8171bb6a2..b24155c3047 100644
--- a/code/modules/atmospherics/environmental/public.dm
+++ b/code/modules/atmospherics/environmental/public.dm
@@ -1,3 +1,6 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
// when writing atmos machinery, prefer using these procs
// this lets us somewhat separate API from implemenetation of the environmental module.
// a lot of things are on atom instead, since stuff like breathing should work with inside, say, bluespace bodybags.
diff --git a/code/modules/atmospherics/environmental/turf.dm b/code/modules/atmospherics/environmental/turf.dm
index 794fbd34778..c2696fc55d3 100644
--- a/code/modules/atmospherics/environmental/turf.dm
+++ b/code/modules/atmospherics/environmental/turf.dm
@@ -112,7 +112,7 @@
RETURN_TYPE(/datum/gas_mixture)
if(zone)
if(!zone.invalid)
- air_master.mark_zone_update(zone)
+ SSair.mark_zone_update(zone)
return zone.air
else
c_copy_air()
diff --git a/code/modules/atmospherics/environmental/zas/_docs.dm b/code/modules/atmospherics/environmental/zas/_docs.dm
index e61f16195ba..75a3f34e101 100644
--- a/code/modules/atmospherics/environmental/zas/_docs.dm
+++ b/code/modules/atmospherics/environmental/zas/_docs.dm
@@ -15,7 +15,7 @@ Every air tick:
Important Functions:
-air_master.mark_for_update(turf)
+SSair.mark_for_update(turf)
When stuff happens, call this. It works on everything. You basically don't need to worry about any other
functions besides CanPass().
diff --git a/code/modules/atmospherics/environmental/zas/connection.dm b/code/modules/atmospherics/environmental/zas/connection.dm
index 09f59bd9995..4a3b53e21c4 100644
--- a/code/modules/atmospherics/environmental/zas/connection.dm
+++ b/code/modules/atmospherics/environmental/zas/connection.dm
@@ -5,7 +5,7 @@
/*
Overview:
- Connections are made between turfs by air_master.connect(). They represent a single point where two zones converge.
+ Connections are made between turfs by SSair.connect(). They represent a single point where two zones converge.
Class Vars:
A - Always a simulated turf.
@@ -60,18 +60,18 @@ Class Procs:
/datum/zas_connection/New(turf/simulated/A, turf/simulated/B)
#ifdef ZAS_ASSERTIONS
ASSERT(A.has_valid_zone())
- //ASSERT(air_master.has_valid_zone(B))
+ //ASSERT(SSair.has_valid_zone(B))
#endif
src.A = A
src.B = B
zoneA = A.zone
if(!istype(B))
mark_space()
- edge = air_master.get_edge(A.zone,B)
+ edge = SSair.get_edge(A.zone,B)
edge.add_connection(src)
else
zoneB = B.zone
- edge = air_master.get_edge(A.zone,B.zone)
+ edge = SSair.get_edge(A.zone,B.zone)
edge.add_connection(src)
/datum/zas_connection/proc/mark_direct()
@@ -132,7 +132,7 @@ Class Procs:
return
else
edge.remove_connection(src)
- edge = air_master.get_edge(A.zone, B)
+ edge = SSair.get_edge(A.zone, B)
edge.add_connection(src)
zoneA = A.zone
@@ -154,7 +154,7 @@ Class Procs:
//to_chat(world, "Zones changed, \...")
if(A.zone && B.zone)
edge.remove_connection(src)
- edge = air_master.get_edge(A.zone, B.zone)
+ edge = SSair.get_edge(A.zone, B.zone)
edge.add_connection(src)
zoneA = A.zone
zoneB = B.zone
diff --git a/code/modules/atmospherics/environmental/zas/connection_manager.dm b/code/modules/atmospherics/environmental/zas/connection_manager.dm
index 8efe6fd4b0a..59a72066e07 100644
--- a/code/modules/atmospherics/environmental/zas/connection_manager.dm
+++ b/code/modules/atmospherics/environmental/zas/connection_manager.dm
@@ -16,7 +16,7 @@ Class Procs:
Preferable to accessing the connection directly because it checks validity.
place(datum/zas_connection/c, d)
- Called by air_master.connect(). Sets the connection in the specified direction to c.
+ Called by SSair.connect(). Sets the connection in the specified direction to c.
update_all()
Called after turf/update_air_properties(). Updates the validity of all connections on this turf.
diff --git a/code/modules/atmospherics/environmental/zas/controller.dm b/code/modules/atmospherics/environmental/zas/controller.dm
index 2664cf6dbcd..1e2c42d5f13 100644
--- a/code/modules/atmospherics/environmental/zas/controller.dm
+++ b/code/modules/atmospherics/environmental/zas/controller.dm
@@ -1,6 +1,3 @@
-var/datum/controller/subsystem/air/air_master
-
-var/tick_multiplier = 2
/*
diff --git a/code/modules/atmospherics/environmental/zas/debug.dm b/code/modules/atmospherics/environmental/zas/debug.dm
index 68c034c2027..634652c27fb 100644
--- a/code/modules/atmospherics/environmental/zas/debug.dm
+++ b/code/modules/atmospherics/environmental/zas/debug.dm
@@ -31,12 +31,12 @@ var/image/mark = image('icons/testing/Zone.dmi', icon_state = "mark")
/*
if(!check_rights(R_DEBUG)) return
- var/result = air_master.Tick()
+ var/result = SSair.Tick()
if(result)
to_chat(src, "Successfully Processed.")
else
- to_chat(src, "Failed to process! ([air_master.tick_progress])")
+ to_chat(src, "Failed to process! ([SSair.tick_progress])")
*/
/client/proc/Zone_Info(turf/T as null|turf)
diff --git a/code/modules/atmospherics/environmental/zas/edge.dm b/code/modules/atmospherics/environmental/zas/edge.dm
index 932106c028a..deea312b03d 100644
--- a/code/modules/atmospherics/environmental/zas/edge.dm
+++ b/code/modules/atmospherics/environmental/zas/edge.dm
@@ -2,7 +2,7 @@
Overview:
These are what handle gas transfers between zones and into space.
- They are found in a zone's edges list and in air_master.edges.
+ They are found in a zone's edges list and in SSair.edges.
Each edge updates every air tick due to their role in gas transfer.
They come in two flavors, /datum/zas_edge/zone and /datum/zas_edge/unsimulated.
As the type names might suggest, they handle inter-zone and spacelike connections respectively.
@@ -83,7 +83,7 @@ Class Procs:
/datum/zas_edge/proc/contains_zone(datum/zas_zone/Z)
/datum/zas_edge/proc/erase()
- air_master.remove_edge(src)
+ SSair.remove_edge(src)
//to_chat(world, "[type] Erased.")
/datum/zas_edge/proc/tick()
@@ -178,19 +178,19 @@ Class Procs:
if(equiv)
if(direct)
erase()
- air_master.merge(A, B)
+ SSair.merge(A, B)
return
else
A.air.equalize(B.air)
- air_master.mark_edge_sleeping(src)
+ SSair.mark_edge_sleeping(src)
- air_master.mark_zone_update(A)
- air_master.mark_zone_update(B)
+ SSair.mark_zone_update(A)
+ SSair.mark_zone_update(B)
/datum/zas_edge/zone/recheck()
// Edges with only one side being vacuum need processing no matter how close.
if(!A.air.compare(B.air, vacuum_exception = 1))
- air_master.mark_edge_active(src)
+ SSair.mark_edge_active(src)
//Helper proc to get connections for a zone.
/datum/zas_edge/zone/proc/get_connected_zone(datum/zas_zone/from)
@@ -242,16 +242,16 @@ Class Procs:
if(equiv)
A.air.copy_from(air)
- air_master.mark_edge_sleeping(src)
+ SSair.mark_edge_sleeping(src)
- air_master.mark_zone_update(A)
+ SSair.mark_zone_update(A)
/datum/zas_edge/unsimulated/recheck()
// Edges with only one side being vacuum need processing no matter how close.
// Note: This handles the glaring flaw of a room holding pressure while exposed to space, but
// does not specially handle the less common case of a simulated room exposed to an unsimulated pressurized turf.
if(!A.air.compare(air, vacuum_exception = 1))
- air_master.mark_edge_active(src)
+ SSair.mark_edge_active(src)
/proc/ShareHeat(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles)
//This implements a simplistic version of the Stefan-Boltzmann law.
diff --git a/code/modules/atmospherics/environmental/zas/fire.dm b/code/modules/atmospherics/environmental/zas/fire.dm
index bd194af2e59..24ecfae6475 100644
--- a/code/modules/atmospherics/environmental/zas/fire.dm
+++ b/code/modules/atmospherics/environmental/zas/fire.dm
@@ -61,7 +61,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
fuel_objs.Cut()
if(!fire_tiles.len)
- air_master.active_fire_zones.Remove(src)
+ SSair.active_fire_zones.Remove(src)
/datum/zas_zone/proc/remove_liquidfuel(var/used_liquid_fuel, var/remove_fire=0)
if(!fuel_objs.len)
@@ -99,7 +99,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
return 1
fire = new(src, fl)
- air_master.active_fire_zones |= zone
+ SSair.active_fire_zones |= zone
var/obj/effect/debris/cleanable/liquid_fuel/fuel = locate() in src
zone.fire_tiles |= src
@@ -136,7 +136,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
set_light(3, 1, color)
firelevel = fl
- air_master.active_hotspots.Add(src)
+ SSair.active_hotspots.Add(src)
/atom/movable/fire/Destroy()
RemoveFire()
@@ -150,7 +150,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin
T.fire = null
else
stack_trace("Mismatching fire on [T] [COORD(T)]")
- air_master.active_hotspots.Remove(src)
+ SSair.active_hotspots.Remove(src)
/atom/movable/fire/process(delta_time)
. = 1
diff --git a/code/modules/atmospherics/environmental/zas/phoron.dm b/code/modules/atmospherics/environmental/zas/phoron.dm
index 2fc1d8d7f21..894038af9bc 100644
--- a/code/modules/atmospherics/environmental/zas/phoron.dm
+++ b/code/modules/atmospherics/environmental/zas/phoron.dm
@@ -63,7 +63,7 @@ var/image/contamination_overlay = image('icons/effects/contamination.dmi')
return
//Burn skin if exposed.
- if(skin_burns && (species.breath_type != /datum/gas/phoron))
+ if(skin_burns && (species.breath_type != GAS_ID_PHORON))
if(!pl_head_protected() || !pl_suit_protected())
burn_skin(0.75)
if(prob(20))
@@ -71,7 +71,7 @@ var/image/contamination_overlay = image('icons/effects/contamination.dmi')
update_health()
//Burn eyes if exposed.
- if(eye_burns && species.breath_type && (species.breath_type != /datum/gas/phoron))
+ if(eye_burns && species.breath_type && (species.breath_type != GAS_ID_PHORON))
var/burn_eyes = 1
//Check for protective glasses
@@ -95,7 +95,7 @@ var/image/contamination_overlay = image('icons/effects/contamination.dmi')
burn_eyes()
//Genetic Corruption
- if(genetic_corruption && (species.breath_type != /datum/gas/phoron))
+ if(genetic_corruption && (species.breath_type != GAS_ID_PHORON))
if(rand(1,10000) < genetic_corruption)
randmutb(src)
to_chat(src, "High levels of toxins cause you to spontaneously mutate!")
@@ -161,6 +161,6 @@ var/image/contamination_overlay = image('icons/effects/contamination.dmi')
if(!env)
return
for(var/g in env.gas)
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_CONTAMINANT && env.gas[g] > GLOB.meta_gas_visibility[g] + 1)
+ if(global.gas_data.flags[g] & GAS_FLAG_CONTAMINANT && env.gas[g] > 1)
I.contaminate()
break
diff --git a/code/modules/atmospherics/environmental/zas/turf.dm b/code/modules/atmospherics/environmental/zas/turf.dm
index e9e25fe0ffe..be51633d6a9 100644
--- a/code/modules/atmospherics/environmental/zas/turf.dm
+++ b/code/modules/atmospherics/environmental/zas/turf.dm
@@ -40,7 +40,7 @@
if(r_block == ATMOS_PASS_AIR_BLOCKED)
continue
- air_master.connect(potential, src, min(block, r_block), d)
+ SSair.connect(potential, src, min(block, r_block), d)
//* credit to kapu/daedalus dock for some of the code below*//
@@ -215,7 +215,7 @@ GLOBAL_REAL_VAR(list/gzn_check) = list(NORTH, SOUTH, EAST, WEST)
to_chat(world, "Connecting to [sim.zone]")
#endif
- air_master.connect(src, potential, min(them_to_us, us_to_them), d)
+ SSair.connect(src, potential, min(them_to_us, us_to_them), d)
#ifdef ZAS_DEBUG_GRAPHICS
else if(zas_verbose)
@@ -245,7 +245,7 @@ GLOBAL_REAL_VAR(list/gzn_check) = list(NORTH, SOUTH, EAST, WEST)
//At this point, a zone should have happened. If it hasn't, don't add more checks, fix the bug.
for(var/turf/T as anything in postponed)
- air_master.connect(src, T, postponed[T], postponed_dirs[T])
+ SSair.connect(src, T, postponed[T], postponed_dirs[T])
/turf/proc/post_update_air_properties()
connections?.update_all()
diff --git a/code/modules/atmospherics/environmental/zas/zone.dm b/code/modules/atmospherics/environmental/zas/zone.dm
index 51b58779da7..09e0de0c4e4 100644
--- a/code/modules/atmospherics/environmental/zas/zone.dm
+++ b/code/modules/atmospherics/environmental/zas/zone.dm
@@ -53,7 +53,7 @@ Class Procs:
var/atom/movable/zas_graphics/renderer_one_for_all
/datum/zas_zone/New()
- air_master.add_zone(src)
+ SSair.add_zone(src)
air.temperature = TCMB
air.group_multiplier = 1
air.volume = CELL_VOLUME
@@ -72,7 +72,7 @@ Class Procs:
if(T.fire)
var/obj/effect/debris/cleanable/liquid_fuel/fuel = locate() in T
fire_tiles += T
- air_master.active_fire_zones |= src
+ SSair.active_fire_zones |= src
if(fuel)
fuel_objs += fuel
if(renderer_one_for_all && T.allow_gas_overlays && !T.outdoors)
@@ -128,7 +128,7 @@ Class Procs:
/datum/zas_zone/proc/c_invalidate()
invalid = 1
- air_master.remove_zone(src)
+ SSair.remove_zone(src)
#ifdef ZAS_DEBUG_GRAPHICS
for(var/turf/simulated/T in contents)
T.dbg(invalid_zone)
@@ -154,7 +154,7 @@ Class Procs:
/datum/zas_zone/proc/tick()
CACHE_VSC_PROP(atmos_vsc, /atmos/fire/firelevel_multiplier, firelevel_multiplier)
- if(air.temperature >= PHORON_FLASHPOINT && !(src in air_master.active_fire_zones) && air.check_combustability() && contents.len)
+ if(air.temperature >= PHORON_FLASHPOINT && !(src in SSair.active_fire_zones) && air.check_combustability() && contents.len)
var/turf/T = pick(contents)
if(istype(T))
T.create_fire(firelevel_multiplier)
@@ -184,9 +184,9 @@ Class Procs:
/datum/zas_zone/proc/dbg_data(mob/M)
to_chat(M, name)
for(var/g in air.gas)
- to_chat(M, "[GLOB.meta_gas_names[g]]: [air.gas[g]]")
+ to_chat(M, "[global.gas_data.names[g]]: [air.gas[g]]")
to_chat(M, "P: [air.return_pressure()] kPa V: [air.volume]L T: [air.temperature]�K ([air.temperature - T0C]�C)")
- to_chat(M, "O2 per N2: [(air.gas[/datum/gas/nitrogen] ? air.gas[/datum/gas/oxygen]/air.gas[/datum/gas/nitrogen] : "N/A")] Moles: [air.total_moles]")
+ to_chat(M, "O2 per N2: [(air.gas[GAS_ID_NITROGEN] ? air.gas[GAS_ID_OXYGEN]/air.gas[GAS_ID_NITROGEN] : "N/A")] Moles: [air.total_moles]")
to_chat(M, "Simulated: [contents.len] ([air.group_multiplier])")
//to_chat(M, "Unsimulated: [unsimulated_contents.len]")
//to_chat(M, "Edges: [edges.len]")
diff --git a/code/modules/atmospherics/gasmixtures/flow.dm b/code/modules/atmospherics/gasmixtures/flow.dm
new file mode 100644
index 00000000000..b0d626ea1bc
--- /dev/null
+++ b/code/modules/atmospherics/gasmixtures/flow.dm
@@ -0,0 +1,357 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/**
+ * @params
+ * * source - mixture to pump from
+ * * sink - mixture to pump into
+ * * limit_moles - if set, only pump this many moles of gas at most
+ * * limit_power - power limit in joules
+ */
+/proc/xgm_pump_gas(datum/gas_mixture/source, datum/gas_mixture/sink, limit_moles, limit_power)
+ // not enough to pump
+ if(source.total_moles < MINIMUM_MOLES_TO_PUMP)
+ return 0
+
+ var/specific_power = calculate_specific_power(source, sink) / ATMOS_ABSTRACT_PUMP_EFFICIENCY
+ limit_moles = isnull(limit_moles)? source.total_moles : min(limit_moles, source.total_moles)
+ if(!isnull(limit_power) && (specific_power > 0))
+ limit_moles = min(limit_moles, limit_power / specific_power)
+
+ if(limit_moles < MINIMUM_MOLES_TO_PUMP)
+ return
+
+ sink.merge(source.remove(limit_moles))
+
+ return specific_power * limit_moles
+
+/**
+ * @params
+ * * source - mixture to scrub
+ * * sink - mixture to scrub into
+ * * gas_ids - list of gas ids to scrub
+ * * gas_groups - list of gas groups to scrub
+ * * limit_moles - if set, only scrubs this many moles of gas at most
+ * * limit_power - power limit in joules
+ *
+ * @return power draw
+ */
+/proc/xgm_scrub_gas(datum/gas_mixture/source, datum/gas_mixture/sink, list/gas_ids, gas_groups, limit_moles, limit_power)
+ // not enough to scrub
+ if(source.total_moles < MINIMUM_MOLES_TO_SCRUB)
+ return 0
+
+ // get actually existing gas
+ var/list/filtered_ids
+ if(gas_groups)
+ filtered_ids = list()
+ // sigh; iterate through source
+ for(var/id in source.gas)
+ if(global.gas_data.groups[id] & gas_groups)
+ filtered_ids += id
+ // then get the explicitly filtered ones if needed
+ if(length(gas_ids))
+ filtered_ids |= (source.gas & gas_ids)
+ else if(length(gas_ids))
+ filtered_ids = gas_ids & source.gas
+
+ // gather
+ var/total_filterable_moles = 0
+ var/total_specific_power = 0
+ for(var/id in filtered_ids)
+ if(source.gas[id] < MINIMUM_MOLES_TO_SCRUB)
+ continue
+ total_filterable_moles += source.gas[id]
+ total_specific_power += calculate_specific_power_gas(id, source, sink)
+ total_specific_power /= ATMOS_ABSTRACT_SCRUB_EFFICIENCY
+
+ // limit by both moles and power
+ limit_moles = isnull(limit_moles)? total_filterable_moles : min(limit_moles, total_filterable_moles)
+ if(!isnull(limit_power) && (total_specific_power > 0))
+ limit_moles = min(limit_moles, limit_power / total_specific_power)
+
+ // can't transfer enough
+ if(limit_moles < MINIMUM_MOLES_TO_SCRUB)
+ return 0
+
+ // unlike in the other procs, ratio here is the amount we can filter vs the amount there is to filter
+ var/ratio = limit_moles / total_filterable_moles
+
+ // do the actual scrubbing
+ for(var/id in filtered_ids)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ sink.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+
+ // update values
+ source.update_values()
+ sink.update_values()
+
+ // return power used in J
+ return limit_moles * total_specific_power
+
+/**
+ * @params
+ * * source - mixture to scrub
+ * * sink - mixture to scrub into
+ * * gas_ids - list of gas ids to scrub
+ * * gas_groups - list of gas groups to scrub
+ * * limit_flow - if set, only scrubs this many liters of gas at most from source. this does *NOT* respect group multiplier.
+ * * limit_power - power limit in joules
+ * * mole_boost - ignore limit_flow to filter atleast this many moles. This is so it doesn't take too long to scrub.
+ *
+ * @return power draw
+ */
+/proc/xgm_scrub_gas_volume(datum/gas_mixture/source, datum/gas_mixture/sink, list/gas_ids, gas_groups, limit_flow, limit_power, mole_boost)
+ // not enough to scrub
+ if(source.total_moles < MINIMUM_MOLES_TO_SCRUB)
+ return 0
+
+ // get actually existing gas
+ var/list/filtered_ids
+ if(gas_groups)
+ filtered_ids = list()
+ // sigh; iterate through source
+ for(var/id in source.gas)
+ if(global.gas_data.groups[id] & gas_groups)
+ filtered_ids += id
+ // then get the explicitly filtered ones if needed
+ if(length(gas_ids))
+ filtered_ids |= (source.gas & gas_ids)
+ else if(length(gas_ids))
+ filtered_ids = gas_ids & source.gas
+
+ // gather
+ var/total_filterable_moles = 0
+ var/total_specific_power = 0
+ for(var/id in filtered_ids)
+ if(source.gas[id] < MINIMUM_MOLES_TO_SCRUB)
+ continue
+ total_filterable_moles += source.gas[id]
+ total_specific_power += calculate_specific_power_gas(id, source, sink)
+ total_specific_power /= ATMOS_ABSTRACT_SCRUB_EFFICIENCY
+
+ // limit by both moles and power
+ var/limit_moles = isnull(limit_flow)? total_filterable_moles : min(max(mole_boost, total_filterable_moles * (limit_flow / source.volume)), total_filterable_moles)
+ if(!isnull(limit_power) && (total_specific_power > 0))
+ limit_moles = min(limit_moles, limit_power / total_specific_power)
+
+ // can't transfer enough
+ if(limit_moles < MINIMUM_MOLES_TO_SCRUB)
+ return 0
+
+ // unlike in the other procs, ratio here is the amount we can filter vs the amount there is to filter
+ var/ratio = limit_moles / total_filterable_moles
+
+ // do the actual scrubbing
+ for(var/id in filtered_ids)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ sink.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+
+ // update values
+ source.update_values()
+ sink.update_values()
+
+ // return power used in J
+ return limit_moles * total_specific_power
+
+/**
+ * @params
+ * * source - mixture to filter
+ * * sink - mixture to send unfiltered gas into
+ * * divert - mixture to send filtered gas into
+ * * to_filter - what to filter, either a gas id or a set of gas groups.
+ * * limit_moles - if set, only scrubs this many moles of gas at most
+ * * limit_power - power limit in joules
+ *
+ * @return power draw
+ */
+/proc/xgm_filter_gas(datum/gas_mixture/source, datum/gas_mixture/sink, datum/gas_mixture/divert, to_filter, limit_moles, limit_power)
+ // not enough to filter
+ if(source.total_moles < MINIMUM_MOLES_TO_FILTER)
+ return 0
+
+ // gather - faster than scrub
+ var/filterable_moles = 0
+ var/list/filter_ids
+ if(isnum(to_filter))
+ // groups
+ filter_ids = list()
+ for(var/id in source.gas)
+ if(global.gas_data.groups[id] & to_filter)
+ filterable_moles += source.gas[id]
+ filter_ids += id
+ if(!length(filter_ids))
+ // not there
+ return xgm_pump_gas(source, sink, limit_moles, limit_power)
+ else
+ // ids
+ if(!(source.gas[to_filter]))
+ // not there
+ return xgm_pump_gas(source, sink, limit_moles, limit_power)
+ filter_ids = list(to_filter)
+ filterable_moles = source.gas[to_filter]
+
+ // calculate work per mole moved through filter, diverting as necessary
+ var/total_specific_power = 0
+ var/list/not_filtered = source.gas - filter_ids
+ for(var/id in not_filtered)
+ total_specific_power += calculate_specific_power_gas(id, source, sink) * source.gas[id] / source.total_moles
+ for(var/id in filter_ids)
+ total_specific_power += calculate_specific_power_gas(id, source, divert) * source.gas[id] / source.total_moles
+ total_specific_power /= ATMOS_ABSTRACT_FILTER_EFFICIENCY
+
+ // limit
+ limit_moles = isnull(limit_moles)? source.total_moles : min(limit_moles, source.total_moles)
+ if(!isnull(limit_power) && (total_specific_power > 0))
+ limit_moles = min(limit_moles, limit_power / total_specific_power)
+
+ var/ratio = limit_moles / source.total_moles
+
+ // not enough to filter
+ if(limit_moles < MINIMUM_MOLES_TO_FILTER)
+ return 0
+
+ // transfer
+ for(var/id in filter_ids)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ divert.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+ for(var/id in not_filtered)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ sink.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+
+ // update values
+ source.update_values()
+ sink.update_values()
+ divert.update_values()
+
+ // return power used in J
+ return limit_moles * total_specific_power
+
+/**
+ * @params
+ * * source - mixture to filter
+ * * sink - mixture to send unfiltered gas into
+ * * filtering - gas ids associated to mixtures to send them to.
+ * * limit_moles - if set, only scrubs this many moles of gas at most
+ * * limit_power - power limit in joules
+ *
+ * @return power draw
+ */
+/proc/xgm_multi_filter_gas_specific(datum/gas_mixture/source, datum/gas_mixture/sink, list/filtering, limit_moles, limit_power)
+ // not enough to filter
+ if(source.total_moles < MINIMUM_MOLES_TO_FILTER)
+ return 0
+
+ // nothing to filter
+ if(!length(filtering))
+ return xgm_pump_gas(source, sink, limit_moles, limit_power)
+
+ // gather - faster than scrub
+ var/filterable_moles = 0
+ var/total_specific_power = 0
+ var/list/not_filtered = source.gas - filtering
+ for(var/id in filtering)
+ filterable_moles[id] += source.gas[id]
+ total_specific_power += calculate_specific_power_gas(id, source, filtering[id]) * source.gas[id] / source.total_moles
+ for(var/id in not_filtered)
+ total_specific_power += calculate_specific_power_gas(id, source, sink) * source.gas[id] / source.total_moles
+ total_specific_power /= ATMOS_ABSTRACT_FILTER_EFFICIENCY
+
+ // limit
+ limit_moles = isnull(limit_moles)? source.total_moles : min(limit_moles, source.total_moles)
+ if(!isnull(limit_power) && (total_specific_power > 0))
+ limit_moles = min(limit_moles, limit_power / total_specific_power)
+
+ var/ratio = limit_moles / source.total_moles
+
+ // not enough to filter
+ if(limit_moles < MINIMUM_MOLES_TO_FILTER)
+ return 0
+
+ // transfer
+ for(var/id in filtering)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ var/datum/gas_mixture/divert = filtering[id]
+ // update immediately, we don't have overlap most of the time.
+ divert.adjust_gas_temp(id, transfer, source.temperature, TRUE)
+ for(var/id in not_filtered)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ sink.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+
+ // update values
+ source.update_values()
+ sink.update_values()
+
+ // return power used in J
+ return limit_moles * total_specific_power
+
+/**
+ * @params
+ * * source - mixture to filter
+ * * sink - mixture to send unfiltered gas into
+ * * divert - mixture to send filtered gas into; this is anything with molar mass between lower and upper, inclusive
+ * * lower - lower bound of target in kg/mol
+ * * upper - upper bound of target in kg/mol
+ * * limit_moles - if set, only scrubs this many moles of gas at most
+ * * limit_power - power limit in joules
+ *
+ * @return power draw
+ */
+/proc/xgm_molar_filter_gas(datum/gas_mixture/source, datum/gas_mixture/sink, datum/gas_mixture/divert, lower, upper, limit_moles, limit_power)
+ // not enough to filter
+ if(source.total_moles < MINIMUM_MOLES_TO_FILTER)
+ return 0
+
+ // gather - faster than scrub
+ var/filterable_moles = 0
+ var/list/filter_ids = list()
+ for(var/id in source.gas)
+ var/mass = global.gas_data.molar_masses[id]
+ if(lower > mass || upper < mass)
+ continue
+ filterable_moles += source.gas[id]
+ filter_ids += id
+
+ // calculate work per mole moved through filter, diverting as necessary
+ var/total_specific_power = 0
+ var/list/not_filtered = source.gas - filter_ids
+ for(var/id in not_filtered)
+ total_specific_power += calculate_specific_power_gas(id, source, sink) * source.gas[id] / source.total_moles
+ for(var/id in filter_ids)
+ total_specific_power += calculate_specific_power_gas(id, source, divert) * source.gas[id] / source.total_moles
+ total_specific_power /= ATMOS_ABSTRACT_FILTER_EFFICIENCY
+
+ // limit
+ limit_moles = isnull(limit_moles)? source.total_moles : min(limit_moles, source.total_moles)
+ if(!isnull(limit_power) && (total_specific_power > 0))
+ limit_moles = min(limit_moles, limit_power / total_specific_power)
+
+ var/ratio = limit_moles / source.total_moles
+
+ // not enough to filter
+ if(limit_moles < MINIMUM_MOLES_TO_FILTER)
+ return 0
+
+ // transfer
+ for(var/id in filter_ids)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ divert.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+ for(var/id in not_filtered)
+ var/transfer = source.gas[id] * ratio
+ source.adjust_gas(id, -transfer, FALSE)
+ sink.adjust_gas_temp(id, transfer, source.temperature, FALSE)
+
+ // update values
+ source.update_values()
+ sink.update_values()
+ divert.update_values()
+
+ // return power used in J
+ return limit_moles * total_specific_power
diff --git a/code/modules/atmospherics/gasmixtures/gas_data.dm b/code/modules/atmospherics/gasmixtures/gas_data.dm
new file mode 100644
index 00000000000..41b92f3391d
--- /dev/null
+++ b/code/modules/atmospherics/gasmixtures/gas_data.dm
@@ -0,0 +1,270 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+GLOBAL_REAL(gas_data, /datum/gas_data) = new
+
+/proc/__recreate_gas_data()
+ if(!isnull(global.gas_data))
+ qdel(global.gas_data)
+ global.gas_data = new
+
+/**
+ * master datum holding all atmospherics gas data
+ * all lists should be accessed directly for speed
+ * all lists are keyed by id
+ */
+/datum/gas_data
+ //* gas data - intrinsics
+ /// ids; associative list to the instantiated datum
+ var/list/gases = list()
+ /// names
+ var/list/names = list()
+ /// flags
+ var/list/flags = list()
+ /// groups
+ var/list/groups = list()
+
+ //* gas data - physics
+ /// specific heat
+ var/list/specific_heats = list()
+ /// molar masses
+ var/list/molar_masses = list()
+
+ //* gas data - reagents
+ /// reagents: id = (reagentid, base amount, minimum moles, mole factor, max amount)
+ /// this is a sparse lookup, not all gases are in here.
+ var/list/reagents = list()
+
+ //* gas data - visuals
+ /// visuals: id = (state, threshold, factor)
+ var/list/visuals = list()
+ /// visuals cache
+ var/list/visual_images = list()
+
+ //* gas data - reactions
+ var/list/rarities = list()
+
+ //* gas data - "fluff"
+ var/list/default_tlvs = list()
+
+ //* cache lists
+ /// list of lists of gas ids by flag
+ var/list/gas_by_flag = list()
+ /// list of lists of gas ids by group
+ var/list/gas_by_group = list()
+ /// list of gas ids in core
+ var/list/gas_ids_core = list()
+
+ //* static caches
+ /// list of non core group names that are filterable
+ var/list/gas_group_names_filterable = list()
+ /// gas group name to group
+ var/list/gas_group_by_name = list()
+
+ //* global data
+ /// next random gas id
+ var/static/next_procedural_gas_id = 0
+
+/datum/gas_data/New()
+ rebuild_static_caches()
+ build_hardcoded()
+
+/datum/gas_data/proc/rebuild_caches()
+ rebuild_static_caches()
+ names = list()
+ flags = list()
+ groups = list()
+ specific_heats = list()
+ molar_masses = list()
+ reagents = list()
+ visuals = list()
+ visual_images = list()
+ rarities = list()
+ default_tlvs = list()
+ gas_by_flag = list()
+ gas_by_group = list()
+ gas_ids_core = list()
+ if(!islist(gases))
+ gases = list()
+ for(var/i in 1 to length(gases))
+ var/id = gases[i]
+ var/datum/gas/G = gases[id]
+ if(!G)
+ gases -= id
+ continue
+ if(id != G.id)
+ gases[i] = G.id
+ gases[G.id] = G
+ _register_gas(G)
+
+/datum/gas_data/proc/rebuild_static_caches()
+ gas_group_names_filterable = list()
+ gas_group_by_name = list()
+ for(var/i in 1 to length(gas_group_names))
+ var/group = 1 << i
+ if(group == GAS_GROUP_CORE)
+ continue
+ if(!(group & GAS_GROUPS_FILTERABLE))
+ continue
+ var/name = gas_group_names[i]
+ gas_group_names_filterable += name
+ gas_group_by_name[name] = group
+
+/datum/gas_data/proc/build_hardcoded()
+ for(var/path in subtypesof(/datum/gas))
+ var/datum/gas/G = new path
+ register_gas(G)
+
+/datum/gas_data/proc/register_gas(datum/gas/G)
+ ASSERT(G.id)
+ ASSERT(!gases[G.id])
+ _register_gas(G)
+
+/datum/gas_data/proc/_register_gas(datum/gas/G)
+ //? intrinsics
+ gases[G.id] = G
+ names[G.id] = G.name
+ if(groups[G.id])
+ var/old_group = groups[G.id]
+ LAZYREMOVE(gas_by_group[old_group], G.id)
+ groups[G.id] = G.gas_groups
+ if(G.gas_groups & GAS_GROUP_CORE)
+ gas_ids_core += G.id
+ if(!((G.gas_flags & (GAS_FLAG_CORE | GAS_FLAG_FILTERABLE)) == (GAS_FLAG_CORE | GAS_FLAG_FILTERABLE)))
+ stack_trace("[G.id] didn't have core/filterable flags even though it was marked as core group. Adding the flags...")
+ G.gas_flags |= (GAS_FLAG_CORE | GAS_FLAG_FILTERABLE)
+ else if(!(G.gas_groups & GAS_GROUPS_FILTERABLE))
+ stack_trace("[G.id] didn't have a filterable gas group. This is probably a bad thing. Adding filterable flag...")
+ G.gas_flags |= (GAS_FLAG_FILTERABLE)
+ if(flags[G.id])
+ var/old_flags = flags[G.id]
+ for(var/bit in bitfield2list(old_flags))
+ LAZYREMOVE(gas_by_flag["[bit]"], G.id)
+ flags[G.id] = G.gas_flags
+ //? physics
+ specific_heats[G.id] = G.specific_heat
+ molar_masses[G.id] = G.molar_mass
+ //? reagents
+ if(G.gas_reagent_id)
+ reagents[G.id] = list(
+ G.gas_reagent_id,
+ G.gas_reagent_amount,
+ G.gas_reagent_threshold,
+ G.gas_reagent_factor,
+ G.gas_reagent_max
+ )
+ else
+ reagents -= G.id
+ //? visuals
+ if(!G.visual_overlay)
+ visuals -= G.id
+ visual_images -= G.id
+ else
+ visuals[G.id] = list(
+ G.visual_overlay,
+ G.visual_threshold,
+ G.visual_factor
+ )
+ visual_images[G.id] = new /list(GAS_VISUAL_STEP_MAX)
+ for(var/i in 1 to GAS_VISUAL_STEP_MAX)
+ var/image/I = image('icons/modules/atmospherics/gas.dmi', icon_state = G.visual_overlay, layer = FLOAT_LAYER + i)
+ I.plane = FLOAT_PLANE
+ I.alpha = i * (255 / GAS_VISUAL_STEP_MAX)
+ I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ I.color = G.visual_color
+ I.appearance_flags = RESET_TRANSFORM | RESET_COLOR | RESET_ALPHA | KEEP_APART
+ visual_images[G.id][i] = I
+ //? reactions
+ rarities[G.id] = G.rarity
+ //? fluff
+ default_tlvs[G.id] = G.default_tlv
+ //? rebuild cheap cache lists
+ //* gas groups
+ for(var/group in bitfield2list(G.gas_groups))
+ LAZYDISTINCTADD(gas_by_group["[group]"], G.id)
+ //* gas flags
+ for(var/bit in bitfield2list(G.gas_flags))
+ LAZYDISTINCTADD(gas_by_flag["[bit]"], G.id)
+
+/**
+ * tgui gas context
+ *
+ * generates data for tgui/interfaces/Atmos.tsx:
+ * * GasContext
+ * * FullGasContext
+ *
+ * @params
+ * * ids - gas ids; defaults to all.
+ * * full - for FullGasContext? Usually not needed.
+ */
+/datum/gas_data/proc/tgui_gas_context(list/ids, full = FALSE)
+ . = list()
+ var/list/gases = list()
+ .["gases"] = gases
+ var/list/core_ids = list()
+ .["coreGases"] = core_ids
+ var/list/filterable_ids = list()
+ .["filterableGases"] = filterable_ids
+ var/filterableGroups = GAS_GROUPS_FILTERABLE
+ .["groupNames"] = global.gas_group_names
+ for(var/id in (ids || src.gases))
+ var/datum/gas/instance = src.gases[id]
+ if(isnull(instance))
+ continue
+ var/list/assembled = list(
+ "id" = instance.id,
+ "name" = instance.name,
+ "flags" = instance.gas_flags,
+ "groups" = instance.gas_groups,
+ "specificHeat" = instance.specific_heat,
+ "molarMass" = instance.molar_mass,
+ )
+ if(instance.gas_flags & GAS_FLAG_CORE)
+ core_ids |= instance.id
+ if(instance.gas_flags & GAS_FLAG_FILTERABLE)
+ filterable_ids += instance.id
+ gases[instance.id] = assembled
+ .["filterableGroups"] = filterableGroups
+
+/**
+ * gets tgui gas context for all non-unknown gasses
+ */
+/datum/gas_data/proc/tgui_known_gas_context(full = FALSE)
+ . = list()
+ for(var/id in flags)
+ if(flags[id] & GAS_FLAG_UNKNOWN)
+ continue
+ . += id
+ return tgui_gas_context(., full)
+
+/**
+ * checks if a gas group or set of gas groups is filterable
+ *
+ * returns all or none
+ */
+/datum/gas_data/proc/gas_groups_all_filterable(group_flag)
+ return ((group_flag & GAS_GROUPS_FILTERABLE) == group_flag)? group_flag : NONE
+
+/**
+ * checks if a gas group or set of gas groups is filterable
+ *
+ * returns filterable groups
+ */
+/datum/gas_data/proc/gas_groups_filterable(group_flag)
+ return (group_flag & GAS_GROUPS_FILTERABLE)
+
+/**
+ * checks if a gas id is specifically filterable
+ */
+/datum/gas_data/proc/gas_id_filterable(gas_id)
+ return flags[gas_id] & (GAS_FLAG_FILTERABLE | GAS_FLAG_CORE)
+
+/**
+ * check if a procedural gas is not conflicting with any other gas
+ */
+/datum/gas_data/proc/procedural_gas_conflicts(datum/procedural_gas/instance)
+ . = FALSE
+ for(var/id in gases)
+ var/datum/gas/gas = gases[id]
+ if(abs(gas.molar_mass - instance.molar_mass) <= GAS_COLLISION_THRESHOLD_MOLAR_MASS)
+ return TRUE
diff --git a/code/modules/atmospherics/gasmixtures/gas_generation.dm b/code/modules/atmospherics/gasmixtures/gas_generation.dm
new file mode 100644
index 00000000000..3ce74318a5e
--- /dev/null
+++ b/code/modules/atmospherics/gasmixtures/gas_generation.dm
@@ -0,0 +1,103 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/proc/generate_procedural_gas(path, list/arguments)
+ var/datum/procedural_gas/instance = new path(arglist(arguments))
+ if(!instance.instance())
+ return
+ if(!instance.generate())
+ return
+ if(!instance.register())
+ return
+ return instance
+
+/**
+ * datums used to procedurally generate gas
+ */
+/datum/procedural_gas
+ /// id to use
+ var/id
+ /// numerical id - used internally
+ var/number
+ /// name
+ var/name
+ /// gas flags
+ var/gas_flags = NONE
+ /// gas groups
+ var/gas_groups = NONE
+ /// specific heat
+ var/specific_heat = 1
+ /// molar mass
+ var/molar_mass = 1
+
+ // todo: reagents
+ // todo: reagents
+ // todo: reagents
+
+ /// gas generation flags
+ var/procedural_gas_flags = NONE
+
+/datum/procedural_gas/New(procedural_gas_flags)
+ src.procedural_gas_flags = procedural_gas_flags
+
+/datum/procedural_gas/proc/instance()
+ RETURN_TYPE(/datum/gas)
+ number = ++global.gas_data.next_procedural_gas_id
+ id = "[GAS_ID_PREFIX_PROCGEN][number]"
+
+/datum/procedural_gas/proc/generate()
+ . = FALSE
+ CRASH("not implemented")
+
+/datum/procedural_gas/proc/register()
+ var/datum/gas/gas = new
+ gas.id = id
+ gas.name = name
+ gas.gas_flags = gas_flags
+ gas.gas_groups = gas_groups
+ gas.specific_heat = specific_heat
+ gas.molar_mass = molar_mass
+
+/datum/procedural_gas/proc/round_stable_random_name()
+ // todo: this is not round stable. we'd need db/whatever for that, deal with this when we do repository systems + persistence
+ return "unkw#[number]"
+
+/**
+ * standard random
+ */
+/datum/procedural_gas/random
+ var/oxidizer_chance = 30
+ var/fuel_chance = 15
+ var/unary_burnmix_chance = 25
+
+ // todo: molar mass vars
+ // todo: specific heat vars
+
+/datum/procedural_gas/random/generate()
+ name = round_stable_random_name()
+ gas_flags = GAS_FLAG_UNKNOWN
+ gas_groups = GAS_GROUP_UNKNOWN
+
+ if(prob(oxidizer_chance) && (procedural_gas_flags & PROCEDURAL_GAS_ALLOW_OXIDIZER))
+ gas_flags |= GAS_FLAG_OXIDIZER
+ if(prob(unary_burnmix_chance) && (procedural_gas_flags & PROCEDURAL_GAS_ALLOW_UNARY_BURNMIX))
+ gas_flags |= GAS_FLAG_FUEL
+ else if(prob(fuel_chance) && (procedural_gas_flags & PROCEDURAL_GAS_ALLOW_FUEL))
+ gas_flags |= GAS_FLAG_FUEL
+ if(prob(unary_burnmix_chance) && (procedural_gas_flags & PROCEDURAL_GAS_ALLOW_UNARY_BURNMIX))
+ gas_flags |= GAS_FLAG_OXIDIZER
+
+ // todo: optimize this shit
+ for(var/i in 1 to 1000)
+ var/potential = rand(1.5 * GAS_COLLISION_FACTOR_MOLAR_MASS, 550 * GAS_COLLISION_FACTOR_MOLAR_MASS) / GAS_COLLISION_FACTOR_MOLAR_MASS
+ var/success = TRUE
+ for(var/id in global.gas_data.molar_masses)
+ var/mass = global.gas_data.molar_masses[id]
+ if(abs(mass - potential) <= GAS_COLLISION_THRESHOLD_MOLAR_MASS)
+ success = FALSE
+ break
+ if(success)
+ molar_mass = potential
+ break
+
+ specific_heat = rand(3 * 10, 300 * 10) / 10
diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
index 5d1dbd49bee..643e387e9ab 100644
--- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm
@@ -1,19 +1,18 @@
/datum/gas_mixture
- //Associative list of gas moles.
- //Gases with 0 moles are not tracked and are pruned by update_values()
+ /// Associative list of gas moles.
+ /// Gases with 0 moles are not tracked and are pruned by update_values()
var/list/gas
- //Temperature in Kelvin of this gas mix.
+ /// Temperature in Kelvin of this gas mix.
var/temperature = 0
-
- //Sum of all the gas moles in this mix. Updated by update_values()
- // DO NOT USE - Planned to be phased out. Use TOTAL_MOLES().
- var/total_moles = 0
- //Volume of this mix.
+ /// Volume of this mix.
var/volume = CELL_VOLUME
- //Size of the group this gas_mixture is representing. 1 for singletons.
+
+ /// Sum of all the gas moles in this mix. Updated by update_values()
+ var/total_moles = 0
+ /// Size of the group this gas_mixture is representing. 1 for singletons.
var/group_multiplier = 1
- //List of active tile overlays for this gas_mixture. Updated by check_tile_graphic()
+ /// List of active tile overlays for this gas_mixture. Updated by check_tile_graphic()
var/list/graphic
/datum/gas_mixture/New(vol = CELL_VOLUME)
@@ -40,7 +39,7 @@
return
var/self_heat_capacity = heat_capacity()
- var/giver_heat_capacity = GLOB.meta_gas_specific_heats[gasid] * moles
+ var/giver_heat_capacity = global.gas_data.specific_heats[gasid] * moles
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
if(combined_heat_capacity != 0)
@@ -96,7 +95,6 @@
update_values()
-
// Used to equalize the mixture between two zones before sleeping an edge.
/datum/gas_mixture/proc/equalize(datum/gas_mixture/sharer)
var/our_heatcap = heat_capacity()
@@ -133,7 +131,7 @@
/datum/gas_mixture/proc/heat_capacity()
. = 0
for(var/g in gas)
- . += GLOB.meta_gas_specific_heats[g] * gas[g]
+ . += global.gas_data.specific_heats[g] * gas[g]
. *= group_multiplier
/**
@@ -196,8 +194,8 @@
return SPECIFIC_ENTROPY_VACUUM //that gas isn't here
//group_multiplier gets divided out in volume/gas[gasid] - also, V/(m*T) = R/(partial pressure)
- var/molar_mass = GLOB.meta_gas_molar_mass[gasid]
- var/specific_heat = GLOB.meta_gas_specific_heats[gasid]
+ var/molar_mass = global.gas_data.molar_masses[gasid] * 0.001
+ var/specific_heat = global.gas_data.specific_heats[gasid]
return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/(gas[gasid] * temperature)) * (molar_mass*specific_heat*temperature)**(2/3) + 1 ) + 15 )
//alternative, simpler equation
@@ -266,13 +264,13 @@
var/sum = 0
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & flag)
+ if(global.gas_data.flags[g] & flag)
sum += gas[g]
var/datum/gas_mixture/removed = new
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & flag)
+ if(global.gas_data.flags[g] & flag)
removed.gas[g] = QUANTIZE((gas[g] / sum) * amount)
gas[g] -= removed.gas[g] / group_multiplier
@@ -282,13 +280,6 @@
return removed
-//Returns the amount of gas that has the given flag, in moles
-/datum/gas_mixture/proc/get_by_flag(flag)
- . = 0
- for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & flag)
- . += gas[g]
-
//Copies gas and temperature from another gas_mixture.
/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample)
gas = sample.gas.Copy()
@@ -352,14 +343,16 @@
/datum/gas_mixture/proc/get_turf_graphics()
. = list()
var/list/gases = src.gas
- var/list/no_overlay_typecache = GLOB.meta_gas_typecache_no_overlays
+ var/list/visual_cache = global.gas_data.visuals
+ var/list/overlay_cache = global.gas_data.visual_images
for(var/id in gases)
- if(no_overlay_typecache[id])
+ if(!visual_cache[id])
continue
+ var/list/v = visual_cache[id]
var/moles = gases[id]
- var/list/gas_overlays = GLOB.meta_gas_overlays[id]
- if(gas_overlays && moles > GLOB.meta_gas_visibility[id])
- . += gas_overlays[min(FACTOR_GAS_VISIBLE_MAX, CEILING(moles / MOLES_GAS_VISIBLE_STEP, 1))]
+ if(moles < v[GAS_VISUAL_INDEX_THRESHOLD])
+ continue
+ . += overlay_cache[id][min(round(moles * v[GAS_VISUAL_INDEX_FACTOR]) + 1, GAS_VISUAL_STEP_MAX)]
return length(.)? . : null
//Equalizes a list of gas mixtures. Used for pipe networks.
@@ -450,20 +443,42 @@
update_values()
return 1
+/**
+ * get mass in kilograms
+ */
/datum/gas_mixture/proc/get_mass()
for(var/g in gas)
- . += gas[g] * GLOB.meta_gas_molar_mass[g] * group_multiplier
+ . += gas[g] * global.gas_data.molar_masses[g] * group_multiplier
+ . *= 0.001
// todo: sort above
+//* Getters
-
-//! Getters
//Returns the pressure of the gas mix. Only accurate if there have been no gas modifications since update_values() has been called.
/datum/gas_mixture/proc/return_pressure()
return (total_moles * R_IDEAL_GAS_EQUATION * temperature) / volume
-//! Gas Strings
+/**
+ * amount of gas of given group
+ */
+/datum/gas_mixture/proc/moles_by_group(group)
+ . = 0
+ for(var/id in gas)
+ if(global.gas_data.groups[id] & group)
+ . += gas[id]
+
+/**
+ * amount of gas of given flag
+ */
+/datum/gas_mixture/proc/moles_by_flag(flag)
+ . = 0
+ for(var/id in gas)
+ if(global.gas_data.flags[id] & flag)
+ . += gas[id]
+
+//* Gas Strings
+
/**
* Copies from a specially formatted gas string, taking on its gas values as our own as well as their temperature.
* if the gas string does not specify temperature, it'll remain unchanged.
@@ -491,11 +506,24 @@
qdel(temp)
return TRUE
-//! Tile Operations
+/**
+ * Get our gas string
+ */
+/datum/gas_mixture/proc/get_gas_string()
+ return "[list2params(gas)][length(gas)? ";TEMP=[temperature]" : ""]"
+
+/**
+ * Get gas string of given list of gas at a given temperature
+ */
+/proc/get_gas_string(list/gas, temperature)
+ if(!length(gas))
+ return "TEMP=[TCMB]"
+ return "[list2params(gas)];TEMP=[temperature]"
+
+//* Tile Operations
+
/**
* get the equivalent of a single tile of this gas mixture
- *
- * TODO: remove group_multiplier, change to tiles_represented
*/
/datum/gas_mixture/proc/copy_single_tile()
RETURN_TYPE(/datum/gas_mixture)
@@ -504,7 +532,83 @@
GM.group_multiplier = 1
return GM
-//! Sharing; usually used for environmental systems.
+//* Scanning
+
+/datum/gas_mixture/proc/chat_analyzer_scan(group_together, molar_masses, exact)
+ RETURN_TYPE(/list)
+ . = list()
+ update_values()
+ if(!total_moles)
+ . += SPAN_WARNING("Pressure: 0 kPa")
+ return
+ var/pressure = return_pressure()
+ . += SPAN_NOTICE("Pressure: [round(pressure, 0.001)] kPa")
+ . += SPAN_NOTICE("Temperature: [round(temperature, 0.001)]°K ([round(temperature - T0C, 0.001)]°C)")
+ var/reagents = 0
+ var/other = 0
+ var/unknown = 0
+ var/list/trace_reagent_masses = list()
+ var/list/trace_other_masses = list()
+ var/list/trace_unknown_masses = list()
+ for(var/id in gas)
+ var/groups = global.gas_data.groups[id]
+ if((groups & GAS_GROUP_REAGENT) && (group_together & GAS_GROUP_REAGENT))
+ reagents += gas[id]
+ trace_reagent_masses += id
+ else if((groups & GAS_GROUP_OTHER) && (group_together & GAS_GROUP_OTHER))
+ other += gas[id]
+ trace_other_masses += id
+ else if((groups & GAS_GROUP_UNKNOWN) && (group_together & GAS_GROUP_UNKNOWN))
+ unknown += gas[id]
+ trace_unknown_masses += id
+ else
+ . += SPAN_NOTICE("[global.gas_data.names[id]]: [exact? "[QUANTIZE(gas[id])] mol @ " : ""][round(gas[id] / total_moles * 100, 0.01)]%[molar_masses? " ([global.gas_data.molar_masses[id]] g/mol)" : ""]")
+ if(reagents)
+ . += SPAN_NOTICE("Reagents: [exact? "[QUANTIZE(reagents)] mol @ " : ""][round(reagents / total_moles * 100, 0.01)]%")
+ if(molar_masses)
+ for(var/id in trace_reagent_masses)
+ . += SPAN_NOTICE("[FOURSPACES] - [global.gas_data.names[id]] ([global.gas_data.molar_masses[id]] g/mol)")
+ if(other)
+ . += SPAN_NOTICE("Other: [exact? "[QUANTIZE(other)] mol @ " : ""][round(other / total_moles * 100, 0.01)]%")
+ if(molar_masses)
+ for(var/id in trace_other_masses)
+ . += SPAN_NOTICE("[FOURSPACES] - [global.gas_data.names[id]] ([global.gas_data.molar_masses[id]] g/mol)")
+ if(unknown)
+ . += SPAN_NOTICE("Unknown: [exact? "[QUANTIZE(unknown)] mol @ " : ""][round(unknown / total_moles * 100, 0.01)]%")
+ if(molar_masses)
+ for(var/id in trace_unknown_masses)
+ . += SPAN_NOTICE("[FOURSPACES] - [global.gas_data.names[id]] ([global.gas_data.molar_masses[id]] g/mol)")
+
+
+/datum/gas_mixture/proc/tgui_analyzer_scan(group_together, molar_masses)
+ . = list()
+ var/pressure = return_pressure()
+ .["moles"] = total_moles
+ .["pressure"] = pressure
+ .["temperature"] = temperature
+ var/list/gases = list()
+ .["gases"] = gases
+ var/list/masses = list()
+ .["masses"] = masses
+ var/list/names = list()
+ .["names"] = names
+ .["showMoles"] = molar_masses
+ for(var/id in gas)
+ var/groups = global.gas_data.groups[id]
+ names[id] = global.gas_data.names[id]
+ if((groups & GAS_GROUP_REAGENT) && (group_together & GAS_GROUP_REAGENT))
+ gases["Reagents"] += gas[id]
+ else if((groups & GAS_GROUP_OTHER) && (group_together & GAS_GROUP_OTHER))
+ gases["Other"] += gas[id]
+ else if((groups & GAS_GROUP_UNKNOWN) && (group_together & GAS_GROUP_UNKNOWN))
+ gases["Unknown"] += gas[id]
+ else
+ gases[id] += gas[id]
+ if(molar_masses)
+ masses[id] = global.gas_data.molar_masses[id]
+
+//* Sharing; usually used for environmental systems.
+
/**
* Default share gas implementation - shares with another gas_mixture non-canonically
* based on connecting tiles. Is just a wrapper to use a lookup table.
@@ -626,7 +730,7 @@
var/their_capacity = 0
for(var/id in gases)
// in the same loop, we'll calculate their total capacity, at the same time expanding their moles to the true value
- their_capacity += GLOB.meta_gas_specific_heats[id] * gases[id] * group_multiplier
+ their_capacity += global.gas_data.specific_heats[id] * gases[id] * group_multiplier
gases[id] *= group_multiplier
for(var/id in our_gas)
diff --git a/code/modules/atmospherics/gasmixtures/gas_types.dm b/code/modules/atmospherics/gasmixtures/gas_types.dm
index 6fcd175161f..df2afc86664 100644
--- a/code/modules/atmospherics/gasmixtures/gas_types.dm
+++ b/code/modules/atmospherics/gasmixtures/gas_types.dm
@@ -1,457 +1,59 @@
-/// List of gases that can't react amongst themselves. KEEP THIS UP TO DATE!
-GLOBAL_LIST_INIT(nonreactive_gases, typecacheof(list(/datum/gas/oxygen, /datum/gas/nitrogen, /datum/gas/carbon_dioxide)))
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
-/**
- * Converts a gas ID to typepath
- */
-/proc/gas_id2path(id)
- return GLOB.meta_gas_id_lookup[id]
-
-//Unomos - global list inits for all of the meta gas lists.
-//This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists
-GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list())
-GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list())
-GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list())
-GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list())
-GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list())
-GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list())
-GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list())
-/// Gas ID to typepath conversion lookup for optimal speed.
-GLOBAL_LIST_INIT(meta_gas_id_lookup, meta_gas_id_lookup_list())
-/// Gas flags by gas
-GLOBAL_LIST_INIT(meta_gas_flags, meta_gas_flag_list())
-/// Gases by gas flag
-GLOBAL_LIST_INIT(meta_gas_by_flag, meta_gas_by_flag_list())
-/// Gas molar mass by gas
-GLOBAL_LIST_INIT(meta_gas_molar_mass, meta_gas_molar_mass_list())
-/// Typecache of gases with no overlays
-GLOBAL_LIST_INIT(meta_gas_typecache_no_overlays, meta_gas_typecache_no_overlays_list())
-/// The reagents gases give mobs when breathed
-GLOBAL_LIST_INIT(meta_gas_reagent_id, meta_gas_reagent_id_list())
-/// The amount of the reagents gases give mobs
-GLOBAL_LIST_INIT(meta_gas_reagent_amount, meta_gas_reagent_amount_list())
-
-
-/proc/meta_gas_heat_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.specific_heat)
-
-/proc/meta_gas_name_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.name)
-
-/proc/meta_gas_visibility_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.moles_visible)
-
-/proc/meta_gas_overlay_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = 0 //gotta make sure if(GLOB.meta_gas_overlays[gaspath]) doesn't break
- if(initial(gas.moles_visible) != null)
- .[gas_path] = new /list(FACTOR_GAS_VISIBLE_MAX)
- for(var/i in 1 to FACTOR_GAS_VISIBLE_MAX)
- var/image/I = image('icons/effects/atmospherics.dmi', icon_state = initial(gas.gas_overlay), layer = FLOAT_LAYER + i)
- I.plane = FLOAT_PLANE
- I.alpha = i * 255 / FACTOR_GAS_VISIBLE_MAX
- I.mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- I.appearance_flags = RESET_TRANSFORM | RESET_COLOR | RESET_ALPHA | KEEP_APART
- .[gas_path][i] = I
-
-/proc/meta_gas_danger_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.dangerous)
-
-/proc/meta_gas_id_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.id)
-
-/proc/meta_gas_fusion_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.fusion_power)
-
-/proc/meta_gas_id_lookup_list()
- var/list/gases = subtypesof(/datum/gas)
- . = list()
- for(var/gas_path in gases)
- var/datum/gas/gas = gas_path
- .[initial(gas.id)] = gas_path
-
-/proc/meta_gas_flag_list()
- . = subtypesof(/datum/gas)
- for(var/gas_path in .)
- var/datum/gas/gas = gas_path
- .[gas_path] = initial(gas.gas_flags)
-
-/proc/meta_gas_by_flag_list()
- . = list()
- // slightly more tricky
- var/list/gases = subtypesof(/datum/gas)
- // for each gas
- for(var/gas_path in gases)
- var/datum/gas/gas = gas_path
- // cache flags
- var/gas_flags = initial(gas.gas_flags)
- // for each bitfield
- for(var/i in GLOB.bitflags)
- // if we have it
- if(gas_flags & i)
- // add to list
- LAZYADD(.["[i]"], gas_path)
-
-/proc/meta_gas_molar_mass_list()
- . = list()
- var/list/gases = subtypesof(/datum/gas)
- for(var/gas_path in gases)
- var/datum/gas/G = gas_path
- .[gas_path] = initial(G.molar_mass)
-
-/proc/meta_gas_typecache_no_overlays_list()
- . = list()
- for(var/gastype in subtypesof(/datum/gas))
- var/datum/gas/gasvar = gastype
- if (!initial(gasvar.gas_overlay))
- .[gastype] = TRUE
-
-/proc/meta_gas_reagent_id_list()
- . = list()
- for(var/gastype in subtypesof(/datum/gas))
- var/datum/gas/gasvar = gastype
- if(initial(gasvar.gas_reagent_id))
- .[gastype] = initial(gasvar.gas_reagent_id)
-
-/proc/meta_gas_reagent_amount_list()
- . = list()
- for(var/gastype in subtypesof(/datum/gas))
- var/datum/gas/gasvar = gastype
- if(initial(gasvar.gas_reagent_amount))
- .[gastype] = initial(gasvar.gas_reagent_amount)
-// Visual overlay
-/*
-/obj/effect/overlay/gas
- icon = 'icons/effects/atmospherics.dmi'
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
- anchored = TRUE // should only appear in vis_contents, but to be safe
- layer = FLY_LAYER
- appearance_flags = TILE_BOUND
- vis_flags = NONE
-
-/obj/effect/overlay/gas/New(state, alph)
- . = ..()
- icon_state = state
- alpha = alph
-*/
-
-/*||||||||||||||/----------\||||||||||||||*\
-||||||||||||||||[GAS DATUMS]||||||||||||||||
-||||||||||||||||\__________/||||||||||||||||
-||||These should never be instantiated. ||||
-||||They exist only to make it easier ||||
-||||to add a new gas. They are accessed ||||
-||||only by meta_gas_list(). ||||
-\*||||||||||||||||||||||||||||||||||||||||*/
/datum/gas
+ //* Intrinsics
/// Text ID for things like gas strings. THIS SHOULD NEVER, EVER, BE CHANGED! Pick one and stick with it. Change this and EVERYTHING breaks. The typepath, infact, is more mutable than this!
var/id = ""
- /// Specific heat in J/(mol*K)
- var/specific_heat = 0
/// Textual name
var/name = "Unnamed Gas"
- /// icon_state in icons/effects/atmospherics.dmi
- var/gas_overlay = ""
- /// How many moles is required to make this gas visible
- var/moles_visible
- /// Is this gas considered dangerous? Used by canister and admin logging in general.
- var/dangerous = FALSE
+ /// Gas flags. See [code/__DEFINES/atmospherics/flags.dm]
+ var/gas_flags = GAS_FLAG_FILTERABLE
+ /// gas group - flag
+ var/gas_groups = GAS_GROUP_OTHER
+
+ //* physics
+ /// Specific heat in J/(mol*K).
+ /// For chemicals that exist in real life this is the specific heat value under constant volume.
+ var/specific_heat = 1
+ /// Molar mass in kg/mol
+ var/molar_mass = 1
+
+ //* reagents
+ /// reagent id to apply
+ var/gas_reagent_id
+ /// reagent amount to add to someone breathing it at minimum moles
+ var/gas_reagent_amount = 0
+ /// minimum moles to affect
+ var/gas_reagent_threshold = 0
+ /// reagent amount to additionally add per mole
+ var/gas_reagent_factor = 0
+ /// reagent amount to not exceed in them
+ var/gas_reagent_max = 10
+ /// reserved for chemgas - how many moles of this gas corrospond to 1 unit; should always be inverse of reagent's side of this lookup
+ var/gas_reagent_moles = 0.1
+
+ //* visuals
+ // todo: visual cache gen should be a proc on /datum/gas so we can do custom colors
+ /// visual overlay color if any
+ var/visual_color
+ /// icon_state in icons/modules/atmospherics/gas.dmi; invisible if null
+ var/visual_overlay
+ /// minimum moles to see gas
+ var/visual_threshold = 0.25
+ /// index addition per mole for cache list
+ var/visual_factor = 1
+
+ //* reactions
/// Fusion is not yet implemented : How much the gas accelerates a fusion reaction
var/fusion_power = 0
/// Relative rarity compared to other gases, used when setting up the reactions list.
var/rarity = 0
- /// Molar mass in kg/mol
- var/molar_mass = 0
- /// Gas flags. See [code/__DEFINES/atmospherics/flags.dm]
- var/gas_flags
- var/gas_reagent_id //What is the ID of the reagent we want to apply
- var/gas_reagent_amount = 0//How much of the reagent is applied
- //For a gas that makes up 21% of the atmos you need to be above 1.39, for it to instill any reagents, for lower percentages the number needs to be higher,and viceversa
+ //* fluff
+ /// default TLV values for air alarms in format of list(danger low, warning low, warning high, danger high)
+ /// null to have air alarms ignore this gas entirely
+ var/list/default_tlv
-/datum/gas/oxygen
- id = "o2"
- name = "Oxygen"
- specific_heat = 20
- molar_mass = 0.032
- gas_flags = GAS_FLAG_OXIDIZER
-
-/datum/gas/nitrogen
- id = "n2"
- name = "Nitrogen"
- specific_heat = 20
- molar_mass = 0.028
-
-/datum/gas/carbon_dioxide
- id = "co2"
- name = "Carbon Dioxide"
- specific_heat = 30
- molar_mass = 0.044
-
-/datum/gas/phoron
- id = "phoron"
- name = "Phoron"
- //Note that this has a significant impact on TTV yield.
- //Because it is so high, any leftover phoron soaks up a lot of heat and drops the yield pressure.
- specific_heat = 200 // J/(mol*K)
-
- //Hypothetical group 14 (same as carbon), period 8 element.
- //Using multiplicity rule, it's atomic number is 162
- //and following a N/Z ratio of 1.5, the molar mass of a monatomic gas is:
- molar_mass = 0.405 // kg/mol
-
- gas_overlay = "phoron"
-
- moles_visible = 0.7
-
- gas_flags = GAS_FLAG_FUEL | GAS_FLAG_FUSION_FUEL | GAS_FLAG_CONTAMINANT
-
-/datum/gas/volatile_fuel
- id = "volatile_fuel"
- name = "Volatile Fuel"
- specific_heat = 253 // J/(mol*K) C8H18 gasoline. Isobaric, but good enough.
- molar_mass = 0.114 // kg/mol. same.
-
- gas_flags = GAS_FLAG_FUEL
-
-/datum/gas/nitrous_oxide
- id = "n2o"
- name = "Nitrous Oxide"
- specific_heat = 40
- molar_mass = 0.044
-
- gas_overlay = "nitrous_oxide"
- moles_visible = 1
-
- gas_flags = GAS_FLAG_OXIDIZER
-
-//The following is partially stolen from Nebula
-//I am not rewriting our handling of air for this, at least for now.
-/datum/gas/helium
- id = "helium"
- name = "Helium"
- specific_heat = 80
- molar_mass = 0.004
-
- gas_flags = GAS_FLAG_FUSION_FUEL
-
-/datum/gas/carbon_monoxide
- id = "carbon monoxide"
- name = "Carbon Monoxide"
- //lore_text = "A highly poisonous gas."
- specific_heat = 30
- molar_mass = 0.028
-
- //gas_symbol_html = "CO"
- //gas_symbol = "CO"
- //taste_description = "stale air"
- //metabolism = 0.05 // As with helium.
-
-/datum/gas/methyl_bromide
- id = "methyl bromide"
- name = "Methyl Bromide"
- //lore_text = "A once-popular fumigant and weedkiller."
- specific_heat = 42.59
- molar_mass = 0.095
- //gas_symbol_html = "CH3Br"
- //gas_symbol = "CH3Br"
- //taste_description = "pestkiller"
- /*vapor_products = list(
- /singleton/material/gas/methyl_bromide = 1
- )
- value = 0.25*/
-
-/datum/gas/nitrodioxide
- id = "nitrogen dioxide"
- name = "Nitrogen Dioxide"
- //color = "#ca6409"
- specific_heat = 37
- molar_mass = 0.054
- gas_flags = GAS_FLAG_OXIDIZER
- //gas_symbol_html = "NO2"
- //gas_symbol = "NO2"
-
-/datum/gas/nitricoxide
- id = "nitric oxide"
- name = "Nitric Oxide"
- specific_heat = 10
- molar_mass = 0.030
- gas_flags = GAS_FLAG_OXIDIZER
- //gas_symbol_html = "NO"
- //gas_symbol = "NO"
-
-/datum/gas/methane
- id = "methane"
- name = "Methane"
- specific_heat = 30
- molar_mass = 0.016
- gas_flags = GAS_FLAG_FUEL
- //gas_symbol_html = "CH4"
- //gas_symbol = "CH4"
-
-/datum/gas/argon
- id = "argon"
- name = "Argon"
- //lore_text = "Just when you need it, all of your supplies argon."
- specific_heat = 10
- molar_mass = 0.018
- //gas_symbol_html = "Ar"
- //gas_symbol = "Ar"
- //value = 0.25
-
-// If narcosis is ever simulated, krypton has a narcotic potency seven times greater than regular airmix.
-/datum/gas/krypton
- id = "krypton"
- name = "Krypton"
- specific_heat = 5
- molar_mass = 0.036
- //gas_symbol_html = "Kr"
- //gas_symbol = "Kr"
- //value = 0.25
-
-/datum/gas/neon
- id = "neon"
- name = "Neon"
- specific_heat = 20
- molar_mass = 0.01
- //gas_symbol_html = "Ne"
- //gas_symbol = "Ne"
- //value = 0.25
-
-/datum/gas/ammonia
- id = "Ammonia"
- name = "ammonia"
- specific_heat = 20
- molar_mass = 0.017
- //gas_symbol_html = "NH3"
- //gas_symbol = "NH3"
- //metabolism = 0.05 // So that low dosages have a chance to build up in the body.
- //taste_description = "mordant"
- //taste_mult = 2
- //lore_text = "A caustic substance commonly used in fertilizer or household cleaners."
- //color = "#404030"
- //metabolism = REM * 0.5
- //overdose = 5
-
-/datum/gas/xenon
- id = "Xenon"
- name = "xenon"
- specific_heat = 3
- molar_mass = 0.054
- //gas_symbol_html = "Xe"
- //gas_symbol = "Xe"
- //value = 0.25
-
-/datum/gas/chlorine
- id = "chlorine"
- name = "Chlorine"
- //color = "#c5f72d"
- //gas_overlay_limit = 0.5
- specific_heat = 5
- molar_mass = 0.017
- gas_flags = GAS_FLAG_CONTAMINANT
- /*gas_symbol_html = "Cl"
- gas_symbol = "Cl"
- taste_description = "bleach"
- metabolism = REM
- heating_point = null
- heating_products = null
- toxicity = 15*/
- gas_overlay = "chlorine"
- moles_visible = 1
-
- gas_reagent_id = "sacid"
- gas_reagent_amount = 10
-
-
-/datum/gas/sulfur_dioxide
- id = "sulfur dioxide"
- name = "Sulfur Dioxide"
- specific_heat = 30
- molar_mass = 0.044
- /*gas_symbol_html = "SO2"
- gas_symbol = "SO2"
- dissolves_into = list(
- /singleton/material/solid/sulfur = 0.5,
- /singleton/material/gas/oxygen = 0.5
- )*/
-
-/datum/gas/hydrogen
- id = "hydrogen"
- name = "Hydrogen"
- //lore_text = "A colorless, flammable gas."
- //flags = MAT_FLAG_FUSION_FUEL
- //wall_name = "bulkhead"
- //construction_difficulty = MAT_VALUE_HARD_DIY
- specific_heat = 100
- molar_mass = 0.002
- gas_flags = GAS_FLAG_FUEL | GAS_FLAG_FUSION_FUEL
- /*burn_product = /singleton/material/liquid/water
- gas_symbol_html = "H2"
- gas_symbol = "H2"
- dissolves_into = list(
- /singleton/material/liquid/fuel/hydrazine = 1
- )
- value = 0.4*/
-
-/datum/gas/hydrogen/tritium
- id = "tritium"
- name = "Tritium"
- /*lore_text = "A radioactive isotope of hydrogen. Useful as a fusion reactor fuel material."
- mechanics_text = "Tritium is useable as a fuel in some forms of portable generator. It can also be converted into a fuel rod suitable for a R-UST fusion plant injector by using a fuel compressor. It fuses hotter than deuterium but is correspondingly more unstable."
- color = "#777777"
- stack_origin_tech = "{'materials':5}"
- value = 0.45
- gas_symbol_html = "T"
- gas_symbol = "T"*/
-
-/datum/gas/hydrogen/deuterium
- id = "Deuterium"
- name = "Deuterium"
- /*lore_text = "One of the two stable isotopes of hydrogen; also known as heavy hydrogen. Useful as a chemically synthesised fusion reactor fuel material."
- mechanics_text = "Deuterium can be converted into a fuel rod suitable for a R-UST fusion plant injector by using a fuel compressor. It is the most 'basic' fusion fuel."
- flags = MAT_FLAG_FUSION_FUEL | MAT_FLAG_FISSIBLE
- color = "#999999"
- stack_origin_tech = "{'materials':3}"
- gas_symbol_html = "D"
- gas_symbol = "D"
- value = 0.5
-
- neutron_interactions = list(
- INTERACTION_ABSORPTION = 1250
- )
- absorption_products = list(
- /singleton/material/gas/hydrogen/tritium = 1
- )
- neutron_absorption = 5
- neutron_cross_section = 3*/
-
-//Special gas type that are very powerful and shouldnt be avaiable in large portions
-/datum/gas/vimur
- id = "vimur"
- name = "Vimur"
- specific_heat = 500 // J/(mol*K) //250% the heat capacity of phoron
- molar_mass = 0.054 // Standard Mass of xenon
-
- gas_overlay = "vimur"
- moles_visible = 0.1
+ // todo: combustion enthalpies / oxidizer powers
+ // todo: combustion product gases
diff --git a/code/modules/atmospherics/gasmixtures/gas_types/core.dm b/code/modules/atmospherics/gasmixtures/gas_types/core.dm
new file mode 100644
index 00000000000..747573b9f9c
--- /dev/null
+++ b/code/modules/atmospherics/gasmixtures/gas_types/core.dm
@@ -0,0 +1,82 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/datum/gas/oxygen
+ id = GAS_ID_OXYGEN
+ name = "O2"
+
+ specific_heat = 21
+ molar_mass = 32
+ gas_flags = GAS_FLAG_OXIDIZER | GAS_FLAG_FILTERABLE | GAS_FLAG_CORE
+ gas_groups = GAS_GROUP_CORE
+
+ default_tlv = list(16, 19, 70, 100)
+
+/datum/gas/nitrogen
+ id = GAS_ID_NITROGEN
+ name = "N2"
+
+ specific_heat = 20.8
+ molar_mass = 28
+ gas_flags = GAS_FLAG_FILTERABLE | GAS_FLAG_CORE
+ gas_groups = GAS_GROUP_CORE
+
+ default_tlv = list(0, 0, 135, 140)
+
+/datum/gas/carbon_dioxide
+ id = GAS_ID_CARBON_DIOXIDE
+ name = "CO2"
+
+ specific_heat = 28.82
+ molar_mass = 44
+ gas_flags = GAS_FLAG_FILTERABLE | GAS_FLAG_CORE
+ gas_groups = GAS_GROUP_CORE
+
+ default_tlv = list(0, 0, 5, 10)
+
+/datum/gas/nitrous_oxide
+ id = GAS_ID_NITROUS_OXIDE
+ name = "N2O"
+
+ visual_overlay = "nitrous_oxide"
+ visual_threshold = 1
+
+ specific_heat = 30.36
+ molar_mass = 44
+ gas_flags = GAS_FLAG_OXIDIZER | GAS_FLAG_FILTERABLE | GAS_FLAG_CORE
+ gas_groups = GAS_GROUP_CORE
+
+ default_tlv = list(0, 0, 3, 7)
+
+/datum/gas/hydrogen
+ id = GAS_ID_HYDROGEN
+ name = "H2"
+
+ specific_heat = 20.32
+ molar_mass = 2
+ gas_flags = GAS_FLAG_FUEL | GAS_FLAG_FUSION_FUEL | GAS_FLAG_FILTERABLE | GAS_FLAG_CORE
+ gas_groups = GAS_GROUP_CORE
+
+ default_tlv = list(0, 0, 0.25, 1)
+
+/datum/gas/phoron
+ id = GAS_ID_PHORON
+ name = "PHR"
+
+ visual_overlay = "phoron"
+ visual_threshold = 0.7
+
+ // Hypothetical group 14, period 8 element
+ // Atomic number 162
+ // Neutron/Proton ratio 1.5
+ molar_mass = 405
+ // wonder scifi carbon-group element
+ // we're going to assume it's a polyatomic gas
+ // infact while we're at it why not just assume it's some
+ // weird magic element that naturally forms complex structures like
+ // carbon nanotubes or something lol i'm sure that'll justify this value
+ specific_heat = 207.52
+ gas_flags = GAS_FLAG_FUEL | GAS_FLAG_FUSION_FUEL | GAS_FLAG_CONTAMINANT | GAS_FLAG_FILTERABLE | GAS_FLAG_CORE
+ gas_groups = GAS_GROUP_CORE
+
+ default_tlv = list(0, 0, 0, 0.5)
diff --git a/code/modules/atmospherics/gasmixtures/gas_types_hardcoded.dm b/code/modules/atmospherics/gasmixtures/gas_types_hardcoded.dm
new file mode 100644
index 00000000000..f456bf42360
--- /dev/null
+++ b/code/modules/atmospherics/gasmixtures/gas_types_hardcoded.dm
@@ -0,0 +1,201 @@
+// todo: eithre rename or remove this and overhaul its uses, fucks sakes.
+/datum/gas/volatile_fuel
+ id = GAS_ID_VOLATILE_FUEL
+ name = "Volatile Fuel"
+ specific_heat = 253 // J/(mol*K) C8H18 gasoline. Isobaric, but good enough.
+ molar_mass = 1000 * 0.114 // kg/mol. same.
+
+ gas_flags = GAS_FLAG_FUEL
+
+//The following is partially stolen from Nebula
+//I am not rewriting our handling of air for this, at least for now.
+/datum/gas/helium
+ id = GAS_ID_HELIUM
+ name = "Helium"
+ specific_heat = 80
+ molar_mass = 1000 * 0.004
+
+ gas_flags = GAS_FLAG_FUSION_FUEL
+
+/datum/gas/carbon_monoxide
+ id = GAS_ID_CARBON_MONOXIDE
+ name = "Carbon Monoxide"
+ //lore_text = "A highly poisonous gas."
+ specific_heat = 30
+ molar_mass = 1000 * 0.028
+
+ //gas_symbol_html = "CO"
+ //gas_symbol = "CO"
+ //taste_description = "stale air"
+ //metabolism = 0.05 // As with helium.
+
+/datum/gas/methyl_bromide
+ id = GAS_ID_METHYL_BROMIDE
+ name = "Methyl Bromide"
+ //lore_text = "A once-popular fumigant and weedkiller."
+ specific_heat = 42.59
+ molar_mass = 1000 * 0.095
+ //gas_symbol_html = "CH3Br"
+ //gas_symbol = "CH3Br"
+ //taste_description = "pestkiller"
+ /*vapor_products = list(
+ /singleton/material/gas/methyl_bromide = 1
+ )
+ value = 0.25*/
+
+/datum/gas/nitrodioxide
+ id = GAS_ID_NITROGEN_DIOXIDE
+ name = "Nitrogen Dioxide"
+ //color = "#ca6409"
+ specific_heat = 37
+ molar_mass = 1000 * 0.054
+ gas_flags = GAS_FLAG_OXIDIZER
+ //gas_symbol_html = "NO2"
+ //gas_symbol = "NO2"
+
+/datum/gas/nitricoxide
+ id = GAS_ID_NITRIC_OXIDE
+ name = "Nitric Oxide"
+ specific_heat = 10
+ molar_mass = 1000 * 0.030
+ gas_flags = GAS_FLAG_OXIDIZER
+ //gas_symbol_html = "NO"
+ //gas_symbol = "NO"
+
+/datum/gas/methane
+ id = GAS_ID_METHANE
+ name = "Methane"
+ specific_heat = 30
+ molar_mass = 1000 * 0.016
+ gas_flags = GAS_FLAG_FUEL
+ //gas_symbol_html = "CH4"
+ //gas_symbol = "CH4"
+
+/datum/gas/argon
+ id = GAS_ID_ARGON
+ name = "Argon"
+ //lore_text = "Just when you need it, all of your supplies argon."
+ specific_heat = 10
+ molar_mass = 1000 * 0.018
+ //gas_symbol_html = "Ar"
+ //gas_symbol = "Ar"
+ //value = 0.25
+
+// If narcosis is ever simulated, krypton has a narcotic potency seven times greater than regular airmix.
+/datum/gas/krypton
+ id = GAS_ID_KRYPTON
+ name = "Krypton"
+ specific_heat = 5
+ molar_mass = 1000 * 0.036
+ //gas_symbol_html = "Kr"
+ //gas_symbol = "Kr"
+ //value = 0.25
+
+/datum/gas/neon
+ id = GAS_ID_NEON
+ name = "Neon"
+ specific_heat = 20
+ molar_mass = 1000 * 0.01
+ //gas_symbol_html = "Ne"
+ //gas_symbol = "Ne"
+ //value = 0.25
+
+/datum/gas/ammonia
+ id = GAS_ID_AMMONIA
+ name = "Ammonia"
+ specific_heat = 20
+ molar_mass = 1000 * 0.017
+ //gas_symbol_html = "NH3"
+ //gas_symbol = "NH3"
+ //metabolism = 0.05 // So that low dosages have a chance to build up in the body.
+ //taste_description = "mordant"
+ //taste_mult = 2
+ //lore_text = "A caustic substance commonly used in fertilizer or household cleaners."
+ //color = "#404030"
+ //metabolism = REM * 0.5
+ //overdose = 5
+
+/datum/gas/xenon
+ id = GAS_ID_XENON
+ name = "Xenon"
+ specific_heat = 3
+ molar_mass = 1000 * 0.054
+ //gas_symbol_html = "Xe"
+ //gas_symbol = "Xe"
+ //value = 0.25
+
+/datum/gas/chlorine
+ id = GAS_ID_CHLORINE
+ name = "Chlorine"
+ //color = "#c5f72d"
+ //visual_overlay_limit = 0.5
+ specific_heat = 5
+ molar_mass = 1000 * 0.017
+ gas_flags = GAS_FLAG_CONTAMINANT
+ /*gas_symbol_html = "Cl"
+ gas_symbol = "Cl"
+ taste_description = "bleach"
+ metabolism = REM
+ heating_point = null
+ heating_products = null
+ toxicity = 15*/
+ visual_overlay = "chlorine"
+ visual_threshold = 1
+
+ gas_reagent_id = "sacid"
+ gas_reagent_amount = 10
+
+
+/datum/gas/sulfur_dioxide
+ id = GAS_ID_SULFUR_DIOXIDE
+ name = "Sulfur Dioxide"
+ specific_heat = 30
+ molar_mass = 1000 * 0.044
+ /*gas_symbol_html = "SO2"
+ gas_symbol = "SO2"
+ dissolves_into = list(
+ /singleton/material/solid/sulfur = 0.5,
+ /singleton/material/gas/oxygen = 0.5
+ )*/
+
+/datum/gas/tritium
+ id = GAS_ID_TRITIUM
+ name = "Tritium"
+ /*lore_text = "A radioactive isotope of hydrogen. Useful as a fusion reactor fuel material."
+ mechanics_text = "Tritium is useable as a fuel in some forms of portable generator. It can also be converted into a fuel rod suitable for a R-UST fusion plant injector by using a fuel compressor. It fuses hotter than deuterium but is correspondingly more unstable."
+ color = "#777777"
+ stack_origin_tech = "{'materials':5}"
+ value = 0.45
+ gas_symbol_html = "T"
+ gas_symbol = "T"*/
+
+/datum/gas/deuterium
+ id = GAS_ID_DEUTERIUM
+ name = "Deuterium"
+ /*lore_text = "One of the two stable isotopes of hydrogen; also known as heavy hydrogen. Useful as a chemically synthesised fusion reactor fuel material."
+ mechanics_text = "Deuterium can be converted into a fuel rod suitable for a R-UST fusion plant injector by using a fuel compressor. It is the most 'basic' fusion fuel."
+ flags = MAT_FLAG_FUSION_FUEL | MAT_FLAG_FISSIBLE
+ color = "#999999"
+ stack_origin_tech = "{'materials':3}"
+ gas_symbol_html = "D"
+ gas_symbol = "D"
+ value = 0.5
+
+ neutron_interactions = list(
+ INTERACTION_ABSORPTION = 1250
+ )
+ absorption_products = list(
+ /singleton/material/gas/hydrogen/tritium = 1
+ )
+ neutron_absorption = 5
+ neutron_cross_section = 3*/
+
+//Special gas type that are very powerful and shouldnt be avaiable in large portions
+/datum/gas/vimur
+ id = GAS_ID_VIMUR
+ name = "Vimur"
+ specific_heat = 500 // J/(mol*K) //250% the heat capacity of phoron
+ molar_mass = 1000 * 0.054 // Standard Mass of xenon
+
+ visual_overlay = "vimur"
+ visual_threshold = 0.1
diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm
index a3d47caf12c..963b4d2646c 100644
--- a/code/modules/atmospherics/gasmixtures/reactions.dm
+++ b/code/modules/atmospherics/gasmixtures/reactions.dm
@@ -18,9 +18,9 @@
//*** Get the fuel and oxidizer amounts
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_FUEL)
+ if(global.gas_data.flags[g] & GAS_FLAG_FUEL)
gas_fuel += gas[g]
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_OXIDIZER)
+ if(global.gas_data.flags[g] & GAS_FLAG_OXIDIZER)
total_oxidizers += gas[g]
gas_fuel *= group_multiplier
total_oxidizers *= group_multiplier
@@ -85,7 +85,7 @@
//remove_by_flag() and adjust_gas() handle the group_multiplier for us.
remove_by_flag(GAS_FLAG_OXIDIZER, used_oxidizers)
remove_by_flag(GAS_FLAG_FUEL, used_gas_fuel)
- adjust_gas(/datum/gas/carbon_dioxide, used_oxidizers)
+ adjust_gas(GAS_ID_CARBON_DIOXIDE, used_oxidizers)
if(zone)
zone.remove_liquidfuel(used_liquid_fuel, !check_combustability())
@@ -104,7 +104,7 @@
/datum/gas_mixture/proc/check_recombustability(list/fuel_objs)
. = 0
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_OXIDIZER && gas[g] >= 0.1)
+ if(global.gas_data.flags[g] & GAS_FLAG_OXIDIZER && gas[g] >= 0.1)
. = 1
break
@@ -116,7 +116,7 @@
. = 0
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_FUEL && gas[g] >= 0.1)
+ if(global.gas_data.flags[g] & GAS_FLAG_FUEL && gas[g] >= 0.1)
. = 1
break
@@ -124,7 +124,7 @@
. = 0
CACHE_VSC_PROP(atmos_vsc, /atmos/fire/consumption_rate, fire_consumption_rate)
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_OXIDIZER && QUANTIZE(gas[g] * fire_consumption_rate) >= 0.1)
+ if(global.gas_data.flags[g] & GAS_FLAG_OXIDIZER && QUANTIZE(gas[g] * fire_consumption_rate) >= 0.1)
. = 1
break
@@ -136,7 +136,7 @@
. = 0
for(var/g in gas)
- if(GLOB.meta_gas_flags[g] & GAS_FLAG_FUEL && (QUANTIZE(gas[g] * fire_consumption_rate) >= max(0.005, MINIMUM_MOLES_TO_SPARK)))
+ if(global.gas_data.flags[g] & GAS_FLAG_FUEL && (QUANTIZE(gas[g] * fire_consumption_rate) >= max(0.005, MINIMUM_MOLES_TO_SPARK)))
. = 1
break
diff --git a/code/modules/atmospherics/gasmixtures/transfer_helpers.dm b/code/modules/atmospherics/gasmixtures/transfer_helpers.dm
index 0a8699805bd..f88a407cd5f 100644
--- a/code/modules/atmospherics/gasmixtures/transfer_helpers.dm
+++ b/code/modules/atmospherics/gasmixtures/transfer_helpers.dm
@@ -1,5 +1,5 @@
// // Helpers for machine transfer, taking into account power draws, max pressures, max volumes, etc.
-// // All of these also, if provided with machine parameter, set the machine's last_power_draw and last_transfer_rate.
+// // All of these also, if provided with machine parameter, set the machine's last_power_draw_legacy and last_transfer_rate.
// /**
// * Gas pumping proc
diff --git a/code/modules/atmospherics/machinery/_atmospherics_helpers.dm b/code/modules/atmospherics/machinery/_atmospherics_helpers.dm
index b01a070b0e7..4f4fbdb81ba 100644
--- a/code/modules/atmospherics/machinery/_atmospherics_helpers.dm
+++ b/code/modules/atmospherics/machinery/_atmospherics_helpers.dm
@@ -8,9 +8,7 @@
*/
-/obj/machinery/atmospherics/var/last_flow_rate = 0
-/obj/machinery/atmospherics/var/last_power_draw = 0
-/obj/machinery/portable_atmospherics/var/last_flow_rate = 0
+/obj/machinery/portable_atmospherics/var/last_flow_rate_legacy = 0
/obj/machinery/atmospherics/var/debug = 0
@@ -35,7 +33,7 @@
transfer_moles = min(source.total_moles, transfer_moles)
//Calculate the amount of energy required and limit transfer_moles based on available power
- var/specific_power = calculate_specific_power(source, sink)/ATMOS_PUMP_EFFICIENCY //this has to be calculated before we modify any gas mixtures
+ var/specific_power = calculate_specific_power(source, sink)/ATMOS_ABSTRACT_PUMP_EFFICIENCY //this has to be calculated before we modify any gas mixtures
if (!isnull(available_power) && specific_power > 0)//specific_power > 0 means we need to use power to move moles, otherwise we can get away with just letting it flow passive
transfer_moles = min(transfer_moles, available_power / specific_power)
@@ -45,7 +43,7 @@
//Update flow rate meter
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
- A.last_flow_rate = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ A.last_flow_rate_legacy = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (A.debug)
A.visible_message("[A]: power avaiable to move gases: [available_power] W")
@@ -56,7 +54,7 @@
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
- P.last_flow_rate = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ P.last_flow_rate_legacy = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/datum/gas_mixture/removed = source.remove(transfer_moles)
if (!removed) //Just in case
@@ -87,13 +85,13 @@
//Update flow rate meter
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
- A.last_flow_rate = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ A.last_flow_rate_legacy = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (A.debug)
A.visible_message("[A]: moles transferred = [transfer_moles] mol")
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
- P.last_flow_rate = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ P.last_flow_rate_legacy = (transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/datum/gas_mixture/removed = source.remove(transfer_moles)
if(!removed) //Just in case
@@ -108,7 +106,7 @@
//total_transfer_moles - Limits the amount of moles to scrub. The actual amount of gas scrubbed may also be limited by available_power, if given.
//available_power - the maximum amount of power that may be used when scrubbing gas. If null then the scrubbing is not limited by power.
/proc/scrub_gas(var/obj/machinery/M, var/list/filtering, var/datum/gas_mixture/source, var/datum/gas_mixture/sink, var/total_transfer_moles = null, var/available_power = null)
- if (source.total_moles < MINIMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
+ if (source.total_moles < MINIMUM_MOLES_TO_SCRUB) //if we cant transfer enough gas just stop to avoid further processing
return -1
filtering = filtering & source.gas //only filter gasses that are actually there. DO NOT USE &=
@@ -117,14 +115,14 @@
var/total_filterable_moles = 0 //the total amount of filterable gas
var/list/specific_power_gas = list() //the power required to remove one mole of pure gas, for each gas type
for (var/g in filtering)
- if (source.gas[g] < MINIMUM_MOLES_TO_FILTER)
+ if (source.gas[g] < MINIMUM_MOLES_TO_SCRUB)
continue
- var/specific_power = calculate_specific_power_gas(g, source, sink)/ATMOS_FILTER_EFFICIENCY
+ var/specific_power = calculate_specific_power_gas(g, source, sink)/ATMOS_ABSTRACT_SCRUB_EFFICIENCY
specific_power_gas[g] = specific_power
total_filterable_moles += source.gas[g]
- if (total_filterable_moles < MINIMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
+ if (total_filterable_moles < MINIMUM_MOLES_TO_SCRUB) //if we cant transfer enough gas just stop to avoid further processing
return -1
//now that we know the total amount of filterable gas, we can calculate the amount of power needed to scrub one mole of gas
@@ -143,16 +141,16 @@
if (!isnull(available_power) && total_specific_power > 0)
total_transfer_moles = min(total_transfer_moles, available_power/total_specific_power)
- if (total_transfer_moles < MINIMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
+ if (total_transfer_moles < MINIMUM_MOLES_TO_SCRUB) //if we cant transfer enough gas just stop to avoid further processing
return -1
//Update flow rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
- A.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ A.last_flow_rate_legacy = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
- P.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ P.last_flow_rate_legacy = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/power_draw = 0
for (var/g in filtering)
@@ -172,81 +170,6 @@
return power_draw
-//Generalized gas filtering proc.
-//Filtering is a bit different from scrubbing. Instead of selectively moving the targeted gas types from one gas mix to another, filtering splits
-//the input gas into two outputs: one that contains /only/ the targeted gas types, and another that completely clean of the targeted gas types.
-//filtering - A list of gasids to be filtered. These gasses get moved to sink_filtered, while the other gasses get moved to sink_clean.
-//total_transfer_moles - Limits the amount of moles to input. The actual amount of gas filtered may also be limited by available_power, if given.
-//available_power - the maximum amount of power that may be used when filtering gas. If null then the filtering is not limited by power.
-/proc/filter_gas(var/obj/machinery/M, var/list/filtering, var/datum/gas_mixture/source, var/datum/gas_mixture/sink_filtered, var/datum/gas_mixture/sink_clean, var/total_transfer_moles = null, var/available_power = null)
- if (source.total_moles < MINIMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
- return -1
-
- filtering = filtering & source.gas //only filter gasses that are actually there. DO NOT USE &=
-
- var/total_specific_power = 0 //the power required to remove one mole of input gas
- var/total_filterable_moles = 0 //the total amount of filterable gas
- var/total_unfilterable_moles = 0 //the total amount of non-filterable gas
- var/list/specific_power_gas = list() //the power required to remove one mole of pure gas, for each gas type
- for (var/g in source.gas)
- if (source.gas[g] < MINIMUM_MOLES_TO_FILTER)
- continue
-
- if (g in filtering)
- specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_filtered)/ATMOS_FILTER_EFFICIENCY
- total_filterable_moles += source.gas[g]
- else
- specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_clean)/ATMOS_FILTER_EFFICIENCY
- total_unfilterable_moles += source.gas[g]
-
- var/ratio = source.gas[g]/source.total_moles //converts the specific power per mole of pure gas to specific power per mole of input gas mix
- total_specific_power += specific_power_gas[g]*ratio
-
- //Figure out how much of each gas to filter
- if (isnull(total_transfer_moles))
- total_transfer_moles = source.total_moles
- else
- total_transfer_moles = min(total_transfer_moles, source.total_moles)
-
- //limit transfer_moles based on available power
- if (!isnull(available_power) && total_specific_power > 0)
- total_transfer_moles = min(total_transfer_moles, available_power/total_specific_power)
-
- if (total_transfer_moles < MINIMUM_MOLES_TO_FILTER) //if we cant transfer enough gas just stop to avoid further processing
- return -1
-
- //Update flow rate var
- if (istype(M, /obj/machinery/atmospherics))
- var/obj/machinery/atmospherics/A = M
- A.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
- if (istype(M, /obj/machinery/portable_atmospherics))
- var/obj/machinery/portable_atmospherics/P = M
- P.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
-
- var/datum/gas_mixture/removed = source.remove(total_transfer_moles)
- if (!removed) //Just in case
- return -1
-
- var/filtered_power_used = 0 //power used to move filterable gas to sink_filtered
- var/unfiltered_power_used = 0 //power used to move unfilterable gas to sink_clean
- for (var/g in removed.gas)
- var/power_used = specific_power_gas[g]*removed.gas[g]
-
- if (g in filtering)
- //use update=0. All the filtered gasses are supposed to be added simultaneously, so we update after the for loop.
- sink_filtered.adjust_gas_temp(g, removed.gas[g], removed.temperature, update=0)
- removed.adjust_gas(g, -removed.gas[g], update=0)
- filtered_power_used += power_used
- else
- unfiltered_power_used += power_used
-
- sink_filtered.update_values()
- removed.update_values()
-
- sink_clean.merge(removed)
-
- return filtered_power_used + unfiltered_power_used
-
//For omni devices. Instead filtering is an associative list mapping gasids to gas mixtures.
//I don't like the copypasta, but I decided to keep both versions of gas filtering as filter_gas is slightly faster (doesn't create as many temporary lists, doesn't call update_values() as much)
//filter_gas can be removed and replaced with this proc if need be.
@@ -266,10 +189,10 @@
if (g in filtering)
var/datum/gas_mixture/sink_filtered = filtering[g]
- specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_filtered)/ATMOS_FILTER_EFFICIENCY
+ specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_filtered)/ATMOS_ABSTRACT_FILTER_EFFICIENCY
total_filterable_moles += source.gas[g]
else
- specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_clean)/ATMOS_FILTER_EFFICIENCY
+ specific_power_gas[g] = calculate_specific_power_gas(g, source, sink_clean)/ATMOS_ABSTRACT_FILTER_EFFICIENCY
total_unfilterable_moles += source.gas[g]
var/ratio = source.gas[g]/source.total_moles //converts the specific power per mole of pure gas to specific power per mole of input gas mix
@@ -291,10 +214,10 @@
//Update Flow Rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
- A.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ A.last_flow_rate_legacy = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
- P.last_flow_rate = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
+ P.last_flow_rate_legacy = (total_transfer_moles/source.total_moles)*source.volume //group_multiplier gets divided out here
var/datum/gas_mixture/removed = source.remove(total_transfer_moles)
if (!removed) //Just in case
@@ -349,7 +272,7 @@
if (isnull(total_mixing_moles) || total_mixing_moles > this_mixing_moles)
total_mixing_moles = this_mixing_moles
- source_specific_power[source] = calculate_specific_power(source, sink)*mix_ratio/ATMOS_FILTER_EFFICIENCY
+ source_specific_power[source] = calculate_specific_power(source, sink)*mix_ratio/ATMOS_ABSTRACT_FILTER_EFFICIENCY
total_specific_power += source_specific_power[source]
total_input_volume += source.volume
total_input_moles += source.total_moles
@@ -372,10 +295,10 @@
//Update flow rate var
if (istype(M, /obj/machinery/atmospherics))
var/obj/machinery/atmospherics/A = M
- A.last_flow_rate = (total_transfer_moles/total_input_moles)*total_input_volume //group_multiplier gets divided out here
+ A.last_flow_rate_legacy = (total_transfer_moles/total_input_moles)*total_input_volume //group_multiplier gets divided out here
if (istype(M, /obj/machinery/portable_atmospherics))
var/obj/machinery/portable_atmospherics/P = M
- P.last_flow_rate = (total_transfer_moles/total_input_moles)*total_input_volume //group_multiplier gets divided out here
+ P.last_flow_rate_legacy = (total_transfer_moles/total_input_moles)*total_input_volume //group_multiplier gets divided out here
var/total_power_draw = 0
for (var/datum/gas_mixture/source in mix_sources)
@@ -411,7 +334,8 @@
return specific_power
-//Calculates the amount of power needed to move one mole of a certain gas from source to sink.
+// Calculates the amount of power needed to move one mole of a certain gas from source to sink.
+// returns wattage
/proc/calculate_specific_power_gas(var/gasid, datum/gas_mixture/source, datum/gas_mixture/sink)
//Calculate the amount of energy required
var/air_temperature = (sink.temperature > 0)? sink.temperature : source.temperature
diff --git a/code/modules/atmospherics/machinery/air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm.dm
new file mode 100644
index 00000000000..a8bb4940f57
--- /dev/null
+++ b/code/modules/atmospherics/machinery/air_alarm.dm
@@ -0,0 +1,782 @@
+GLOBAL_LIST_EMPTY(air_alarms)
+
+#define DECLARE_TLV_VALUES var/red_min; var/yel_min; var/yel_max; var/red_max; var/tlv_comparitor;
+#define LOAD_TLV_VALUES(x, y) red_min = x[1]; yel_min = x[2]; yel_max = x[3]; red_max = x[4]; tlv_comparitor = y;
+#define TEST_TLV_VALUES (((tlv_comparitor >= red_max && red_max > 0) || tlv_comparitor <= red_min) ? AIR_ALARM_RAISE_DANGER : ((tlv_comparitor >= yel_max && yel_max > 0) || tlv_comparitor <= yel_min) ? AIR_ALARM_RAISE_WARNING : AIR_ALARM_RAISE_OKAY)
+
+#define MAX_TEMPERATURE 90
+#define MIN_TEMPERATURE -40
+
+//all air alarms in area are connected via magic
+/area
+ var/obj/machinery/air_alarm/master_air_alarm
+
+/obj/machinery/air_alarm
+ name = "alarm"
+ desc = "Used to control various station atmospheric systems. The light indicates the current air status of the area."
+ icon = 'icons/obj/monitors.dmi'
+ icon_state = "alarm0"
+ plane = TURF_PLANE
+ layer = ABOVE_TURF_LAYER
+ anchored = TRUE
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 80
+ active_power_usage = 1000 //For heating/cooling rooms. 1000 joules equates to about 1 degree every 2 seconds for a single tile of air.
+ power_channel = ENVIRON
+ req_one_access = list(ACCESS_ENGINEERING_ATMOS, ACCESS_ENGINEERING_ENGINE)
+ clicksound = "button"
+ clickvol = 30
+ //blocks_emissive = NONE
+ light_power = 0.25
+
+ /// The area we're registered to
+ var/area/registered_area
+ /// keys are gas group names
+ /// values are TLV lists
+ var/list/tlv_groups = list()
+ /// keys are gas ids
+ /// values are TLV lists
+ var/list/tlv_ids = list()
+ /// pressure tlv list
+ var/list/tlv_pressure
+ /// temperature tlv list
+ var/list/tlv_temperature
+ /// mode
+ var/mode = AIR_ALARM_MODE_SCRUB
+
+ var/alarm_id = null
+ ///Whether to use automatic breach detection or not
+ var/breach_detection = TRUE
+ var/frequency = 1439
+ //var/skipprocess = 0 //Experimenting
+ var/alarm_frequency = 1437
+ var/remote_control = FALSE
+ var/rcon_setting = RCON_AUTO
+ var/rcon_time = 0
+ var/locked = TRUE
+ ///If it's been screwdrivered open.
+ panel_open = FALSE
+ var/aidisabled = FALSE
+ var/shorted = FALSE
+ circuit = /obj/item/circuitboard/airalarm
+ //armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 0, BIO = 100, FIRE = 90, ACID = 30)
+
+ var/datum/wires/alarm/wires
+
+ var/area_uid
+ var/area/alarm_area
+
+ var/target_temperature = T20C
+ var/regulating_temperature = 0
+
+ var/datum/radio_frequency/radio_connection
+
+ var/danger_level = 0
+ var/pressure_dangerlevel = 0
+
+ var/report_danger_level = 1
+ ///If the alarms from this machine are visible on consoles
+ var/alarms_hidden = FALSE
+
+/obj/machinery/air_alarm/Initialize(mapload)
+ . = ..()
+ GLOB.air_alarms += src
+ if(!pixel_x && !pixel_y)
+ offset_airalarm()
+ first_run()
+ set_frequency(frequency)
+ if(!master_is_operating())
+ elect_master()
+
+/obj/machinery/air_alarm/Destroy()
+ GLOB.air_alarms -= src
+ unregister_radio(src, frequency)
+ qdel(wires)
+ wires = null
+ if(alarm_area && alarm_area.master_air_alarm == src)
+ alarm_area.master_air_alarm = null
+ elect_master(exclude_self = TRUE)
+ return ..()
+
+/obj/machinery/air_alarm/proc/offset_airalarm()
+ pixel_x = (dir & 3) ? 0 : (dir == 4 ? -26 : 26)
+ pixel_y = (dir & 3) ? (dir == 1 ? -26 : 26) : 0
+
+/obj/machinery/air_alarm/proc/first_run()
+ alarm_area = get_area(src)
+ registered_area = alarm_area
+ area_uid = "\ref[alarm_area]"
+ if(name == "alarm")
+ name = "[alarm_area.name] Air Alarm"
+
+ if(!wires)
+ wires = new(src)
+
+ update_icon()
+ create_tlv()
+
+/obj/machinery/air_alarm/proc/create_tlv()
+ if(isnull(tlv_pressure))
+ tlv_pressure = AIR_ALARM_MAKE_TLV(0.7 * ONE_ATMOSPHERE, 0.9 * ONE_ATMOSPHERE, 1.1 * ONE_ATMOSPHERE, 1.3 * ONE_ATMOSPHERE)
+ if(isnull(tlv_temperature))
+ tlv_temperature = AIR_ALARM_MAKE_TLV(T0C - 26, T0C, T0C + 35, T0C + 60)
+ for(var/id in global.gas_data.gas_ids_core)
+ var/list/default = global.gas_data.default_tlvs[id]
+ if(isnull(default))
+ continue
+ if(!isnull(tlv_ids[id]))
+ continue
+ tlv_ids[id] = default
+ for(var/group in global.gas_data.gas_group_names_filterable)
+ if(!isnull(tlv_groups[group]))
+ continue
+ tlv_groups[group] = AIR_ALARM_MAKE_TLV(0, 0, 0.5, 1)
+
+/obj/machinery/air_alarm/process(delta_time)
+ if((machine_stat & (NOPOWER|BROKEN)) || shorted)
+ return
+
+ var/turf/simulated/location = src.loc
+ if(!istype(location)) return//returns if loc is not simulated
+
+ var/datum/gas_mixture/environment = location.return_air()
+
+ //Handle temperature adjustment here.
+ handle_heating_cooling(environment)
+
+ var/old_level = danger_level
+ var/old_pressurelevel = pressure_dangerlevel
+ danger_level = overall_danger_level(environment)
+
+ if(old_level != danger_level)
+ apply_danger_level(danger_level)
+
+ if(old_pressurelevel != pressure_dangerlevel)
+ if(breach_detected())
+ mode = AIR_ALARM_MODE_OFF
+ apply_mode()
+
+ if(mode == AIR_ALARM_MODE_CYCLE && environment.return_pressure() < ONE_ATMOSPHERE * 0.05)
+ mode = AIR_ALARM_MODE_FILL
+ apply_mode()
+
+ //atmos computer remote control stuff
+ switch(rcon_setting)
+ if(RCON_NO)
+ remote_control = 0
+ if(RCON_AUTO)
+ if(danger_level == 2)
+ remote_control = 1
+ else
+ remote_control = 0
+ if(RCON_YES)
+ remote_control = 1
+
+ return
+
+/obj/machinery/air_alarm/proc/handle_heating_cooling(var/datum/gas_mixture/environment)
+ var/list/tlv = tlv_temperature
+ var/this_is_fine = AIR_ALARM_TEST_TLV(target_temperature, tlv)
+ if(!regulating_temperature)
+ //check for when we should start adjusting temperature
+ if((this_is_fine == AIR_ALARM_RAISE_OKAY) && abs(environment.temperature - target_temperature) > 2.0 && environment.return_pressure() >= 1)
+ update_use_power(USE_POWER_ACTIVE)
+ regulating_temperature = 1
+ audible_message("\The [src] clicks as it starts [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\
+ "You hear a click and a faint electronic hum.")
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+ else
+ //check for when we should stop adjusting temperature
+ if((this_is_fine != AIR_ALARM_RAISE_OKAY) || abs(environment.temperature - target_temperature) <= 0.5 || environment.return_pressure() < 1)
+ update_use_power(USE_POWER_IDLE)
+ regulating_temperature = 0
+ audible_message("\The [src] clicks quietly as it stops [environment.temperature > target_temperature ? "cooling" : "heating"] the room.",\
+ "You hear a click as a faint electronic humming stops.")
+ playsound(src, 'sound/machines/click.ogg', 50, 1)
+
+ if(regulating_temperature)
+ if(target_temperature > T0C + MAX_TEMPERATURE)
+ target_temperature = T0C + MAX_TEMPERATURE
+
+ if(target_temperature < T0C + MIN_TEMPERATURE)
+ target_temperature = T0C + MIN_TEMPERATURE
+
+ var/datum/gas_mixture/gas
+ gas = environment.remove(0.25 * environment.total_moles)
+ if(gas)
+
+ if(gas.temperature <= target_temperature) //gas heating
+ var/energy_used = min(gas.get_thermal_energy_change(target_temperature) , active_power_usage)
+
+ gas.adjust_thermal_energy(energy_used)
+ //use_power(energy_used, ENVIRON) //handle by update_use_power instead
+ else //gas cooling
+ var/heat_transfer = min(abs(gas.get_thermal_energy_change(target_temperature)), active_power_usage)
+
+ //Assume the heat is being pumped into the hull which is fixed at 20 C
+ //none of this is really proper thermodynamics but whatever
+
+ var/cop = gas.temperature / T20C //coefficient of performance -> power used = heat_transfer/cop
+
+ heat_transfer = min(heat_transfer, cop * active_power_usage) //this ensures that we don't use more than active_power_usage amount of power
+
+ heat_transfer = -gas.adjust_thermal_energy(-heat_transfer) //get the actual heat transfer
+
+ //use_power(heat_transfer / cop, ENVIRON) //handle by update_use_power instead
+
+ environment.merge(gas)
+
+/obj/machinery/air_alarm/proc/overall_danger_level(datum/gas_mixture/environment)
+ var/environment_pressure = environment.return_pressure()
+ var/partial_pressure_factor = (R_IDEAL_GAS_EQUATION * environment.temperature) / environment.volume
+
+ var/dangerlevel = AIR_ALARM_TEST_TLV(environment_pressure, tlv_pressure)
+ if(dangerlevel >= AIR_ALARM_RAISE_DANGER)
+ return dangerlevel
+ dangerlevel = max(dangerlevel, AIR_ALARM_TEST_TLV(environment.temperature, tlv_temperature))
+ if(dangerlevel >= AIR_ALARM_RAISE_DANGER)
+ return dangerlevel
+
+ // todo: would be faster to iterate once and store the groups we care about...
+
+ for(var/id in tlv_ids)
+ var/list/tlv = tlv_ids[id]
+ var/partial = environment.gas[id] * partial_pressure_factor
+ dangerlevel = max(dangerlevel, AIR_ALARM_TEST_TLV(partial, tlv))
+ if(dangerlevel >= AIR_ALARM_RAISE_DANGER)
+ return dangerlevel
+
+ for(var/name in tlv_groups)
+ var/list/tlv = tlv_groups[name]
+ var/partial = environment.moles_by_group(global.gas_data.gas_group_by_name[name]) * partial_pressure_factor
+ dangerlevel = max(dangerlevel, AIR_ALARM_TEST_TLV(partial, tlv))
+ if(dangerlevel >= AIR_ALARM_RAISE_DANGER)
+ return dangerlevel
+
+ return dangerlevel
+
+/// Returns whether this air alarm thinks there is a breach, given the sensors that are available to it.
+/obj/machinery/air_alarm/proc/breach_detected()
+ var/turf/simulated/location = src.loc
+
+ if(!istype(location))
+ return FALSE
+
+ if(!breach_detection)
+ return FALSE
+
+ var/datum/gas_mixture/environment = location.return_air()
+ var/environment_pressure = environment.return_pressure()
+ var/list/pressure_levels = tlv_pressure
+
+ if(environment_pressure <= pressure_levels[1]) // Low pressures
+ switch(mode)
+ if(AIR_ALARM_MODE_CYCLE)
+ return FALSE
+ if(AIR_ALARM_MODE_PANIC)
+ return FALSE
+ if(AIR_ALARM_MODE_SIPHON)
+ return FALSE
+ if(AIR_ALARM_MODE_REPLACE)
+ return FALSE
+ return TRUE
+ return FALSE
+
+/obj/machinery/air_alarm/proc/master_is_operating()
+ return alarm_area && alarm_area.master_air_alarm && !(alarm_area.master_air_alarm.machine_stat & (NOPOWER | BROKEN))
+
+/obj/machinery/air_alarm/proc/elect_master(exclude_self = FALSE)
+ for(var/obj/machinery/air_alarm/AA in alarm_area)
+ if(exclude_self && AA == src)
+ continue
+ if(!(AA.machine_stat & (NOPOWER|BROKEN)))
+ alarm_area.master_air_alarm = AA
+ return TRUE
+ return FALSE
+
+/obj/machinery/air_alarm/setDir(ndir)
+ . = ..()
+ base_pixel_x = 0
+ base_pixel_y = 0
+ var/turf/T = get_step(get_turf(src), turn(dir, 180))
+ if(istype(T) && T.density)
+ switch(dir)
+ if(NORTH)
+ base_pixel_y = -21
+ if(SOUTH)
+ base_pixel_y = 21
+ if(WEST)
+ base_pixel_x = 21
+ if(EAST)
+ base_pixel_x = -21
+ reset_pixel_offsets()
+
+/obj/machinery/air_alarm/update_icon()
+ cut_overlays()
+
+ if(panel_open)
+ icon_state = "alarmx"
+ set_light(0)
+ //set_light_on(FALSE)
+ return
+ if((machine_stat & (NOPOWER|BROKEN)) || shorted)
+ icon_state = "alarmp"
+ set_light(0)
+ //set_light_on(FALSE)
+ return
+
+ var/icon_level = danger_level
+ if(alarm_area?.atmosalm)
+ icon_level = max(icon_level, 1) //if there's an atmos alarm but everything is okay locally, no need to go past yellow
+
+ var/new_color = null
+ switch(icon_level)
+ if(0)
+ icon_state = "alarm0"
+ //add_overlay(mutable_appearance(icon, "alarm_ov0"))
+ //add_overlay(emissive_appearance(icon, "alarm_ov0"))
+ new_color = "#03A728"
+ if(1)
+ icon_state = "alarm2" //yes, alarm2 is yellow alarm
+ //add_overlay(mutable_appearance(icon, "alarm_ov2"))
+ //add_overlay(emissive_appearance(icon, "alarm_ov2"))
+ new_color = "#EC8B2F"
+ if(2)
+ icon_state = "alarm1"
+ //add_overlay(mutable_appearance(icon, "alarm_ov1"))
+ //add_overlay(emissive_appearance(icon, "alarm_ov1"))
+ new_color = "#DA0205"
+
+ set_light(l_range = 2, l_power = 0.25, l_color = new_color)
+ //set_light_on(TRUE)
+
+/obj/machinery/air_alarm/receive_signal(datum/signal/signal)
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+ if(alarm_area.master_air_alarm != src)
+ if(master_is_operating())
+ return
+ elect_master()
+ if(alarm_area.master_air_alarm != src)
+ return
+ if(!signal || signal.encryption)
+ return
+ var/id_tag = signal.data["tag"]
+ if(!id_tag)
+ return
+ if(signal.data["area"] != area_uid)
+ return
+ if(signal.data["sigtype"] != "status")
+ return
+
+ SStgui.update_uis(src)
+
+/obj/machinery/air_alarm/proc/set_frequency(new_frequency)
+ radio_controller.remove_object(src, frequency)
+ frequency = new_frequency
+ radio_connection = radio_controller.add_object(src, frequency, RADIO_TO_AIRALARM)
+
+/obj/machinery/air_alarm/proc/send_signal(var/target, var/list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise
+ if(!radio_connection)
+ return 0
+
+ var/datum/signal/signal = new
+ signal.transmission_method = TRANSMISSION_RADIO //radio signal
+ signal.source = src
+
+ signal.data = command
+ signal.data["tag"] = target
+ signal.data["sigtype"] = "command"
+
+ radio_connection.post_signal(src, signal, RADIO_FROM_AIRALARM)
+// TO_WORLD("Signal [command] Broadcasted to [target]")
+
+ return 1
+
+/obj/machinery/air_alarm/proc/apply_mode(mode)
+ // set our mode
+ src.mode = mode
+ // legacy: propagate mode to other air alarms in the area
+ //TODO: make it so that players can choose between applying the new mode to the room they are in (related area) vs the entire alarm area
+ for(var/obj/machinery/air_alarm/AA in alarm_area)
+ AA.mode = mode
+
+ switch(mode)
+ if(AIR_ALARM_MODE_OFF)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE, "power" = FALSE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = FALSE))
+ if(AIR_ALARM_MODE_SCRUB)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE, "power" = TRUE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = TRUE))
+ if(AIR_ALARM_MODE_REPLACE)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE, "power" = TRUE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = TRUE, "siphon" = TRUE))
+ if(AIR_ALARM_MODE_SIPHON, AIR_ALARM_MODE_CYCLE)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE, "power" = FALSE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = TRUE, "siphon" = TRUE))
+ if(AIR_ALARM_MODE_PANIC)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE, "power" = FALSE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = TRUE, "siphon" = TRUE, "expand" = TRUE))
+ if(AIR_ALARM_MODE_CONTAMINATED)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = TRUE, "expand" = TRUE))
+ if(AIR_ALARM_MODE_FILL)
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ if(!pump.controllable_from_alarm || !pump.environmental)
+ continue
+ send_signal(pump.id_tag, list("hard_reset" = TRUE))
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ if(!scrubber.controllable_from_alarm || !scrubber.environmental)
+ continue
+ send_signal(scrubber.id_tag, list("hard_reset" = TRUE, "power" = FALSE))
+
+/obj/machinery/air_alarm/proc/apply_danger_level(var/new_danger_level)
+ if(report_danger_level && alarm_area.atmosalert(new_danger_level, src))
+ post_alert(new_danger_level)
+
+ update_icon()
+
+/obj/machinery/air_alarm/proc/post_alert(alert_level)
+ var/datum/radio_frequency/frequency = radio_controller.return_frequency(alarm_frequency)
+ if(!frequency)
+ return
+
+ var/datum/signal/alert_signal = new
+ alert_signal.source = src
+ alert_signal.transmission_method = TRANSMISSION_RADIO
+ alert_signal.data["zone"] = alarm_area.name
+ alert_signal.data["type"] = "Atmospheric"
+
+ if(alert_level==2)
+ alert_signal.data["alert"] = "severe"
+ else if(alert_level==1)
+ alert_signal.data["alert"] = "minor"
+ else if(alert_level==0)
+ alert_signal.data["alert"] = "clear"
+
+ frequency.post_signal(src, alert_signal)
+
+/obj/machinery/air_alarm/attack_ai(mob/user)
+ ui_interact(user)
+
+/obj/machinery/air_alarm/attack_hand(mob/user, list/params)
+ . = ..()
+ if(.)
+ return
+ return interact(user)
+
+/obj/machinery/air_alarm/interact(mob/user)
+ ui_interact(user)
+ wires.Interact(user)
+
+/obj/machinery/air_alarm/ui_status(mob/user)
+ if(isAI(user) && aidisabled)
+ to_chat(user, "AI control has been disabled.")
+ else if(!shorted)
+ return ..()
+ return UI_CLOSE
+
+/obj/machinery/air_alarm/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui, datum/ui_state/state)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "AirAlarm", name, parent_ui)
+ if(state)
+ ui.set_state(state)
+ ui.open()
+
+/obj/machinery/air_alarm/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ var/datum/gas_mixture/environment = loc.return_air()
+ .["environment"] = environment.tgui_analyzer_scan(GAS_GROUP_REAGENT | GAS_GROUP_UNKNOWN)
+ // todo: static data with event hooks for updates for these two
+ var/list/vents = list()
+ var/list/scrubbers = list()
+ for(var/obj/machinery/atmospherics/component/unary/vent_pump/pump as anything in registered_area.vent_pumps)
+ var/list/returned = pump.ui_vent_data()
+ returned["name"] = pump.name
+ vents[pump.id_tag] = returned
+ for(var/obj/machinery/atmospherics/component/unary/vent_scrubber/scrubber as anything in registered_area.vent_scrubbers)
+ var/list/returned = scrubber.ui_scrubber_data()
+ returned["name"] = scrubber.name
+ scrubbers[scrubber.id_tag] = returned
+ .["vents"] = vents
+ .["scrubbers"] = scrubbers
+ .["mode"] = mode
+
+ //! legacy below
+ var/list/data = list(
+ "locked" = locked,
+ "siliconUser" = issilicon(user),
+ "remoteUser" = !!ui?.parent_ui,
+ "danger_level" = danger_level,
+ "target_temperature" = "[target_temperature - T0C]C",
+ "rcon" = rcon_setting,
+ )
+ var/area/A = get_area(src)
+ data["atmos_alarm"] = A?.atmosalm
+ data["fire_alarm"] = A?.fire
+ . += data
+ //! end
+
+/obj/machinery/air_alarm/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["gasContext"] = global.gas_data.tgui_gas_context()
+ .["gasTLV"] = tlv_ids
+ .["groupTLV"] = tlv_groups
+ .["pressureTLV"] = tlv_pressure
+ .["temperatureTLV"] = tlv_temperature
+
+/obj/machinery/air_alarm/proc/push_ui_tlv()
+ var/list/data = list()
+ data["gasTLV"] = tlv_ids
+ data["groupTLV"] = tlv_groups
+ data["pressureTLV"] = tlv_pressure
+ data["temperatureTLV"] = tlv_temperature
+ push_ui_data(data = data)
+
+/obj/machinery/air_alarm/ui_act(action, params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+
+ // unlocked actions
+ switch(action)
+ if("rcon")
+ //! warning: legacy
+ var/attempted_rcon_setting = text2num(params["rcon"])
+ switch(attempted_rcon_setting)
+ if(RCON_NO)
+ rcon_setting = RCON_NO
+ if(RCON_AUTO)
+ rcon_setting = RCON_AUTO
+ if(RCON_YES)
+ rcon_setting = RCON_YES
+ return TRUE
+ if("temperature")
+ //! warning: legacy
+ var/list/selected = tlv_temperature
+ var/max_temperature = min(selected[3] - T0C, MAX_TEMPERATURE)
+ var/min_temperature = max(selected[2] - T0C, MIN_TEMPERATURE)
+ var/input_temperature = tgui_input_number(usr, "What temperature would you like the system to mantain? (Capped between [min_temperature] and [max_temperature]C)", "Thermostat Controls", (target_temperature - T0C), max_temperature, min_temperature)
+ if(isnum(input_temperature))
+ if(input_temperature > max_temperature || input_temperature < min_temperature)
+ to_chat(usr, "Temperature must be between [min_temperature]C and [max_temperature]C")
+ else
+ target_temperature = input_temperature + T0C
+ return TRUE
+
+ // Account for remote users here.
+ // Yes, this is kinda snowflaky; however, I would argue it would be far more snowflakey
+ // to include "custom hrefs" and all the other bullshit that nano states have just for the
+ // like, two UIs, that want remote access to other UIs.
+ //! warning: legacy
+ if((locked && !issilicon(usr) && !istype(ui.state, /datum/ui_state/air_alarm_remote)) || (issilicon(usr) && aidisabled))
+ return
+
+ // locked actions
+ switch(action)
+ if("vent")
+ var/id = params["id"]
+ var/obj/machinery/atmospherics/component/unary/vent_pump/machine = registered_area.vent_pump_by_id(id)
+ if(isnull(machine) || !machine.controllable_from_alarm)
+ return TRUE
+ var/command = params["command"]
+ var/target = params["target"]
+ switch(command)
+ if("direction")
+ send_signal(id, list("direction" = !machine.pump_direction))
+ if("internalPressure")
+ send_signal(id, list("set_internal_pressure" = text2num(target)))
+ if("externalPressure")
+ send_signal(id, list("set_external_pressure" = text2num(target)))
+ if("internalToggle")
+ send_signal(id, list("checks_toggle" = ATMOS_VENT_CHECK_INTERNAL))
+ if("externalToggle")
+ send_signal(id, list("checks_toggle" = ATMOS_VENT_CHECK_EXTERNAL))
+ if("power")
+ send_signal(id, list("power" = !machine.use_power))
+ return TRUE
+ if("scrubber")
+ var/id = params["id"]
+ var/obj/machinery/atmospherics/component/unary/vent_scrubber/machine = registered_area.vent_scrubber_by_id(id)
+ if(isnull(machine) || !machine.controllable_from_alarm)
+ return TRUE
+ var/command = params["command"]
+ var/target = params["target"]
+ switch(command)
+ if("siphon")
+ send_signal(id, list("siphon" = !machine.siphoning))
+ if("gasID")
+ send_signal(id, list("scrub_ids_toggle" = list(target)))
+ if("gasGroup")
+ send_signal(id, list("scrub_groups_toggle" = text2num(target)))
+ if("highPower")
+ send_signal(id, list("expand" = !machine.expanded))
+ if("power")
+ send_signal(id, list("power" = !machine.use_power))
+ return TRUE
+ if("mode")
+ var/mode = params["mode"]
+ apply_mode(mode)
+ return TRUE
+ if("tlv")
+ var/entry = params["entry"]
+ var/index = text2num(params["index"]) + 1
+ if((index < AIR_ALARM_TLV_INDEX_MIN) || (index > AIR_ALARM_TLV_INDEX_MAX))
+ return TRUE
+ var/val = clamp(0, text2num(params["val"]), 1000000)
+ var/list/target
+ switch(entry)
+ if("pressure")
+ target = tlv_pressure
+ if("temperature")
+ target = tlv_temperature
+ else
+ var/group = global.gas_data.gas_group_by_name[entry]
+ if(group)
+ target = tlv_groups[entry]
+ else
+ target = tlv_ids[entry]
+ if(isnull(target))
+ return TRUE
+ target[index] = val
+ clamp_tlv_list(target, index)
+ push_ui_tlv()
+ return TRUE
+ if("alarm")
+ //! warning: legacy
+ if(alarm_area.atmosalert(2, src))
+ apply_danger_level(2)
+ return TRUE
+ if("reset")
+ //! warning: legacy
+ atmos_reset()
+ return TRUE
+ if("lock")
+ //! warning: legacy
+ if(issilicon(usr) && !wires.is_cut(WIRE_IDSCAN))
+ locked = !locked
+ return TRUE
+
+/**
+ * clamps tlv to be sensical
+ *
+ * source_indx is the one the user / or something else is changing; rest will be updated to make sense with it.
+ */
+/obj/machinery/air_alarm/proc/clamp_tlv_list(list/tlv, source_index)
+ for(var/i in AIR_ALARM_TLV_INDEX_MIN to (source_index - 1))
+ if(tlv[i] > tlv[source_index])
+ tlv[i] = tlv[source_index]
+ for(var/i in (source_index + 1) to AIR_ALARM_TLV_INDEX_MAX)
+ if(tlv[i] < tlv[source_index])
+ tlv[i] = tlv[source_index]
+
+/obj/machinery/air_alarm/proc/atmos_reset()
+ if(alarm_area.atmosalert(0, src))
+ apply_danger_level(0)
+ update_icon()
+
+/obj/machinery/air_alarm/attackby(obj/item/W as obj, mob/user as mob)
+ add_fingerprint(user)
+ if(alarm_deconstruction_screwdriver(user, W))
+ return
+ if(alarm_deconstruction_wirecutters(user, W))
+ return
+
+ if(istype(W, /obj/item/card/id) || istype(W, /obj/item/pda))// trying to unlock the interface with an ID card
+ togglelock()
+ return ..()
+
+/obj/machinery/air_alarm/verb/togglelock(mob/user as mob)
+ if(machine_stat & (NOPOWER|BROKEN))
+ to_chat(user, "It does nothing.")
+ return
+ else
+ if(allowed(usr) && !wires.is_cut(WIRE_IDSCAN))
+ locked = !locked
+ to_chat(user, SPAN_NOTICE("You [locked ? "lock" : "unlock"] the Air Alarm interface."))
+ else
+ to_chat(user, SPAN_BOLDWARNING("Access denied."))
+ return
+
+/obj/machinery/air_alarm/AltClick()
+ ..()
+ togglelock()
+
+/obj/machinery/air_alarm/power_change()
+ ..()
+ spawn(rand(0,15))
+ update_icon()
+
+#undef LOAD_TLV_VALUES
+#undef TEST_TLV_VALUES
+#undef DECLARE_TLV_VALUES
+
+/obj/machinery/air_alarm/alarms_hidden
+ alarms_hidden = TRUE
+
+/obj/machinery/air_alarm/angled
+// icon = 'icons/obj/wall_machines_angled.dmi'
+
+/obj/machinery/air_alarm/angled/hidden
+ alarms_hidden = TRUE
+
+/obj/machinery/air_alarm/angled/offset_airalarm()
+ pixel_x = (dir & 3) ? 0 : (dir == 4 ? -21 : 21)
+ pixel_y = (dir & 3) ? (dir == 1 ? -18 : 20) : 0
+
+/obj/machinery/air_alarm/freezer
+ target_temperature = T0C - 13.15 // Chilly freezer room
+
+/obj/machinery/air_alarm/freezer/create_tlv()
+ if(isnull(tlv_temperature))
+ tlv_temperature = list(T0C - 40, T0C - 20, T0C + 40, T0C + 60)
+ return ..()
+
+/obj/machinery/air_alarm/monitor
+ report_danger_level = 0
+ breach_detection = 0
+
+/obj/machinery/air_alarm/nobreach
+ breach_detection = 0
+
+/obj/machinery/air_alarm/server/Initialize(mapload)
+ . = ..()
+ req_access = list(ACCESS_SCIENCE_RD, ACCESS_ENGINEERING_ATMOS, ACCESS_ENGINEERING_ENGINE)
+ setDir(dir)
diff --git a/code/modules/atmospherics/machinery/analyzer_hooks.dm b/code/modules/atmospherics/machinery/analyzer_hooks.dm
index 303e80dcc8a..9dd52d52b0f 100644
--- a/code/modules/atmospherics/machinery/analyzer_hooks.dm
+++ b/code/modules/atmospherics/machinery/analyzer_hooks.dm
@@ -25,20 +25,7 @@
return 0
/proc/atmosanalyzer_scan(var/atom/target, var/datum/gas_mixture/mixture, var/mob/user)
- var/list/results = list()
-
- if (mixture && mixture.total_moles > 0)
- var/pressure = mixture.return_pressure()
- var/total_moles = mixture.total_moles
- results += "Pressure: [QUANTIZE(pressure)] kPa"
- for(var/mix in mixture.gas)
- results += "[GLOB.meta_gas_names[mix]]: [QUANTIZE((mixture.gas[mix] / total_moles) * 100)]%"
- results += "Temperature: [QUANTIZE(mixture.temperature-T0C)]°C"
- results += "Total Moles: [QUANTIZE(total_moles)]"
- else
- results += "\The [target] is empty!"
-
- return results
+ return mixture.chat_analyzer_scan(GAS_GROUP_UNKNOWN, TRUE, TRUE)
/turf/atmosanalyze(var/mob/user)
return atmosanalyzer_scan(src, src.air, user)
@@ -66,10 +53,12 @@
/obj/machinery/atmospherics/component/unary/atmosanalyze(var/mob/user)
return atmosanalyzer_scan(src, src.air_contents, user)
+// todo: components should allow each end to be analyzed separately.
+
/obj/machinery/atmospherics/component/binary/atmosanalyze(var/mob/user)
return atmosanalyzer_scan(src, src.air1, user)
-/obj/machinery/atmospherics/component/trinary/atmos_filter/atmosanalyze(var/mob/user)
+/obj/machinery/atmospherics/component/trinary/filter/atmosanalyze(var/mob/user)
return atmosanalyzer_scan(src, src.air1, user)
/obj/machinery/atmospherics/component/trinary/mixer/atmosanalyze(var/mob/user)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/algae_generator_vr.dm b/code/modules/atmospherics/machinery/components/binary_devices/algae_generator_vr.dm
index 8cb2ad900c3..5edb1466299 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/algae_generator_vr.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/algae_generator_vr.dm
@@ -31,8 +31,8 @@
var/ui_error = null // For error messages to show up in nano ui.
var/datum/gas_mixture/internal = new()
- var/const/input_gas = /datum/gas/carbon_dioxide
- var/const/output_gas = /datum/gas/oxygen
+ var/const/input_gas = GAS_ID_CARBON_DIOXIDE
+ var/const/output_gas = GAS_ID_OXYGEN
/obj/machinery/atmospherics/component/binary/algae_farm/filled
stored_material = list(MATERIAL_ALGAE = 10000, MATERIAL_CARBON = 0)
@@ -63,12 +63,12 @@
ui_error = null
update_icon()
if(use_power == USE_POWER_IDLE)
- last_power_draw = idle_power_usage
+ last_power_draw_legacy = idle_power_usage
else
- last_power_draw = 0
+ last_power_draw_legacy = 0
return 0
- last_power_draw = active_power_usage
+ last_power_draw_legacy = active_power_usage
// STEP 1 - Check material resources
if(stored_material[MATERIAL_ALGAE] < algae_per_mole)
@@ -89,19 +89,19 @@
network1.update = 1
if (power_draw > 0)
use_power(power_draw)
- last_power_draw += power_draw
+ last_power_draw_legacy += power_draw
// STEP 3 - Convert CO2 to O2 (Note: We know our internal group multipier is 1, so just be cool)
var/co2_moles = internal.gas[input_gas]
if(co2_moles < MINIMUM_MOLES_TO_FILTER)
- ui_error = "Insufficient [GLOB.meta_gas_names[input_gas]] to process."
+ ui_error = "Insufficient [global.gas_data.names[input_gas]] to process."
update_icon()
return
// STEP 4 - Consume the resources
var/converted_moles = min(co2_moles, moles_per_tick)
use_power(converted_moles * power_per_mole)
- last_power_draw += converted_moles * power_per_mole
+ last_power_draw_legacy += converted_moles * power_per_mole
stored_material[MATERIAL_ALGAE] -= converted_moles * algae_per_mole
stored_material[MATERIAL_CARBON] += converted_moles * carbon_per_mole
@@ -187,8 +187,8 @@
"max" = storage_capacity[M],
"percent" = (stored_material[M] / storage_capacity[M] * 100))
data["materials"] = materials_ui
- data["last_flow_rate"] = last_flow_rate
- data["last_power_draw"] = last_power_draw
+ data["last_flow_rate"] = last_flow_rate_legacy
+ data["last_power_draw"] = last_power_draw_legacy
data["inputDir"] = dir2text(global.reverse_dir[dir])
data["outputDir"] = dir2text(dir)
data["usePower"] = use_power
@@ -197,13 +197,13 @@
if(air1 && network1 && node1)
data["input"] = list(
"pressure" = air1.return_pressure(),
- "name" = GLOB.meta_gas_names[input_gas],
+ "name" = global.gas_data.names[input_gas],
"percent" = air1.total_moles > 0 ? round((air1.gas[input_gas] / air1.total_moles) * 100) : 0,
"moles" = round(air1.gas[input_gas], 0.01))
if(air2 && network2 && node2)
data["output"] = list(
"pressure" = air2.return_pressure(),
- "name" = GLOB.meta_gas_names[output_gas],
+ "name" = global.gas_data.names[output_gas],
"percent" = air2.total_moles ? round((air2.gas[output_gas] / air2.total_moles) * 100) : 0,
"moles" = round(air2.gas[output_gas], 0.01))
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
index 8c2fc6e5750..b74f9b7643d 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
@@ -15,6 +15,7 @@
depth_level = 12
anchored = FALSE
pipe_flags = PIPING_DEFAULT_LAYER_ONLY|PIPING_ONE_PER_TURF
+ default_deconstruct = null
var/kinetic_efficiency = 0.04 //combined kinetic and kinetic-to-electric efficiency
var/volume_ratio = 0.2
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
index bb0da270d2f..d48486f4f72 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/dp_vent_pump.dm
@@ -107,8 +107,8 @@
/obj/machinery/atmospherics/component/binary/dp_vent_pump/process(delta_time)
..()
- last_power_draw = 0
- last_flow_rate = 0
+ last_power_draw_legacy = 0
+ last_flow_rate_legacy = 0
if(machine_stat & (NOPOWER|BROKEN) || !use_power)
return 0
@@ -140,7 +140,7 @@
network2.update = 1
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
return 1
@@ -201,7 +201,7 @@
/obj/machinery/atmospherics/component/binary/dp_vent_pump/examine(mob/user, dist)
. = ..()
- . += "A small gauge in the corner reads [round(last_flow_rate, 0.1)] L/s; [round(last_power_draw)] W"
+ . += "A small gauge in the corner reads [round(last_flow_rate_legacy, 0.1)] L/s; [round(last_power_draw_legacy)] W"
/obj/machinery/atmospherics/component/unary/vent_pump/power_change()
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm
index 7a78442ca03..8e01b7cee05 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/heat_pump.dm
@@ -32,7 +32,6 @@
var/lowest_temp = TCMB
var/max_temp = 99999999//Need to bottle it somewhere, the sun's core has 15 million kelvin
- var/on = 0
var/efficiency = 0
/obj/machinery/atmospherics/component/binary/heat_pump/CtrlClick(mob/user)
@@ -150,7 +149,7 @@
var/power_draw = abs(energy_transfered/performance_factor)
air2.adjust_thermal_energy(-air1.adjust_thermal_energy(-energy_transfered*efficiency))//only adds the energy actually removed from air one to air two(- infront of air1 because energy was removed)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
if(network1)
network1.update = 1
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/massive_gas_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/massive_gas_pump.dm
index 99a478cd435..0dc4032904c 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/massive_gas_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/massive_gas_pump.dm
@@ -58,22 +58,22 @@
build_network()//built networks if we are missing them
network1?.update = 1
network2?.update = 1
- last_flow_rate = last_power_draw = 0
+ last_flow_rate_legacy = last_power_draw_legacy = 0
return
if((machine_stat & (NOPOWER|BROKEN)) || !use_power)
- last_flow_rate = last_power_draw = 0
+ last_flow_rate_legacy = last_power_draw_legacy = 0
return
if(!power_machine || !power_machine.powernet)
if(!power_machine || !power_machine.connect_to_network())//returns 0 if it fails to find a
- last_flow_rate = last_power_draw = 0
+ last_flow_rate_legacy = last_power_draw_legacy = 0
return//make sure we are connected to a powernet
power_rating = power_machine.surplus() * 1000 //update power rateing to what ever is avaiable
power_rating = clamp(power_rating, 0, power_level)
if(power_rating <= 0)
- last_flow_rate = last_power_draw = 0
+ last_flow_rate_legacy = last_power_draw_legacy = 0
return//no point in continuing if we dont have any power
var/power_draw = -1
@@ -85,7 +85,7 @@
power_draw = pump_gas(src, air1, air2, transfer_moles, power_rating)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
power_machine.draw_power(power_draw * 0.001)
if(network1)
@@ -112,7 +112,7 @@
if(inoperable() || !anchored || !power_machine.powernet)
icon_state = "pump"
else if(use_power)
- switch(last_power_draw)
+ switch(last_power_draw_legacy)
if(1 to (1 MEGAWATTS))
icon_state = "pump_1"
if((1 MEGAWATTS) to (10 MEGAWATTS))
@@ -140,8 +140,8 @@
"pressure_set" = round(target_pressure*100),
"max_pressure" = max_pressure_setting,
"power_level" = power_level,
- "last_flow_rate" = round(last_flow_rate*10),
- "last_power_draw" = round(last_power_draw),
+ "last_flow_rate" = round(last_flow_rate_legacy*10),
+ "last_power_draw" = round(last_power_draw_legacy),
"max_power_draw" = MAX_POWER_FOR_MASSIVE,
)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm
index f60a58d9cb5..b689c6986e2 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/massive_heat_pump.dm
@@ -29,7 +29,6 @@
var/obj/machinery/power/powersupply/power_machine //for funky massive power machines
//if its not null the machine attempts to draw from the grid the power machinery is connected to
//see examples in the file "code\modules\atmospherics\components\binary_devices\massive_heat_pump.dm"
- var/on = 0
var/efficiency = 0
/obj/machinery/atmospherics/component/binary/massive_heat_pump/Initialize(mapload)
@@ -94,7 +93,7 @@
air2.adjust_thermal_energy(-air1.adjust_thermal_energy(-energy_transfered*efficiency))//only adds the energy actually removed from air one to air two(- infront of air1 because energy was removed)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
power_machine.draw_power(power_draw * 0.001)
if(network1)
@@ -146,7 +145,7 @@
if(inoperable() || !anchored || !power_machine.powernet)
icon_state = "pump"
else if(use_power)
- switch(last_power_draw)
+ switch(last_power_draw_legacy)
if(1 to (1 MEGAWATTS))
icon_state = "heat_1"
if((1 MEGAWATTS) to (10 MEGAWATTS))
@@ -177,7 +176,7 @@
"power_level" = power_level,
"current_temp" = air2.temperature,
"sink_temp" = air1.temperature,
- "last_power_draw" = round(last_power_draw),
+ "last_power_draw" = round(last_power_draw_legacy),
"max_power_draw" = MAX_POWER_FOR_MASSIVE,
"efficiency" = efficiency,
)
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
index f4f88bd898c..5582317ccee 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm
@@ -54,7 +54,7 @@
/obj/machinery/atmospherics/component/binary/passive_gate/process(delta_time)
..()
- last_flow_rate = 0
+ last_flow_rate_legacy = 0
if(!unlocked)
return 0
@@ -94,7 +94,7 @@
if(network2)
network2.update = 1
- if (last_flow_rate)
+ if (last_flow_rate_legacy)
flowing = 1
update_icon()
@@ -197,7 +197,7 @@
"output_pressure" = round(air2.return_pressure()*100),
"regulate_mode" = regulate_mode,
"set_flow_rate" = round(set_flow_rate*10),
- "last_flow_rate" = round(last_flow_rate*10),
+ "last_flow_rate" = round(last_flow_rate_legacy*10),
)
return data
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
index 930198c712f..f9742e64dad 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm
@@ -120,8 +120,8 @@ Thus, the two variables affect pump operation are set in New():
update_underlays()
/obj/machinery/atmospherics/component/binary/pump/process(delta_time)
- last_power_draw = 0
- last_flow_rate = 0
+ last_power_draw_legacy = 0
+ last_flow_rate_legacy = 0
if((machine_stat & (NOPOWER|BROKEN)) || !use_power)
return
@@ -135,7 +135,7 @@ Thus, the two variables affect pump operation are set in New():
power_draw = pump_gas(src, air1, air2, transfer_moles, power_rating)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
if(network1)
@@ -190,8 +190,8 @@ Thus, the two variables affect pump operation are set in New():
"on" = use_power,
"pressure_set" = round(target_pressure*100),
"max_pressure" = max_pressure_setting,
- "last_flow_rate" = round(last_flow_rate*10),
- "last_power_draw" = round(last_power_draw),
+ "last_flow_rate" = round(last_flow_rate_legacy*10),
+ "last_power_draw" = round(last_power_draw_legacy),
"max_power_draw" = power_rating,
)
diff --git a/code/modules/atmospherics/machinery/components/component.dm b/code/modules/atmospherics/machinery/components/component.dm
new file mode 100644
index 00000000000..e2ffac8c168
--- /dev/null
+++ b/code/modules/atmospherics/machinery/components/component.dm
@@ -0,0 +1,145 @@
+//* This file is explicitly licensed under the MIT license. *//
+//* Copyright (c) 2023 Citadel Station developers. *//
+
+/obj/machinery/atmospherics/component
+ interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_OFFLINE | INTERACT_MACHINE_OFFLINE_SILICON
+ default_deconstruct = 4 SECONDS
+ tool_deconstruct = TOOL_WRENCH
+ default_unanchor = null
+ tool_unanchor = null
+ default_panel = null
+ tool_panel = null
+
+ /// allow multitool "hijacking" even if this is controlled by something else
+ /// set to non-null for delay, 0 for instant.
+ var/default_multitool_hijack = null
+ /// allow access normally
+ var/default_access_interface = FALSE
+ /// need tile pried to access or to be above tile
+ var/interface_require_exposed = FALSE
+ /// need tiled pried to access or to be above tile
+ var/hijack_require_exposed = FALSE
+ /// tgui interface ID - set to non-null to use new atmospherics UI system
+ // todo: convert all atmos machinery to new system
+ var/tgui_interface
+ /// allowed default ui controls
+ var/atmos_component_ui_flags = NONE
+
+ /// efficiency multiplier
+ var/efficiency_multiplier = 1
+
+ /// are we on?
+ var/on = FALSE
+ /// maximum power limit in watts
+ var/power_maximum = 7500
+ /// power setting configured in watts
+ var/power_setting
+ /// current power usage in watts
+ var/power_current = 0
+
+ //! legacy below
+ /// legacy power limit in watts
+ var/power_rating = 7500
+
+/obj/machinery/atmospherics/component/Initialize(mapload, newdir)
+ . = ..()
+ if(isnull(power_setting))
+ power_setting = power_maximum
+ //! LEGACY IF STATEMENT
+ // tgui_interface being set usually means we got refactored, and therefore we can be relied on to have
+ // proper on/off behavior, as opposed to the old "use power idle vs off" behavior."
+ if(tgui_interface)
+ set_on(on)
+
+/obj/machinery/atmospherics/component/proc/set_power(watts)
+ power_setting = watts
+
+/obj/machinery/atmospherics/component/proc/set_on(enabled)
+ on = enabled
+ if(enabled)
+ update_use_power(USE_POWER_IDLE)
+ else
+ update_use_power(USE_POWER_OFF)
+ update_icon()
+
+/obj/machinery/atmospherics/component/process(delta_time)
+ power_current = 0
+ // todo: don't need this after pipenet rework
+ build_network()
+
+// todo: use a special state to handle multitool hijacking
+
+// /obj/machinery/atmospherics/component/ui_state(mob/user, datum/tgui_module/module)
+// return GLOB.default_state
+
+/obj/machinery/atmospherics/component/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["powerRating"] = power_maximum
+ .["controlFlags"] = atmos_component_ui_flags
+
+/obj/machinery/atmospherics/component/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(!tgui_interface)
+ return
+ .["on"] = on
+ .["powerSetting"] = power_setting
+ .["powerUsage"] = power_current
+
+/obj/machinery/atmospherics/component/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ if(!tgui_interface)
+ return
+ switch(action)
+ if("togglePower")
+ if(!(atmos_component_ui_flags & ATMOS_COMPONENT_UI_TOGGLE_POWER))
+ return TRUE
+ set_on(!on)
+ return TRUE
+ if("setPowerDraw")
+ if(!(atmos_component_ui_flags & ATMOS_COMPONENT_UI_SET_POWER))
+ return TRUE
+ var/watts = text2num(params["target"])
+ set_power(watts)
+ return TRUE
+
+/obj/machinery/atmospherics/component/interact(mob/user)
+ // todo: interact needs a refactor; doing it like this stops fingerprints from applying.
+ if(!allowed(user))
+ user.action_feedback(SPAN_WARNING("Access denied."), src)
+ return FALSE
+ if(!default_access_interface)
+ return FALSE
+ // todo: refactor silicon interaction
+ if(interface_require_exposed && is_hidden_underfloor() && !issilicon(user))
+ return FALSE
+ return ..()
+
+/obj/machinery/atmospherics/component/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
+ if(!tgui_interface)
+ return ..()
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(isnull(ui))
+ ui = new(user, src, tgui_interface)
+ ui.open()
+
+/obj/machinery/atmospherics/component/multitool_act(obj/item/I, mob/user, flags, hint)
+ . = ..()
+ if(.)
+ return
+ if(isnull(default_multitool_hijack))
+ return FALSE
+ if(hijack_require_exposed && is_hidden_underfloor())
+ user.action_feedback(SPAN_WARNING("You can't reach the controls of [src] while it's covered by flooring."), src)
+ return TRUE
+ user.visible_action_feedback(
+ target = src,
+ hard_range = MESSAGE_RANGE_CONFIGURATION,
+ visible_hard = SPAN_WARNING("[user] starts tinkering with [src] using their [I]!"),
+ )
+ if(!do_after(user, default_multitool_hijack, src, mobility_flags = MOBILITY_CAN_USE))
+ return TRUE
+ ui_interact(user)
+ return TRUE
diff --git a/code/modules/atmospherics/machinery/components/omni_devices/_omni_extras.dm b/code/modules/atmospherics/machinery/components/omni_devices/_omni_extras.dm
index 14117e2dd98..d567e6a1e29 100644
--- a/code/modules/atmospherics/machinery/components/omni_devices/_omni_extras.dm
+++ b/code/modules/atmospherics/machinery/components/omni_devices/_omni_extras.dm
@@ -96,14 +96,14 @@
/proc/mode_to_gasid(var/mode)
switch(mode)
if(ATM_O2)
- return /datum/gas/oxygen
+ return GAS_ID_OXYGEN
if(ATM_N2)
- return /datum/gas/nitrogen
+ return GAS_ID_NITROGEN
if(ATM_CO2)
- return /datum/gas/carbon_dioxide
+ return GAS_ID_CARBON_DIOXIDE
if(ATM_P)
- return /datum/gas/phoron
+ return GAS_ID_PHORON
if(ATM_N2O)
- return /datum/gas/nitrous_oxide
+ return GAS_ID_NITROUS_OXIDE
else
return null
diff --git a/code/modules/atmospherics/machinery/components/omni_devices/filter.dm b/code/modules/atmospherics/machinery/components/omni_devices/filter.dm
index 715f26a7fcd..09ec71b720c 100644
--- a/code/modules/atmospherics/machinery/components/omni_devices/filter.dm
+++ b/code/modules/atmospherics/machinery/components/omni_devices/filter.dm
@@ -1,6 +1,9 @@
//--------------------------------------------
// Gas filter - omni variant
//--------------------------------------------
+
+// todo: refactor omni, and add gas group support for filters.
+
/obj/machinery/atmospherics/component/quaternary/atmos_filter
name = "omni gas filter"
icon_state = "map_filter"
@@ -73,7 +76,7 @@
power_draw = filter_gas_multi(src, filtering_outputs, input_air, output_air, transfer_moles, power_rating)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
if(input.network)
@@ -128,7 +131,7 @@
data["ports"] = portData
if(output)
data["set_flow_rate"] = round(set_flow_rate*10) //because nanoui can't handle rounded decimals.
- data["last_flow_rate"] = round(last_flow_rate*10)
+ data["last_flow_rate"] = round(last_flow_rate_legacy*10)
return data
diff --git a/code/modules/atmospherics/machinery/components/omni_devices/mixer.dm b/code/modules/atmospherics/machinery/components/omni_devices/mixer.dm
index 11e0e7789ed..ee481208f05 100644
--- a/code/modules/atmospherics/machinery/components/omni_devices/mixer.dm
+++ b/code/modules/atmospherics/machinery/components/omni_devices/mixer.dm
@@ -112,7 +112,7 @@
power_draw = mix_gas(src, mixing_inputs, output.air, transfer_moles, power_rating)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
for(var/datum/omni_port/P in inputs)
@@ -159,7 +159,7 @@
data["ports"] = portData
if(output)
data["set_flow_rate"] = round(set_flow_rate*10) //because nanoui can't handle rounded decimals.
- data["last_flow_rate"] = round(last_flow_rate*10)
+ data["last_flow_rate"] = round(last_flow_rate_legacy*10)
return data
diff --git a/code/modules/atmospherics/machinery/components/omni_devices/omni_base.dm b/code/modules/atmospherics/machinery/components/omni_devices/omni_base.dm
index 3d61ae0adf2..c628bd8994e 100644
--- a/code/modules/atmospherics/machinery/components/omni_devices/omni_base.dm
+++ b/code/modules/atmospherics/machinery/components/omni_devices/omni_base.dm
@@ -61,8 +61,8 @@
return
/obj/machinery/atmospherics/component/quaternary/process(delta_time)
- last_power_draw = 0
- last_flow_rate = 0
+ last_power_draw_legacy = 0
+ last_flow_rate_legacy = 0
if(error_check())
update_use_power(USE_POWER_OFF)
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
index 049bdbbf189..cd89e667103 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm
@@ -1,188 +1,105 @@
-/obj/machinery/atmospherics/component/trinary/atmos_filter
+/obj/machinery/atmospherics/component/trinary/filter
+ name = "gas filter"
icon = 'icons/atmos/filter.dmi'
icon_state = "map"
+ density = FALSE
construction_type = /obj/item/pipe/trinary/flippable
pipe_state = "filter"
- density = 0
level = 1
- name = "Gas filter"
-
use_power = USE_POWER_IDLE
- idle_power_usage = 150 //internal circuitry, friction losses and stuff
- power_rating = 7500 //This also doubles as a measure of how powerful the filter is, in Watts. 7500 W ~ 10 HP
+ idle_power_usage = 150
+ power_maximum = 7500
- var/temp = null // -- TLE
+ tgui_interface = "AtmosTrinaryFilter"
+ default_access_interface = TRUE
+ atmos_component_ui_flags = ATMOS_COMPONENT_UI_TOGGLE_POWER
- var/set_flow_rate = ATMOS_DEFAULT_VOLUME_FILTER
+ /// target gas id, or groups; can be type on init.
+ var/filtering
+ // todo: put flow rate at component level
+ /// flow rate in L
+ var/flow_setting = ATMOS_DEFAULT_VOLUME_FILTER
+ /// current flow rate in L
+ var/flow_current = 0
- /*
- Filter types:
- -1: Nothing
- 0: Phoron: Phoron, Oxygen Agent B
- 1: Oxygen: Oxygen ONLY
- 2: Nitrogen: Nitrogen ONLY
- 3: Carbon Dioxide: Carbon Dioxide ONLY
- 4: Sleeping Agent (N2O)
- */
- var/filter_type = -1
- var/list/filtered_out = list()
-
-
- var/frequency = 0
- var/datum/radio_frequency/radio_connection
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/proc/set_frequency(new_frequency)
- radio_controller.remove_object(src, frequency)
- frequency = new_frequency
- if(frequency)
- radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA)
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/Initialize(mapload)
+/obj/machinery/atmospherics/component/trinary/filter/Initialize(mapload)
. = ..()
- switch(filter_type)
- if(0) //removing hydrocarbons
- filtered_out += /datum/gas/phoron
- filtered_out += /datum/gas/volatile_fuel
- if(1) //removing O2
- filtered_out += /datum/gas/oxygen
- if(2) //removing N2
- filtered_out += /datum/gas/nitrogen
- if(3) //removing CO2
- filtered_out += /datum/gas/carbon_dioxide
- if(4)//removing N2O
- filtered_out += /datum/gas/nitrous_oxide
-
air1.volume = ATMOS_DEFAULT_VOLUME_FILTER
air2.volume = ATMOS_DEFAULT_VOLUME_FILTER
air3.volume = ATMOS_DEFAULT_VOLUME_FILTER
+ if(ispath(filtering))
+ var/datum/gas/casted = filtering
+ filtering = initial(casted.id)
-/obj/machinery/atmospherics/component/trinary/atmos_filter/Destroy()
- unregister_radio(src, frequency)
- . = ..()
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/update_icon()
+/obj/machinery/atmospherics/component/trinary/filter/update_icon_state()
if(mirrored)
icon_state = "m"
else
icon_state = ""
-
- if(!powered())
+ if(!powered() || !on)
icon_state += "off"
else if(node2 && node3 && node1)
- icon_state += use_power ? "on" : "off"
+ icon_state += on ? "on" : "off"
else
icon_state += "off"
- update_use_power(USE_POWER_OFF)
+ return ..()
-/obj/machinery/atmospherics/component/trinary/atmos_filter/process(delta_time)
+/obj/machinery/atmospherics/component/trinary/filter/process(delta_time)
..()
- last_power_draw = 0
- last_flow_rate = 0
-
- if((machine_stat & (NOPOWER|BROKEN)) || !use_power)
+ if(!on || inoperable())
return
- //Figure out the amount of moles to transfer
- var/transfer_moles = (set_flow_rate/air1.volume)*air1.total_moles
+ var/old_mols = air1.total_moles
+ var/transfer_mols = (flow_setting / air1.volume) * old_mols
- var/power_draw = -1
- if (transfer_moles > MINIMUM_MOLES_TO_FILTER)
- power_draw = filter_gas(src, filtered_out, air1, air2, air3, transfer_moles, power_rating)
+ if(transfer_mols > MINIMUM_MOLES_TO_FILTER)
+ power_current = xgm_filter_gas(air1, air3, air2, filtering, transfer_mols, power_setting * efficiency_multiplier) / efficiency_multiplier
+ flow_current = (1 - air1.total_moles / old_mols) * air1.volume
- if(network2)
- network2.update = 1
+ // todo: better API for this
+ network1?.update = TRUE
+ network2?.update = TRUE
+ network3?.update = TRUE
- if(network3)
- network3.update = 1
+ if(power_current)
+ use_power(power_current)
+ // switch to watts instead of joules by dividing out tick time
+ power_current /= delta_time
- if(network1)
- network1.update = 1
+/obj/machinery/atmospherics/component/trinary/filter/proc/set_rate(liters)
+ flow_setting = clamp(liters, 0, air1.volume)
- if (power_draw >= 0)
- last_power_draw = power_draw
- use_power(power_draw)
-
- return 1
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/Initialize(mapload)
+/obj/machinery/atmospherics/component/trinary/filter/ui_data(mob/user)
. = ..()
- if(frequency)
- set_frequency(frequency)
+ .["filtering"] = filtering
+ .["maxRate"] = air1.volume
+ .["rate"] = flow_setting
-/obj/machinery/atmospherics/component/trinary/atmos_filter/attack_hand(mob/user, list/params)
- if(..())
+/obj/machinery/atmospherics/component/trinary/filter/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["atmosContext"] = global.gas_data.tgui_gas_context()
+
+/obj/machinery/atmospherics/component/trinary/filter/ui_act(action, params)
+ . = ..()
+ if(.)
return
- if(!src.allowed(user))
- to_chat(user, SPAN_WARNING("Access denied."))
- return
- ui_interact(user)
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "AtmosFilter", name)
- ui.open()
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/ui_data(mob/user)
- var/list/data = list()
-
- data["on"] = use_power
- data["rate"] = set_flow_rate
- data["max_rate"] = air1.volume
- data["last_flow_rate"] = round(last_flow_rate, 0.1)
-
- data["filter_types"] = list()
- data["filter_types"] += list(list("name" = "Nothing", "f_type" = -1, "selected" = filter_type == -1))
- data["filter_types"] += list(list("name" = "Phoron", "f_type" = 0, "selected" = filter_type == 0))
- data["filter_types"] += list(list("name" = "Oxygen", "f_type" = 1, "selected" = filter_type == 1))
- data["filter_types"] += list(list("name" = "Nitrogen", "f_type" = 2, "selected" = filter_type == 2))
- data["filter_types"] += list(list("name" = "Carbon Dioxide", "f_type" = 3, "selected" = filter_type == 3))
- data["filter_types"] += list(list("name" = "Nitrous Oxide", "f_type" = 4, "selected" = filter_type == 4))
-
- return data
-
-/obj/machinery/atmospherics/component/trinary/atmos_filter/ui_act(action, params)
- if(..())
- return TRUE
-
switch(action)
- if("power")
- update_use_power(!use_power)
- if("rate")
- var/rate = params["rate"]
- if(rate == "max")
- rate = air1.volume
- . = TRUE
- else if(text2num(rate) != null)
- rate = text2num(rate)
- . = TRUE
- if(.)
- set_flow_rate = clamp(rate, 0, air1.volume)
if("filter")
- . = TRUE
- filter_type = text2num(params["filterset"])
- filtered_out.len = 0 //no need to create new lists unnecessarily
- switch(filter_type)
- if(0) //removing hydrocarbons
- filtered_out += /datum/gas/phoron
- filtered_out += /datum/gas/volatile_fuel
- if(1) //removing O2
- filtered_out += /datum/gas/oxygen
- if(2) //removing N2
- filtered_out += /datum/gas/nitrogen
- if(3) //removing CO2
- filtered_out += /datum/gas/carbon_dioxide
- if(4)//removing N2O
- filtered_out += /datum/gas/nitrous_oxide
+ var/target = params["target"]
+ if(!(isnum(target)? global.gas_data.gas_groups_filterable(target) : global.gas_data.gas_id_filterable(target)))
+ return TRUE
+ filtering = target
+ return TRUE
+ if("rate")
+ var/liters = params["rate"]
+ if(isnull(liters))
+ return FALSE
+ set_rate(liters)
+ return TRUE
- update_icon()
-
-//
-// Mirrored Orientation - Flips the output dir to opposite side from normal.
-//
-/obj/machinery/atmospherics/component/trinary/atmos_filter/m_filter
+/obj/machinery/atmospherics/component/trinary/filter/mirrored
icon_state = "mmap"
dir = SOUTH
initialize_directions = SOUTH|NORTH|EAST
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
index c535d6d7789..c0f8e340a36 100644
--- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm
@@ -49,8 +49,8 @@
/obj/machinery/atmospherics/component/trinary/mixer/process(delta_time)
..()
- last_power_draw = 0
- last_flow_rate = 0
+ last_power_draw_legacy = 0
+ last_flow_rate_legacy = 0
if((machine_stat & (NOPOWER|BROKEN)) || !use_power)
return
@@ -72,7 +72,7 @@
network3.update = 1
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
return 1
diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/molar_filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/molar_filter.dm
new file mode 100644
index 00000000000..fd7bafa66c4
--- /dev/null
+++ b/code/modules/atmospherics/machinery/components/trinary_devices/molar_filter.dm
@@ -0,0 +1,111 @@
+/obj/machinery/atmospherics/component/trinary/molar_filter
+ name = "molar filter"
+ desc = "A prototype filter that filters gases based on their molar mass."
+ icon = 'icons/modules/atmospherics/components/trinary/molar_filter.dmi'
+ base_icon_state = "molar"
+ icon_state = "molar-map"
+ density = FALSE
+ construction_type = /obj/item/pipe/trinary/flippable
+ pipe_state = "filter"
+ level = 1
+
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 150
+ power_maximum = 7500
+
+ tgui_interface = "AtmosTrinaryMolarFilter"
+ default_access_interface = TRUE
+ atmos_component_ui_flags = ATMOS_COMPONENT_UI_TOGGLE_POWER
+
+ /// inverted mode
+ var/invert = FALSE
+ /// lower molar bound
+ var/mass_lower = 0
+ /// upper molar bound
+ var/mass_upper = 10000
+ // todo: put flow rate at component level
+ /// flow rate in L
+ var/flow_setting = ATMOS_DEFAULT_VOLUME_FILTER
+ /// current flow rate in L
+ var/flow_current = 0
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/Initialize(mapload)
+ . = ..()
+ air1.volume = ATMOS_DEFAULT_VOLUME_FILTER
+ air2.volume = ATMOS_DEFAULT_VOLUME_FILTER
+ air3.volume = ATMOS_DEFAULT_VOLUME_FILTER
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/update_icon_state()
+ var/is_on = on && powered() && (node1 && node2 && node3)
+ icon_state = "[base_icon_state][mirrored? "-f" : ""][(is_on? "-on" : "")]"
+ return ..()
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/process(delta_time)
+ ..()
+
+ if(!on || inoperable())
+ return
+
+ var/old_mols = air1.total_moles
+ var/transfer_mols = (flow_setting / air1.volume) * old_mols
+
+ if(transfer_mols > MINIMUM_MOLES_TO_FILTER)
+ if(invert)
+ power_current = xgm_molar_filter_gas(air1, air2, air3, mass_lower, mass_upper, transfer_mols, power_setting * efficiency_multiplier) / efficiency_multiplier
+ else
+ power_current = xgm_molar_filter_gas(air1, air3, air2, mass_lower, mass_upper, transfer_mols, power_setting * efficiency_multiplier) / efficiency_multiplier
+ flow_current = (1 - air1.total_moles / old_mols) * air1.volume
+
+ // todo: better API for this
+ network1?.update = TRUE
+ network2?.update = TRUE
+ network3?.update = TRUE
+
+ if(power_current)
+ use_power(power_current)
+ // switch to watts instead of joules by dividing out tick time
+ power_current /= delta_time
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/proc/set_rate(liters)
+ flow_setting = clamp(liters, 0, air1.volume)
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/ui_data(mob/user)
+ . = ..()
+ .["lower"] = mass_lower
+ .["upper"] = mass_upper
+ .["invert"] = invert
+ .["maxRate"] = air1.volume
+ .["rate"] = flow_setting
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("lower")
+ var/target = params["target"]
+ target = clamp(text2num(target), 0, INFINITY)
+ mass_lower = target
+ mass_upper = max(mass_upper, mass_lower)
+ return TRUE
+ if("upper")
+ var/target = params["target"]
+ target = clamp(text2num(target), 0, INFINITY)
+ mass_upper = target
+ mass_lower = min(mass_lower, mass_upper)
+ return TRUE
+ if("invert")
+ invert = !invert
+ return TRUE
+ if("rate")
+ var/liters = params["rate"]
+ if(isnull(liters))
+ return FALSE
+ set_rate(liters)
+ return TRUE
+
+/obj/machinery/atmospherics/component/trinary/molar_filter/mirrored
+ dir = SOUTH
+ initialize_directions = SOUTH|NORTH|EAST
+ mirrored = TRUE
+ icon_state = "molar-f-map"
diff --git a/code/modules/atmospherics/machinery/components/unary/cold_sink.dm b/code/modules/atmospherics/machinery/components/unary/cold_sink.dm
index 85709a23073..96f17418729 100644
--- a/code/modules/atmospherics/machinery/components/unary/cold_sink.dm
+++ b/code/modules/atmospherics/machinery/components/unary/cold_sink.dm
@@ -14,7 +14,7 @@
var/internal_volume = 600 // L
var/max_power_rating = 20000 // Power rating when the usage is turned up to 100
- var/power_setting = 100
+ var/power_setting_legacy = 100
var/set_temperature = T20C // Thermostat
var/cooling = 0
@@ -77,7 +77,7 @@
data["minGasTemperature"] = 0
data["maxGasTemperature"] = round(T20C+500)
data["targetGasTemperature"] = round(set_temperature)
- data["powerSetting"] = power_setting
+ data["powerSetting"] = power_setting_legacy
var/temp_class = "good"
if(air_contents.temperature > (T0C - 20))
@@ -155,11 +155,11 @@
max_power_rating = initial(max_power_rating) * cap_rating / 2 //more powerful
heatsink_temperature = initial(heatsink_temperature) / ((manip_rating + bin_rating) / 2) //more efficient
air_contents.volume = max(initial(internal_volume) - 200, 0) + 200 * bin_rating
- set_power_level(power_setting)
+ set_power_level(power_setting_legacy)
-/obj/machinery/atmospherics/component/unary/freezer/proc/set_power_level(var/new_power_setting)
- power_setting = new_power_setting
- power_rating = max_power_rating * (power_setting/100)
+/obj/machinery/atmospherics/component/unary/freezer/proc/set_power_level(var/new_power_setting_legacy)
+ power_setting_legacy = new_power_setting_legacy
+ power_rating = max_power_rating * (power_setting_legacy/100)
/obj/machinery/atmospherics/component/unary/freezer/attackby(var/obj/item/O as obj, var/mob/user as mob)
if(default_deconstruction_screwdriver(user, O))
diff --git a/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm b/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm
index d307391bcfe..e21a782c5cb 100644
--- a/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm
+++ b/code/modules/atmospherics/machinery/components/unary/heat_exchanger.dm
@@ -33,11 +33,11 @@
if(!partner)
return 0
- if(!air_master || air_master.current_cycle <= update_cycle)
+ if(!SSair || SSair.current_cycle <= update_cycle)
return 0
- update_cycle = air_master.current_cycle
- partner.update_cycle = air_master.current_cycle
+ update_cycle = SSair.current_cycle
+ partner.update_cycle = SSair.current_cycle
var/air_heat_capacity = air_contents.heat_capacity()
var/other_air_heat_capacity = partner.air_contents.heat_capacity()
diff --git a/code/modules/atmospherics/machinery/components/unary/heat_source.dm b/code/modules/atmospherics/machinery/components/unary/heat_source.dm
index c2a16315e36..4e37184dbdc 100644
--- a/code/modules/atmospherics/machinery/components/unary/heat_source.dm
+++ b/code/modules/atmospherics/machinery/components/unary/heat_source.dm
@@ -14,7 +14,7 @@
var/internal_volume = 600 //L
var/max_power_rating = 20000 //power rating when the usage is turned up to 100
- var/power_setting = 100
+ var/power_setting_legacy = 100
var/set_temperature = T20C //thermostat
var/heating = 0 //mainly for icon updates
@@ -91,7 +91,7 @@
data["minGasTemperature"] = 0
data["maxGasTemperature"] = round(max_temperature)
data["targetGasTemperature"] = round(set_temperature)
- data["powerSetting"] = power_setting
+ data["powerSetting"] = power_setting_legacy
var/temp_class = "average"
if(air_contents.temperature > (T20C+40))
@@ -134,11 +134,11 @@
max_power_rating = initial(max_power_rating) * cap_rating / 2
max_temperature = max(initial(max_temperature) - T20C, 0) * ((bin_rating * 4 + cap_rating) / 5) + T20C
air_contents.volume = max(initial(internal_volume) - 200, 0) + 200 * bin_rating
- set_power_level(power_setting)
+ set_power_level(power_setting_legacy)
-/obj/machinery/atmospherics/component/unary/heater/proc/set_power_level(var/new_power_setting)
- power_setting = new_power_setting
- power_rating = max_power_rating * (power_setting/100)
+/obj/machinery/atmospherics/component/unary/heater/proc/set_power_level(var/new_power_setting_legacy)
+ power_setting_legacy = new_power_setting_legacy
+ power_rating = max_power_rating * (power_setting_legacy/100)
/obj/machinery/atmospherics/component/unary/heater/attackby(var/obj/item/O as obj, var/mob/user as mob)
if(default_deconstruction_screwdriver(user, O))
diff --git a/code/modules/atmospherics/machinery/components/unary/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary/outlet_injector.dm
index 0773434506f..4314a1d5a4d 100644
--- a/code/modules/atmospherics/machinery/components/unary/outlet_injector.dm
+++ b/code/modules/atmospherics/machinery/components/unary/outlet_injector.dm
@@ -55,8 +55,8 @@
/obj/machinery/atmospherics/component/unary/outlet_injector/process(delta_time)
..()
- last_power_draw = 0
- last_flow_rate = 0
+ last_power_draw_legacy = 0
+ last_flow_rate_legacy = 0
if((machine_stat & (NOPOWER|BROKEN)) || !use_power)
return
@@ -69,7 +69,7 @@
power_draw = pump_gas(src, air_contents, environment, transfer_moles, power_rating)
if (power_draw >= 0)
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
use_power(power_draw)
if(network)
diff --git a/code/modules/atmospherics/machinery/components/unary/oxygen_generator.dm b/code/modules/atmospherics/machinery/components/unary/oxygen_generator.dm
index 82e6e2bf538..d47883ae893 100644
--- a/code/modules/atmospherics/machinery/components/unary/oxygen_generator.dm
+++ b/code/modules/atmospherics/machinery/components/unary/oxygen_generator.dm
@@ -9,8 +9,6 @@
dir = SOUTH
initialize_directions = SOUTH
- var/on = 0
-
var/oxygen_content = 10
/obj/machinery/atmospherics/component/unary/oxygen_generator/update_icon()
@@ -18,11 +16,8 @@
icon_state = "intact_[on?("on"):("off")]"
else
icon_state = "exposed_off"
-
on = 0
- return
-
/obj/machinery/atmospherics/component/unary/oxygen_generator/New()
..()
air_contents.volume = 50
@@ -40,7 +35,7 @@
var/added_oxygen = oxygen_content - total_moles
air_contents.temperature = (current_heat_capacity*air_contents.temperature + 20*added_oxygen*T0C)/(current_heat_capacity+20*added_oxygen)
- air_contents.adjust_gas(/datum/gas/oxygen, added_oxygen)
+ air_contents.adjust_gas(GAS_ID_OXYGEN, added_oxygen)
if(network)
network.update = 1
diff --git a/code/modules/atmospherics/machinery/components/unary/retro_pump_and_scrubber.dm b/code/modules/atmospherics/machinery/components/unary/retro_pump_and_scrubber.dm
deleted file mode 100644
index 2fd98e972dc..00000000000
--- a/code/modules/atmospherics/machinery/components/unary/retro_pump_and_scrubber.dm
+++ /dev/null
@@ -1,72 +0,0 @@
-//// Retro Styles of vents and scrubbers///
-///
-/// Vents first
-
-/obj/machinery/atmospherics/component/unary/vent_pump/retro
- icon_state = "map_vent"
-
-/obj/machinery/atmospherics/component/unary/vent_pump/retro/on
- use_power = USE_POWER_IDLE
- icon_state = "map_vent_out"
-
-/obj/machinery/atmospherics/component/unary/vent_pump/retro/on/welded
- welded = 1
-
-/obj/machinery/atmospherics/component/unary/vent_pump/retro/update_icon(safety = 0)
- if(!check_icon_cache())
- return
-
- cut_overlays()
-
- var/vent_icon = "vent"
-
- var/turf/T = get_turf(src)
- if(!istype(T))
- return
-
- if(!T.is_plating() && node && node.level == 1 && istype(node, /obj/machinery/atmospherics/pipe))
- vent_icon += "h"
-
- if(welded)
- vent_icon += "retro_weld"
- else if(!use_power || !node || (machine_stat & (NOPOWER|BROKEN)))
- vent_icon += "retro_off"
- else
- vent_icon += "[pump_direction ? "retro_out" : "retro_in"]"
-
- add_overlay(icon_manager.get_atmos_icon("device", , , vent_icon))
-
-
-//////////////////////
-/// Scrubbers now ////
-
-/obj/machinery/atmospherics/component/unary/vent_scrubber/retro
- icon_state = "map_scrubber_off" /// Will get replaced on mapload
-
-/obj/machinery/atmospherics/component/unary/vent_scrubber/retro/on
- use_power = USE_POWER_IDLE
- icon_state = "map_scrubber_on"
-
-/obj/machinery/atmospherics/component/unary/vent_scrubber/retro/on/welded
- welded = 1
-
-/obj/machinery/atmospherics/component/unary/vent_scrubber/retro/update_icon(safety = 0)
- if(!check_icon_cache())
- return
-
- cut_overlays()
-
- var/scrubber_icon = "scrubber"
-
- var/turf/T = get_turf(src)
- if(!istype(T))
- return
-
- if(welded)
- scrubber_icon += "retro_weld"
- else if(!powered())
- scrubber_icon += "retro_off"
- else
- scrubber_icon += "[use_power ? "[scrubbing ? "retro_on" : "retro_in"]" : "retro_off"]"
-
- add_overlay(icon_manager.get_atmos_icon("device", , , scrubber_icon))
diff --git a/code/modules/atmospherics/machinery/components/unary/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary/vent_pump.dm
index b35f3ce2170..086b243e4b8 100644
--- a/code/modules/atmospherics/machinery/components/unary/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary/vent_pump.dm
@@ -1,49 +1,58 @@
#define DEFAULT_PRESSURE_DELTA 10000
-#define EXTERNAL_PRESSURE_BOUND ONE_ATMOSPHERE
-#define INTERNAL_PRESSURE_BOUND 0
-#define PRESSURE_CHECKS 1
-
-#define PRESSURE_CHECK_EXTERNAL 1
-#define PRESSURE_CHECK_INTERNAL 2
-
+// todo: better interface logging
/obj/machinery/atmospherics/component/unary/vent_pump
+ name = "Air Vent"
+ desc = "Has a valve and pump attached to it"
icon = 'icons/atmos/vent_pump.dmi'
icon_state = "map_vent"
pipe_state = "uvent"
-
- name = "Air Vent"
- desc = "Has a valve and pump attached to it"
- use_power = USE_POWER_OFF
idle_power_usage = 150 //internal circuitry, friction losses and stuff
power_rating = 30000
connect_types = CONNECT_TYPE_REGULAR|CONNECT_TYPE_SUPPLY //connects to regular and supply pipes
- var/area/initial_loc
+ hijack_require_exposed = TRUE
+ default_multitool_hijack = TRUE
+ tgui_interface = "AtmosVentPump"
+ atmos_component_ui_flags = NONE
+
level = 1
+
+ /// registered area
+ var/area/registered_area
+ /// auto name by area
+ var/name_from_area = TRUE
+ /// show on area
+ var/controllable_from_alarm = TRUE
+ /// cares about siphoning/filling/alarm modes
+ var/environmental = TRUE
+
+ /// pump direction
+ var/pump_direction
+ /// default pump direction
+ var/pump_direction_default = ATMOS_VENT_DIRECTION_RELEASE
+ /// external pressure limit
+ var/external_pressure_bound = ONE_ATMOSPHERE
+ /// external pressure limit default
+ var/external_pressure_bound_default = ONE_ATMOSPHERE
+ /// internal pressure limit
+ var/internal_pressure_bound = 0
+ /// internal pressure limit default
+ var/internal_pressure_bound_default = 0
+ /// pressure checks flag
+ var/pressure_checks
+ /// default pressure checks
+ var/pressure_checks_default = ATMOS_VENT_CHECK_EXTERNAL
+
var/area_uid
var/id_tag = null
- var/hibernate = 0 //Do we even process?
- var/pump_direction = 1 //0 = siphoning, 1 = releasing
-
- var/external_pressure_bound = EXTERNAL_PRESSURE_BOUND
- var/internal_pressure_bound = INTERNAL_PRESSURE_BOUND
-
- var/pressure_checks = PRESSURE_CHECKS
- //1: Do not pass external_pressure_bound
- //2: Do not pass internal_pressure_bound
- //3: Do not pass either
-
- // Used when handling incoming radio signals requesting default settings
- var/external_pressure_bound_default = EXTERNAL_PRESSURE_BOUND
- var/internal_pressure_bound_default = INTERNAL_PRESSURE_BOUND
- var/pressure_checks_default = PRESSURE_CHECKS
-
var/frequency = 1439
var/datum/radio_frequency/radio_connection
+ var/hibernate = 0 //Do we even process?
+
var/radio_filter_out
var/radio_filter_in
@@ -51,8 +60,357 @@
var/datum/looping_sound/air_pump/soundloop
*/
+/obj/machinery/atmospherics/component/unary/vent_pump/Initialize(mapload)
+ . = ..()
+ if(isnull(external_pressure_bound))
+ external_pressure_bound = external_pressure_bound_default
+ if(isnull(internal_pressure_bound))
+ internal_pressure_bound = internal_pressure_bound_default
+ if(isnull(pressure_checks))
+ pressure_checks = pressure_checks_default
+ if(isnull(pump_direction))
+ pump_direction = pump_direction_default
+ /*
+ soundloop = new(list(src), FALSE)
+ */
+ air_contents.volume = ATMOS_DEFAULT_VOLUME_PUMP
+
+ icon = null
+ registered_area = get_area(loc)
+ area_uid = registered_area.uid
+ registered_area?.register_vent(src)
+ if (!id_tag)
+ assign_uid()
+ id_tag = num2text(uid)
+
+/obj/machinery/atmospherics/component/unary/vent_pump/Destroy()
+ unregister_radio(src, frequency)
+ registered_area?.unregister_vent(src)
+ //QDEL_NULL(soundloop)
+ return ..()
+
+/obj/machinery/atmospherics/component/unary/vent_pump/update_icon(safety = 0)
+ if(!check_icon_cache())
+ return
+
+ cut_overlays()
+
+ var/vent_icon = "vent"
+
+ var/turf/T = get_turf(src)
+ if(!istype(T))
+ return
+
+ if(!T.is_plating() && node && node.level == 1 && istype(node, /obj/machinery/atmospherics/pipe))
+ vent_icon += "h"
+
+ if(welded)
+ vent_icon += "weld"
+ else if(!on || !node || (machine_stat & (NOPOWER|BROKEN)))
+ vent_icon += "off"
+ else
+ vent_icon += "[pump_direction ? "out" : "in"]"
+
+ add_overlay(icon_manager.get_atmos_icon("device", , , vent_icon))
+
+/obj/machinery/atmospherics/component/unary/vent_pump/update_underlays()
+ if(..())
+ underlays.Cut()
+ var/turf/T = get_turf(src)
+ if(!istype(T))
+ return
+ if(!T.is_plating() && node && node.level == 1 && istype(node, /obj/machinery/atmospherics/pipe))
+ return
+ else
+ if(node)
+ add_underlay(T, node, dir, node.icon_connect_type)
+ else
+ add_underlay(T,, dir)
+
+/obj/machinery/atmospherics/component/unary/vent_pump/hide()
+ update_icon()
+ update_underlays()
+
+/obj/machinery/atmospherics/component/unary/vent_pump/proc/can_pump()
+ if(machine_stat & (NOPOWER|BROKEN))
+ //soundloop.stop()
+ return 0
+ if(!on)
+ //soundloop.stop()
+ return 0
+ if(welded)
+ //soundloop.stop()
+ return 0
+ //soundloop.start()
+ return 1
+
+/obj/machinery/atmospherics/component/unary/vent_pump/process(delta_time)
+ ..()
+ if (hibernate)
+ return 1
+ if(!node)
+ set_on(FALSE)
+ if(!can_pump())
+ return 0
+
+ var/datum/gas_mixture/environment = return_air()
+
+ var/power_draw = -1
+
+ //Figure out the target pressure difference
+ var/pressure_delta = get_pressure_delta(environment)
+ //src.visible_message("DEBUG >>> [src]: pressure_delta = [pressure_delta]")
+
+ if((environment.temperature || air_contents.temperature) && pressure_delta > 0.5)
+ if(pump_direction) //internal -> external
+ var/transfer_moles = calculate_transfer_moles(air_contents, environment, pressure_delta)
+ power_draw = pump_gas(src, air_contents, environment, transfer_moles, power_rating)
+ else //external -> internal
+ var/transfer_moles = calculate_transfer_moles(environment, air_contents, pressure_delta, (network)? network.volume : 0)
+
+ //limit flow rate from turfs
+ transfer_moles = min(transfer_moles, environment.total_moles*air_contents.volume/environment.volume) //group_multiplier gets divided out here
+ power_draw = pump_gas(src, environment, air_contents, transfer_moles, power_rating)
+
+
+ if (power_draw >= 0)
+ last_power_draw_legacy = power_draw
+ use_power(power_draw)
+ if(network)
+ network.update = 1
+
+ return 1
+
+/obj/machinery/atmospherics/component/unary/vent_pump/proc/get_pressure_delta(datum/gas_mixture/environment)
+ var/pressure_delta = DEFAULT_PRESSURE_DELTA
+ var/environment_pressure = environment.return_pressure()
+
+ if(pump_direction) //internal -> external
+ if(pressure_checks & ATMOS_VENT_CHECK_EXTERNAL)
+ pressure_delta = min(pressure_delta, external_pressure_bound - environment_pressure) //increasing the pressure here
+ if(pressure_checks & ATMOS_VENT_CHECK_INTERNAL)
+ pressure_delta = min(pressure_delta, air_contents.return_pressure() - internal_pressure_bound) //decreasing the pressure here
+ else //external -> internal
+ if(pressure_checks & ATMOS_VENT_CHECK_EXTERNAL)
+ pressure_delta = min(pressure_delta, environment_pressure - external_pressure_bound) //decreasing the pressure here
+ if(pressure_checks & ATMOS_VENT_CHECK_INTERNAL)
+ pressure_delta = min(pressure_delta, internal_pressure_bound - air_contents.return_pressure()) //increasing the pressure here
+
+ return pressure_delta
+
+/obj/machinery/atmospherics/component/unary/vent_pump/proc/broadcast_status()
+ if(!radio_connection)
+ return 0
+
+ var/datum/signal/signal = new
+ signal.transmission_method = 1 //radio signal
+ signal.source = src
+
+ signal.data = list(
+ "area" = src.area_uid,
+ "tag" = src.id_tag,
+ "device" = "AVP",
+ "power" = on,
+ "direction" = pump_direction?("release"):("siphon"),
+ "checks" = pressure_checks,
+ "internal" = internal_pressure_bound,
+ "external" = external_pressure_bound,
+ "timestamp" = world.time,
+ "sigtype" = "status",
+ "power_draw" = last_power_draw_legacy,
+ "flow_rate" = last_flow_rate_legacy,
+ )
+
+ radio_connection.post_signal(src, signal, radio_filter_out)
+
+ return 1
+
+/obj/machinery/atmospherics/component/unary/vent_pump/atmos_init()
+ ..()
+
+ //some vents work his own special way
+ radio_filter_in = frequency==1439?(RADIO_FROM_AIRALARM):null
+ radio_filter_out = frequency==1439?(RADIO_TO_AIRALARM):null
+ if(frequency)
+ radio_connection = register_radio(src, frequency, frequency, radio_filter_in)
+ src.broadcast_status()
+
+/obj/machinery/atmospherics/component/unary/vent_pump/attackby(obj/item/W, mob/user)
+ if(istype(W, /obj/item/weldingtool))
+ var/obj/item/weldingtool/WT = W
+ if (WT.remove_fuel(0,user))
+ to_chat(user, "Now welding the vent.")
+ if(do_after(user, 20 * WT.tool_speed))
+ if(!src || !WT.isOn()) return
+ playsound(src.loc, WT.tool_sound, 50, 1)
+ if(!welded)
+ user.visible_message("\The [user] welds the vent shut.", "You weld the vent shut.", "You hear welding.")
+ welded = 1
+ update_icon()
+ else
+ user.visible_message("[user] unwelds the vent.", "You unweld the vent.", "You hear welding.")
+ welded = 0
+ update_icon()
+ else
+ to_chat(user, "The welding tool needs to be on to start this task.")
+ else
+ to_chat(user, "You need more welding fuel to complete this task.")
+ return 1
+ else
+ ..()
+
+/obj/machinery/atmospherics/component/unary/vent_pump/examine(mob/user, dist)
+ . = ..()
+ . += "A small gauge in the corner reads [round(last_flow_rate_legacy, 0.1)] L/s; [round(last_power_draw_legacy)] W"
+ if(welded)
+ . += "It seems welded shut."
+
+/obj/machinery/atmospherics/component/unary/vent_pump/power_change()
+ var/old_stat = machine_stat
+ ..()
+ if(old_stat != machine_stat)
+ update_icon()
+
+/obj/machinery/atmospherics/component/unary/vent_pump/attackby(obj/item/W, mob/user)
+ if (!W.is_wrench())
+ return ..()
+ if (!(machine_stat & NOPOWER) && on)
+ to_chat(user, "You cannot unwrench \the [src], turn it off first.")
+ return 1
+ var/turf/T = src.loc
+ if (node && node.level==1 && isturf(T) && !T.is_plating())
+ to_chat(user, "You must remove the plating first.")
+ return 1
+ if(unsafe_pressure())
+ to_chat(user, "You feel a gust of air blowing in your face as you try to unwrench [src]. Maybe you should reconsider..")
+ add_fingerprint(user)
+ playsound(src, W.tool_sound, 50, 1)
+ to_chat(user, "You begin to unfasten \the [src]...")
+ if (do_after(user, 40 * W.tool_speed))
+ user.visible_message( \
+ "\The [user] unfastens \the [src].", \
+ "You have unfastened \the [src].", \
+ "You hear a ratchet.")
+ deconstruct()
+
+/**
+ * Encodes UI data for AtmosVentState in tgui/interfaces/machines/AtmosVent.tsx
+ */
+/obj/machinery/atmospherics/component/unary/vent_pump/proc/ui_vent_data()
+ return list(
+ "pressureChecks" = pressure_checks,
+ "internalPressure" = internal_pressure_bound,
+ "externalPressure" = external_pressure_bound,
+ "power" = on,
+ "siphon" = !pump_direction,
+ )
+
+/obj/machinery/atmospherics/component/unary/vent_pump/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!isnull(ui))
+ return
+ ui = new(user, src, "AtmosVentPump")
+ ui.open()
+
+/obj/machinery/atmospherics/component/unary/vent_pump/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["state"] = ui_vent_data()
+ .["name"] = name
+
+/obj/machinery/atmospherics/component/unary/vent_pump/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ var/target = params["target"]
+ switch(action)
+ if("toggle")
+ set_on(!on)
+ return TRUE
+ if("intPressure")
+ internal_pressure_bound = target == "default"? internal_pressure_bound_default : clamp(text2num(target), 0, ATMOS_VENT_MAX_PRESSURE_LIMIT)
+ return TRUE
+ if("extPressure")
+ external_pressure_bound = target == "default"? external_pressure_bound_default : clamp(text2num(target), 0, ATMOS_VENT_MAX_PRESSURE_LIMIT)
+ return TRUE
+ if("intCheck")
+ pressure_checks ^= ATMOS_VENT_CHECK_INTERNAL
+ return TRUE
+ if("extCheck")
+ pressure_checks ^= ATMOS_VENT_CHECK_EXTERNAL
+ return TRUE
+ if("siphon")
+ //! warning: legacy
+ pump_direction = !pump_direction
+ update_icon()
+ return TRUE
+
+//* Signal Handling - Check / Application order is in order of these comments.
+/// environmental: void. set to ignore the signal if we're not an environmental vent.
+/// hard_reset: resets everything to default.
+/// power: 0 | 1. sets us to be on/off. overrides power_toggle.
+/// power_toggle: void. toggles us on/off.
+/// checks: bitfield | "default". sets our checks to that. overrides checks_toggle.
+/// checks_toggle: bitfield. toggles those checks.
+/// direction: enum | "default". sets direction to that
+/// direction_toggle: void. toggles direction.
+/// set_internal_pressure: number | "default". sets internal pressure. overrides adjust_internal_pressure.
+/// adjust_internal_pressure: number. adjusts internal pressure.
+/// set_external_pressure: number | "default". sets external pressure. overrides adjust_external_pressure.
+/// adjust_external_pressure: number. adjusts external pressure.
+
+/obj/machinery/atmospherics/component/unary/vent_pump/receive_signal(datum/signal/signal)
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+
+ hibernate = 0
+
+ if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
+ return 0
+
+ if(!isnull(signal.data["environmental"]) && environmental)
+ return FALSE
+
+ if(!isnull(signal.data["hard_reset"]))
+ pressure_checks = pressure_checks_default
+ pump_direction = pump_direction_default
+ internal_pressure_bound = internal_pressure_bound_default
+ external_pressure_bound = external_pressure_bound_default
+ if(!isnull(signal.data["power"]))
+ set_on(!!text2num(signal.data["power"]))
+ else if(!isnull(signal.data["power_toggle"]))
+ set_on(!on)
+ if(!isnull(signal.data["checks"]))
+ pressure_checks = (signal.data["checks"] == "default")? pressure_checks_default : (text2num(signal.data) & ATMOS_VENT_CHECKS)
+ else if(!isnull(signal.data["checks_toggle"]))
+ pressure_checks ^= (text2num(signal.data["checks_toggle"]) & ATMOS_VENT_CHECKS)
+ if(!isnull(signal.data["direction"]))
+ pump_direction = signal.data["direction"] == "default"? pump_direction_default : clamp(text2num(signal.data["direction"]), 0, 1)
+ else if(!isnull(signal.data["direction_toggle"]))
+ pump_direction = !pump_direction
+ if(!isnull(signal.data["set_internal_pressure"]))
+ internal_pressure_bound = signal.data["set_internal_presure"] == "default"? internal_pressure_bound_default : clamp(text2num(signal.data["set_internal_pressure"]), 0, ATMOS_VENT_MAX_PRESSURE_LIMIT)
+ else if(!isnull(signal.data["adjust_internal_pressure"]))
+ internal_pressure_bound = clamp(internal_pressure_bound + text2num(signal.data["set_internal_pressure"]), 0, ATMOS_VENT_MAX_PRESSURE_LIMIT)
+ if(!isnull(signal.data["set_external_pressure"]))
+ external_pressure_bound = signal.data["set_external_presure"] == "default"? external_pressure_bound_default : clamp(text2num(signal.data["set_external_pressure"]), 0, ATMOS_VENT_MAX_PRESSURE_LIMIT)
+ else if(!isnull(signal.data["adjust_external_pressure"]))
+ external_pressure_bound = clamp(external_pressure_bound + text2num(signal.data["set_external_pressure"]), 0, ATMOS_VENT_MAX_PRESSURE_LIMIT)
+
+ //! legacy below
+ if(signal.data["status"] != null)
+ spawn(2)
+ broadcast_status()
+ return //do not update_icon
+
+ //log_admin("DEBUG \[[world.timeofday]\]: vent_pump/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
+ spawn(2)
+ broadcast_status()
+ update_icon()
+
+//* Subtypes
+
/obj/machinery/atmospherics/component/unary/vent_pump/on
- use_power = USE_POWER_IDLE
+ on = TRUE
icon_state = "map_vent_out"
/obj/machinery/atmospherics/component/unary/vent_pump/on/welded
@@ -67,16 +425,16 @@
pump_direction = 0
/obj/machinery/atmospherics/component/unary/vent_pump/siphon/on
- use_power = USE_POWER_IDLE
+ on = TRUE
icon_state = "map_vent_in"
/obj/machinery/atmospherics/component/unary/vent_pump/positive // For some reason was buried in tether_things.dm -Bloop
- use_power = USE_POWER_IDLE
+ on = TRUE
icon_state = "map_vent_out"
external_pressure_bound = ONE_ATMOSPHERE * 1.1
/obj/machinery/atmospherics/component/unary/vent_pump/siphon/on/atmos
- use_power = USE_POWER_IDLE
+ on = TRUE
icon_state = "map_vent_in"
external_pressure_bound = 0
external_pressure_bound_default = 0
@@ -85,31 +443,6 @@
pressure_checks = 2
pressure_checks_default = 2
-/obj/machinery/atmospherics/component/unary/vent_pump/Initialize(mapload)
- . = ..()
- /*
- soundloop = new(list(src), FALSE)
- */
-
-/obj/machinery/atmospherics/component/unary/vent_pump/Initialize(mapload)
- . = ..()
- air_contents.volume = ATMOS_DEFAULT_VOLUME_PUMP
-
- icon = null
- initial_loc = get_area(loc)
- area_uid = initial_loc.uid
- if (!id_tag)
- assign_uid()
- id_tag = num2text(uid)
-
-/obj/machinery/atmospherics/component/unary/vent_pump/Destroy()
- unregister_radio(src, frequency)
- if(initial_loc)
- initial_loc.air_vent_info -= id_tag
- initial_loc.air_vent_names -= id_tag
- //QDEL_NULL(soundloop)
- return ..()
-
/obj/machinery/atmospherics/component/unary/vent_pump/high_volume
name = "Large Air Vent"
power_channel = EQUIP
@@ -144,7 +477,17 @@
. = ..()
air_contents.volume = ATMOS_DEFAULT_VOLUME_PUMP + 500 //meant to match air injector
-/obj/machinery/atmospherics/component/unary/vent_pump/update_icon(safety = 0)
+/obj/machinery/atmospherics/component/unary/vent_pump/retro
+ icon_state = "map_vent"
+
+/obj/machinery/atmospherics/component/unary/vent_pump/retro/on
+ on = TRUE
+ icon_state = "map_vent_out"
+
+/obj/machinery/atmospherics/component/unary/vent_pump/retro/on/welded
+ welded = 1
+
+/obj/machinery/atmospherics/component/unary/vent_pump/retro/update_icon(safety = 0)
if(!check_icon_cache())
return
@@ -160,301 +503,12 @@
vent_icon += "h"
if(welded)
- vent_icon += "weld"
+ vent_icon += "retro_weld"
else if(!use_power || !node || (machine_stat & (NOPOWER|BROKEN)))
- vent_icon += "off"
+ vent_icon += "retro_off"
else
- vent_icon += "[pump_direction ? "out" : "in"]"
+ vent_icon += "[pump_direction ? "retro_out" : "retro_in"]"
add_overlay(icon_manager.get_atmos_icon("device", , , vent_icon))
-/obj/machinery/atmospherics/component/unary/vent_pump/update_underlays()
- if(..())
- underlays.Cut()
- var/turf/T = get_turf(src)
- if(!istype(T))
- return
- if(!T.is_plating() && node && node.level == 1 && istype(node, /obj/machinery/atmospherics/pipe))
- return
- else
- if(node)
- add_underlay(T, node, dir, node.icon_connect_type)
- else
- add_underlay(T,, dir)
-
-/obj/machinery/atmospherics/component/unary/vent_pump/hide()
- update_icon()
- update_underlays()
-
-/obj/machinery/atmospherics/component/unary/vent_pump/proc/can_pump()
- if(machine_stat & (NOPOWER|BROKEN))
- //soundloop.stop()
- return 0
- if(!use_power)
- //soundloop.stop()
- return 0
- if(welded)
- //soundloop.stop()
- return 0
- //soundloop.start()
- return 1
-
-/obj/machinery/atmospherics/component/unary/vent_pump/process(delta_time)
- ..()
-
- if (hibernate)
- return 1
-
- if (!node)
- update_use_power(USE_POWER_OFF)
- if(!can_pump())
- return 0
-
- var/datum/gas_mixture/environment = return_air()
-
- var/power_draw = -1
-
- //Figure out the target pressure difference
- var/pressure_delta = get_pressure_delta(environment)
- //src.visible_message("DEBUG >>> [src]: pressure_delta = [pressure_delta]")
-
- if((environment.temperature || air_contents.temperature) && pressure_delta > 0.5)
- if(pump_direction) //internal -> external
- var/transfer_moles = calculate_transfer_moles(air_contents, environment, pressure_delta)
- power_draw = pump_gas(src, air_contents, environment, transfer_moles, power_rating)
- else //external -> internal
- var/transfer_moles = calculate_transfer_moles(environment, air_contents, pressure_delta, (network)? network.volume : 0)
-
- //limit flow rate from turfs
- transfer_moles = min(transfer_moles, environment.total_moles*air_contents.volume/environment.volume) //group_multiplier gets divided out here
- power_draw = pump_gas(src, environment, air_contents, transfer_moles, power_rating)
-
-
- if (power_draw >= 0)
- last_power_draw = power_draw
- use_power(power_draw)
- if(network)
- network.update = 1
-
- return 1
-
-/obj/machinery/atmospherics/component/unary/vent_pump/proc/get_pressure_delta(datum/gas_mixture/environment)
- var/pressure_delta = DEFAULT_PRESSURE_DELTA
- var/environment_pressure = environment.return_pressure()
-
- if(pump_direction) //internal -> external
- if(pressure_checks & PRESSURE_CHECK_EXTERNAL)
- pressure_delta = min(pressure_delta, external_pressure_bound - environment_pressure) //increasing the pressure here
- if(pressure_checks & PRESSURE_CHECK_INTERNAL)
- pressure_delta = min(pressure_delta, air_contents.return_pressure() - internal_pressure_bound) //decreasing the pressure here
- else //external -> internal
- if(pressure_checks & PRESSURE_CHECK_EXTERNAL)
- pressure_delta = min(pressure_delta, environment_pressure - external_pressure_bound) //decreasing the pressure here
- if(pressure_checks & PRESSURE_CHECK_INTERNAL)
- pressure_delta = min(pressure_delta, internal_pressure_bound - air_contents.return_pressure()) //increasing the pressure here
-
- return pressure_delta
-
-/obj/machinery/atmospherics/component/unary/vent_pump/proc/broadcast_status()
- if(!radio_connection)
- return 0
-
- var/datum/signal/signal = new
- signal.transmission_method = 1 //radio signal
- signal.source = src
-
- signal.data = list(
- "area" = src.area_uid,
- "tag" = src.id_tag,
- "device" = "AVP",
- "power" = use_power,
- "direction" = pump_direction?("release"):("siphon"),
- "checks" = pressure_checks,
- "internal" = internal_pressure_bound,
- "external" = external_pressure_bound,
- "timestamp" = world.time,
- "sigtype" = "status",
- "power_draw" = last_power_draw,
- "flow_rate" = last_flow_rate,
- )
-
- if(!initial_loc.air_vent_names[id_tag])
- var/new_name = "[initial_loc.name] Vent Pump #[initial_loc.air_vent_names.len+1]"
- initial_loc.air_vent_names[id_tag] = new_name
- src.name = new_name
- initial_loc.air_vent_info[id_tag] = signal.data
-
- radio_connection.post_signal(src, signal, radio_filter_out)
-
- return 1
-
-
-/obj/machinery/atmospherics/component/unary/vent_pump/atmos_init()
- ..()
-
- //some vents work his own special way
- radio_filter_in = frequency==1439?(RADIO_FROM_AIRALARM):null
- radio_filter_out = frequency==1439?(RADIO_TO_AIRALARM):null
- if(frequency)
- radio_connection = register_radio(src, frequency, frequency, radio_filter_in)
- src.broadcast_status()
-
-/obj/machinery/atmospherics/component/unary/vent_pump/receive_signal(datum/signal/signal)
- if(machine_stat & (NOPOWER|BROKEN))
- return
-
- hibernate = 0
-
- //log_admin("DEBUG \[[world.timeofday]\]: /obj/machinery/atmospherics/component/unary/vent_pump/receive_signal([signal.debug_print()])")
- if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
- return 0
-
- if(signal.data["purge"] != null)
- pressure_checks &= ~1
- pump_direction = 0
-
- if(signal.data["stabalize"] != null)
- pressure_checks |= 1
- pump_direction = 1
-
- if(signal.data["power"] != null)
- update_use_power(text2num(signal.data["power"]))
-
- if(signal.data["power_toggle"] != null)
- update_use_power(!use_power)
-
- if(signal.data["checks"] != null)
- if (signal.data["checks"] == "default")
- pressure_checks = pressure_checks_default
- else
- pressure_checks = text2num(signal.data["checks"])
-
- if(signal.data["checks_toggle"] != null)
- pressure_checks = (pressure_checks?0:3)
-
- if(signal.data["direction"] != null)
- pump_direction = text2num(signal.data["direction"])
-
- if(signal.data["set_internal_pressure"] != null)
- if (signal.data["set_internal_pressure"] == "default")
- internal_pressure_bound = internal_pressure_bound_default
- else
- internal_pressure_bound = between(
- 0,
- text2num(signal.data["set_internal_pressure"]),
- ONE_ATMOSPHERE*50
- )
-
- if(signal.data["set_external_pressure"] != null)
- if (signal.data["set_external_pressure"] == "default")
- external_pressure_bound = external_pressure_bound_default
- else
- external_pressure_bound = between(
- 0,
- text2num(signal.data["set_external_pressure"]),
- ONE_ATMOSPHERE*50
- )
-
- if(signal.data["adjust_internal_pressure"] != null)
- internal_pressure_bound = between(
- 0,
- internal_pressure_bound + text2num(signal.data["adjust_internal_pressure"]),
- ONE_ATMOSPHERE*50
- )
-
- if(signal.data["adjust_external_pressure"] != null)
-
-
- external_pressure_bound = between(
- 0,
- external_pressure_bound + text2num(signal.data["adjust_external_pressure"]),
- ONE_ATMOSPHERE*50
- )
-
- if("reset_external_pressure" in signal.data)
- external_pressure_bound = ONE_ATMOSPHERE
-
- if("reset_internal_pressure" in signal.data)
- internal_pressure_bound = 0
-
- if(signal.data["init"] != null)
- name = signal.data["init"]
- return
-
- if(signal.data["status"] != null)
- spawn(2)
- broadcast_status()
- return //do not update_icon
-
- //log_admin("DEBUG \[[world.timeofday]\]: vent_pump/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
- spawn(2)
- broadcast_status()
- update_icon()
- return
-
-/obj/machinery/atmospherics/component/unary/vent_pump/attackby(obj/item/W, mob/user)
- if(istype(W, /obj/item/weldingtool))
- var/obj/item/weldingtool/WT = W
- if (WT.remove_fuel(0,user))
- to_chat(user, "Now welding the vent.")
- if(do_after(user, 20 * WT.tool_speed))
- if(!src || !WT.isOn()) return
- playsound(src.loc, WT.tool_sound, 50, 1)
- if(!welded)
- user.visible_message("\The [user] welds the vent shut.", "You weld the vent shut.", "You hear welding.")
- welded = 1
- update_icon()
- else
- user.visible_message("[user] unwelds the vent.", "You unweld the vent.", "You hear welding.")
- welded = 0
- update_icon()
- else
- to_chat(user, "The welding tool needs to be on to start this task.")
- else
- to_chat(user, "You need more welding fuel to complete this task.")
- return 1
- else
- ..()
-
-/obj/machinery/atmospherics/component/unary/vent_pump/examine(mob/user, dist)
- . = ..()
- . += "A small gauge in the corner reads [round(last_flow_rate, 0.1)] L/s; [round(last_power_draw)] W"
- if(welded)
- . += "It seems welded shut."
-
-/obj/machinery/atmospherics/component/unary/vent_pump/power_change()
- var/old_stat = machine_stat
- ..()
- if(old_stat != machine_stat)
- update_icon()
-
-/obj/machinery/atmospherics/component/unary/vent_pump/attackby(obj/item/W, mob/user)
- if (!W.is_wrench())
- return ..()
- if (!(machine_stat & NOPOWER) && use_power)
- to_chat(user, "You cannot unwrench \the [src], turn it off first.")
- return 1
- var/turf/T = src.loc
- if (node && node.level==1 && isturf(T) && !T.is_plating())
- to_chat(user, "You must remove the plating first.")
- return 1
- if(unsafe_pressure())
- to_chat(user, "You feel a gust of air blowing in your face as you try to unwrench [src]. Maybe you should reconsider..")
- add_fingerprint(user)
- playsound(src, W.tool_sound, 50, 1)
- to_chat(user, "You begin to unfasten \the [src]...")
- if (do_after(user, 40 * W.tool_speed))
- user.visible_message( \
- "\The [user] unfastens \the [src].", \
- "You have unfastened \the [src].", \
- "You hear a ratchet.")
- deconstruct()
-
#undef DEFAULT_PRESSURE_DELTA
-
-#undef EXTERNAL_PRESSURE_BOUND
-#undef INTERNAL_PRESSURE_BOUND
-#undef PRESSURE_CHECKS
-
-#undef PRESSURE_CHECK_EXTERNAL
-#undef PRESSURE_CHECK_INTERNAL
diff --git a/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm b/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm
index a715ca559ba..da062da22b7 100644
--- a/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm
+++ b/code/modules/atmospherics/machinery/components/unary/vent_scrubber.dm
@@ -1,11 +1,11 @@
+// todo: better interface logging
/obj/machinery/atmospherics/component/unary/vent_scrubber
+ name = "Air Scrubber"
+ desc = "Has a valve and pump attached to it"
icon = 'icons/atmos/vent_scrubber.dmi'
icon_state = "map_scrubber_off"
pipe_state = "scrubber"
- name = "Air Scrubber"
- desc = "Has a valve and pump attached to it"
- use_power = USE_POWER_OFF
idle_power_usage = 150 //internal circuitry, friction losses and stuff
power_rating = 7500 //7500 W ~ 10 HP
@@ -13,56 +13,89 @@
level = 1
- var/area/initial_loc
+ hijack_require_exposed = TRUE
+ default_multitool_hijack = TRUE
+ tgui_interface = "AtmosVentScrubber"
+ atmos_component_ui_flags = NONE
+
+ /// registered area
+ var/area/registered_area
+ /// auto name by area
+ var/name_from_area = TRUE
+ /// show on area
+ var/controllable_from_alarm = TRUE
+ /// cares about siphoning/filling/alarm modes
+ var/environmental = TRUE
+
+ /// filter ids. both scrub_ids and scrub_groups must be set to not automatically use default.
+ var/list/scrub_ids
+ /// filter groups. both scrub_ids and scrub_groups must be set to not automatically use default.
+ var/list/scrub_groups
+ /// filter defaults - either an enum for SCRUBBER_DEFAULT_* or a list of ids and groups.
+ var/scrub_default = SCRUBBER_DEFAULT_STATION
+ /// siphoning?
+ var/siphoning
+ /// siphoning default
+ var/siphoning_default = FALSE
+ /// high power?
+ var/expanded
+ /// high power default
+ var/expanded_default = FALSE
+
+ /// default scrub volume
+ var/scrub_volume = 2500
+ /// additional power when expanded
+ var/expanded_power = 7500
+ /// additional volume when expanded
+ var/expanded_scrub = 1250
+ /// mole boost
+ var/scrub_boost = 50
+
+ var/area_uid
var/id_tag = null
+
var/frequency = 1439
var/datum/radio_frequency/radio_connection
var/hibernate = 0 //Do we even process?
- var/scrubbing = 1 //0 = siphoning, 1 = scrubbing
- var/list/scrubbing_gas = list(/datum/gas/carbon_dioxide, /datum/gas/phoron)
- var/panic = 0 //is this scrubber panicked?
-
- var/area_uid
var/radio_filter_out
var/radio_filter_in
-/obj/machinery/atmospherics/component/unary/vent_scrubber/on
- use_power = USE_POWER_IDLE
- icon_state = "map_scrubber_on"
-
-/obj/machinery/atmospherics/component/unary/vent_scrubber/on/welded
- welded = 1
-
-
/obj/machinery/atmospherics/component/unary/vent_scrubber/Initialize(mapload)
. = ..()
+ if(isnull(siphoning))
+ siphoning = siphoning_default
+ if(isnull(expanded))
+ expanded = expanded_default
+ if(isnull(scrub_ids) || isnull(scrub_groups))
+ reset_scrubbing_to_default()
air_contents.volume = ATMOS_DEFAULT_VOLUME_FILTER
- for(var/i in scrubbing_gas)
- if(!ispath(i))
- scrubbing_gas -= i
- var/path = gas_id2path(i)
- if(!path)
- stack_trace("Invalid gasid [i]")
- else
- scrubbing_gas += path
-
icon = null
- initial_loc = get_area(loc)
- area_uid = initial_loc.uid
+ registered_area = get_area(loc)
+ area_uid = registered_area.uid
+ registered_area?.register_scrubber(src)
if (!id_tag)
assign_uid()
id_tag = num2text(uid)
/obj/machinery/atmospherics/component/unary/vent_scrubber/Destroy()
unregister_radio(src, frequency)
- if(initial_loc)
- initial_loc.air_scrub_info -= id_tag
- initial_loc.air_scrub_names -= id_tag
+ registered_area?.unregister_scrubber(src)
return ..()
+/obj/machinery/atmospherics/component/unary/vent_scrubber/proc/reset_scrubbing_to_default()
+ scrub_ids = list()
+ scrub_groups = NONE
+ var/list/default = SSair.scrubber_defaults[scrub_default]
+ if(isnull(default))
+ return
+ for(var/key in default)
+ if(istext(key))
+ scrub_ids += key
+ else if(isnum(key))
+ scrub_groups |= key
/obj/machinery/atmospherics/component/unary/vent_scrubber/update_icon(safety = 0)
if(!check_icon_cache())
@@ -81,7 +114,7 @@
else if(!powered())
scrubber_icon += "off"
else
- scrubber_icon += "[use_power ? "[scrubbing ? "on" : "in"]" : "off"]"
+ scrubber_icon += "[on ? "[siphoning ? "in" : "on"]" : "off"]"
add_overlay(icon_manager.get_atmos_icon("device", , , scrubber_icon))
@@ -116,22 +149,16 @@
"tag" = id_tag,
"device" = "AScr",
"timestamp" = world.time,
- "power" = use_power,
- "scrubbing" = scrubbing,
- "panic" = panic,
- "filter_o2" = (/datum/gas/oxygen in scrubbing_gas),
- "filter_n2" = (/datum/gas/nitrogen in scrubbing_gas),
- "filter_co2" = (/datum/gas/carbon_dioxide in scrubbing_gas),
- "filter_phoron" = (/datum/gas/phoron in scrubbing_gas),
- "filter_n2o" = (/datum/gas/nitrous_oxide in scrubbing_gas),
- "filter_fuel" = (/datum/gas/volatile_fuel in scrubbing_gas),
- "sigtype" = "status"
+ "power" = on,
+ "scrubbing" = !siphoning, // legacy
+ "siphoning" = siphoning,
+ "expanded" = expanded,
+ "panic" = siphoning, // legacy
+ "scrub_ids" = scrub_ids,
+ "scrub_groups" = scrub_groups,
+ "sigtype" = "status",
)
- if(!initial_loc.air_scrub_names[id_tag])
- var/new_name = "[initial_loc.name] Air Scrubber #[initial_loc.air_scrub_names.len+1]"
- initial_loc.air_scrub_names[id_tag] = new_name
- src.name = new_name
- initial_loc.air_scrub_info[id_tag] = signal.data
+
radio_connection.post_signal(src, signal, radio_filter_out)
return 1
@@ -148,7 +175,7 @@
/obj/machinery/atmospherics/component/unary/vent_scrubber/proc/can_scrub()
if(machine_stat & (NOPOWER|BROKEN))
return 0
- if(!use_power)
+ if(!on)
return 0
if(welded)
return 0
@@ -160,31 +187,23 @@
if (hibernate)
return 1
-
if (!node)
- update_use_power(USE_POWER_OFF)
- //broadcast_status()
+ set_on(FALSE)
if(!can_scrub())
return 0
var/datum/gas_mixture/environment = loc.return_air()
+ var/using_power = expanded? expanded_power + power_rating : power_rating
- var/power_draw = -1
- if(scrubbing)
- //limit flow rate from turfs
- var/transfer_moles = min(environment.total_moles, environment.total_moles*MAX_SCRUBBER_FLOWRATE/environment.volume) //group_multiplier gets divided out here
+ if(siphoning)
+ power_current = xgm_pump_gas(environment, air_contents, limit_power = using_power)
+ else
+ var/using_volume = expanded? expanded_scrub + scrub_volume : scrub_volume
+ power_current = xgm_scrub_gas_volume(environment, air_contents, scrub_ids, scrub_groups, using_volume, using_power, scrub_boost)
- power_draw = scrub_gas(src, scrubbing_gas, environment, air_contents, transfer_moles, power_rating)
- else //Just siphon all air
- //limit flow rate from turfs
- var/transfer_moles = min(environment.total_moles, environment.total_moles*MAX_SIPHON_FLOWRATE/environment.volume) //group_multiplier gets divided out here
-
- power_draw = pump_gas(src, environment, air_contents, transfer_moles, power_rating)
-
-
- if (power_draw >= 0)
- last_power_draw = power_draw
- use_power(power_draw)
+ if (power_current >= 0)
+ last_power_draw_legacy = power_current
+ use_power(power_current)
if(network)
network.update = 1
@@ -195,90 +214,6 @@
update_icon()
update_underlays()
-/obj/machinery/atmospherics/component/unary/vent_scrubber/receive_signal(datum/signal/signal)
- if(machine_stat & (NOPOWER|BROKEN))
- return
- if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
- return 0
-
- if(signal.data["power"] != null)
- update_use_power(text2num(signal.data["power"]))
- if(signal.data["power_toggle"] != null)
- update_use_power(!use_power)
-
- if(signal.data["panic_siphon"]) //must be before if("scrubbing" thing
- panic = text2num(signal.data["panic_siphon"])
- if(panic)
- update_use_power(USE_POWER_IDLE)
- scrubbing = 0
- else
- scrubbing = 1
- if(signal.data["toggle_panic_siphon"] != null)
- panic = !panic
- if(panic)
- update_use_power(USE_POWER_IDLE)
- scrubbing = 0
- else
- scrubbing = 1
-
- if(signal.data["scrubbing"] != null)
- scrubbing = text2num(signal.data["scrubbing"])
- if(scrubbing)
- panic = 0
- if(signal.data["toggle_scrubbing"])
- scrubbing = !scrubbing
- if(scrubbing)
- panic = 0
-
- var/list/toggle = list()
-
- if(!isnull(signal.data["o2_scrub"]) && text2num(signal.data["o2_scrub"]) != (/datum/gas/oxygen in scrubbing_gas))
- toggle += /datum/gas/oxygen
- else if(signal.data["toggle_o2_scrub"])
- toggle += /datum/gas/oxygen
-
- if(!isnull(signal.data["n2_scrub"]) && text2num(signal.data["n2_scrub"]) != (/datum/gas/nitrogen in scrubbing_gas))
- toggle += /datum/gas/nitrogen
- else if(signal.data["toggle_n2_scrub"])
- toggle += /datum/gas/nitrogen
-
- if(!isnull(signal.data["co2_scrub"]) && text2num(signal.data["co2_scrub"]) != (/datum/gas/carbon_dioxide in scrubbing_gas))
- toggle += /datum/gas/carbon_dioxide
- else if(signal.data["toggle_co2_scrub"])
- toggle += /datum/gas/carbon_dioxide
-
- if(!isnull(signal.data["tox_scrub"]) && text2num(signal.data["tox_scrub"]) != (/datum/gas/phoron in scrubbing_gas))
- toggle += /datum/gas/phoron
- else if(signal.data["toggle_tox_scrub"])
- toggle += /datum/gas/phoron
-
- if(!isnull(signal.data["n2o_scrub"]) && text2num(signal.data["n2o_scrub"]) != (/datum/gas/nitrous_oxide in scrubbing_gas))
- toggle += /datum/gas/nitrous_oxide
- else if(signal.data["toggle_n2o_scrub"])
- toggle += /datum/gas/nitrous_oxide
-
- if(!isnull(signal.data["fuel_scrub"]) && text2num(signal.data["fuel_scrub"]) != (/datum/gas/volatile_fuel in scrubbing_gas))
- toggle += /datum/gas/volatile_fuel
- else if(signal.data["toggle_fuel_scrub"])
- toggle += /datum/gas/volatile_fuel
-
- scrubbing_gas ^= toggle
-
- if(signal.data["init"] != null)
- name = signal.data["init"]
- return
-
- if(signal.data["status"] != null)
- spawn(2)
- broadcast_status()
- return //do not update_icon
-
-// log_admin("DEBUG \[[world.timeofday]\]: vent_scrubber/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
- spawn(2)
- broadcast_status()
- update_icon()
- return
-
/obj/machinery/atmospherics/component/unary/vent_scrubber/power_change()
var/old_stat = machine_stat
..()
@@ -288,7 +223,7 @@
/obj/machinery/atmospherics/component/unary/vent_scrubber/attackby(var/obj/item/W as obj, var/mob/user as mob)
if (!W.is_wrench())
return ..()
- if (!(machine_stat & NOPOWER) && use_power)
+ if (!(machine_stat & NOPOWER) && on)
to_chat(user, "You cannot unwrench \the [src], turn it off first.")
return 1
var/turf/T = src.loc
@@ -309,7 +244,7 @@
/obj/machinery/atmospherics/component/unary/vent_scrubber/examine(mob/user, dist)
. = ..()
- . += "A small gauge in the corner reads [round(last_flow_rate, 0.1)] L/s; [round(last_power_draw)] W"
+ . += "A small gauge in the corner reads [round(last_flow_rate_legacy, 0.1)] L/s; [round(last_power_draw_legacy)] W"
if(welded)
. += "It seems welded shut."
@@ -338,3 +273,175 @@
return 1
else
..()
+
+/**
+ * encodes data for AtmosScrubberControl interface component
+ */
+/obj/machinery/atmospherics/component/unary/vent_scrubber/proc/ui_scrubber_data()
+ return list(
+ "siphon" = siphoning,
+ "expand" = expanded,
+ "scrubIDs" = scrub_ids,
+ "scrubGroups" = scrub_groups,
+ "power" = on,
+ )
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ var/target = params["target"]
+ switch(action)
+ if("power")
+ set_on(!on)
+ return TRUE
+ if("expand")
+ //! warning: legacy
+ expanded = !expanded
+ update_icon()
+ return TRUE
+ if("siphon")
+ //! warning: legacy
+ siphoning = !siphoning
+ update_icon()
+ return TRUE
+ if("id")
+ if(!global.gas_data.gas_id_filterable(target))
+ return TRUE
+ scrub_ids ^= target
+ return TRUE
+ if("group")
+ // the << 0 gets rid of any floating points incase someone somehow puts in a non bitfield
+ target = (text2num(target) << 0)
+ if(!global.gas_data.gas_groups_all_filterable(target))
+ return TRUE
+ scrub_groups ^= target
+ return TRUE
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["state"] = ui_scrubber_data()
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["gasContext"] = global.gas_data.tgui_gas_context()
+ .["name"] = name
+
+//* Signal Handling - Order of application is same as these comments.
+/// environmental: void. set to ignore the signal if we're not an environmental vent.
+/// hard_reset: resets everything to default.
+/// power: 0 | 1. sets us to be on/off. overrides power_toggle.
+/// power_toggle: void. toggles us on/off.
+/// siphon: 0 | 1 | "default". sets if we're siphoning. overrides siphon_toggle.
+/// siphon_toggle: void. toggles siphoning on/off.
+/// expand: 0 | 1 | "default". sets if we're expanded / high powered mode. overrides expand_toggle.
+/// expand_toggle: void. sets if we're in high powered mode.
+/// scrub_reset: resets scrubbing to default.
+/// scrub_ids: list[string]. sets scrub ids. overrides scrub_ids_toggle.
+/// scrub_ids_toggle: list[string]. toggles scrubbing gas ids.
+/// scrub_groups: bitfield. sets groups to scrub. overrides scrub_groups_toggle.
+/// scrub_groups_toggle: bitfield. toggles scrubbing these groups.
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/receive_signal(datum/signal/signal)
+ if(machine_stat & (NOPOWER|BROKEN))
+ return
+ if(!signal.data["tag"] || (signal.data["tag"] != id_tag) || (signal.data["sigtype"]!="command"))
+ return 0
+
+ if(!isnull(signal.data["environmental"]) && environmental)
+ return FALSE
+
+ if(!isnull(signal.data["hard_reset"]))
+ expanded = expanded_default
+ siphoning = siphoning_default
+ reset_scrubbing_to_default()
+ if(!isnull(signal.data["power"]))
+ set_on(!!text2num(signal.data["power"]))
+ else if(!isnull(signal.data["power_toggle"]))
+ set_on(!on)
+ if(!isnull(signal.data["siphon"]))
+ siphoning = signal.data["siphon"] == "default"? siphoning_default : !!text2num(signal.data["siphon"])
+ else if(!isnull(signal.data["siphon_toggle"]))
+ siphoning = !siphoning
+ if(!isnull(signal.data["expand"]))
+ expanded = signal.data["expand"] == "default"? expanded_default : !!text2num(signal.data["expand"])
+ else if(!isnull(signal.data["expand_toggle"]))
+ expanded = !expanded
+ if(!isnull(signal.data["scrub_reset"]))
+ reset_scrubbing_to_default()
+ if(!isnull(signal.data["scrub_ids"]))
+ var/list/target_ids = signal.data["scrub_ids"]
+ if(islist(target_ids))
+ if(target_ids.len > SCRUBBER_MAX_GAS_IDS)
+ target_ids.len = SCRUBBER_MAX_GAS_IDS
+ for(var/key in target_ids)
+ if(!global.gas_data.gas_id_filterable(key))
+ target_ids -= key
+ scrub_ids = target_ids
+ else if(!isnull(signal.data["scrub_ids_toggle"]))
+ var/list/target_ids = signal.data["scrub_ids_toggle"]
+ if(islist(target_ids))
+ if(target_ids.len > SCRUBBER_MAX_GAS_IDS * 2)
+ target_ids.len = SCRUBBER_MAX_GAS_IDS * 2
+ for(var/key in target_ids)
+ if(!global.gas_data.gas_id_filterable(key))
+ target_ids -= key
+ scrub_ids ^= target_ids
+ if(scrub_ids.len > SCRUBBER_MAX_GAS_IDS)
+ scrub_ids.len = SCRUBBER_MAX_GAS_IDS
+ if(!isnull(signal.data["scrub_groups"]))
+ scrub_groups = (GAS_GROUPS_FILTERABLE & (text2num(signal.data["scrub_groups"])))
+ else if(!isnull(signal.data["scrub_groups_toggle"]))
+ scrub_groups ^= (GAS_GROUPS_FILTERABLE & (text2num(signal.data["scrub_groups_toggle"])))
+
+ //! legacy below
+
+ if(signal.data["status"] != null)
+ spawn(2)
+ broadcast_status()
+ return //do not update_icon
+
+// log_admin("DEBUG \[[world.timeofday]\]: vent_scrubber/receive_signal: unknown command \"[signal.data["command"]]\"\n[signal.debug_print()]")
+ spawn(2)
+ broadcast_status()
+ update_icon()
+
+//* Subtypes
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/on
+ on = TRUE
+ icon_state = "map_scrubber_on"
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/on/welded
+ welded = 1
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/retro
+ icon_state = "map_scrubber_off" /// Will get replaced on mapload
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/retro/on
+ on = TRUE
+ icon_state = "map_scrubber_on"
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/retro/on/welded
+ welded = 1
+
+/obj/machinery/atmospherics/component/unary/vent_scrubber/retro/update_icon(safety = 0)
+ if(!check_icon_cache())
+ return
+
+ cut_overlays()
+
+ var/scrubber_icon = "scrubber"
+
+ var/turf/T = get_turf(src)
+ if(!istype(T))
+ return
+
+ if(welded)
+ scrubber_icon += "retro_weld"
+ else if(!powered())
+ scrubber_icon += "retro_off"
+ else
+ scrubber_icon += "[use_power ? "[siphoning ? "retro_in" : "retro_on"]" : "retro_off"]"
+
+ add_overlay(icon_manager.get_atmos_icon("device", , , scrubber_icon))
diff --git a/code/modules/atmospherics/machinery/machinery.dm b/code/modules/atmospherics/machinery/machinery.dm
index dfe8bf9a638..0526cc978f1 100644
--- a/code/modules/atmospherics/machinery/machinery.dm
+++ b/code/modules/atmospherics/machinery/machinery.dm
@@ -25,8 +25,6 @@ Pipelines + Other Objects -> Pipe network
///The color of the pipe
var/pipe_color
- ///The maximum amount of power the machine can use to do work, affects how powerful the machine is, in Watts
- var/power_rating
///The flags of the pipe/component (PIPING_ALL_LAYER | PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY | PIPING_CARDINAL_AUTONORMALIZE)
var/pipe_flags = PIPING_DEFAULT_LAYER_ONLY
///What pipe layer can this connect to.
@@ -48,6 +46,9 @@ Pipelines + Other Objects -> Pipe network
var/obj/machinery/atmospherics/node1
var/obj/machinery/atmospherics/node2
+ var/last_flow_rate_legacy = 0
+ var/last_power_draw_legacy = 0
+
/obj/machinery/atmospherics/Initialize(mapload, newdir)
. = ..()
if(!icon_manager)
@@ -91,8 +92,8 @@ Pipelines + Other Objects -> Pipe network
/obj/machinery/atmospherics/proc/check_connectable(obj/machinery/atmospherics/target)
return (src.connect_types & target.connect_types)
-/obj/machinery/atmospherics/attackby(atom/A, mob/user as mob)
- if(istype(A, /obj/item/pipe_painter))
+/obj/machinery/atmospherics/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
+ if(istype(I, /obj/item/pipe_painter))
return
..()
@@ -131,8 +132,8 @@ Pipelines + Other Objects -> Pipe network
return node.pipe_color
/obj/machinery/atmospherics/process(delta_time)
- last_flow_rate = 0
- last_power_draw = 0
+ last_flow_rate_legacy = 0
+ last_power_draw_legacy = 0
build_network()
@@ -164,9 +165,6 @@ Pipelines + Other Objects -> Pipe network
/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference)
-/obj/machinery/atmospherics/update_icon()
- return null
-
/obj/machinery/atmospherics/proc/unsafe_pressure()
var/datum/gas_mixture/int_air = return_air()
var/datum/gas_mixture/env_air = loc.return_air()
@@ -241,3 +239,19 @@ Pipelines + Other Objects -> Pipe network
layer = PIPE_LAYER
else
reset_plane_and_layer()
+
+// todo: refactor
+
+/obj/machinery/atmospherics/is_hidden_underfloor()
+ var/turf/T = loc
+ return istype(T) && (level == 1) && !T.is_plating()
+
+/obj/machinery/atmospherics/should_hide_underfloor()
+ return level == 1
+
+/**
+ * currently unimplemented
+ * call when our internal settings change to push changes to relevant uis.
+ */
+/obj/machinery/atmospherics/proc/ui_settings_updated()
+ // todo: implement
diff --git a/code/game/machinery/atmoalter/meter.dm b/code/modules/atmospherics/machinery/meter.dm
similarity index 100%
rename from code/game/machinery/atmoalter/meter.dm
rename to code/modules/atmospherics/machinery/meter.dm
diff --git a/code/modules/atmospherics/machinery/pipes/tank.dm b/code/modules/atmospherics/machinery/pipes/tank.dm
index 36891d524bc..928e23754e5 100644
--- a/code/modules/atmospherics/machinery/pipes/tank.dm
+++ b/code/modules/atmospherics/machinery/pipes/tank.dm
@@ -71,8 +71,8 @@
if(istype(W, /obj/item/pipe_painter))
return
- if(istype(W, /obj/item/analyzer) && in_range(user, src))
- var/obj/item/analyzer/A = W
+ if(istype(W, /obj/item/atmos_analyzer) && in_range(user, src))
+ var/obj/item/atmos_analyzer/A = W
A.analyze_gases(src, user)
/obj/machinery/atmospherics/pipe/tank/air
@@ -84,8 +84,8 @@
air_temporary.volume = volume
air_temporary.temperature = T20C
- air_temporary.adjust_multi(/datum/gas/oxygen, (start_pressure*O2STANDARD)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature), \
- /datum/gas/nitrogen,(start_pressure*N2STANDARD)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_multi(GAS_ID_OXYGEN, (start_pressure*O2STANDARD)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature), \
+ GAS_ID_NITROGEN,(start_pressure*N2STANDARD)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "air"
@@ -99,7 +99,7 @@
air_temporary.volume = volume
air_temporary.temperature = T20C
- air_temporary.adjust_gas(/datum/gas/oxygen, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_gas(GAS_ID_OXYGEN, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "o2"
@@ -113,7 +113,7 @@
air_temporary.volume = volume
air_temporary.temperature = T20C
- air_temporary.adjust_gas(/datum/gas/nitrogen, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_gas(GAS_ID_NITROGEN, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "n2"
@@ -127,7 +127,7 @@
air_temporary.volume = volume
air_temporary.temperature = T20C
- air_temporary.adjust_gas(/datum/gas/carbon_dioxide, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_gas(GAS_ID_CARBON_DIOXIDE, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "co2"
@@ -142,7 +142,7 @@
air_temporary.volume = volume
air_temporary.temperature = T20C
- air_temporary.adjust_gas(/datum/gas/phoron, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_gas(GAS_ID_PHORON, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "phoron"
@@ -156,7 +156,7 @@
air_temporary.volume = volume
air_temporary.temperature = T0C
- air_temporary.adjust_gas(/datum/gas/nitrous_oxide, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_gas(GAS_ID_NITROUS_OXIDE, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "n2o"
@@ -171,7 +171,7 @@
air_temporary.volume = volume
air_temporary.temperature = T20C
- air_temporary.adjust_gas(/datum/gas/chlorine, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
+ air_temporary.adjust_gas(GAS_ID_CHLORINE, (start_pressure)*(air_temporary.volume)/(R_IDEAL_GAS_EQUATION*air_temporary.temperature))
. = ..()
icon_state = "hazard"
diff --git a/code/game/machinery/atmoalter/area_atmos_computer.dm b/code/modules/atmospherics/machinery/portable/area_atmos_computer.dm
similarity index 97%
rename from code/game/machinery/atmoalter/area_atmos_computer.dm
rename to code/modules/atmospherics/machinery/portable/area_atmos_computer.dm
index ab76632a49e..e4ea08ae3da 100644
--- a/code/game/machinery/atmoalter/area_atmos_computer.dm
+++ b/code/modules/atmospherics/machinery/portable/area_atmos_computer.dm
@@ -1,3 +1,5 @@
+// todo: wrong folder?
+
/obj/machinery/computer/area_atmos
name = "Area Air Control"
desc = "A computer used to control the stationary scrubbers and pumps in the area."
@@ -44,8 +46,8 @@
"name" = scrubber.name,
"on" = scrubber.on,
"pressure" = scrubber.air_contents.return_pressure(),
- "flow_rate" = scrubber.last_flow_rate,
- "load" = scrubber.last_power_draw,
+ "flow_rate" = scrubber.last_flow_rate_legacy,
+ "load" = scrubber.last_power_draw_legacy,
"area" = get_area(scrubber),
)))
diff --git a/code/game/machinery/atmoalter/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
similarity index 89%
rename from code/game/machinery/atmoalter/canister.dm
rename to code/modules/atmospherics/machinery/portable/canister.dm
index 402cc3c211e..5aa036fced3 100644
--- a/code/game/machinery/atmoalter/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -292,7 +292,7 @@ update_flag
..()
/obj/machinery/portable_atmospherics/canister/attackby(var/obj/item/W as obj, var/mob/user as mob)
- if(!W.is_wrench() && !istype(W, /obj/item/tank) && !istype(W, /obj/item/analyzer) && !istype(W, /obj/item/pda))
+ if(!W.is_wrench() && !istype(W, /obj/item/tank) && !istype(W, /obj/item/atmos_analyzer) && !istype(W, /obj/item/pda))
visible_message(SPAN_WARNING("\The [user] hits \the [src] with \a [W]!"))
src.health -= W.damage_force
healthcheck()
@@ -439,109 +439,109 @@ update_flag
/obj/machinery/portable_atmospherics/canister/phoron/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/phoron, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_PHORON, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/oxygen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/oxygen, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_OXYGEN, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/oxygen/prechilled/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/oxygen, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_OXYGEN, MolesForPressure())
src.air_contents.temperature = 80
src.update_icon()
/obj/machinery/portable_atmospherics/canister/nitrous_oxide/Initialize(mapload)
. = ..()
- air_contents.adjust_gas(/datum/gas/nitrous_oxide, MolesForPressure())
+ air_contents.adjust_gas(GAS_ID_NITROUS_OXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/helium/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/helium, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_HELIUM, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/carbon_monoxide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/carbon_monoxide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_CARBON_MONOXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/methyl_bromide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/methyl_bromide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_METHYL_BROMIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/nitrodioxide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/nitrodioxide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_NITROGEN_DIOXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/nitricoxide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/nitricoxide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_NITRIC_OXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/methane/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/methane, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_METHANE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/argon/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/argon, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_ARGON, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/krypton/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/krypton, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_KRYPTON, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/neon/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/neon, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_NEON, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/ammonia/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/ammonia, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_AMMONIA, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/xenon/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/xenon, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_XENON, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/chlorine/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/chlorine, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_CHLORINE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/sulfur_dioxide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/sulfur_dioxide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_SULFUR_DIOXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/hydrogen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/hydrogen, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_HYDROGEN, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/tritium/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/hydrogen/tritium, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_TRITIUM, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/deuterium/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/hydrogen/deuterium, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_DEUTERIUM, MolesForPressure())
src.update_icon()
//Dirty way to fill room with gas. However it is a bit easier to do than creating some floor/engine/n2o -rastaf0
/obj/machinery/portable_atmospherics/canister/nitrous_oxide/roomfiller/Initialize(mapload)
. = ..()
- air_contents.gas[/datum/gas/nitrous_oxide] = 9*4000
+ air_contents.gas[GAS_ID_NITROUS_OXIDE] = 9*4000
return INITIALIZE_HINT_LATELOAD
/obj/machinery/portable_atmospherics/canister/nitrous_oxide/roomfiller/LateInitialize()
@@ -555,35 +555,35 @@ update_flag
/obj/machinery/portable_atmospherics/canister/nitrogen/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/nitrogen, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_NITROGEN, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/carbon_dioxide/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/carbon_dioxide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_CARBON_DIOXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/air/Initialize(mapload)
. = ..()
var/list/air_mix = StandardAirMix()
- src.air_contents.adjust_multi(/datum/gas/oxygen, air_mix[/datum/gas/oxygen], /datum/gas/nitrogen, air_mix[/datum/gas/nitrogen])
+ src.air_contents.adjust_multi(GAS_ID_OXYGEN, air_mix[GAS_ID_OXYGEN], GAS_ID_NITROGEN, air_mix[GAS_ID_NITROGEN])
src.update_icon()
//R-UST port
// Special types used for engine setup admin verb, they contain double amount of that of normal canister.
/obj/machinery/portable_atmospherics/canister/nitrogen/engine_setup/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/nitrogen, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_NITROGEN, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/carbon_dioxide/engine_setup/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/carbon_dioxide, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_CARBON_DIOXIDE, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/phoron/engine_setup/Initialize(mapload)
. = ..()
- src.air_contents.adjust_gas(/datum/gas/phoron, MolesForPressure())
+ src.air_contents.adjust_gas(GAS_ID_PHORON, MolesForPressure())
src.update_icon()
/obj/machinery/portable_atmospherics/canister/take_damage(var/damage)
diff --git a/code/game/machinery/atmoalter/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
similarity index 60%
rename from code/game/machinery/atmoalter/portable_atmospherics.dm
rename to code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
index 763ace9a7e7..499241555ca 100644
--- a/code/game/machinery/atmoalter/portable_atmospherics.dm
+++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm
@@ -2,9 +2,32 @@
name = "atmoalter"
use_power = USE_POWER_OFF
layer = OBJ_LAYER // These are mobile, best not be under everything.
+ interaction_flags_machine = INTERACT_MACHINE_OPEN | INTERACT_MACHINE_OFFLINE
+
+ /// allow multitool "hijacking" even if this is controlled by something else
+ /// set to non-null for delay.
+ var/default_multitool_hijack = null
+ /// allow access normally
+ var/default_access_interface = FALSE
+ /// tgui interface
+ var/tgui_interface
+ /// ui flags
+ var/atmos_portable_ui_flags = NONE
+
+ /// on right now
+ var/on = FALSE
+ /// current flow, liters
+ var/flow_current = 0
+ /// flow maximum
+ var/flow_maximum = 1000
+ /// flow setting
+ var/flow_setting
+
+
var/datum/gas_mixture/air_contents = new
var/obj/machinery/atmospherics/portables_connector/connected_port
+ // todo: tweak this; not all portables can / should necessarily be able to hold tanks.
var/obj/item/tank/holding
var/volume = 0
@@ -16,6 +39,8 @@
/obj/machinery/portable_atmospherics/Initialize(mapload)
. = ..()
+ if(isnull(flow_setting))
+ flow_setting = flow_maximum
air_contents.volume = volume
air_contents.temperature = T20C
@@ -26,26 +51,94 @@
/obj/machinery/portable_atmospherics/Initialize(mapload)
. = ..()
- spawn()
- var/obj/machinery/atmospherics/portables_connector/port = locate() in loc
- if(port)
- connect(port)
- update_icon()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/portable_atmospherics/LateInitialize()
+ var/obj/machinery/atmospherics/portables_connector/port = locate() in loc
+ if(port)
+ connect(port)
+ update_icon()
+ return ..()
/obj/machinery/portable_atmospherics/process(delta_time)
+ flow_current = 0
+
if(!connected_port) //only react when pipe_network will ont it do it for you
//Allow for reactions
air_contents.react()
else
update_icon()
+/obj/machinery/portable_atmospherics/interact(mob/user)
+ if(!default_access_interface)
+ user.action_feedback(SPAN_WARNING("You can't directly interact with [src]. Use an area atmospherics control computer, if there is one."), src)
+ return FALSE
+ ui_interact(user)
+ return TRUE
+
+/obj/machinery/portable_atmospherics/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
+ if(!tgui_interface)
+ return ..()
+
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(isnull(ui))
+ ui = new(user, src, tgui_interface)
+
+/obj/machinery/portable_atmospherics/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["controlFlags"] = atmos_portable_ui_flags
+ .["useCharge"] = FALSE
+ .["flowMax"] = flow_maximum
+
+/obj/machinery/portable_atmospherics/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["flow"] = flow_current
+ .["on"] = on
+ .["tank"] = holding?.tgui_tank_data()
+
+/obj/machinery/portable_atmospherics/ui_act(action, list/params, datum/tgui/ui)
+ . = ..()
+ if(.)
+ return
+ switch(action)
+ if("togglePower")
+ if(!(atmos_portable_ui_flags & ATMOS_PORTABLE_UI_TOGGLE_POWER))
+ return TRUE
+ set_on(!on)
+ return TRUE
+ if("setFlow")
+ var/amt = params["value"]
+ if(!isnum(amt))
+ return TRUE
+ if(!(atmos_portable_ui_flags & ATMOS_PORTABLE_UI_SET_FLOW))
+ return TRUE
+ set_flow(amt)
+ return TRUE
+ if("eject")
+ if(isnull(holding))
+ return TRUE
+ usr.action_feedback(SPAN_NOTICE("You remove [holding] from [src]."), src)
+ usr.grab_item_from_interacted_with(holding, src)
+ holding = null
+ return TRUE
+
+/obj/machinery/portable_atmospherics/proc/set_on(enabled)
+ on = enabled
+ update_icon()
+
+/obj/machinery/portable_atmospherics/proc/set_flow(liters)
+ flow_setting = clamp(liters, 0, flow_maximum)
+
+/obj/machinery/portable_atmospherics/return_air()
+ return air_contents
+
/obj/machinery/portable_atmospherics/blob_act()
qdel(src)
/obj/machinery/portable_atmospherics/proc/StandardAirMix()
return list(
- /datum/gas/oxygen = O2STANDARD * MolesForPressure(),
- /datum/gas/nitrogen = N2STANDARD * MolesForPressure())
+ GAS_ID_OXYGEN = O2STANDARD * MolesForPressure(),
+ GAS_ID_NITROGEN = N2STANDARD * MolesForPressure())
/obj/machinery/portable_atmospherics/proc/MolesForPressure(var/target_pressure = start_pressure)
return (target_pressure * air_contents.volume) / (R_IDEAL_GAS_EQUATION * air_contents.temperature)
@@ -97,13 +190,13 @@
if (network)
network.update = 1
-/obj/machinery/portable_atmospherics/attackby(var/obj/item/W as obj, var/mob/user as mob)
- if ((istype(W, /obj/item/tank) && !( src.destroyed )))
+/obj/machinery/portable_atmospherics/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
+ if ((istype(I, /obj/item/tank) && !( src.destroyed )))
if (holding && (user.a_intent != INTENT_GRAB))
return
- if(!user.attempt_insert_item_for_installation(W, src))
+ if(!user.attempt_insert_item_for_installation(I, src))
return
- var/obj/item/tank/T = W
+ var/obj/item/tank/T = I
if(holding)
user.grab_item_from_interacted_with(holding, src)
to_chat(user, SPAN_NOTICE("You quickly swap the tanks with the quick release valve."))
@@ -111,12 +204,12 @@
update_icon()
return
- else if (W.is_wrench())
+ else if (I.is_wrench())
if(connected_port)
disconnect()
to_chat(user, "You disconnect \the [src] from the port.")
update_icon()
- playsound(src, W.tool_sound, 50, 1)
+ playsound(src, I.tool_sound, 50, 1)
return
else
var/obj/machinery/atmospherics/portables_connector/possible_port = locate(/obj/machinery/atmospherics/portables_connector/) in loc
@@ -124,7 +217,7 @@
if(connect(possible_port))
to_chat(user, "You connect \the [src] to the port.")
update_icon()
- playsound(src, W.tool_sound, 50, 1)
+ playsound(src, I.tool_sound, 50, 1)
return
else
to_chat(user, "\The [src] failed to connect to the port.")
@@ -133,8 +226,8 @@
to_chat(user, "Nothing happens.")
return
- else if ((istype(W, /obj/item/analyzer)) && Adjacent(user))
- var/obj/item/analyzer/A = W
+ else if ((istype(I, /obj/item/atmos_analyzer)) && Adjacent(user))
+ var/obj/item/atmos_analyzer/A = I
A.analyze_gases(src, user)
/obj/machinery/portable_atmospherics/MouseDroppedOnLegacy(mob/living/carbon/O, mob/user as mob)
@@ -165,51 +258,6 @@
if (get_turf(user) == get_turf(src))
usr.visible_message("[user] climbs onto \the [src]!")
-/obj/machinery/portable_atmospherics/powered
- var/power_rating
- var/power_losses
- var/last_power_draw = 0
- var/obj/item/cell/cell
- var/use_cell = TRUE
- var/removeable_cell = TRUE
-
-/obj/machinery/portable_atmospherics/powered/powered()
- if(use_power) //using area power
- return ..()
- if(cell && cell.charge)
- return 1
- return 0
-
-/obj/machinery/portable_atmospherics/powered/attackby(obj/item/I, mob/user)
- if(use_cell && istype(I, /obj/item/cell))
- if(cell)
- to_chat(user, "There is already a power cell installed.")
- return
- if(!user.attempt_insert_item_for_installation(I, src))
- return
-
- var/obj/item/cell/C = I
-
- C.add_fingerprint(user)
- cell = C
- user.visible_message("[user] opens the panel on [src] and inserts [C].", "You open the panel on [src] and insert [C].")
- power_change()
- return
-
- if(I.is_screwdriver() && removeable_cell)
- if(!cell)
- to_chat(user, "There is no power cell installed.")
- return
-
- user.visible_message("[user] opens the panel on [src] and removes [cell].", "You open the panel on [src] and remove [cell].")
- playsound(src, I.tool_sound, 50, 1)
- cell.add_fingerprint(user)
- cell.forceMove(drop_location())
- cell = null
- power_change()
- return
- ..()
-
/obj/machinery/portable_atmospherics/proc/log_open()
if(air_contents.gas.len == 0)
return
diff --git a/code/modules/atmospherics/machinery/portable/powered.dm b/code/modules/atmospherics/machinery/portable/powered.dm
new file mode 100644
index 00000000000..e7a83f75bbd
--- /dev/null
+++ b/code/modules/atmospherics/machinery/portable/powered.dm
@@ -0,0 +1,85 @@
+/obj/machinery/portable_atmospherics/powered
+ /// power rating in watts
+ var/power_maximum = 7500
+ /// power setting in watts
+ var/power_setting
+ /// power usage current
+ var/power_current = 0
+
+ /// efficiency multiplier
+ var/efficiency_multiplier = 1
+
+ var/last_power_draw_legacy = 0
+ var/obj/item/cell/cell
+ var/use_cell = TRUE
+ var/removeable_cell = TRUE
+
+/obj/machinery/portable_atmospherics/powered/Initialize(mapload)
+ . = ..()
+ if(isnull(power_setting))
+ power_setting = power_maximum
+
+/obj/machinery/portable_atmospherics/powered/process(delta_time)
+ ..()
+ power_current = 0
+
+/obj/machinery/portable_atmospherics/powered/powered()
+ if(use_power) //using area power
+ return ..()
+ if(cell && cell.charge)
+ return 1
+ return 0
+
+/obj/machinery/portable_atmospherics/powered/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["useCharge"] = TRUE
+ .["maxCharge"] = isnull(cell)? cell.maxcharge : 0
+ .["powerRating"] = power_maximum
+ .["useCell"] = use_cell
+
+/obj/machinery/portable_atmospherics/powered/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["charge"] = isnull(cell)? cell.charge : 0
+ if(atmos_portable_ui_flags & (ATMOS_PORTABLE_UI_SEE_POWER | ATMOS_PORTABLE_UI_SET_POWER))
+ .["powerCurrent"] = power_current
+ .["hasCell"] = !isnull(cell)
+
+/obj/machinery/portable_atmospherics/powered/attackby(obj/item/I, mob/living/user, list/params, clickchain_flags, damage_multiplier)
+ if(use_cell && istype(I, /obj/item/cell))
+ if(cell)
+ to_chat(user, "There is already a power cell installed.")
+ return
+ if(!user.attempt_insert_item_for_installation(I, src))
+ return
+
+ var/obj/item/cell/C = I
+
+ C.add_fingerprint(user)
+ cell = C
+ user.visible_message("[user] opens the panel on [src] and inserts [C].", "You open the panel on [src] and insert [C].")
+ power_change()
+ update_static_data()
+ return
+
+ if(I.is_screwdriver() && removeable_cell)
+ if(!cell)
+ to_chat(user, "There is no power cell installed.")
+ return
+
+ user.visible_message("[user] opens the panel on [src] and removes [cell].", "You open the panel on [src] and remove [cell].")
+ playsound(src, I.tool_sound, 50, 1)
+ cell.add_fingerprint(user)
+ cell.forceMove(drop_location())
+ cell = null
+ power_change()
+ update_static_data()
+ return
+ ..()
+
+/obj/machinery/portable_atmospherics/powered/use_power(amount, chan, dt)
+ if(!use_cell)
+ return ..()
+ . = cell.use(DYNAMIC_W_TO_CELL_UNITS(amount, dt))
+ if(!cell.charge)
+ power_change()
+ update_icon()
diff --git a/code/game/machinery/atmoalter/pump.dm b/code/modules/atmospherics/machinery/portable/pump.dm
similarity index 94%
rename from code/game/machinery/atmoalter/pump.dm
rename to code/modules/atmospherics/machinery/portable/pump.dm
index 4b1f0faca85..1ae3d3bca5a 100644
--- a/code/game/machinery/atmoalter/pump.dm
+++ b/code/modules/atmospherics/machinery/portable/pump.dm
@@ -1,12 +1,10 @@
/obj/machinery/portable_atmospherics/powered/pump
name = "portable air pump"
-
icon = 'icons/obj/atmos.dmi'
icon_state = "psiphon:0"
density = 1
w_class = ITEMSIZE_NORMAL
- var/on = 0
var/direction_out = 0 //0 = siphoning, 1 = releasing
var/target_pressure = ONE_ATMOSPHERE
@@ -15,8 +13,7 @@
volume = 1000
- power_rating = 7500 //7500 W ~ 10 HP
- power_losses = 150
+ var/power_rating = 7500 //7500 W ~ 10 HP
/obj/machinery/portable_atmospherics/powered/pump/filled
start_pressure = 90 * ONE_ATMOSPHERE
@@ -25,9 +22,6 @@
. = ..()
cell = new/obj/item/cell/apc(src)
- var/list/air_mix = StandardAirMix()
- src.air_contents.adjust_multi(/datum/gas/oxygen, air_mix[/datum/gas/oxygen], /datum/gas/nitrogen, air_mix[/datum/gas/nitrogen])
-
/obj/machinery/portable_atmospherics/powered/pump/update_icon()
cut_overlays()
var/list/overlays_to_add = list()
@@ -85,12 +79,11 @@
power_draw = pump_gas(src, environment, air_contents, transfer_moles, power_rating)
if (power_draw < 0)
- last_flow_rate = 0
- last_power_draw = 0
+ last_flow_rate_legacy = 0
+ last_power_draw_legacy = 0
else
- power_draw = max(power_draw, power_losses)
cell.use_scaled(DYNAMIC_W_TO_CELL_UNITS(power_draw, 1))
- last_power_draw = power_draw
+ last_power_draw_legacy = power_draw
update_connected_network()
@@ -136,7 +129,7 @@
data["max_pressure"] = round(pressuremax)
- data["powerDraw"] = round(last_power_draw)
+ data["powerDraw"] = round(last_power_draw_legacy)
data["cellCharge"] = cell ? cell.charge : 0
data["cellMaxCharge"] = cell ? cell.maxcharge : 1
@@ -228,8 +221,8 @@
/obj/machinery/portable_atmospherics/powered/pump/huge/process(delta_time)
if(!anchored || (machine_stat & (NOPOWER|BROKEN)))
on = 0
- last_flow_rate = 0
- last_power_draw = 0
+ last_flow_rate_legacy = 0
+ last_power_draw_legacy = 0
update_icon()
var/new_use_power = 1 + on
if(new_use_power != use_power)
@@ -262,8 +255,8 @@
power_draw = pump_gas(src, environment, air_contents, transfer_moles, power_rating)
if (power_draw < 0)
- last_flow_rate = 0
- last_power_draw = 0
+ last_flow_rate_legacy = 0
+ last_power_draw_legacy = 0
else
use_power(power_draw)
update_connected_network()
diff --git a/code/modules/atmospherics/machinery/portable/scrubber.dm b/code/modules/atmospherics/machinery/portable/scrubber.dm
new file mode 100644
index 00000000000..7a6eb429664
--- /dev/null
+++ b/code/modules/atmospherics/machinery/portable/scrubber.dm
@@ -0,0 +1,205 @@
+/obj/machinery/portable_atmospherics/powered/scrubber
+ name = "portable air scrubber"
+ icon = 'icons/obj/atmos.dmi'
+ icon_state = "pscrubber:0"
+ density = TRUE
+ w_class = ITEMSIZE_NORMAL
+
+ atmos_portable_ui_flags = ATMOS_PORTABLE_UI_TOGGLE_POWER | ATMOS_PORTABLE_UI_SEE_POWER | ATMOS_PORTABLE_UI_SEE_FLOW
+ power_maximum = 7500
+ flow_maximum = 5000
+
+ volume = 1000
+
+ /// scrubbing ids
+ var/list/scrubbing_ids
+ /// scrubbing groups
+ var/scrubbing_groups
+ /// molar rate current
+ var/transfer_current = 0
+ /// minimum moles to scrub per tick (if enough power) even if flow is not enough
+ var/scrub_mole_boost = 50
+
+/obj/machinery/portable_atmospherics/powered/scrubber/Initialize(mapload)
+ . = ..()
+ cell = new /obj/item/cell/apc(src)
+
+//! LEGACY BELOW
+
+/obj/machinery/portable_atmospherics/powered/scrubber/emp_act(severity)
+ if(machine_stat & (BROKEN|NOPOWER))
+ ..(severity)
+ return
+
+ if(prob(50/severity))
+ on = !on
+ update_icon()
+
+ ..(severity)
+
+/obj/machinery/portable_atmospherics/powered/scrubber/update_icon_state()
+ . = ..()
+ if(on && cell && cell.charge)
+ icon_state = "pscrubber:1"
+ else
+ icon_state = "pscrubber:0"
+
+/obj/machinery/portable_atmospherics/powered/scrubber/update_overlays()
+ . = ..()
+ if(holding)
+ . += "scrubber-open"
+
+ if(connected_port)
+ . += "scrubber-connector"
+
+//! LEGACY ABOVE
+
+/obj/machinery/portable_atmospherics/powered/scrubber/ui_static_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["atmosContext"] = global.gas_data.tgui_gas_context()
+ .["scrubbingIds"] = scrubbing_ids
+ .["scrubbingGroups"] = scrubbing_groups
+
+/obj/machinery/portable_atmospherics/powered/scrubber/ui_data(mob/user)
+ . = ..()
+ .["moleRate"] = transfer_current
+
+/obj/machinery/portable_atmospherics/powered/scrubber/ui_act(action, params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("scrubID")
+ var/target = params["target"]
+ if(!istext(target))
+ return FALSE
+ if(!global.gas_data.gas_id_filterable(target))
+ return FALSE
+ if(target in scrubbing_ids)
+ scrubbing_ids -= target
+ else
+ scrubbing_ids += target
+ push_ui_data(data = list("scrubbingIds" = scrubbing_ids))
+ return TRUE
+ if("scrubGroup")
+ var/target = params["target"]
+ if(!isnum(target))
+ return FALSE
+ if(!global.gas_data.gas_groups_filterable(target))
+ return FALSE
+ scrubbing_groups ^= target
+ push_ui_data(data = list("scrubbingGroups" = scrubbing_groups))
+ return TRUE
+
+/obj/machinery/portable_atmospherics/powered/scrubber/process(delta_time)
+ ..()
+
+ if(on && cell?.charge)
+ var/datum/gas_mixture/scrubbing = isnull(holding)? loc.return_air() : holding.air_contents
+ var/old_mols = scrubbing.total_moles
+ // todo: compensate for delta_time, right now this is not stable and will go faster/slower based on SSair tick rate.
+ power_current = xgm_scrub_gas_volume(scrubbing, air_contents, scrubbing_ids, scrubbing_groups, flow_setting / scrubbing.group_multiplier, power_setting * efficiency_multiplier, scrub_mole_boost) / efficiency_multiplier
+ transfer_current = (old_mols - scrubbing.total_moles) / delta_time
+ update_connected_network()
+
+ if(power_current)
+ use_power(power_current, dt = delta_time)
+
+//Huge scrubber
+/obj/machinery/portable_atmospherics/powered/scrubber/huge
+ name = "Huge Air Scrubber"
+ icon = 'icons/obj/atmos_vr.dmi'
+ icon_state = "scrubber:0"
+ anchored = TRUE
+ // just 1 million because no way to offload. yet.
+ volume = 1000000
+ flow_maximum = 50000
+ use_cell = FALSE
+
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 50 //internal circuitry, friction losses and stuff
+ active_power_usage = 1000 // Blowers running
+ power_maximum = 100000 //100 kW ~ 135 HP
+
+ var/global/gid = 1
+ var/id = 0
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/Initialize(mapload)
+ . = ..()
+ cell = null
+
+ id = gid
+ gid++
+
+ name = "[name] (ID [id])"
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/attack_hand(mob/user, list/params)
+ to_chat(user, "You can't directly interact with this machine. Use the scrubber control console.")
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/update_icon()
+ cut_overlays()
+
+ if(on && !(machine_stat & (NOPOWER|BROKEN)))
+ icon_state = "scrubber:1"
+ else
+ icon_state = "scrubber:0"
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/power_change()
+ var/old_stat = machine_stat
+ ..()
+ if (old_stat != machine_stat)
+ update_icon()
+
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/attackby(var/obj/item/I as obj, var/mob/user as mob)
+ if(I.is_wrench())
+ if(on)
+ to_chat(user, "Turn \the [src] off first!")
+ return
+
+ anchored = !anchored
+ playsound(src.loc, I.tool_sound, 50, 1)
+ to_chat(user, "You [anchored ? "wrench" : "unwrench"] \the [src].")
+
+ return
+
+ //doesn't use power cells
+ if(istype(I, /obj/item/cell))
+ return
+ if(I.is_screwdriver())
+ return
+
+ //doesn't hold tanks
+ if(istype(I, /obj/item/tank))
+ return
+
+ ..()
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary
+ name = "Stationary Air Scrubber"
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/attackby(var/obj/item/I as obj, var/mob/user as mob)
+ if(I.is_wrench())
+ to_chat(user, "The bolts are too tight for you to unscrew!")
+ return
+
+ ..()
+
+// Tether tram air scrubbers for keeping arrivals clean - they work even with no area power
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/tram
+ name = "\improper Tram Air Scrubber"
+ icon_state = "scrubber:1"
+ on = TRUE
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/tram/powered()
+ return TRUE // Always be powered
+
+// Triumph shuttle air scrubbers for keeping arrivals clean - they work even with no area power
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/shuttle
+ name = "\improper Shuttle Air Scrubber"
+ icon_state = "scrubber:1"
+ on = TRUE
+
+/obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/shuttle/powered()
+ return TRUE // Always be powered
diff --git a/code/game/machinery/atmoalter/zvent.dm b/code/modules/atmospherics/machinery/zvent.dm
similarity index 96%
rename from code/game/machinery/atmoalter/zvent.dm
rename to code/modules/atmospherics/machinery/zvent.dm
index 0857757e7c9..2c22dbe9e65 100644
--- a/code/game/machinery/atmoalter/zvent.dm
+++ b/code/modules/atmospherics/machinery/zvent.dm
@@ -1,3 +1,5 @@
+// todo: this is in the wrong folder
+
/obj/machinery/zvent
name = "Interfloor Air Transfer System"
diff --git a/code/modules/cargo/supplypacks/hydroponics.dm b/code/modules/cargo/supplypacks/hydroponics.dm
index 9788fd40238..7c3ceea420a 100644
--- a/code/modules/cargo/supplypacks/hydroponics.dm
+++ b/code/modules/cargo/supplypacks/hydroponics.dm
@@ -56,7 +56,7 @@
/obj/item/reagent_containers/glass/bottle/ammonia = 2,
/obj/item/material/knife/machete/hatchet,
/obj/item/material/minihoe,
- /obj/item/analyzer/plant_analyzer,
+ /obj/item/plant_analyzer,
/obj/item/clothing/gloves/botanic_leather,
/obj/item/clothing/suit/storage/apron,
/obj/item/material/minihoe,
diff --git a/code/modules/cargo/supplypacks/misc.dm b/code/modules/cargo/supplypacks/misc.dm
index ebb5de441e8..b051d957c6f 100644
--- a/code/modules/cargo/supplypacks/misc.dm
+++ b/code/modules/cargo/supplypacks/misc.dm
@@ -302,7 +302,7 @@
contains = list(
/obj/fiftyspawner/wax = 2,
/obj/item/healthanalyzer/apidean = 1,
- /obj/item/analyzer/apidean = 1,
+ /obj/item/atmos_analyzer/apidean = 1,
/obj/item/reagent_scanner/apidean = 1,
/mob/living/bot/medibot/apidean = 1,
/obj/item/reagent_containers/food/drinks/bottle/ambrosia_mead = 2,
diff --git a/code/modules/cargo/supplypacks/supply.dm b/code/modules/cargo/supplypacks/supply.dm
index ea67dbe749f..f6ffa2e7119 100644
--- a/code/modules/cargo/supplypacks/supply.dm
+++ b/code/modules/cargo/supplypacks/supply.dm
@@ -102,7 +102,7 @@
/obj/item/clothing/under/rank/miner,
/obj/item/clothing/gloves/black,
/obj/item/clothing/shoes/black,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/storage/bag/ore,
/obj/item/flashlight/lantern,
/obj/item/shovel,
diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm
index 3aae23b9922..31240d01e16 100644
--- a/code/modules/clothing/masks/gasmask.dm
+++ b/code/modules/clothing/masks/gasmask.dm
@@ -12,7 +12,7 @@
siemens_coefficient = 0.9
armor_type = /datum/armor/mask/gas
var/gas_filter_strength = 1 //For gas mask filters
- var/list/filtered_gases = list(/datum/gas/phoron, /datum/gas/nitrous_oxide)
+ var/list/filtered_gases = list(GAS_ID_PHORON, GAS_ID_NITROUS_OXIDE)
/obj/item/clothing/mask/gas/filter_air(datum/gas_mixture/air)
var/datum/gas_mixture/gas_filtered = new
@@ -107,7 +107,7 @@
atom_flags = PHORONGUARD
clothing_flags = BLOCK_GAS_SMOKE_EFFECT | ALLOWINTERNALS
species_restricted = list(SPECIES_VOX)
- filtered_gases = list(/datum/gas/oxygen, /datum/gas/nitrous_oxide)
+ filtered_gases = list(GAS_ID_OXYGEN, GAS_ID_NITROUS_OXIDE)
var/mask_open = FALSE // Controls if the Vox can eat through this mask
action_button_name = "Toggle Feeding Port"
@@ -127,7 +127,6 @@
if(.)
return
feeding_port(user)
- ..()
/obj/item/clothing/mask/gas/zaddat
name = "Zaddat Veil"
@@ -136,8 +135,8 @@
item_state = "vax_mask"
//body_cover_flags = 0
species_restricted = list(SPECIES_ZADDAT)
+ filtered_gases = list(GAS_ID_PHORON, GAS_ID_NITROUS_OXIDE, GAS_ID_NITROGEN)
inv_hide_flags = HIDEEARS //semi-transparent
- filtered_gases = list(/datum/gas/phoron, /datum/gas/nitrous_oxide, /datum/gas/nitrogen)
/obj/item/clothing/mask/gas/opaque
name = "Opaque Mask"
diff --git a/code/modules/clothing/spacesuits/void/event.dm b/code/modules/clothing/spacesuits/void/event.dm
index fcfbc95dd36..7524e74fa5f 100644
--- a/code/modules/clothing/spacesuits/void/event.dm
+++ b/code/modules/clothing/spacesuits/void/event.dm
@@ -248,7 +248,7 @@
/obj/item/measuring_tape,
/obj/item/reagent_scanner,
/obj/item/robotanalyzer,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/cataloguer,
/obj/item/universal_translator,
/obj/item/tool/crowbar,
diff --git a/code/modules/clothing/suits/aliens/tajara.dm b/code/modules/clothing/suits/aliens/tajara.dm
index 379ed1cca5f..e86caf3513b 100644
--- a/code/modules/clothing/suits/aliens/tajara.dm
+++ b/code/modules/clothing/suits/aliens/tajara.dm
@@ -43,7 +43,7 @@
blood_overlay_type = "coat"
body_cover_flags = UPPER_TORSO|ARMS
inv_hide_flags = HIDEHOLSTER
- allowed = list(/obj/item/analyzer,/obj/item/stack/medical,/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,/obj/item/healthanalyzer,/obj/item/flashlight/pen,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle,/obj/item/paper)
+ allowed = list(/obj/item/atmos_analyzer,/obj/item/stack/medical,/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,/obj/item/healthanalyzer,/obj/item/flashlight/pen,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle,/obj/item/paper)
armor_type = /datum/armor/suit/labcoat
/obj/item/clothing/suit/storage/toggle/tajaran/coat/wool
diff --git a/code/modules/clothing/suits/hooded.dm b/code/modules/clothing/suits/hooded.dm
index b0e05f03b9e..c50a1548aae 100644
--- a/code/modules/clothing/suits/hooded.dm
+++ b/code/modules/clothing/suits/hooded.dm
@@ -196,7 +196,7 @@
armor_type = /datum/armor/medical/coat
hoodtype = /obj/item/clothing/head/hood/winter/medical
allowed = list (/obj/item/pen, /obj/item/paper, /obj/item/flashlight,/obj/item/tank/emergency/oxygen, /obj/item/storage/fancy/cigarettes,
- /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/analyzer,/obj/item/stack/medical,
+ /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/atmos_analyzer,/obj/item/stack/medical,
/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,
/obj/item/healthanalyzer,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle)
@@ -239,7 +239,7 @@
armor_type = /datum/armor/science/coat
hoodtype = /obj/item/clothing/head/hood/winter/science
allowed = list (/obj/item/pen, /obj/item/paper, /obj/item/flashlight,/obj/item/tank/emergency/oxygen, /obj/item/storage/fancy/cigarettes,
- /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/analyzer,/obj/item/stack/medical,
+ /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/atmos_analyzer,/obj/item/stack/medical,
/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,
/obj/item/healthanalyzer,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle)
@@ -264,7 +264,7 @@
armor_type = /datum/armor/engineering/coat
hoodtype = /obj/item/clothing/head/hood/winter/engineering
allowed = list (/obj/item/pen, /obj/item/paper, /obj/item/flashlight,/obj/item/tank/emergency/oxygen, /obj/item/storage/fancy/cigarettes,
- /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/analyzer, /obj/item/flashlight,
+ /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/atmos_analyzer, /obj/item/flashlight,
/obj/item/multitool, /obj/item/pipe_painter, /obj/item/radio, /obj/item/t_scanner, /obj/item/tool/crowbar, /obj/item/tool/screwdriver,
/obj/item/weldingtool, /obj/item/tool/wirecutters, /obj/item/tool/wrench, /obj/item/tank/emergency/oxygen, /obj/item/clothing/mask/gas,
/obj/item/barrier_tape_roll/engineering, /obj/item/rcd, /obj/item/pipe_dispenser)
@@ -290,7 +290,7 @@
hoodtype = /obj/item/clothing/head/hood/winter/hydro
allowed = list (/obj/item/pen, /obj/item/paper, /obj/item/flashlight,/obj/item/tank/emergency/oxygen, /obj/item/storage/fancy/cigarettes,
/obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/reagent_containers/spray/plantbgone,
- /obj/item/analyzer/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/material/minihoe)
+ /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/material/minihoe)
/obj/item/clothing/suit/storage/hooded/wintercoat/cargo
name = "cargo winter coat"
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index 1387735860e..a70a3195cdd 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -10,7 +10,7 @@
item_state_slots = list(SLOT_ID_RIGHT_HAND = "overalls", SLOT_ID_LEFT_HAND = "overalls")
blood_overlay_type = "armor"
body_cover_flags = 0
- allowed = list (/obj/item/reagent_containers/spray/plantbgone, /obj/item/analyzer/plant_analyzer, /obj/item/seeds,
+ allowed = list (/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds,
/obj/item/reagent_containers/glass/bottle, /obj/item/material/minihoe)
/obj/item/clothing/suit/storage/apron/white
@@ -176,7 +176,7 @@
desc = "A high-visibility vest used in work zones."
icon_state = "hazard"
blood_overlay_type = "armor"
- allowed = list (/obj/item/analyzer, /obj/item/flashlight, /obj/item/multitool, /obj/item/pipe_painter, /obj/item/radio, /obj/item/t_scanner,
+ allowed = list (/obj/item/atmos_analyzer, /obj/item/flashlight, /obj/item/multitool, /obj/item/pipe_painter, /obj/item/radio, /obj/item/t_scanner,
/obj/item/tool/crowbar, /obj/item/tool/screwdriver, /obj/item/weldingtool, /obj/item/tool/wirecutters, /obj/item/tool/wrench, /obj/item/tank/emergency/oxygen,
/obj/item/clothing/mask/gas, /obj/item/barrier_tape_roll/engineering)
body_cover_flags = UPPER_TORSO
@@ -285,7 +285,7 @@
armor_type = /datum/armor/medical/coat/field_medic
valid_accessory_slots = (ACCESSORY_SLOT_INSIGNIA)
allowed = list (/obj/item/gun,/obj/item/pen, /obj/item/paper, /obj/item/flashlight,/obj/item/tank/emergency/oxygen, /obj/item/storage/fancy/cigarettes,
- /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/analyzer,/obj/item/stack/medical,
+ /obj/item/storage/box/matches, /obj/item/reagent_containers/food/drinks/flask, /obj/item/suit_cooling_unit, /obj/item/atmos_analyzer,/obj/item/stack/medical,
/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,
/obj/item/healthanalyzer,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,
/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle)
diff --git a/code/modules/clothing/suits/labcoat.dm b/code/modules/clothing/suits/labcoat.dm
index 02e1fc86a8a..53c0abab1e9 100644
--- a/code/modules/clothing/suits/labcoat.dm
+++ b/code/modules/clothing/suits/labcoat.dm
@@ -7,7 +7,7 @@
blood_overlay_type = "coat"
body_cover_flags = UPPER_TORSO|ARMS
inv_hide_flags = HIDEHOLSTER
- allowed = list(/obj/item/analyzer,/obj/item/stack/medical,/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,/obj/item/healthanalyzer,/obj/item/flashlight/pen,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle,/obj/item/paper)
+ allowed = list(/obj/item/atmos_analyzer,/obj/item/stack/medical,/obj/item/dnainjector,/obj/item/reagent_containers/dropper,/obj/item/reagent_containers/syringe,/obj/item/reagent_containers/hypospray,/obj/item/healthanalyzer,/obj/item/flashlight/pen,/obj/item/reagent_containers/glass/bottle,/obj/item/reagent_containers/glass/beaker,/obj/item/reagent_containers/pill,/obj/item/storage/pill_bottle,/obj/item/paper)
armor_type = /datum/armor/suit/labcoat
worn_render_flags = WORN_RENDER_SLOT_ONE_FOR_ALL
diff --git a/code/modules/clothing/suits/oricon.dm b/code/modules/clothing/suits/oricon.dm
index 399a6e98075..fa77da88b1c 100644
--- a/code/modules/clothing/suits/oricon.dm
+++ b/code/modules/clothing/suits/oricon.dm
@@ -11,7 +11,7 @@
armor_type = /datum/armor/none
siemens_coefficient = 0.9
inv_hide_flags = HIDEHOLSTER
- allowed = list(/obj/item/tank/emergency/oxygen,/obj/item/flashlight,/obj/item/pen,/obj/item/clothing/head/soft,/obj/item/clothing/head/beret,/obj/item/storage/fancy/cigarettes,/obj/item/flame/lighter,/obj/item/tape_recorder,/obj/item/analyzer,/obj/item/radio,/obj/item/barrier_tape_roll)
+ allowed = list(/obj/item/tank/emergency/oxygen,/obj/item/flashlight,/obj/item/pen,/obj/item/clothing/head/soft,/obj/item/clothing/head/beret,/obj/item/storage/fancy/cigarettes,/obj/item/flame/lighter,/obj/item/tape_recorder,/obj/item/atmos_analyzer,/obj/item/radio,/obj/item/barrier_tape_roll)
/obj/item/clothing/suit/storage/service/sysguard
name = "\improper SysGuard jacket"
diff --git a/code/modules/events/atmos_leak.dm b/code/modules/events/atmos_leak.dm
index 59bf63b2c1d..ef5d6a2b04f 100644
--- a/code/modules/events/atmos_leak.dm
+++ b/code/modules/events/atmos_leak.dm
@@ -21,11 +21,11 @@
// Decide which area will be targeted!
/datum/event/atmos_leak/setup()
- var/gas_choices = list(/datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide) // Annoying
+ var/gas_choices = list(GAS_ID_CARBON_DIOXIDE, GAS_ID_NITROUS_OXIDE) // Annoying
if(severity >= EVENT_LEVEL_MODERATE)
- gas_choices += /datum/gas/phoron // Dangerous
+ gas_choices += GAS_ID_PHORON // Dangerous
// if(severity >= EVENT_LEVEL_MAJOR)
- // gas_choices += /datum/gas/volatile_fuel // Dangerous and no default atmos setup!
+ // gas_choices += GAS_ID_VOLATILE_FUEL // Dangerous and no default atmos setup!
gas_type = pick(gas_choices)
var/list/area/grand_list_of_areas = get_station_areas(excluded)
@@ -54,7 +54,7 @@
return
/datum/event/atmos_leak/announce()
- command_announcement.Announce("Warning, hazardous [GLOB.meta_gas_names[gas_type]] gas leak detected in \the [target_area], evacuate the area and contain the damage!", "Hazard Alert")
+ command_announcement.Announce("Warning, hazardous [global.gas_data.names[gas_type]] gas leak detected in \the [target_area], evacuate the area and contain the damage!", "Hazard Alert")
/datum/event/atmos_leak/start()
// Okay, time to actually put the gas in the room!
diff --git a/code/modules/events/supply_demand.dm b/code/modules/events/supply_demand.dm
index aaad6d47c47..3e830155852 100644
--- a/code/modules/events/supply_demand.dm
+++ b/code/modules/events/supply_demand.dm
@@ -230,7 +230,7 @@
var/total_moles = mixture.total_moles
var desc = "Canister filled to [round(pressure,0.1)] kPa with gas mixture:\n"
for(var/gas in mixture.gas)
- desc += " - [GLOB.meta_gas_names[gas]]: [round((mixture.gas[gas] / total_moles) * 100)]%\n"
+ desc += " - [global.gas_data.names[gas]]: [round((mixture.gas[gas] / total_moles) * 100)]%\n"
return desc
/datum/supply_demand_order/gas/match_item(var/obj/machinery/portable_atmospherics/canister)
@@ -322,7 +322,7 @@
/datum/event/supply_demand/proc/choose_atmos_items(var/differentTypes)
var/datum/gas_mixture/mixture = new
mixture.temperature = T20C
- var/unpickedTypes = gas_data.gases.Copy()
+ var/unpickedTypes = global.gas_data.gases.Copy()
unpickedTypes -= "volatile_fuel" // Don't do that one
for(var/i in 1 to differentTypes)
var/gasId = pick(unpickedTypes)
diff --git a/code/modules/gamemaster/actions/atmos_leak.dm b/code/modules/gamemaster/actions/atmos_leak.dm
index 63f7546fc04..59dd4422cd1 100644
--- a/code/modules/gamemaster/actions/atmos_leak.dm
+++ b/code/modules/gamemaster/actions/atmos_leak.dm
@@ -22,11 +22,11 @@
EVENT_LEVEL_MAJOR = 3
)
- var/gas_choices = list(/datum/gas/carbon_dioxide, /datum/gas/nitrous_oxide) // Annoying
+ var/gas_choices = list(GAS_ID_CARBON_DIOXIDE, GAS_ID_NITROUS_OXIDE) // Annoying
if(severity >= EVENT_LEVEL_MODERATE)
- gas_choices += /datum/gas/phoron // Dangerous
+ gas_choices += GAS_ID_PHORON // Dangerous
if(severity >= EVENT_LEVEL_MAJOR)
- gas_choices += /datum/gas/volatile_fuel // Dangerous and no default atmos setup!
+ gas_choices += GAS_ID_VOLATILE_FUEL // Dangerous and no default atmos setup!
gas_type = pick(gas_choices)
var/list/area/grand_list_of_areas = get_station_areas(excluded)
@@ -55,7 +55,7 @@
/datum/gm_action/atmos_leak/announce()
if(target_area)
- command_announcement.Announce("Warning, hazardous [GLOB.meta_gas_names[gas_type]] gas leak detected in \the [target_area], evacuate the area.", "Hazard Alert")
+ command_announcement.Announce("Warning, hazardous [global.gas_data.names[gas_type]] gas leak detected in \the [target_area], evacuate the area.", "Hazard Alert")
/datum/gm_action/atmos_leak/start()
if(!target_turf)
diff --git a/code/modules/hardsuits/suits/combat.dm b/code/modules/hardsuits/suits/combat.dm
index 4e65120d7b9..6f0dcf4db3f 100644
--- a/code/modules/hardsuits/suits/combat.dm
+++ b/code/modules/hardsuits/suits/combat.dm
@@ -80,7 +80,7 @@
/obj/item/tool,
/obj/item/multitool,
/obj/item/radio,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/storage/briefcase/inflatable,
/obj/item/melee/baton,
/obj/item/gun,
diff --git a/code/modules/hardsuits/suits/ert.dm b/code/modules/hardsuits/suits/ert.dm
index a2ec96dd72b..5673f15fd00 100644
--- a/code/modules/hardsuits/suits/ert.dm
+++ b/code/modules/hardsuits/suits/ert.dm
@@ -35,7 +35,7 @@
/obj/item/tool/wrench,
/obj/item/multitool,
/obj/item/radio,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/storage/briefcase/inflatable,
/obj/item/melee/baton,
/obj/item/gun,
diff --git a/code/modules/hardsuits/suits/pmc.dm b/code/modules/hardsuits/suits/pmc.dm
index 4d28da9118b..df6f2242f75 100644
--- a/code/modules/hardsuits/suits/pmc.dm
+++ b/code/modules/hardsuits/suits/pmc.dm
@@ -33,7 +33,7 @@
/obj/item/tool/wrench,
/obj/item/multitool,
/obj/item/radio,
- /obj/item/analyzer,
+ /obj/item/atmos_analyzer,
/obj/item/storage/briefcase/inflatable,
/obj/item/melee/baton,
/obj/item/gun,
diff --git a/code/modules/hydroponics/beekeeping/beehive.dm b/code/modules/hydroponics/beekeeping/beehive.dm
index a70359683fe..2ad9e7ca486 100644
--- a/code/modules/hydroponics/beekeeping/beehive.dm
+++ b/code/modules/hydroponics/beekeeping/beehive.dm
@@ -98,7 +98,7 @@
B.fill()
update_icon()
return
- else if(istype(I, /obj/item/analyzer/plant_analyzer))
+ else if(istype(I, /obj/item/plant_analyzer))
to_chat(user, "Scan result of \the [src]...")
to_chat(user, "Beehive is [bee_count ? "[round(bee_count)]% full" : "empty"].[bee_count > 90 ? " Colony is ready to split." : ""]")
if(frames)
diff --git a/code/modules/hydroponics/seed.dm b/code/modules/hydroponics/seed.dm
index 1553cfa6920..67aac64879b 100644
--- a/code/modules/hydroponics/seed.dm
+++ b/code/modules/hydroponics/seed.dm
@@ -417,12 +417,12 @@
if(prob(5))
consume_gasses = list()
- var/gas = pick(/datum/gas/oxygen,/datum/gas/nitrogen,/datum/gas/phoron,/datum/gas/carbon_dioxide)
+ var/gas = pick(GAS_ID_OXYGEN,GAS_ID_NITROGEN,GAS_ID_PHORON,GAS_ID_CARBON_DIOXIDE)
consume_gasses[gas] = rand(3,9)
if(prob(5))
exude_gasses = list()
- var/gas = pick(/datum/gas/oxygen,/datum/gas/nitrogen,/datum/gas/phoron,/datum/gas/carbon_dioxide)
+ var/gas = pick(GAS_ID_OXYGEN,GAS_ID_NITROGEN,GAS_ID_PHORON,GAS_ID_CARBON_DIOXIDE)
exude_gasses[gas] = rand(3,9)
chems = list()
diff --git a/code/modules/hydroponics/trays/tray_tools.dm b/code/modules/hydroponics/trays/tray_tools.dm
index 4bb1cc6b0c8..b940411fd9e 100644
--- a/code/modules/hydroponics/trays/tray_tools.dm
+++ b/code/modules/hydroponics/trays/tray_tools.dm
@@ -17,152 +17,6 @@
item_state = "hedget"
damage_force = 7 //One point extra than standard wire cutters.
-/obj/item/analyzer/plant_analyzer
- name = "plant analyzer"
- icon = 'icons/obj/device.dmi'
- icon_state = "hydro"
- item_state = "analyzer"
- var/datum/seed/last_seed
- var/list/last_reagents
-
-/obj/item/analyzer/plant_analyzer/attack_self(mob/user)
- . = ..()
- if(.)
- return
- ui_interact(user)
-
-/obj/item/analyzer/plant_analyzer/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "PlantAnalyzer", name)
- ui.open()
-
-/obj/item/analyzer/plant_analyzer/ui_state(mob/user, datum/tgui_module/module)
- return GLOB.inventory_state
-
-/obj/item/analyzer/plant_analyzer/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
- var/list/data = ..()
-
- var/datum/seed/grown_seed = locate(last_seed)
- if(!istype(grown_seed))
- return list("no_seed" = TRUE)
-
- data["no_seed"] = FALSE
- data["seed"] = grown_seed.get_tgui_analyzer_data(user)
- data["reagents"] = last_reagents
-
- return data
-
-/obj/item/analyzer/plant_analyzer/ui_act(action, list/params, datum/tgui/ui)
- if(..())
- return TRUE
-
- switch(action)
- if("print")
- print_report(usr)
- return TRUE
- if("close")
- last_seed = null
- last_reagents = null
- return TRUE
-
-/obj/item/analyzer/plant_analyzer/afterattack(atom/target, mob/user, clickchain_flags, list/params)
- if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY))
- return
-
- var/datum/seed/grown_seed
- var/datum/reagents/grown_reagents
- if(istype(target,/obj/structure/table))
- return ..()
- else if(istype(target,/obj/item/reagent_containers/food/snacks/grown))
-
- var/obj/item/reagent_containers/food/snacks/grown/G = target
- grown_seed = SSplants.seeds[G.plantname]
- grown_reagents = G.reagents
-
- else if(istype(target,/obj/item/grown))
-
- var/obj/item/grown/G = target
- grown_seed = SSplants.seeds[G.plantname]
- grown_reagents = G.reagents
-
- else if(istype(target,/obj/item/seeds))
-
- var/obj/item/seeds/S = target
- grown_seed = S.seed
-
- else if(istype(target,/obj/machinery/portable_atmospherics/hydroponics))
-
- var/obj/machinery/portable_atmospherics/hydroponics/H = target
- if(H.frozen == 1)
- to_chat(user, "Disable the cryogenic freezing first!")
- return
- grown_seed = H.seed
- grown_reagents = H.reagents
-
- if(!grown_seed)
- to_chat(user, "[src] can tell you nothing about \the [target].")
- return
-
- last_seed = REF(grown_seed)
-
- user.visible_message("[user] runs the scanner over \the [target].")
-
- last_reagents = list()
- if(grown_reagents && grown_reagents.reagent_list && grown_reagents.reagent_list.len)
- for(var/datum/reagent/R in grown_reagents.reagent_list)
- last_reagents.Add(list(list(
- "name" = R.name,
- "volume" = grown_reagents.get_reagent_amount(R.id),
- )))
-
- ui_interact(user)
-
-/obj/item/analyzer/plant_analyzer/proc/print_report_verb()
- set name = "Print Plant Report"
- set category = "Object"
- set src = usr
-
- if(usr.stat || usr.restrained() || usr.lying)
- return
- print_report(usr)
-
-/obj/item/analyzer/plant_analyzer/proc/print_report(var/mob/living/user)
- var/datum/seed/grown_seed = locate(last_seed)
- if(!istype(grown_seed))
- to_chat(user, SPAN_WARNING("There is no scan data to print."))
- return
-
- var/form_title = "[grown_seed.seed_name] (#[grown_seed.uid])"
- var/dat = "
Plant data for [form_title]
"
- dat += "
General Data
"
- dat += "
"
- dat += "
Endurance
[grown_seed.get_trait(TRAIT_ENDURANCE)]
"
- dat += "
Yield
[grown_seed.get_trait(TRAIT_YIELD)]
"
- dat += "
Maturation time
[grown_seed.get_trait(TRAIT_MATURATION)]
"
- dat += "
Production time
[grown_seed.get_trait(TRAIT_PRODUCTION)]
"
- dat += "
Potency
[grown_seed.get_trait(TRAIT_POTENCY)]
"
- dat += "
"
-
- if(LAZYLEN(last_reagents))
- dat += "
Reagent Data
"
- dat += " This sample contains: "
- for(var/i in 1 to LAZYLEN(last_reagents))
- dat += " - [last_reagents[i]["name"]], [last_reagents[i]["volume"]] unit(s)"
-
- dat += "