mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-15 20:22:07 +00:00
## About The Pull Request Off shuttle move doesn't need to happen because shuttles use abstract move which calls it anyway Similar story for the ghost stuff, this was actually causing pretty common double calls so we may actually get somethin out of this Oh and something in air which is past me knifing me in the back
932 lines
32 KiB
Plaintext
932 lines
32 KiB
Plaintext
SUBSYSTEM_DEF(air)
|
|
name = "Atmospherics"
|
|
init_order = INIT_ORDER_AIR
|
|
priority = FIRE_PRIORITY_AIR
|
|
wait = 0.5 SECONDS
|
|
flags = SS_BACKGROUND
|
|
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
|
|
|
|
var/cached_cost = 0
|
|
|
|
var/cost_atoms = 0
|
|
var/cost_turfs = 0
|
|
var/cost_hotspots = 0
|
|
var/cost_groups = 0
|
|
var/cost_highpressure = 0
|
|
var/cost_superconductivity = 0
|
|
var/cost_pipenets = 0
|
|
var/cost_atmos_machinery = 0
|
|
var/cost_rebuilds = 0
|
|
var/cost_adjacent = 0
|
|
|
|
var/list/excited_groups = list()
|
|
var/list/active_turfs = list()
|
|
var/list/hotspots = list()
|
|
var/list/networks = list()
|
|
var/list/rebuild_queue = list()
|
|
//Subservient to rebuild queue
|
|
var/list/expansion_queue = list()
|
|
/// List of turfs to recalculate adjacent turfs on before processing
|
|
var/list/adjacent_rebuild = list()
|
|
/// A list of machines that will be processed when currentpart == SSAIR_ATMOSMACHINERY. Use SSair.begin_processing_machine and SSair.stop_processing_machine to add and remove machines.
|
|
var/list/obj/machinery/atmos_machinery = list()
|
|
|
|
var/list/pipe_init_dirs_cache = list()
|
|
//atmos singletons
|
|
var/list/gas_reactions = list()
|
|
var/list/atmos_gen
|
|
var/list/planetary = list() //Lets cache static planetary mixes
|
|
/// List of gas string -> canonical gas mixture
|
|
var/list/strings_to_mix = list()
|
|
|
|
|
|
//Special functions lists
|
|
var/list/turf/active_super_conductivity = list()
|
|
var/list/turf/open/high_pressure_delta = list()
|
|
var/list/atom_process = list()
|
|
/// Reactions which will contribute to a hotspot's size.
|
|
var/list/hotspot_reactions
|
|
|
|
/// A cache of objects that perisists between processing runs when resumed == TRUE. Dangerous, qdel'd objects not cleared from this may cause runtimes on processing.
|
|
var/list/currentrun = list()
|
|
var/currentpart = SSAIR_PIPENETS
|
|
|
|
var/map_loading = TRUE
|
|
var/list/queued_for_activation
|
|
var/display_all_groups = FALSE
|
|
|
|
var/list/reaction_handbook
|
|
var/list/gas_handbook
|
|
|
|
|
|
/datum/controller/subsystem/air/stat_entry(msg)
|
|
msg += "C:{"
|
|
msg += "AT:[round(cost_turfs,1)]|"
|
|
msg += "HS:[round(cost_hotspots,1)]|"
|
|
msg += "EG:[round(cost_groups,1)]|"
|
|
msg += "HP:[round(cost_highpressure,1)]|"
|
|
msg += "SC:[round(cost_superconductivity,1)]|"
|
|
msg += "PN:[round(cost_pipenets,1)]|"
|
|
msg += "AM:[round(cost_atmos_machinery,1)]|"
|
|
msg += "AO:[round(cost_atoms, 1)]|"
|
|
msg += "RB:[round(cost_rebuilds,1)]|"
|
|
msg += "AJ:[round(cost_adjacent,1)]|"
|
|
msg += "} "
|
|
msg += "AT:[active_turfs.len]|"
|
|
msg += "HS:[hotspots.len]|"
|
|
msg += "EG:[excited_groups.len]|"
|
|
msg += "HP:[high_pressure_delta.len]|"
|
|
msg += "SC:[active_super_conductivity.len]|"
|
|
msg += "PN:[networks.len]|"
|
|
msg += "AM:[atmos_machinery.len]|"
|
|
msg += "AO:[atom_process.len]|"
|
|
msg += "RB:[rebuild_queue.len]|"
|
|
msg += "EP:[expansion_queue.len]|"
|
|
msg += "AJ:[adjacent_rebuild.len]|"
|
|
msg += "AT/MS:[round((cost ? active_turfs.len/cost : 0),0.1)]"
|
|
return ..()
|
|
|
|
|
|
/datum/controller/subsystem/air/Initialize()
|
|
map_loading = FALSE
|
|
gas_reactions = init_gas_reactions()
|
|
hotspot_reactions = init_hotspot_reactions()
|
|
|
|
setup_allturfs()
|
|
setup_atmos_machinery()
|
|
setup_pipenets()
|
|
setup_turf_visuals()
|
|
process_adjacent_rebuild()
|
|
atmos_handbooks_init()
|
|
return SS_INIT_SUCCESS
|
|
|
|
|
|
/datum/controller/subsystem/air/fire(resumed = FALSE)
|
|
var/timer = TICK_USAGE_REAL
|
|
|
|
//Rebuilds can happen at any time, so this needs to be done outside of the normal system
|
|
cost_rebuilds = 0
|
|
cost_adjacent = 0
|
|
|
|
// We need to have a solid setup for turfs before fire, otherwise we'll get massive runtimes and strange behavior
|
|
if(length(adjacent_rebuild))
|
|
timer = TICK_USAGE_REAL
|
|
process_adjacent_rebuild()
|
|
//This does mean that the apperent rebuild costs fluctuate very quickly, this is just the cost of having them always process, no matter what
|
|
cost_adjacent = TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
|
|
// Every time we fire, we want to make sure pipenets are rebuilt. The game state could have changed between each fire() proc call
|
|
// and anything missing a pipenet can lead to unintended behaviour at worse and various runtimes at best.
|
|
if(length(rebuild_queue) || length(expansion_queue))
|
|
timer = TICK_USAGE_REAL
|
|
process_rebuilds()
|
|
//This does mean that the apperent rebuild costs fluctuate very quickly, this is just the cost of having them always process, no matter what
|
|
cost_rebuilds = TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
|
|
if(currentpart == SSAIR_PIPENETS || !resumed)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_pipenets(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_pipenets = MC_AVERAGE(cost_pipenets, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_ATMOSMACHINERY
|
|
|
|
if(currentpart == SSAIR_ATMOSMACHINERY)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_atmos_machinery(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_atmos_machinery = MC_AVERAGE(cost_atmos_machinery, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_ACTIVETURFS
|
|
|
|
if(currentpart == SSAIR_ACTIVETURFS)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_active_turfs(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_HOTSPOTS
|
|
|
|
if(currentpart == SSAIR_HOTSPOTS) //We do this before excited groups to allow breakdowns to be independent of adding turfs while still *mostly preventing mass fires
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_hotspots(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_hotspots = MC_AVERAGE(cost_hotspots, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_EXCITEDGROUPS
|
|
|
|
if(currentpart == SSAIR_EXCITEDGROUPS)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_excited_groups(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_groups = MC_AVERAGE(cost_groups, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_HIGHPRESSURE
|
|
|
|
if(currentpart == SSAIR_HIGHPRESSURE)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_high_pressure_delta(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_highpressure = MC_AVERAGE(cost_highpressure, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_SUPERCONDUCTIVITY
|
|
|
|
if(currentpart == SSAIR_SUPERCONDUCTIVITY)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_super_conductivity(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_superconductivity = MC_AVERAGE(cost_superconductivity, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
currentpart = SSAIR_PROCESS_ATOMS
|
|
|
|
if(currentpart == SSAIR_PROCESS_ATOMS)
|
|
timer = TICK_USAGE_REAL
|
|
if(!resumed)
|
|
cached_cost = 0
|
|
process_atoms(resumed)
|
|
cached_cost += TICK_USAGE_REAL - timer
|
|
if(state != SS_RUNNING)
|
|
return
|
|
cost_atoms = MC_AVERAGE(cost_atoms, TICK_DELTA_TO_MS(cached_cost))
|
|
resumed = FALSE
|
|
|
|
currentpart = SSAIR_PIPENETS
|
|
SStgui.update_uis(SSair) //Lightning fast debugging motherfucker
|
|
|
|
/datum/controller/subsystem/air/Recover()
|
|
excited_groups = SSair.excited_groups
|
|
active_turfs = SSair.active_turfs
|
|
hotspots = SSair.hotspots
|
|
networks = SSair.networks
|
|
rebuild_queue = SSair.rebuild_queue
|
|
expansion_queue = SSair.expansion_queue
|
|
adjacent_rebuild = SSair.adjacent_rebuild
|
|
atmos_machinery = SSair.atmos_machinery
|
|
pipe_init_dirs_cache = SSair.pipe_init_dirs_cache
|
|
gas_reactions = SSair.gas_reactions
|
|
atmos_gen = SSair.atmos_gen
|
|
planetary = SSair.planetary
|
|
active_super_conductivity = SSair.active_super_conductivity
|
|
high_pressure_delta = SSair.high_pressure_delta
|
|
atom_process = SSair.atom_process
|
|
currentrun = SSair.currentrun
|
|
queued_for_activation = SSair.queued_for_activation
|
|
|
|
/datum/controller/subsystem/air/proc/process_adjacent_rebuild(init = FALSE)
|
|
var/list/queue = adjacent_rebuild
|
|
|
|
while (length(queue))
|
|
var/turf/currT = queue[1]
|
|
var/goal = queue[currT]
|
|
queue.Cut(1,2)
|
|
|
|
currT.immediate_calculate_adjacent_turfs()
|
|
if(goal == MAKE_ACTIVE)
|
|
add_to_active(currT)
|
|
else if(goal == KILL_EXCITED)
|
|
add_to_active(currT, TRUE)
|
|
|
|
if(init)
|
|
CHECK_TICK
|
|
else
|
|
if(MC_TICK_CHECK)
|
|
break
|
|
|
|
/datum/controller/subsystem/air/proc/process_pipenets(resumed = FALSE)
|
|
if (!resumed)
|
|
src.currentrun = networks.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/datum/thing = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
if(thing)
|
|
thing.process()
|
|
else
|
|
networks.Remove(thing)
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/add_to_rebuild_queue(obj/machinery/atmospherics/atmos_machine)
|
|
if(istype(atmos_machine, /obj/machinery/atmospherics) && !atmos_machine.rebuilding)
|
|
rebuild_queue += atmos_machine
|
|
atmos_machine.rebuilding = TRUE
|
|
|
|
/datum/controller/subsystem/air/proc/add_to_expansion(datum/pipeline/line, starting_point)
|
|
var/list/new_packet = new(SSAIR_REBUILD_QUEUE)
|
|
new_packet[SSAIR_REBUILD_PIPELINE] = line
|
|
new_packet[SSAIR_REBUILD_QUEUE] = list(starting_point)
|
|
expansion_queue += list(new_packet)
|
|
|
|
/datum/controller/subsystem/air/proc/remove_from_expansion(datum/pipeline/line)
|
|
for(var/list/packet in expansion_queue)
|
|
if(packet[SSAIR_REBUILD_PIPELINE] == line)
|
|
expansion_queue -= packet
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_atoms(resumed = FALSE)
|
|
if(!resumed)
|
|
src.currentrun = atom_process.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/atom/talk_to = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
if(!talk_to)
|
|
return
|
|
talk_to.process_exposure()
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_atmos_machinery(resumed = FALSE)
|
|
if (!resumed)
|
|
src.currentrun = atmos_machinery.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/obj/machinery/M = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
if(!M)
|
|
atmos_machinery -= M
|
|
if(M.process_atmos() == PROCESS_KILL)
|
|
stop_processing_machine(M)
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
|
|
/datum/controller/subsystem/air/proc/process_super_conductivity(resumed = FALSE)
|
|
if (!resumed)
|
|
src.currentrun = active_super_conductivity.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/turf/T = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
T.super_conduct()
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_hotspots(resumed = FALSE)
|
|
if (!resumed)
|
|
src.currentrun = hotspots.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/obj/effect/hotspot/H = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
if (H)
|
|
H.process()
|
|
else
|
|
hotspots -= H
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_high_pressure_delta(resumed = FALSE)
|
|
while (high_pressure_delta.len)
|
|
var/turf/open/T = high_pressure_delta[high_pressure_delta.len]
|
|
high_pressure_delta.len--
|
|
T.high_pressure_movements()
|
|
T.pressure_difference = 0
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_active_turfs(resumed = FALSE)
|
|
//cache for sanic speed
|
|
var/fire_count = times_fired
|
|
if (!resumed)
|
|
src.currentrun = active_turfs.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/turf/open/T = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
if (T)
|
|
T.process_cell(fire_count)
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_excited_groups(resumed = FALSE)
|
|
if (!resumed)
|
|
src.currentrun = excited_groups.Copy()
|
|
//cache for sanic speed (lists are references anyways)
|
|
var/list/currentrun = src.currentrun
|
|
while(currentrun.len)
|
|
var/datum/excited_group/EG = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
EG.breakdown_cooldown++
|
|
EG.dismantle_cooldown++
|
|
if(EG.breakdown_cooldown >= EXCITED_GROUP_BREAKDOWN_CYCLES)
|
|
EG.self_breakdown(poke_turfs = TRUE)
|
|
else if(EG.dismantle_cooldown >= EXCITED_GROUP_DISMANTLE_CYCLES && !(EG.turf_reactions & (REACTING | STOP_REACTIONS)))
|
|
EG.dismantle()
|
|
EG.turf_reactions = NONE
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
/datum/controller/subsystem/air/proc/process_rebuilds()
|
|
//Yes this does mean rebuilding pipenets can freeze up the subsystem forever, but if we're in that situation something else is very wrong
|
|
var/list/currentrun = rebuild_queue
|
|
while(currentrun.len || length(expansion_queue))
|
|
while(currentrun.len && !length(expansion_queue)) //If we found anything, process that first
|
|
var/obj/machinery/atmospherics/remake = currentrun[currentrun.len]
|
|
currentrun.len--
|
|
if (!remake)
|
|
continue
|
|
remake.rebuild_pipes()
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
var/list/queue = expansion_queue
|
|
while(queue.len)
|
|
var/list/pack = queue[queue.len]
|
|
//We operate directly with the pipeline like this because we can trust any rebuilds to remake it properly
|
|
var/datum/pipeline/linepipe = pack[SSAIR_REBUILD_PIPELINE]
|
|
var/list/border = pack[SSAIR_REBUILD_QUEUE]
|
|
expand_pipeline(linepipe, border)
|
|
if(state != SS_RUNNING) //expand_pipeline can fail a tick check, we shouldn't let things get too fucky here
|
|
return
|
|
|
|
linepipe.building = FALSE
|
|
queue.len--
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
///Rebuilds a pipeline by expanding outwards, while yielding when sane
|
|
/datum/controller/subsystem/air/proc/expand_pipeline(datum/pipeline/net, list/border)
|
|
while(border.len)
|
|
var/obj/machinery/atmospherics/borderline = border[border.len]
|
|
border.len--
|
|
|
|
var/list/result = borderline.pipeline_expansion(net)
|
|
if(!length(result))
|
|
continue
|
|
for(var/obj/machinery/atmospherics/considered_device in result)
|
|
if(!istype(considered_device, /obj/machinery/atmospherics/pipe))
|
|
considered_device.set_pipenet(net, borderline)
|
|
net.add_machinery_member(considered_device)
|
|
continue
|
|
var/obj/machinery/atmospherics/pipe/item = considered_device
|
|
if(net.members.Find(item))
|
|
continue
|
|
if(item.parent)
|
|
var/static/pipenetwarnings = 10
|
|
if(pipenetwarnings > 0)
|
|
log_mapping("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) around [AREACOORD(item)].")
|
|
pipenetwarnings--
|
|
if(pipenetwarnings == 0)
|
|
log_mapping("build_pipeline(): further messages about pipenets will be suppressed")
|
|
|
|
net.members += item
|
|
border += item
|
|
|
|
net.air.volume += item.volume
|
|
item.replace_pipenet(item.parent, net)
|
|
|
|
if(item.air_temporary)
|
|
net.air.merge(item.air_temporary)
|
|
item.air_temporary = null
|
|
|
|
if (MC_TICK_CHECK)
|
|
return
|
|
|
|
///Removes a turf from processing, and causes its excited group to clean up so things properly adapt to the change
|
|
/datum/controller/subsystem/air/proc/remove_from_active(turf/open/T)
|
|
active_turfs -= T
|
|
if(currentpart == SSAIR_ACTIVETURFS)
|
|
currentrun -= T
|
|
#ifdef VISUALIZE_ACTIVE_TURFS //Use this when you want details about how the turfs are moving, display_all_groups should work for normal operation
|
|
T.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_VIBRANT_LIME)
|
|
#endif
|
|
if(istype(T))
|
|
T.excited = FALSE
|
|
if(T.excited_group)
|
|
//If this fires during active turfs it'll cause a slight removal of active turfs, as they breakdown if they have no excited group
|
|
//The group also expands by a tile per rebuild on each edge, suffering
|
|
T.excited_group.garbage_collect() //Kill the excited group, it'll reform on its own later
|
|
|
|
///Puts an active turf to sleep so it doesn't process. Do this without cleaning up its excited group.
|
|
/datum/controller/subsystem/air/proc/sleep_active_turf(turf/open/T)
|
|
active_turfs -= T
|
|
if(currentpart == SSAIR_ACTIVETURFS)
|
|
currentrun -= T
|
|
#ifdef VISUALIZE_ACTIVE_TURFS
|
|
T.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_VIBRANT_LIME)
|
|
#endif
|
|
if(istype(T))
|
|
T.excited = FALSE
|
|
|
|
///Adds a turf to active processing, handles duplicates. Call this with blockchanges == TRUE if you want to nuke the assoc excited group
|
|
/datum/controller/subsystem/air/proc/add_to_active(turf/open/T, blockchanges = FALSE)
|
|
if(istype(T) && T.air)
|
|
T.significant_share_ticker = 0
|
|
if(blockchanges && T.excited_group) //This is used almost exclusivly for shuttles, so the excited group doesn't stay behind
|
|
T.excited_group.garbage_collect() //Nuke it
|
|
if(T.excited) //Don't keep doing it if there's no point
|
|
return
|
|
#ifdef VISUALIZE_ACTIVE_TURFS
|
|
T.add_atom_colour(COLOR_VIBRANT_LIME, TEMPORARY_COLOUR_PRIORITY)
|
|
#endif
|
|
T.excited = TRUE
|
|
active_turfs += T
|
|
if(currentpart == SSAIR_ACTIVETURFS)
|
|
currentrun += T
|
|
else if(T.flags_1 & INITIALIZED_1)
|
|
for(var/turf/S in T.atmos_adjacent_turfs)
|
|
add_to_active(S, TRUE)
|
|
else if(map_loading)
|
|
if(queued_for_activation)
|
|
queued_for_activation[T] = T
|
|
return
|
|
else
|
|
T.requires_activation = TRUE
|
|
|
|
/datum/controller/subsystem/air/StartLoadingMap()
|
|
LAZYINITLIST(queued_for_activation)
|
|
map_loading = TRUE
|
|
|
|
/datum/controller/subsystem/air/StopLoadingMap()
|
|
map_loading = FALSE
|
|
for(var/T in queued_for_activation)
|
|
add_to_active(T, TRUE)
|
|
queued_for_activation.Cut()
|
|
|
|
/datum/controller/subsystem/air/proc/setup_allturfs()
|
|
var/list/active_turfs = src.active_turfs
|
|
times_fired++
|
|
|
|
// Clear active turfs - faster than removing every single turf in the world
|
|
// one-by-one, and Initalize_Atmos only ever adds `src` back in.
|
|
#ifdef VISUALIZE_ACTIVE_TURFS
|
|
for(var/jumpy in active_turfs)
|
|
var/turf/active = jumpy
|
|
active.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_VIBRANT_LIME)
|
|
#endif
|
|
active_turfs.Cut()
|
|
// We compare this against turf.current cycle using <= to ensure O(n)
|
|
// It defaults to 0, so we start at -1
|
|
var/time = -1
|
|
|
|
var/list/turf/open/difference_check = list()
|
|
for(var/turf/setup as anything in ALL_TURFS())
|
|
if (!setup.init_air)
|
|
continue
|
|
// We pass the tick as the current step so if we sleep the step changes
|
|
// This way we can make setting up adjacent turfs O(n) rather then O(n^2)
|
|
setup.Initalize_Atmos(time)
|
|
// We assert that we'll only get open turfs here
|
|
difference_check += setup
|
|
if(CHECK_TICK)
|
|
time--
|
|
|
|
// Now we're gonna compare for differences
|
|
// Taking advantage of current cycle being set to negative before this run to do A->B B->A prevention
|
|
for(var/turf/open/potential_diff as anything in difference_check)
|
|
// I can't use 0 here, so we're gonna do this instead. If it ever breaks I'll eat my shoe
|
|
potential_diff.current_cycle = -INFINITE
|
|
for(var/turf/open/enemy_tile as anything in potential_diff.atmos_adjacent_turfs)
|
|
// If it's already been processed, then it's already talked to us
|
|
if(enemy_tile.current_cycle == -INFINITE)
|
|
continue
|
|
// .air instead of .return_air() because we can guarentee that the proc won't do anything
|
|
if(potential_diff.air.compare(enemy_tile.air))
|
|
//testing("Active turf found. Return value of compare(): [T.air.compare(enemy_tile.air)]")
|
|
if(!potential_diff.excited)
|
|
potential_diff.excited = TRUE
|
|
SSair.active_turfs += potential_diff
|
|
if(!enemy_tile.excited)
|
|
enemy_tile.excited = TRUE
|
|
SSair.active_turfs += enemy_tile
|
|
// No sense continuing to iterate
|
|
break
|
|
CHECK_TICK
|
|
|
|
if(active_turfs.len)
|
|
var/starting_ats = active_turfs.len
|
|
sleep(world.tick_lag)
|
|
var/timer = world.timeofday
|
|
|
|
log_mapping("There are [starting_ats] active turfs at roundstart caused by a difference of the air between the adjacent turfs. \
|
|
To locate these active turfs, go into the \"Debug\" tab of your stat-panel. Then hit the verb that says \"Mapping Verbs - Enable\". \
|
|
Now, you can see all of the associated coordinates using \"Mapping -> Show roundstart AT list\" verb.")
|
|
|
|
for(var/turf/T in active_turfs)
|
|
GLOB.active_turfs_startlist += T
|
|
|
|
//now lets clear out these active turfs
|
|
var/list/turfs_to_check = active_turfs.Copy()
|
|
do
|
|
var/list/new_turfs_to_check = list()
|
|
for(var/turf/open/T in turfs_to_check)
|
|
new_turfs_to_check += T.resolve_active_graph()
|
|
CHECK_TICK
|
|
|
|
active_turfs += new_turfs_to_check
|
|
turfs_to_check = new_turfs_to_check
|
|
while (turfs_to_check.len)
|
|
|
|
var/ending_ats = active_turfs.len
|
|
for(var/thing in excited_groups)
|
|
var/datum/excited_group/EG = thing
|
|
EG.self_breakdown(roundstart = TRUE)
|
|
EG.dismantle()
|
|
CHECK_TICK
|
|
|
|
log_active_turfs() // invoke this here so we can count the time it takes to run this proc as "wasted time", quite simple honestly.
|
|
|
|
var/msg = "HEY! LISTEN! [DisplayTimeText(world.timeofday - timer, 0.00001)] were wasted processing [starting_ats] turf(s) (connected to [ending_ats - starting_ats] other turfs) with atmos differences at round start."
|
|
to_chat(world, span_boldannounce("[msg]"))
|
|
warning(msg)
|
|
|
|
/// Logs all active turfs at roundstart to the mapping log so it can be readily accessed.
|
|
/datum/controller/subsystem/air/proc/log_active_turfs()
|
|
// sadly this has to be here because we can't realistically expect that all active turfs will be resolved in every possible situation when running through CI.
|
|
// In an ideal world, we would have absolutely zero active turfs 99.99% of the time, but that's not the case. `log_mapping()` during world initialize triggers a CI fail.
|
|
#ifdef UNIT_TESTS
|
|
return
|
|
#else
|
|
// Associated lists, left-hand-side is the z-level or z-trait, right-hand-side is the number of active turfs associated with that.
|
|
var/list/tally_by_level = list()
|
|
// Discriminate for certain z-traits, stuff like "Linkage" is not helpful.
|
|
var/list/tally_by_level_trait = list(
|
|
ZTRAIT_AWAY = 0,
|
|
ZTRAIT_CENTCOM = 0,
|
|
ZTRAIT_ICE_RUINS = 0,
|
|
ZTRAIT_ICE_RUINS_UNDERGROUND = 0,
|
|
ZTRAIT_ISOLATED_RUINS = 0,
|
|
ZTRAIT_LAVA_RUINS = 0,
|
|
ZTRAIT_MINING = 0,
|
|
ZTRAIT_RESERVED = 0,
|
|
ZTRAIT_SPACE_RUINS = 0,
|
|
ZTRAIT_STATION = 0,
|
|
)
|
|
|
|
var/list/message_to_log = list()
|
|
|
|
message_to_log += "\nAll that follows is a turf with an active air difference at roundstart. To clear this, make sure that all of the turfs listed below are connected to a turf with the same air contents.\n\
|
|
In an ideal world, this list should have enough information to help you locate the active turf(s) in question. Unfortunately, this might not be an ideal world.\n\
|
|
If the round is still ongoing, you can use the \"Mapping -> Show roundstart AT list\" verb to see exactly what active turfs were detected. Otherwise, good luck."
|
|
|
|
for(var/turf/active_turf as anything in GLOB.active_turfs_startlist)
|
|
var/turf_z = active_turf.z
|
|
var/datum/space_level/level = SSmapping.z_list[turf_z]
|
|
var/list/level_traits = list()
|
|
for(var/trait in level.traits)
|
|
if(!isnull(tally_by_level_trait[trait]))
|
|
level_traits += trait
|
|
tally_by_level_trait[trait]++
|
|
|
|
// so we can pass along the area type for the log, making it much easier to locate the active turf for a mapper assuming all area types are unique. This is only really a problem for stuff like ruin areas.
|
|
var/area/turf_area = get_area(active_turf)
|
|
message_to_log += "Active turf: [AREACOORD(active_turf)] ([turf_area.type]). Turf type: [active_turf.type]. Relevant Z-Trait(s): [english_list(level_traits)]."
|
|
|
|
tally_by_level["[turf_z]"]++
|
|
|
|
// Following is so we can detect which rounds were "problematic" as far as active turfs go.
|
|
SSblackbox.record_feedback("amount", "overall_roundstart_active_turfs", length(GLOB.active_turfs_startlist))
|
|
|
|
for(var/z_level in tally_by_level)
|
|
var/level_turf_count = tally_by_level[z_level]
|
|
if(level_turf_count == 0) // no point logging it
|
|
continue
|
|
message_to_log += "Z-Level [z_level] has [level_turf_count] active turf(s)."
|
|
SSblackbox.record_feedback("tally", "roundstart_active_turfs_per_z", level_turf_count, z_level)
|
|
|
|
for(var/z_trait in tally_by_level_trait)
|
|
var/trait_turf_count = tally_by_level_trait[z_trait]
|
|
if(trait_turf_count == 0)
|
|
continue
|
|
message_to_log += "Z-Level trait [z_trait] has [trait_turf_count] active turf(s)."
|
|
SSblackbox.record_feedback("amount", "roundstart_active_turfs_for_trait_[z_trait]", trait_turf_count)
|
|
|
|
message_to_log += "End of active turf list."
|
|
log_mapping(message_to_log.Join("\n"))
|
|
#endif
|
|
|
|
/turf/open/proc/resolve_active_graph()
|
|
. = list()
|
|
var/datum/excited_group/EG = excited_group
|
|
if (blocks_air || !air)
|
|
return
|
|
if (!EG)
|
|
EG = new
|
|
EG.add_turf(src)
|
|
|
|
for (var/turf/open/ET in atmos_adjacent_turfs)
|
|
if (ET.blocks_air || !ET.air)
|
|
continue
|
|
|
|
var/ET_EG = ET.excited_group
|
|
if (ET_EG)
|
|
if (ET_EG != EG)
|
|
EG.merge_groups(ET_EG)
|
|
EG = excited_group //merge_groups() may decide to replace our current EG
|
|
else
|
|
EG.add_turf(ET)
|
|
if (!ET.excited)
|
|
ET.excited = TRUE
|
|
. += ET
|
|
|
|
/turf/open/space/resolve_active_graph()
|
|
return list()
|
|
|
|
/datum/controller/subsystem/air/proc/setup_atmos_machinery()
|
|
for (var/obj/machinery/atmospherics/AM in atmos_machinery)
|
|
AM.atmos_init()
|
|
CHECK_TICK
|
|
|
|
//this can't be done with setup_atmos_machinery() because
|
|
// all atmos machinery has to initalize before the first
|
|
// pipenet can be built.
|
|
/datum/controller/subsystem/air/proc/setup_pipenets()
|
|
for (var/obj/machinery/atmospherics/AM in atmos_machinery)
|
|
var/list/targets = AM.get_rebuild_targets()
|
|
for(var/datum/pipeline/build_off as anything in targets)
|
|
build_off.build_pipeline_blocking(AM)
|
|
CHECK_TICK
|
|
|
|
GLOBAL_LIST_EMPTY(colored_turfs)
|
|
GLOBAL_LIST_EMPTY(colored_images)
|
|
/datum/controller/subsystem/air/proc/setup_turf_visuals()
|
|
for(var/sharp_color in GLOB.contrast_colors)
|
|
var/list/add_to = list()
|
|
GLOB.colored_turfs += list(add_to)
|
|
for(var/offset in 0 to SSmapping.max_plane_offset)
|
|
var/obj/effect/overlay/atmos_excited/suger_high = new()
|
|
SET_PLANE_W_SCALAR(suger_high, HIGH_GAME_PLANE, offset)
|
|
add_to += suger_high
|
|
var/image/shiny = new('icons/effects/effects.dmi', suger_high, "atmos_top")
|
|
SET_PLANE_W_SCALAR(shiny, HIGH_GAME_PLANE, offset)
|
|
shiny.color = sharp_color
|
|
GLOB.colored_images += shiny
|
|
|
|
/datum/controller/subsystem/air/proc/setup_template_machinery(list/atmos_machines)
|
|
var/obj/machinery/atmospherics/AM
|
|
for(var/A in 1 to atmos_machines.len)
|
|
AM = atmos_machines[A]
|
|
AM.atmos_init()
|
|
CHECK_TICK
|
|
|
|
for(var/A in 1 to atmos_machines.len)
|
|
AM = atmos_machines[A]
|
|
var/list/targets = AM.get_rebuild_targets()
|
|
for(var/datum/pipeline/build_off as anything in targets)
|
|
build_off.build_pipeline_blocking(AM)
|
|
CHECK_TICK
|
|
|
|
|
|
/datum/controller/subsystem/air/proc/get_init_dirs(type, dir, init_dir)
|
|
|
|
if(!pipe_init_dirs_cache[type])
|
|
pipe_init_dirs_cache[type] = list()
|
|
|
|
if(!pipe_init_dirs_cache[type]["[init_dir]"])
|
|
pipe_init_dirs_cache[type]["[init_dir]"] = list()
|
|
|
|
if(!pipe_init_dirs_cache[type]["[init_dir]"]["[dir]"])
|
|
var/obj/machinery/atmospherics/temp = new type(null, FALSE, dir, init_dir)
|
|
pipe_init_dirs_cache[type]["[init_dir]"]["[dir]"] = temp.get_init_directions()
|
|
qdel(temp)
|
|
|
|
return pipe_init_dirs_cache[type]["[init_dir]"]["[dir]"]
|
|
|
|
/datum/controller/subsystem/air/proc/generate_atmos()
|
|
atmos_gen = list()
|
|
for(var/T in subtypesof(/datum/atmosphere))
|
|
var/datum/atmosphere/atmostype = T
|
|
atmos_gen[initial(atmostype.id)] = new atmostype
|
|
|
|
/// Takes a gas string, returns the matching mutable gas_mixture
|
|
/datum/controller/subsystem/air/proc/parse_gas_string(gas_string, gastype = /datum/gas_mixture)
|
|
var/datum/gas_mixture/cached = strings_to_mix["[gas_string]-[gastype]"]
|
|
|
|
if(cached)
|
|
if(istype(cached, /datum/gas_mixture/immutable))
|
|
return cached
|
|
return cached.copy()
|
|
|
|
var/datum/gas_mixture/canonical_mix = new gastype()
|
|
// We set here so any future key changes don't fuck us
|
|
strings_to_mix["[gas_string]-[gastype]"] = canonical_mix
|
|
gas_string = preprocess_gas_string(gas_string)
|
|
|
|
var/list/gases = canonical_mix.gases
|
|
var/list/gas = params2list(gas_string)
|
|
if(gas["TEMP"])
|
|
canonical_mix.temperature = text2num(gas["TEMP"])
|
|
canonical_mix.temperature_archived = canonical_mix.temperature
|
|
gas -= "TEMP"
|
|
else // if we do not have a temp in the new gas mix lets assume room temp.
|
|
canonical_mix.temperature = T20C
|
|
for(var/id in gas)
|
|
var/path = id
|
|
if(!ispath(path))
|
|
path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
|
|
ADD_GAS(path, gases)
|
|
gases[path][MOLES] = text2num(gas[id])
|
|
|
|
if(istype(canonical_mix, /datum/gas_mixture/immutable))
|
|
return canonical_mix
|
|
return canonical_mix.copy()
|
|
|
|
/datum/controller/subsystem/air/proc/preprocess_gas_string(gas_string)
|
|
if(!atmos_gen)
|
|
generate_atmos()
|
|
if(!atmos_gen[gas_string])
|
|
return gas_string
|
|
var/datum/atmosphere/mix = atmos_gen[gas_string]
|
|
return mix.gas_string
|
|
|
|
/**
|
|
* Adds a given machine to the processing system for SSAIR_ATMOSMACHINERY processing.
|
|
*
|
|
* Arguments:
|
|
* * machine - The machine to start processing. Can be any /obj/machinery.
|
|
*/
|
|
/datum/controller/subsystem/air/proc/start_processing_machine(obj/machinery/machine)
|
|
if(machine.atmos_processing)
|
|
return
|
|
if(QDELETED(machine))
|
|
stack_trace("We tried to add a garbage collecting machine to SSair. Don't")
|
|
return
|
|
machine.atmos_processing = TRUE
|
|
atmos_machinery += machine
|
|
|
|
/**
|
|
* Removes a given machine to the processing system for SSAIR_ATMOSMACHINERY processing.
|
|
*
|
|
* Arguments:
|
|
* * machine - The machine to stop processing.
|
|
*/
|
|
/datum/controller/subsystem/air/proc/stop_processing_machine(obj/machinery/machine)
|
|
if(!machine.atmos_processing)
|
|
return
|
|
machine.atmos_processing = FALSE
|
|
atmos_machinery -= machine
|
|
|
|
// If we're currently processing atmos machines, there's a chance this machine is in
|
|
// the currentrun list, which is a cache of atmos_machinery. Remove it from that list
|
|
// as well to prevent processing qdeleted objects in the cache.
|
|
if(currentpart == SSAIR_ATMOSMACHINERY)
|
|
currentrun -= machine
|
|
|
|
/datum/controller/subsystem/air/ui_state(mob/user)
|
|
return GLOB.debug_state
|
|
|
|
/datum/controller/subsystem/air/ui_interact(mob/user, datum/tgui/ui)
|
|
ui = SStgui.try_update_ui(user, src, ui)
|
|
if(!ui)
|
|
ui = new(user, src, "AtmosControlPanel")
|
|
ui.set_autoupdate(FALSE)
|
|
ui.open()
|
|
|
|
/datum/controller/subsystem/air/ui_data(mob/user)
|
|
var/list/data = list()
|
|
data["excited_groups"] = list()
|
|
for(var/datum/excited_group/group in excited_groups)
|
|
var/turf/T = group.turf_list[1]
|
|
var/area/target = get_area(T)
|
|
var/max = 0
|
|
#ifdef TRACK_MAX_SHARE
|
|
for(var/who in group.turf_list)
|
|
var/turf/open/lad = who
|
|
max = max(lad.max_share, max)
|
|
#endif
|
|
data["excited_groups"] += list(list(
|
|
"jump_to" = REF(T), //Just go to the first turf
|
|
"group" = REF(group),
|
|
"area" = target.name,
|
|
"breakdown" = group.breakdown_cooldown,
|
|
"dismantle" = group.dismantle_cooldown,
|
|
"size" = group.turf_list.len,
|
|
"should_show" = group.should_display,
|
|
"max_share" = max
|
|
))
|
|
data["active_size"] = active_turfs.len
|
|
data["hotspots_size"] = hotspots.len
|
|
data["excited_size"] = excited_groups.len
|
|
data["conducting_size"] = active_super_conductivity.len
|
|
data["frozen"] = can_fire
|
|
data["show_all"] = display_all_groups
|
|
data["fire_count"] = times_fired
|
|
#ifdef TRACK_MAX_SHARE
|
|
data["display_max"] = TRUE
|
|
#else
|
|
data["display_max"] = FALSE
|
|
#endif
|
|
data["showing_user"] = user.hud_used.atmos_debug_overlays
|
|
return data
|
|
|
|
/datum/controller/subsystem/air/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
|
. = ..()
|
|
if(. || !check_rights_for(usr.client, R_DEBUG))
|
|
return
|
|
switch(action)
|
|
if("move-to-target")
|
|
var/turf/target = locate(params["spot"])
|
|
if(!target)
|
|
return
|
|
usr.forceMove(target)
|
|
if("toggle-freeze")
|
|
can_fire = !can_fire
|
|
return TRUE
|
|
if("toggle_show_group")
|
|
var/datum/excited_group/group = locate(params["group"])
|
|
if(!group)
|
|
return
|
|
group.should_display = !group.should_display
|
|
if(display_all_groups)
|
|
return TRUE
|
|
if(group.should_display)
|
|
group.display_turfs()
|
|
else
|
|
group.hide_turfs()
|
|
return TRUE
|
|
if("toggle_show_all")
|
|
display_all_groups = !display_all_groups
|
|
for(var/datum/excited_group/group in excited_groups)
|
|
if(display_all_groups)
|
|
group.display_turfs()
|
|
else if(!group.should_display) //Don't flicker yeah?
|
|
group.hide_turfs()
|
|
return TRUE
|
|
if("toggle_user_display")
|
|
var/mob/user = ui.user
|
|
user.hud_used.atmos_debug_overlays = !user.hud_used.atmos_debug_overlays
|
|
if(user.hud_used.atmos_debug_overlays)
|
|
user.client.images += GLOB.colored_images
|
|
else
|
|
user.client.images -= GLOB.colored_images
|
|
return TRUE
|