From 4dd9a19c011e11427d758d74213972163e9ac8fc Mon Sep 17 00:00:00 2001 From: Will <7099514+Willburd@users.noreply.github.com> Date: Sun, 17 May 2026 19:08:54 -0400 Subject: [PATCH] Cryogenic planet controller and station boiler upport (#19452) * boiler basics * more stuff * tgui ported * more cleanup * more framework * lets get this settled * oops * disabled regularly * subsystem tweaks * clean up spans * name oopsie * optimize * you too * actually optimize that * linting * much better optimization * clean that too * optimizing * runtime on stack insertion * more cleanup * material handling fix * pluralized * oops * clean that up * tgui cleanup * these should just be structures, tgui rework * some tweaks * small runtime caught * polishing * cleanup * last tweak * fixed burn calculation * Keeeeeeeeeellllllllllll * scroll fix * move to proc * requested changes * optimization * buh * Well that didn't work * clear those refs * back to flags --------- Co-authored-by: Cameron Lennox Co-authored-by: Selis <12716288+ItsSelis@users.noreply.github.com> --- .../binary_devices/station_boiler.dm | 212 ++++++++++++++++++ code/__defines/_planes+layers.dm | 1 + code/__defines/misc.dm | 7 +- code/__defines/subsystems.dm | 2 + code/controllers/subsystems/air.dm | 3 + code/controllers/subsystems/cryoplanets.dm | 97 ++++++++ code/controllers/subsystems/stationheater.dm | 77 +++++++ code/game/area/asteroid_areas.dm | 2 +- .../atmoalter/station_boiler_radiator.dm | 40 ++++ .../items/devices/communicator/UI_tgui.dm | 2 +- code/modules/planet/planet.dm | 2 + code/modules/planet/weather.dm | 5 +- icons/obj/machines/floor_radiator.dmi | Bin 0 -> 691 bytes icons/obj/machines/heat_boiler.dmi | Bin 0 -> 19248 bytes maps/gateway_vr/lucky_7.dm | 2 +- maps/groundbase/groundbase_areas.dm | 2 +- maps/redgate/casino_canal/casino_canal.dm | 2 +- maps/stellar_delight/stellar_delight_areas.dm | 2 +- maps/virtual_reality/constructVR.dm | 2 +- .../tgui/interfaces/StationBoiler.tsx | 72 ++++++ vorestation.dme | 4 + 21 files changed, 525 insertions(+), 11 deletions(-) create mode 100644 code/ATMOSPHERICS/components/binary_devices/station_boiler.dm create mode 100644 code/controllers/subsystems/cryoplanets.dm create mode 100644 code/controllers/subsystems/stationheater.dm create mode 100644 code/game/machinery/atmoalter/station_boiler_radiator.dm create mode 100644 icons/obj/machines/floor_radiator.dmi create mode 100644 icons/obj/machines/heat_boiler.dmi create mode 100644 tgui/packages/tgui/interfaces/StationBoiler.tsx diff --git a/code/ATMOSPHERICS/components/binary_devices/station_boiler.dm b/code/ATMOSPHERICS/components/binary_devices/station_boiler.dm new file mode 100644 index 00000000000..13cf28ecb7b --- /dev/null +++ b/code/ATMOSPHERICS/components/binary_devices/station_boiler.dm @@ -0,0 +1,212 @@ +/obj/structure/stationboiler + name = "Station Boiler" + desc = "A large, phoron-infused wood powered, super boiler. Capable of keeping a entire colony heated up" + icon = 'icons/obj/machines/heat_boiler.dmi' + icon_state = "boiler_off" + anchored = TRUE + density = TRUE + pixel_x = -32 + layer = UNDER_JUNK_LAYER + + VAR_PRIVATE/is_active = FALSE // Check init for actual auto startup + VAR_PRIVATE/target_heat_temperature = T20C //The temperature we want the pipes to be heated to + VAR_PRIVATE/wood_per_process = 1SECOND + VAR_PRIVATE/list/stored_material = list( + MAT_LOG = 1 HOUR, //1 hour of mats free + MAT_WOODEN_STICK = 0, + MAT_WOOD = 0, + MAT_SIFWOOD = 0, + MAT_SIFLOG = 0, + MAT_HARDWOOD = 0, + MAT_HARDLOG = 0, + MAT_WOODEN_STICK = 0, + MAT_BIRCHWOOD = 0, + MAT_PINEWOOD = 0, + MAT_OAKWOOD = 0, + MAT_ACACIAWOOD = 0, + MAT_REDWOOD = 0, + ) + VAR_PRIVATE/storage_capacity = 4 HOURS + VAR_PRIVATE/datum/looping_sound/oven/boiler_loop + +/obj/structure/stationboiler/Initialize(mapload) + . = ..() + SSstationheater.boilers += src + update_icon() + START_PROCESSING(SSobj, src) + boiler_loop = new(list(src), FALSE) + set_state(TRUE) + +/obj/structure/stationboiler/Destroy() + QDEL_NULL(boiler_loop) + STOP_PROCESSING(SSobj, src) + SSstationheater.boilers -= src + . = ..() + +/obj/structure/stationboiler/process() + var/list/available_mats = list() + for(var/mat_check in stored_material) + if(stored_material[mat_check] <= 0) + continue + available_mats += mat_check + + if(!length(available_mats)) //Out of wood + set_state(FALSE) + return + + var/mat_drain = pick(available_mats) + stored_material[mat_drain] = max(stored_material[mat_drain] - wood_per_process, 0) + +/obj/structure/stationboiler/proc/set_state(activated) + if(activated == is_active) + return + is_active = activated + update_icon() + if(is_active) + set_light(4, 3, "#e9400dff") + boiler_loop.start(src) + return + set_light(0) + boiler_loop.stop(src) + +/obj/structure/stationboiler/update_icon() + if(is_active) + icon_state = "boiler_on" + return + icon_state = "boiler_off" + +/obj/structure/stationboiler/attack_ai(mob/user) + add_hiddenprint(user) + tgui_interact(user) + +/obj/structure/stationboiler/attack_hand(mob/user) + add_fingerprint(user) + tgui_interact(user) + +/obj/structure/stationboiler/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "StationBoiler", name) + ui.open() + +/obj/structure/stationboiler/tgui_act(action, params) + if(..()) + return TRUE + switch(action) + if("ejectMaterial") + var/matName = params["mat"] + if(!(matName in stored_material)) + return FALSE + eject_materials(matName) + return TRUE + + if("ignite") + try_ignite() + return TRUE + +/obj/structure/stationboiler/tgui_data(mob/user) + var/list/data = list() + var/list/materials_ui = list() + for(var/M in stored_material) + if(stored_material[M] > 0) + var/datum/material/matdata = get_material_by_name(M) + var/obj/item/stack/material/stack_type = matdata.stack_type + var/stack_units = initial(stack_type.perunit) + var/sheet_count = FLOOR(stored_material[M] / stack_units, 1) + materials_ui[++materials_ui.len] = list( + "name" = M, + "display" = material_display_name(M), + "qty" = "[sheet_count] [sheet_count > 1 ? matdata.sheet_plural_name : matdata.sheet_singular_name]", // Show the number of sheets + "can_eject" = (stored_material[M] >= stack_units)) + data["materials"] = materials_ui + + var/mat_store = get_total_stored_mats() + data["stored"] = mat_store + data["max"] = storage_capacity + data["timeleft"] = get_time_left() + + return data + +/obj/structure/stationboiler/proc/get_total_stored_mats() + var/mat_store = 0 + for(var/mat_check in stored_material) + mat_store += stored_material[mat_check] + return mat_store + +/obj/structure/stationboiler/proc/get_time_left() + var/org_ticks = get_total_stored_mats()/wood_per_process + var/second = org_ticks % 60 + var/minute = FLOOR((org_ticks / 60), 1) % 60 + var/hours = FLOOR((org_ticks / 3600), 1) + if(second < 10) + second = "0[second]" + if(minute < 10) + minute = "0[minute]" + return "[hours]:[minute]:[second]" + +/obj/structure/stationboiler/ex_act(severity) + return + +/obj/structure/stationboiler/proc/try_ignite() + if(stored_material[MAT_LOG] >= wood_per_process) + set_state(TRUE) + +/obj/structure/stationboiler/proc/is_heating() + return is_active + +/obj/structure/stationboiler/proc/is_on_same_planet(obj/machinery/stationboiler_radiator/rad) + var/turf/rad_turf = get_turf(rad) + var/turf/our_turf = get_turf(src) + return AreConnectedZLevels(rad_turf.z,our_turf.z) + +/obj/structure/stationboiler/proc/try_load_materials(mob/user, obj/item/stack/material/S) + if(!istype(S)) + return FALSE + + // Can we even put the material in? + if(!(S.material.name in stored_material)) + to_chat(user, span_warning("\The [src] doesn't accept [material_display_name(S.material)]!")) + return TRUE + + // Check if there is room left to store a sheet of the material + var/remaining_stack_space = FLOOR((storage_capacity - get_total_stored_mats()) / S.perunit, 1) + if(remaining_stack_space < 1) + to_chat(user, span_warning("\The [src] cannot hold more [S.name].")) + return TRUE + + // Put the material in, convert the sheet into material units + var/mat_name = S.material.name + var/inserting_sheets = S.get_amount() + inserting_sheets = min(inserting_sheets, remaining_stack_space) + stored_material[mat_name] += inserting_sheets * S.perunit + S.use(inserting_sheets) + user.visible_message("\The [user] inserts [mat_name] into \the [src].", span_notice("You insert [inserting_sheets] [mat_name]\s into \the [src].")) + return TRUE + +/obj/structure/stationboiler/attackby(obj/item/W as obj, mob/user as mob) + add_fingerprint(user) + if(try_load_materials(user, W)) + return + else + to_chat(user, span_notice("You cannot insert this item into \the [src]!")) + return + +// 0 amount = 0 means ejecting a full stack; -1 means eject everything +/obj/structure/stationboiler/proc/eject_materials(material_name) + var/datum/material/matdata = get_material_by_name(material_name) + var/obj/item/stack/material/stack_type = matdata.stack_type + var/stack_units = initial(stack_type.perunit) + + var/sheets_loaded = FLOOR(stored_material[material_name] / stack_units, 1) + if(sheets_loaded < 1) + return + var/amount = sheets_loaded + + // Drop stacks of sheets till empty + while(amount >= 1) + var/obj/item/stack/material/S = new stack_type(get_turf(src)) + var/export_amount = min(amount, S.max_amount) + S.set_amount(export_amount) + S.update_icon() + stored_material[material_name] -= stack_units * export_amount + amount -= export_amount diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index a201d8785b3..504d612b864 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -59,6 +59,7 @@ What is the naming convention for planes or layers? // Turf Planes #define PLATING_PLANE -44 // Plating + #define FLOOR_HEATER_LAYER 2.03 // Under objects, even when planeswapped #define DISPOSAL_LAYER 2.1 // Under objects, even when planeswapped #define PIPES_LAYER 2.2 // Under objects, even when planeswapped #define WIRES_LAYER 2.3 // Under objects, even when planeswapped diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 2f2c7c2069c..8d3b39737a2 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -120,11 +120,14 @@ #define AREA_BLOCK_GHOST_SIGHT 0x2000 // If an area blocks sight for ghosts #define AREA_BLOCK_INSTANT_BUILDING 0x4000 // If an area blocks the usage of instant building creation items/mechanics such as shelter capsules #define AREA_ALWAYS_HAS_GRAVITY 0x8000 // If an area should always have gravity, even during events that would otherwise remove it. -// The 0x800000 is blocked by INITIALIZED, do NOT use it! - +#define AREA_CRYOPLANET_SHIELDED 0x10000 // If an area is protected from SScryoplanet temperature shifts +// UNUSED 0x20000 +// UNUSED 0x40000 +// UNUSED 0x80000 #define PHASE_SHIELDED 0x100000 // A less rough way to prevent phase shifting without blocking access //VOREStation Note: Not implemented on VS. Used downstream. #define AREA_LIMIT_DARK_RESPITE 0x200000 // Shadekin will die normally in those areas //VOREStation Note: Not implemented on VS. Used downstream. #define AREA_ALLOW_CLOCKOUT 0x400000 // The PDA timeclock app can only be used in these areas //VOREStation Note: Not implemented on VS. Used downstream. +// The 0x800000 is blocked by INITIALIZED, do NOT use it! // OnTopic return values #define TOPIC_NOACTION 0 diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 966d9c5862f..1e579301732 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -131,6 +131,8 @@ #define FIRE_PRIORITY_CHARSETUP 25 #define FIRE_PRIORITY_AIRFLOW 30 #define FIRE_PRIORITY_AIR 35 +#define FIRE_PRIORITY_STATIONBOILER 36 +#define FIRE_PRIORITY_CRYOPLANETS 37 #define FIRE_PRIORITY_BURNING 40 #define FIRE_PRIORITY_OBJ 40 #define FIRE_PRIORITY_PROCESS 45 diff --git a/code/controllers/subsystems/air.dm b/code/controllers/subsystems/air.dm index 14723a7ddcd..c544bcb12a3 100644 --- a/code/controllers/subsystems/air.dm +++ b/code/controllers/subsystems/air.dm @@ -362,6 +362,7 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun // Reset all the lists zones.Cut() + SScryoplanets.cryo_zones.Cut() edges.Cut() tiles_to_update.Cut() zones_to_update.Cut() @@ -380,10 +381,12 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun zones.Add(z) z.name = "Zone [next_id++]" mark_zone_update(z) + SScryoplanets.cryo_zones.Add(z) /datum/controller/subsystem/air/proc/remove_zone(datum/zone/z) zones.Remove(z) zones_to_update.Remove(z) + SScryoplanets.cryo_zones.Remove(z) /datum/controller/subsystem/air/proc/air_blocked(turf/A, turf/B) #ifdef ZASDBG diff --git a/code/controllers/subsystems/cryoplanets.dm b/code/controllers/subsystems/cryoplanets.dm new file mode 100644 index 00000000000..7f78c402064 --- /dev/null +++ b/code/controllers/subsystems/cryoplanets.dm @@ -0,0 +1,97 @@ +#define THERMAL_ENERGY_CHANGE 12000 + +SUBSYSTEM_DEF(cryoplanets) + name = "Cryogenic Planets" + wait = 8 SECOND + runlevels = RUNLEVEL_GAME + priority = FIRE_PRIORITY_STATIONBOILER + flags = SS_BACKGROUND // Extremely low priority + + dependencies = list( + /datum/controller/subsystem/planets, + /datum/controller/subsystem/air + ) + + var/list/cryo_zones = list() + VAR_PRIVATE/list/current_run = list() + +/datum/controller/subsystem/cryoplanets/Initialize() + for(var/datum/planet/check in SSplanets.planets) + if(check.cryogenic_temp_shift) + return SS_INIT_SUCCESS + flags |= SS_NO_FIRE + cryo_zones.Cut() // Populated by Zone/add_zone() which is done by SSair before this. Clear those hardrefs if we are not gonna use them! + return SS_INIT_NO_NEED // No cryoplanets to deal with! + +/datum/controller/subsystem/cryoplanets/stat_entry(msg) + msg = " Cr: [length(current_run)] | Zs: [length(cryo_zones)]" + . = ..() + +/datum/controller/subsystem/cryoplanets/fire(resumed) + if(!resumed) + current_run = cryo_zones.Copy() + + while(length(current_run)) + var/datum/zone/zone = current_run[length(current_run)] + current_run.len-- + if(QDELETED(zone) || zone.invalid) + cryo_zones.Remove(zone) + continue + + var/turf/T = pick(zone.contents) + if(!SScryoplanets.is_station_temp_change_turf(T)) + cryo_zones.Remove(zone) + continue + + equalize_temperature_to_planet(T, zone, THERMAL_ENERGY_CHANGE) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/cryoplanets/proc/equalize_temperature_to_planet(turf/T, datum/zone/zone, max_thermal_change) + var/datum/gas_mixture/currentAir = zone.air + if(!currentAir) + return + + // Get the temperature energy needed to shift the zones' temp toward our goal... Maxes out at THERMAL_ENERGY_CHANGE per subsystem tick + var/datum/planet/P = SSplanets.z_to_planet[T.z] + var/target_temp = P.weather_holder.temperature + var/neededEnergy = currentAir.get_thermal_energy_change(target_temp) + if(neededEnergy > 0) + neededEnergy = min(neededEnergy, max_thermal_change) + else + neededEnergy = max(neededEnergy, -max_thermal_change) + + // Check if this would be affected by the station mainboiler's radiators. + var/area/check_area = get_area(T) // If we have an active radiator in the area, then there is no point in starting a temperature war.... + if(check_area && length(SSstationheater.radiators) && target_temp < SSstationheater.target_heat_temperature) + var/obj/structure/stationboiler/current_heater = SSstationheater.get_current_boiler() + if(current_heater?.is_heating()) // If the current boiler isn't on... It won't matter. + for(var/obj/machinery/stationboiler_radiator/radiator in SSstationheater.radiators) + CHECK_TICK + if(get_area(radiator) != check_area) + continue + return // Area had an active radiator, we'll drop out! + //testing("Energy: [neededEnergy]") + currentAir.add_thermal_energy(neededEnergy) + +/datum/controller/subsystem/cryoplanets/proc/is_station_temp_change_turf(turf/T) + if(!issimulatedturf(T)) // What are you even doing? + return FALSE + + if(T.is_outdoors()) // Radiators can't help you outside + return FALSE + + if(T.z > length(SSplanets.z_to_planet)) // Not even on a planet + return FALSE + + var/datum/planet/P = SSplanets.z_to_planet[T.z] + if(!P || !P.cryogenic_temp_shift) // Not on a temp shifting planet + return FALSE + + var/area/area_check = get_area(T) //Do not freeze dorms + if(!area_check || (area_check.flags & AREA_CRYOPLANET_SHIELDED)) + return FALSE + + return TRUE + +#undef THERMAL_ENERGY_CHANGE diff --git a/code/controllers/subsystems/stationheater.dm b/code/controllers/subsystems/stationheater.dm new file mode 100644 index 00000000000..53b8ad8957e --- /dev/null +++ b/code/controllers/subsystems/stationheater.dm @@ -0,0 +1,77 @@ +#define THERMAL_ENERGY_CHANGE 12000 + +SUBSYSTEM_DEF(stationheater) + name = "Station Boiler" + wait = 8 SECOND + runlevels = RUNLEVEL_GAME + priority = FIRE_PRIORITY_CRYOPLANETS + flags = SS_BACKGROUND // Extremely low priority + + dependencies = list( + /datum/controller/subsystem/planets, + /datum/controller/subsystem/air + ) + + var/list/boilers = list() + var/list/radiators = list() + VAR_PRIVATE/list/current_run = list() + VAR_PRIVATE/datum/weakref/current_loop_heater + + // Radiator power + var/static/target_heat_temperature = T20C //The temperature the radiator wants the room to reach + +/datum/controller/subsystem/stationheater/Initialize() + for(var/datum/planet/check in SSplanets.planets) + if(check.cryogenic_temp_shift) + return SS_INIT_SUCCESS + flags |= SS_NO_FIRE + return SS_INIT_NO_NEED // No cryoplanets to deal with! + +/datum/controller/subsystem/stationheater/stat_entry(msg) + msg = " Cr: [length(current_run)] | Br: [length(boilers)] | Rs: [length(radiators)]" + . = ..() + +/datum/controller/subsystem/stationheater/fire(resumed) + if(!length(boilers)) // No boilers? No point, but keep running incase we get one + return + + // Get current run of all radiators on station + if(!resumed) + current_run = radiators.Copy() + current_loop_heater = WEAKREF(pick(boilers)) + + // Process the current run until all raditors are done heating + var/obj/structure/stationboiler/current_heater = get_current_boiler() + while(length(current_run)) + var/obj/machinery/stationboiler_radiator/rad = current_run[length(current_run)] + current_run.len-- + if(!QDELETED(rad)) + handle_radiate(rad, current_heater) + if(MC_TICK_CHECK) + return + +/datum/controller/subsystem/stationheater/proc/get_current_boiler() + return current_loop_heater?.resolve() + +/datum/controller/subsystem/stationheater/proc/handle_radiate(obj/machinery/stationboiler_radiator/radiator, obj/structure/stationboiler/assigned_boiler) + // Check if this boiler is OUR boiler..... And by that just see if it's in the same zlevel complex. + if(!assigned_boiler?.is_on_same_planet(radiator)) + return + + // Update radiator icon if the main boiler is functioning or not + radiator.set_state(assigned_boiler?.is_heating()) + radiator.update_icon() + + // Remove us if we are in an invalid radiator turf + var/turf/radiator_turf = get_turf(radiator) + if(!SScryoplanets.is_station_temp_change_turf(radiator_turf)) + return + + // If the temp is colder than the radiator, begin heating! + var/datum/gas_mixture/environment = radiator_turf.return_air() + var/neededEnergy = environment.get_thermal_energy_change(target_heat_temperature) + if(neededEnergy > 0) + neededEnergy = min(neededEnergy, THERMAL_ENERGY_CHANGE) + environment.add_thermal_energy(neededEnergy) + +#undef THERMAL_ENERGY_CHANGE diff --git a/code/game/area/asteroid_areas.dm b/code/game/area/asteroid_areas.dm index 2beaa886131..fafc36d125e 100644 --- a/code/game/area/asteroid_areas.dm +++ b/code/game/area/asteroid_areas.dm @@ -3,7 +3,7 @@ /area/mine icon_state = "mining" sound_env = ASTEROID - flags = AREA_FLAG_IS_NOT_PERSISTENT + flags = AREA_FLAG_IS_NOT_PERSISTENT | AREA_CRYOPLANET_SHIELDED /area/mine/explored name = "Mine" diff --git a/code/game/machinery/atmoalter/station_boiler_radiator.dm b/code/game/machinery/atmoalter/station_boiler_radiator.dm new file mode 100644 index 00000000000..7ec62a3a546 --- /dev/null +++ b/code/game/machinery/atmoalter/station_boiler_radiator.dm @@ -0,0 +1,40 @@ +/// A floor radiator made to work with the station_boiler. If none are on the map then they are purely asthetic and safe to map. +/obj/machinery/stationboiler_radiator + name = "Station Radiator" + desc = "A radiator pipe connected to the station boiler, used to keep the rooms warm" + icon = 'icons/obj/machines/floor_radiator.dmi' + + plane = PLATING_PLANE + layer = FLOOR_HEATER_LAYER + + icon_state = "off" + use_power = USE_POWER_OFF + bullet_vulnerability = 0 //Invincible machine + anchored = TRUE + density = FALSE + VAR_PRIVATE/actively_radiating = FALSE + +/obj/machinery/stationboiler_radiator/Initialize(mapload) + . = ..() + SSstationheater.radiators += src + +/obj/machinery/stationboiler_radiator/Destroy() + SSstationheater.radiators -= src + . = ..() + +/obj/machinery/stationboiler_radiator/proc/set_state(activate) + if(actively_radiating == activate) + return + actively_radiating = activate + update_icon() + set_light(1, 0.9, "#e9400dff", l_on = actively_radiating) + +/obj/machinery/stationboiler_radiator/update_icon() + icon_state = actively_radiating ? "on" : "off" + +// TODO - Make a viable sane construction method for these +/obj/machinery/stationboiler_radiator/ex_act(severity) + return //Invincible machine + +/obj/machinery/stationboiler_radiator/fall_apart(severity = 3, scatter = TRUE) + return //Invincible machine diff --git a/code/game/objects/items/devices/communicator/UI_tgui.dm b/code/game/objects/items/devices/communicator/UI_tgui.dm index 0e32a8b05dd..5bc39ccc81c 100644 --- a/code/game/objects/items/devices/communicator/UI_tgui.dm +++ b/code/game/objects/items/devices/communicator/UI_tgui.dm @@ -266,7 +266,7 @@ "Low" = planet.weather_holder.current_weather.temp_low - T0C, "WindDir" = planet.weather_holder.wind_dir ? dir2text(planet.weather_holder.wind_dir) : "None", "WindSpeed" = planet.weather_holder.wind_speed ? "[planet.weather_holder.wind_speed > 2 ? "Severe" : "Normal"]" : "None", - "Forecast" = english_list(planet.weather_holder.forecast, and_text = "→", comma_text = "→", final_comma_text = "→") // Unicode RIGHTWARDS ARROW. + "Forecast" = english_list(list("\[[planet.weather_holder.current_weather?.name]\]") + planet.weather_holder.get_forecast_data(), and_text = "→", comma_text = "→", final_comma_text = "→") // Unicode RIGHTWARDS ARROW. ) weather.Add(list(W)) diff --git a/code/modules/planet/planet.dm b/code/modules/planet/planet.dm index a286d65f9a5..d8ec041aabe 100644 --- a/code/modules/planet/planet.dm +++ b/code/modules/planet/planet.dm @@ -28,6 +28,8 @@ var/moon_name = null // Purely for flavor. Null means no moon exists. var/moon_phase = null // Set if above is defined. + var/cryogenic_temp_shift = FALSE // If true, ALL areas that are not AREA_CRYOPLANET_SHIELDED will be brought toward the current weather's temperature. Causing indoor station areas to slowly match the planet's environment. + /datum/planet/New() ..() weather_holder = new(src) diff --git a/code/modules/planet/weather.dm b/code/modules/planet/weather.dm index cba8a262e1b..9feb1b94b6f 100644 --- a/code/modules/planet/weather.dm +++ b/code/modules/planet/weather.dm @@ -9,7 +9,7 @@ var/list/roundstart_weather_chances = list() // Assoc list of weather identifiers and their odds of being picked to happen at roundstart. var/next_weather_shift = null // world.time when the weather subsystem will advance the forecast. var/imminent_weather_shift = null // world.time when weather will shift towards pre-set imminent weather type. - var/list/forecast = list() // A list of what the weather will be in the future. This allows it to be pre-determined and planned around. + VAR_PROTECTED/list/forecast = list() // A list of what the weather will be in the future. This allows it to be pre-determined and planned around. // Holds the weather icon, using vis_contents. Documentation says an /atom/movable is required for placing inside another atom's vis_contents. var/atom/movable/weather_visuals/visuals = null @@ -132,7 +132,8 @@ forecast.Cut() build_forecast() - +/datum/weather_holder/proc/get_forecast_data() + return forecast /datum/weather_holder/proc/update_icon_effects() visuals.icon_state = current_weather.icon_state diff --git a/icons/obj/machines/floor_radiator.dmi b/icons/obj/machines/floor_radiator.dmi new file mode 100644 index 0000000000000000000000000000000000000000..33fe4854c494a35a764485332d609d8f8e52fb5a GIT binary patch literal 691 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 X>xE_UmyWMW0V(lx^>bP0l+XkKu>(lP literal 0 HcmV?d00001 diff --git a/maps/gateway_vr/lucky_7.dm b/maps/gateway_vr/lucky_7.dm index c379b7a54e9..e353fb17605 100644 --- a/maps/gateway_vr/lucky_7.dm +++ b/maps/gateway_vr/lucky_7.dm @@ -97,7 +97,7 @@ /area/awaymission/lucky7/dorms name = "\improper Gateway - Dorms" icon_state = "crew_quarters" - flags = RAD_SHIELDED | BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_BLOCK_INSTANT_BUILDING + flags = RAD_SHIELDED | BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_BLOCK_INSTANT_BUILDING | AREA_CRYOPLANET_SHIELDED /area/awaymission/lucky7/dorms/one name = "\improper Gateway - Dorm 1" diff --git a/maps/groundbase/groundbase_areas.dm b/maps/groundbase/groundbase_areas.dm index 3b997ead073..38820fe2ef1 100644 --- a/maps/groundbase/groundbase_areas.dm +++ b/maps/groundbase/groundbase_areas.dm @@ -463,7 +463,7 @@ holomap_color = HOLOMAP_AREACOLOR_DORMS icon_state = "grawhisqu" ambience = AMBIENCE_GENERIC - flags = RAD_SHIELDED | BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING + flags = RAD_SHIELDED | BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_CRYOPLANET_SHIELDED /area/groundbase/dorms/bathroom name = "Dormitory Bathroom" diff --git a/maps/redgate/casino_canal/casino_canal.dm b/maps/redgate/casino_canal/casino_canal.dm index a9c237fde3a..f32c469daec 100644 --- a/maps/redgate/casino_canal/casino_canal.dm +++ b/maps/redgate/casino_canal/casino_canal.dm @@ -201,7 +201,7 @@ /area/redgate/underwater_casino/indoors/lower/dorms name = UNDERWATER_CASINO_NAME + " - Dorms" - flags = RAD_SHIELDED | BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_BLOCK_INSTANT_BUILDING + flags = RAD_SHIELDED | BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_BLOCK_INSTANT_BUILDING | AREA_CRYOPLANET_SHIELDED /area/redgate/underwater_casino/indoors/lower/dorms/dorm1 name = UNDERWATER_CASINO_NAME + " - Dormitory One" diff --git a/maps/stellar_delight/stellar_delight_areas.dm b/maps/stellar_delight/stellar_delight_areas.dm index 904b029cb1d..0d1b69b224f 100644 --- a/maps/stellar_delight/stellar_delight_areas.dm +++ b/maps/stellar_delight/stellar_delight_areas.dm @@ -126,7 +126,7 @@ /area/stellardelight/deck1/dorms name = "Dormitory" sound_env = SMALL_SOFTFLOOR - flags = RAD_SHIELDED| BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_BLOCK_INSTANT_BUILDING + flags = RAD_SHIELDED| BLUE_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | AREA_FORBID_EVENTS | AREA_FORBID_SINGULO | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_BLOCK_INSTANT_BUILDING | AREA_CRYOPLANET_SHIELDED /area/stellardelight/deck1/dorms/dorm1 name = "Dormitory One" diff --git a/maps/virtual_reality/constructVR.dm b/maps/virtual_reality/constructVR.dm index 96b23ea3876..e36664b38bd 100644 --- a/maps/virtual_reality/constructVR.dm +++ b/maps/virtual_reality/constructVR.dm @@ -162,7 +162,7 @@ /area/vr/powered/mall/dorms name = "VR Mall Dorms" icon_state = "Sleep" - flags = RAD_SHIELDED | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_LIMIT_DARK_RESPITE + flags = RAD_SHIELDED | AREA_SOUNDPROOF | AREA_ALLOW_LARGE_SIZE | AREA_BLOCK_SUIT_SENSORS | AREA_BLOCK_TRACKING | AREA_LIMIT_DARK_RESPITE | AREA_CRYOPLANET_SHIELDED /area/vr/powered/mall/dorms/dorms1 diff --git a/tgui/packages/tgui/interfaces/StationBoiler.tsx b/tgui/packages/tgui/interfaces/StationBoiler.tsx new file mode 100644 index 00000000000..9493b301b3b --- /dev/null +++ b/tgui/packages/tgui/interfaces/StationBoiler.tsx @@ -0,0 +1,72 @@ +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { + Button, + LabeledList, + ProgressBar, + Section, +} from 'tgui-core/components'; +import type { BooleanLike } from 'tgui-core/react'; +import { capitalize } from 'tgui-core/string'; + +type Data = { + materials: MaterialStack[]; + stored: number; + max: number; + percent: number; + timeleft: string; +}; + +type MaterialStack = { + name: string; + display: string; + qty: string; + can_eject: BooleanLike; +}; + +export const StationBoiler = (props) => { + const { act, data } = useBackend(); + const { materials, timeleft, stored, max } = data; + return ( + + +

act('ignite')}>Ignite} + fill + scrollable + > + + + + {timeleft} + + + + {materials.map((material) => ( + + act('ejectMaterial', { + mat: material.name, + }) + } + > + Eject + + } + > + {material.qty} + + ))} + +
+ + + ); +}; diff --git a/vorestation.dme b/vorestation.dme index 961cd73e99b..fdf699fe3ce 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -513,6 +513,7 @@ #include "code\ATMOSPHERICS\components\binary_devices\passive_gate_vr.dm" #include "code\ATMOSPHERICS\components\binary_devices\pipeturbine.dm" #include "code\ATMOSPHERICS\components\binary_devices\pump.dm" +#include "code\ATMOSPHERICS\components\binary_devices\station_boiler.dm" #include "code\ATMOSPHERICS\components\binary_devices\volume_pump.dm" #include "code\ATMOSPHERICS\components\omni_devices\_omni_extras.dm" #include "code\ATMOSPHERICS\components\omni_devices\filter.dm" @@ -576,6 +577,7 @@ #include "code\controllers\subsystems\chemistry.dm" #include "code\controllers\subsystems\circuits.dm" #include "code\controllers\subsystems\communications.dm" +#include "code\controllers\subsystems\cryoplanets.dm" #include "code\controllers\subsystems\dbcore.dm" #include "code\controllers\subsystems\dcs.dm" #include "code\controllers\subsystems\early_assets.dm" @@ -623,6 +625,7 @@ #include "code\controllers\subsystems\sqlite.dm" #include "code\controllers\subsystems\SSvote.dm" #include "code\controllers\subsystems\starmover.dm" +#include "code\controllers\subsystems\stationheater.dm" #include "code\controllers\subsystems\statpanel.dm" #include "code\controllers\subsystems\sun.dm" #include "code\controllers\subsystems\supply.dm" @@ -1352,6 +1355,7 @@ #include "code\game\machinery\atmoalter\pump.dm" #include "code\game\machinery\atmoalter\pump_vr.dm" #include "code\game\machinery\atmoalter\scrubber.dm" +#include "code\game\machinery\atmoalter\station_boiler_radiator.dm" #include "code\game\machinery\camera\camera.dm" #include "code\game\machinery\camera\camera_assembly.dm" #include "code\game\machinery\camera\motion.dm"