From c7178bfe45921326450bb508f3441c255d5031ec Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 26 May 2025 08:32:11 -0500 Subject: [PATCH] `map_export` admin verb can now save atmos for turfs and canisters (#91051) ## About The Pull Request Due to the atmos complexity, this was split off from: - #90998 This takes the atmos gas mixture from a turf and converts it into a string for saving via the `map_export` verb. The behavior is as follows: - Space and wall turfs atmos are skipped - Rounds all gases to 0.01 decimal for their individual mole value - Any gases less than 0.01 moles get skipped This does not keep track of active fires since those have hotspots, active turfs, etc., which is way more complicated to save. Another caveat is the amount of characters the map reader needs to write is much larger since every turf (besides space/walls) will have different atmos due to mobs breathing, airlocks opening, etc. so I made this setting into a `SAVE_ATMOS` flag that you can apply when saving. This also keeps track of air inside of canisters. ## Why It's Good For The Game Atmospherics changes to the station can now be saved properly. ## Changelog :cl: qol: `map_export` admin verb can now save atmos for turfs and air canisters /:cl: --- code/__DEFINES/map_exporter.dm | 1 + code/modules/admin/verbs/map_export.dm | 14 ++++++++++- .../atmospherics/gasmixtures/gas_mixture.dm | 23 +++++++++++++++++++ .../machinery/portable/canister.dm | 9 +++++++- .../portable/portable_atmospherics.dm | 16 +++++++++++-- code/modules/buildmode/submodes/map_export.dm | 1 + 6 files changed, 60 insertions(+), 4 deletions(-) diff --git a/code/__DEFINES/map_exporter.dm b/code/__DEFINES/map_exporter.dm index becedcd23e5..dac7e08860b 100644 --- a/code/__DEFINES/map_exporter.dm +++ b/code/__DEFINES/map_exporter.dm @@ -5,6 +5,7 @@ #define SAVE_AREAS (1 << 4) //! Save areas? #define SAVE_SPACE (1 << 5) //! Save space areas? (If not they will be saved as NOOP) #define SAVE_OBJECT_PROPERTIES (1 << 6) //! Save custom properties of objects (obj.on_object_saved() output) +#define SAVE_ATMOS (1 << 7) //! Save turf atmos //Ignore turf if it contains #define SAVE_SHUTTLEAREA_DONTCARE 0 diff --git a/code/modules/admin/verbs/map_export.dm b/code/modules/admin/verbs/map_export.dm index eb14fd965a3..74af2d01025 100644 --- a/code/modules/admin/verbs/map_export.dm +++ b/code/modules/admin/verbs/map_export.dm @@ -100,6 +100,13 @@ ADMIN_VERB(map_export, R_DEBUG, "Map Export", "Select a part of the map by coord . += NAMEOF(src, anchored) return . +/turf/open/get_save_vars() + . = ..() + var/datum/gas_mixture/turf_gasmix = return_air() + initial_gas_mix = turf_gasmix.to_string() + . += NAMEOF(src, initial_gas_mix) + return . + /obj/get_save_vars() . = ..() . += NAMEOF(src, req_access) @@ -292,7 +299,12 @@ GLOBAL_LIST_INIT(save_file_chars, list( var/metadata = generate_tgm_metadata(thing) current_header += "[empty ? "" : ",\n"][thing.type][metadata]" empty = FALSE - current_header += "[empty ? "" : ",\n"][place],\n[location])\n" + current_header += "[empty ? "" : ",\n"][place]" + //====SAVING ATMOS==== + if((save_flag & SAVE_TURFS) && (save_flag & SAVE_ATMOS) && !isspaceturf(pull_from)) + var/metadata = generate_tgm_metadata(pull_from) + current_header += "[metadata]" + current_header += ",\n[location])\n" //====Fill the contents file==== var/textiftied_header = current_header.Join() // If we already know this header just use its key, otherwise we gotta make a new one diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index ac3dab1503c..a50cefb34dc 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -743,3 +743,26 @@ GLOBAL_LIST_INIT(gaslist_cache, init_gaslist_cache()) current_reaction.react(air_mixture = src, working_power = working_power, electrolyzer_args = electrolyzer_args) garbage_collect() + +/// Convert a gas mixture to a string (ie. "o2=22;n2=82;TEMP=180") +/// Rounds all temperature and gases to 0.01 and skips any gases less than that amount +/datum/gas_mixture/proc/to_string() + var/list/cached_gases = gases + var/rounded_temp = round(temperature, 0.01) + + var/list/atmos_contents = list() + var/temperature_str = "TEMP=[num2text(rounded_temp)]" + + if(!length(cached_gases) || total_moles() < 0.01) + return temperature_str + + for(var/gas_path in cached_gases) + var/gas_moles = cached_gases[gas_path][MOLES] + var/gas_id = cached_gases[gas_path][GAS_META][META_GAS_ID] + + gas_moles = round(gas_moles, 0.01) + if(gas_moles >= 0.01) + atmos_contents += "[gas_id]=[num2text(gas_moles)]" + + atmos_contents += temperature_str + return atmos_contents.Join(";") diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index e1b9db1c78d..b1615a82032 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -49,13 +49,20 @@ fire = 80 acid = 50 +/obj/machinery/portable_atmospherics/canister/get_save_vars() + . = ..() + . += NAMEOF(src, valve_open) + . += NAMEOF(src, release_pressure) + return . + /obj/machinery/portable_atmospherics/canister/Initialize(mapload) . = ..() if(mapload) internal_cell = new /obj/item/stock_parts/power_store/cell/high(src) - create_gas() + if(!initial_gas_mix) + create_gas() if(ispath(gas_type, /datum/gas)) desc = "[GLOB.meta_gas_info[gas_type][META_GAS_NAME]]. [GLOB.meta_gas_info[gas_type][META_GAS_DESC]]" diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index f9020860edf..72c322621ac 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -13,6 +13,8 @@ ///Stores the gas mixture of the portable component. Don't access this directly, use return_air() so you support the temporary processing it provides var/datum/gas_mixture/air_contents + ///Stores the gas mixture in string form for map loading and saving + var/initial_gas_mix ///Stores the reference of the connecting port var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port ///Stores the reference of the tank the machine is holding @@ -42,11 +44,21 @@ fire = 60 acid = 30 +/obj/machinery/portable_atmospherics/get_save_vars() + . = ..() + var/datum/gas_mixture/gasmix = air_contents + initial_gas_mix = gasmix.to_string() + . += NAMEOF(src, initial_gas_mix) + return . + /obj/machinery/portable_atmospherics/Initialize(mapload) . = ..() - air_contents = new + if(initial_gas_mix) + air_contents = SSair.parse_gas_string(initial_gas_mix) + else + air_contents = new + air_contents.temperature = T20C air_contents.volume = volume - air_contents.temperature = T20C SSair.start_processing_machine(src) AddElement(/datum/element/climbable, climb_time = 3 SECONDS, climb_stun = 3 SECONDS) AddElement(/datum/element/elevation, pixel_shift = 8) diff --git a/code/modules/buildmode/submodes/map_export.dm b/code/modules/buildmode/submodes/map_export.dm index 0276fd199f2..a096a9432f5 100644 --- a/code/modules/buildmode/submodes/map_export.dm +++ b/code/modules/buildmode/submodes/map_export.dm @@ -14,6 +14,7 @@ "Area Saving" = SAVE_AREAS, "Space Turf Saving" = SAVE_SPACE, "Object Property Saving" = SAVE_OBJECT_PROPERTIES, + "Atmos Saving" = SAVE_ATMOS, ) var/what_to_change = tgui_input_list(builder, "What export setting would you like to toggle?", "Map Exporter", options) if (!what_to_change)