mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-26 06:29:23 +01:00
## About The Pull Request ### Summary This PR changes internal structure of `/datum/gas_mixture`: `gases[gas_id][MOLES]` refactored into `moles[gas_id]`, `gases[gas_id][ARCHIVE]` into `moles_archive[gas_id]` and `gases[gas_id][GAS_META]` into `gas_meta` static variable. This allows us to use BYOND 516 vector functions for calculating total moles and heat capacity. Also it simplifies some parts of the code, allowing us to get rid of macros `ADD_GAS` and `ASSERT_GAS`. According to the profiler `/turf/open/process_cell` time is reduced by ~20%. ### Details `gas_mixture.gases` was a nested 2d-list with MOLES, ARCHIVE and GAS_META for each gas_id. For example, to get gas moles you had to do `gases[gas_id][MOLES]`. I've changed this structure to be as follows: `moles[gas_id]` - moles for the gas, `moles_archive[gas_id]` - archived version of moles, `gas_meta[KEY][gas_id]` - static var with meta information for the gas. Since I removed key GAS_META from the gases, `gas_meta` was moved to the static variable and the order of keys in the array was changed from `gas_meta[gas_id][META_KEY]' to 'gas_meta[META_KEY][gas_id]`. This was done to allow using it in vector calculations (for example heat capacity or fusion power). Static variable access is very fast and it is considered as accessing a global in the bytecode. Byond 516 introduced new vector functions: `values_sum`, `values_dot` and others. These functions are very fast, but operate only on associative lists. This allows us to change the way we calculate total_moles and heat_capacity - very hot and heavily used functions. `total_moles()` became just `values_sum(moles)`, and `heat_capacity` is just a dot product: `values_dot(moles, gas_meta[META_GAS_SPECIFIC_HEAT])`. As a side bonus, since `moles` is just an associative list, you don't really need old macros `ADD_GAS` and `ASSERT_GAS` - all they did was to make a copy of a list[3] with default value [0, 0, gas_meta] for specific gas. Now when you're adding gas you can just use `moles[gas_id] += amount` and when you query amount of gas you can just query the key (for example `moles[/datum/gas/oxygen]`) if the key does not exist, it returns null and works as 0 for all arithmetic and logic operations. For example, old code would be `if (!air.gases[/datum/gas/oxygen] || air.gases[/datum/gas/oxygen][MOLES] < 1)` and now it is `if (moles[/datum/gas/oxygen] < 1)`. This simplifies some parts of the code and also speeds things up. For the performance comparison I used Tracy profiler. I've done many different tests, and they all show slightly different numbers, but overall speedup for `process_cell` is about 20%. (-20% to average time per call from ). My testing setup was as follows: Load Icebox, drop 30/60/90 radius bomb in the middle of the bridge, set code to blue, wait 10 minutes until the round ends. Also I fixed random seed in the master controller and in the planetary gas randomization so generated maps are the same between tests. Althought it's not very realistic, it generates a lot of samples for the `process_cell` (around ~3.5M per 10 minutes). Another test I did was a plasmafire in an 8x8 space, on runtime station, it showed (-24% time on process_cell). Another test was a emagged holodeck burn test, it showed (-13% time) As for other functions of gas_mixture: `total_moles`: -50%(2x speedup), `heat_capacity`: -65%(3x speedup), `share`: -30%, `react`: -20%. Timings of all those functions is in microseconds range and they are very hot (call count is in the same order as process_cell) <details><summary>Some pictures from profiler</summary> <img width="569" height="642" alt="process_cell" src="https://github.com/user-attachments/assets/76fa0c27-719d-485d-9bfc-859fef788999" /> <img width="572" height="315" alt="image" src="https://github.com/user-attachments/assets/f68497e9-4db8-4a9c-b43f-ad04e6dc5cac" /> <img width="569" height="317" alt="image" src="https://github.com/user-attachments/assets/d7249e7b-f344-47a6-8b39-1bab0521182d" /> <img width="541" height="316" alt="image" src="https://github.com/user-attachments/assets/1bef71fd-533d-40fa-a85e-7a803ad322f7" /> <img width="519" height="409" alt="image" src="https://github.com/user-attachments/assets/51165289-174e-403d-a09c-787dd9af136a" /> <img width="523" height="318" alt="image" src="https://github.com/user-attachments/assets/fe4b1db0-17d8-47f9-8fd0-e2ecef2ee66a" /> </details> <details><summary>Setting up a profiler</summary> If you wanna to reproduce my results here is a list of steps 1. download: https://github.com/goonstation/byond-tracy-writer (this one has offsets for my version 1677) 2. build the dll, drop in the tgstation/ folder 3. download rtracy https://github.com/Dimach/rtracy 4. download Tracy profiler (0.13.1) https://github.com/wolfpld/tracy 5. uncomment `#define USE_BYOND_TRACY` in `_compile_options.dm` 6. build tgstation 7. open dream daemon, run the desired test, after round end dream daemon closes 8. navigate to tgstation/data/profiler, find the `123412341234.utracy` file 9. run `rtracty 123412341234.utracy` 10. open tracy-profiler.exe, press Connect, save the profiler data 11. repeat steps 5-10 with another branch, save another profiler data 12. open tracy-profiler, open first data, press compare, open second data </details> ## Why It's Good For The Game ## Changelog 🆑 refactor: Atmos refactor & speedup by utilizing BYOND 516 vector functions /🆑 --------- Co-authored-by: san7890 <the@san7890.com>
700 lines
27 KiB
Plaintext
700 lines
27 KiB
Plaintext
/turf
|
|
///used for temperature calculations in superconduction
|
|
var/thermal_conductivity = 0.05
|
|
///Amount of heat necessary to activate some atmos processes (there is a weird usage of this var because is compared directly to the temperature instead of heat energy)
|
|
var/heat_capacity = INFINITY //This should be opt in rather then opt out
|
|
///Archived version of the temperature on a turf
|
|
var/temperature_archived
|
|
///All currently stored conductivities changes
|
|
var/list/thermal_conductivities
|
|
|
|
///list of turfs adjacent to us that air can flow onto
|
|
var/list/atmos_adjacent_turfs
|
|
///bitfield of dirs in which we are superconducitng
|
|
var/atmos_supeconductivity = NONE
|
|
|
|
///used to determine whether we should archive
|
|
var/archived_cycle = 0
|
|
var/current_cycle = 0
|
|
|
|
/**
|
|
* used for mapping and for breathing while in walls (because that's a thing that needs to be accounted for...)
|
|
* string parsed by /datum/gas/proc/copy_from_turf
|
|
* approximation of MOLES_O2STANDARD and MOLES_N2STANDARD pending byond allowing constant expressions to be embedded in constant strings
|
|
* If someone will place 0 of some gas there, SHIT WILL BREAK. Do not do that.
|
|
**/
|
|
var/initial_gas_mix = OPENTURF_DEFAULT_ATMOS
|
|
|
|
/turf/open
|
|
//used for spacewind
|
|
///Pressure difference between two turfs
|
|
var/pressure_difference = 0
|
|
///Where the difference come from (from higher pressure to lower pressure)
|
|
var/pressure_direction = 0
|
|
|
|
///Excited group we are part of
|
|
var/datum/excited_group/excited_group
|
|
///Are we active?
|
|
var/excited = FALSE
|
|
///Our gas mix
|
|
var/datum/gas_mixture/air
|
|
|
|
///If there is an active hotspot on us store a reference to it here
|
|
var/obj/effect/hotspot/active_hotspot
|
|
/// air will slowly revert to initial_gas_mix
|
|
var/planetary_atmos = FALSE
|
|
/// once our paired turfs are finished with all other shares, do one 100% share
|
|
/// exists so things like space can ask to take 100% of a tile's gas
|
|
var/run_later = FALSE
|
|
|
|
///gas IDs of current active gas overlays
|
|
var/list/atmos_overlay_types
|
|
var/significant_share_ticker = 0
|
|
///the cooldown on playing a fire starting sound each time a tile is ignited
|
|
COOLDOWN_DECLARE(fire_puff_cooldown)
|
|
#ifdef TRACK_MAX_SHARE
|
|
var/max_share = 0
|
|
#endif
|
|
|
|
/turf/open/Initialize(mapload)
|
|
if(!blocks_air)
|
|
air = create_gas_mixture()
|
|
if(planetary_atmos)
|
|
if(!SSair.planetary[initial_gas_mix])
|
|
var/datum/gas_mixture/immutable/planetary/mix = new
|
|
mix.parse_string_immutable(initial_gas_mix)
|
|
SSair.planetary[initial_gas_mix] = mix
|
|
return ..()
|
|
|
|
/turf/open/Destroy()
|
|
if(active_hotspot)
|
|
QDEL_NULL(active_hotspot)
|
|
// Adds the adjacent turfs to the current atmos processing
|
|
for(var/near_turf in atmos_adjacent_turfs)
|
|
SSair.add_to_active(near_turf)
|
|
return ..()
|
|
|
|
/////////////////GAS MIXTURE PROCS///////////////////
|
|
|
|
///Copies all gas info from the turf into a new gas_mixture, along with our temperature
|
|
///Returns the created gas_mixture
|
|
/turf/proc/create_gas_mixture()
|
|
var/datum/gas_mixture/mix = SSair.parse_gas_string(initial_gas_mix, /datum/gas_mixture/turf)
|
|
|
|
//acounts for changes in temperature
|
|
var/turf/parent = parent_type
|
|
if(temperature != initial(temperature) || temperature != initial(parent.temperature))
|
|
mix.temperature = temperature
|
|
|
|
return mix
|
|
|
|
/turf/open/assume_air(datum/gas_mixture/giver) //use this for machines to adjust air
|
|
if(!giver)
|
|
return FALSE
|
|
air.merge(giver)
|
|
update_visuals()
|
|
air_update_turf(FALSE, FALSE)
|
|
return TRUE
|
|
|
|
/turf/open/remove_air(amount)
|
|
var/datum/gas_mixture/ours = return_air()
|
|
var/datum/gas_mixture/removed = ours.remove(amount)
|
|
update_visuals()
|
|
air_update_turf(FALSE, FALSE)
|
|
return removed
|
|
|
|
/turf/open/proc/copy_air_with_tile(turf/open/target_turf)
|
|
if(istype(target_turf))
|
|
air.copy_from(target_turf.air)
|
|
|
|
/turf/open/proc/copy_air(datum/gas_mixture/copy)
|
|
if(copy)
|
|
air.copy_from(copy)
|
|
|
|
/turf/return_air()
|
|
RETURN_TYPE(/datum/gas_mixture)
|
|
var/datum/gas_mixture/copied_mixture = create_gas_mixture()
|
|
return copied_mixture
|
|
|
|
/turf/open/return_air()
|
|
RETURN_TYPE(/datum/gas_mixture)
|
|
return air
|
|
|
|
/turf/open/return_analyzable_air()
|
|
return return_air()
|
|
|
|
/turf/should_atmos_process(datum/gas_mixture/air, exposed_temperature)
|
|
return (exposed_temperature >= heat_capacity || to_be_destroyed)
|
|
|
|
/turf/atmos_expose(datum/gas_mixture/air, exposed_temperature)
|
|
if(exposed_temperature >= heat_capacity)
|
|
to_be_destroyed = TRUE
|
|
if(to_be_destroyed && exposed_temperature >= max_fire_temperature_sustained)
|
|
max_fire_temperature_sustained = min(exposed_temperature, max_fire_temperature_sustained + heat_capacity / 4) //Ramp up to 100% yeah?
|
|
if(to_be_destroyed && !changing_turf)
|
|
burn_turf()
|
|
|
|
/turf/proc/burn_turf()
|
|
burn_tile()
|
|
var/chance_of_deletion
|
|
if (heat_capacity) //beware of division by zero
|
|
chance_of_deletion = max_fire_temperature_sustained / heat_capacity * 8 //there is no problem with prob(23456), min() was redundant --rastaf0
|
|
else
|
|
chance_of_deletion = 100
|
|
if(prob(chance_of_deletion))
|
|
Melt()
|
|
max_fire_temperature_sustained = 0
|
|
else
|
|
to_be_destroyed = FALSE
|
|
|
|
/turf/temperature_expose(datum/gas_mixture/air, exposed_temperature)
|
|
atmos_expose(air, exposed_temperature)
|
|
|
|
/turf/open/temperature_expose(datum/gas_mixture/air, exposed_temperature)
|
|
SEND_SIGNAL(src, COMSIG_TURF_EXPOSE, air, exposed_temperature)
|
|
check_atmos_process(src, air, exposed_temperature) //Manually do this to avoid needing to use elements, don't want 200 second atom init times
|
|
|
|
/turf/proc/archive()
|
|
temperature_archived = temperature
|
|
|
|
/turf/open/archive()
|
|
LINDA_CYCLE_ARCHIVE(src)
|
|
|
|
/////////////////////////GAS OVERLAYS//////////////////////////////
|
|
|
|
|
|
/turf/open/proc/update_visuals()
|
|
var/list/atmos_overlay_types = src.atmos_overlay_types // Cache for free performance
|
|
|
|
if(!air) // 2019-05-14: was not able to get this path to fire in testing. Consider removing/looking at callers -Naksu
|
|
if (atmos_overlay_types)
|
|
for(var/overlay in atmos_overlay_types)
|
|
vis_contents -= overlay
|
|
src.atmos_overlay_types = null
|
|
return
|
|
|
|
var/list/moles = air.moles
|
|
var/list/new_overlay_types
|
|
GAS_OVERLAYS(moles, new_overlay_types, src)
|
|
|
|
if (atmos_overlay_types)
|
|
for(var/overlay in atmos_overlay_types-new_overlay_types) //doesn't remove overlays that would only be added
|
|
vis_contents -= overlay
|
|
|
|
if (length(new_overlay_types))
|
|
if (atmos_overlay_types)
|
|
vis_contents += new_overlay_types - atmos_overlay_types //don't add overlays that already exist
|
|
else
|
|
vis_contents += new_overlay_types
|
|
|
|
UNSETEMPTY(new_overlay_types)
|
|
src.atmos_overlay_types = new_overlay_types
|
|
|
|
/proc/typecache_of_gases_with_no_overlays()
|
|
. = list()
|
|
for (var/gastype in subtypesof(/datum/gas))
|
|
var/datum/gas/gasvar = gastype
|
|
if (!initial(gasvar.gas_overlay))
|
|
.[gastype] = TRUE
|
|
|
|
/////////////////////////////SIMULATION///////////////////////////////////
|
|
#ifdef TRACK_MAX_SHARE
|
|
#define LAST_SHARE_CHECK \
|
|
var/last_share = our_air.last_share;\
|
|
max_share = max(last_share, max_share);\
|
|
if(last_share > MINIMUM_AIR_TO_SUSPEND){\
|
|
our_excited_group.reset_cooldowns();\
|
|
cached_ticker = 0;\
|
|
enemy_tile.significant_share_ticker = 0;\
|
|
} else if(last_share > MINIMUM_MOLES_DELTA_TO_MOVE) {\
|
|
our_excited_group.dismantle_cooldown = 0;\
|
|
cached_ticker = 0;\
|
|
enemy_tile.significant_share_ticker = 0;\
|
|
}
|
|
#else
|
|
#define LAST_SHARE_CHECK \
|
|
var/last_share = our_air.last_share;\
|
|
if(last_share > MINIMUM_AIR_TO_SUSPEND){\
|
|
our_excited_group.reset_cooldowns();\
|
|
cached_ticker = 0;\
|
|
enemy_tile.significant_share_ticker = 0;\
|
|
} else if(last_share > MINIMUM_MOLES_DELTA_TO_MOVE) {\
|
|
our_excited_group.dismantle_cooldown = 0;\
|
|
cached_ticker = 0;\
|
|
enemy_tile.significant_share_ticker = 0;\
|
|
}
|
|
#endif
|
|
#ifdef TRACK_MAX_SHARE
|
|
#define PLANET_SHARE_CHECK \
|
|
var/last_share = our_air.last_share;\
|
|
max_share = max(last_share, max_share);\
|
|
if(last_share > MINIMUM_AIR_TO_SUSPEND){\
|
|
our_excited_group.reset_cooldowns();\
|
|
cached_ticker = 0;\
|
|
} else if(last_share > MINIMUM_MOLES_DELTA_TO_MOVE) {\
|
|
our_excited_group.dismantle_cooldown = 0;\
|
|
cached_ticker = 0;\
|
|
}
|
|
#else
|
|
#define PLANET_SHARE_CHECK \
|
|
var/last_share = our_air.last_share;\
|
|
if(last_share > MINIMUM_AIR_TO_SUSPEND){\
|
|
our_excited_group.reset_cooldowns();\
|
|
cached_ticker = 0;\
|
|
} else if(last_share > MINIMUM_MOLES_DELTA_TO_MOVE) {\
|
|
our_excited_group.dismantle_cooldown = 0;\
|
|
cached_ticker = 0;\
|
|
}
|
|
#endif
|
|
|
|
/turf/proc/process_cell(fire_count)
|
|
SSair.remove_from_active(src)
|
|
|
|
/turf/open/process_cell(fire_count)
|
|
if(archived_cycle < fire_count) //archive self if not already done
|
|
LINDA_CYCLE_ARCHIVE(src)
|
|
|
|
current_cycle = fire_count
|
|
var/cached_ticker = significant_share_ticker
|
|
cached_ticker += 1
|
|
|
|
//cache for sanic speed
|
|
var/list/adjacent_turfs = atmos_adjacent_turfs
|
|
var/datum/excited_group/our_excited_group = excited_group
|
|
var/our_share_coeff = 1/(LAZYLEN(adjacent_turfs) + 1)
|
|
|
|
var/datum/gas_mixture/our_air = air
|
|
|
|
var/list/share_end
|
|
|
|
#ifdef TRACK_MAX_SHARE
|
|
max_share = 0 //Gotta reset our tracker
|
|
#endif
|
|
|
|
for(var/turf/open/enemy_tile as anything in adjacent_turfs)
|
|
#ifdef UNIT_TESTS
|
|
if(!istype(enemy_tile))
|
|
stack_trace("closed turf inside of adjacent turfs")
|
|
continue
|
|
#endif
|
|
|
|
// This var is only rarely set, exists so turfs can request to share at the end of our sharing
|
|
// We need this so we can assume share is communative, which we need to do to avoid a hellish amount of garbage_collect()s
|
|
if(enemy_tile.run_later)
|
|
LAZYADD(share_end, enemy_tile)
|
|
|
|
if(fire_count <= enemy_tile.current_cycle)
|
|
continue
|
|
LINDA_CYCLE_ARCHIVE(enemy_tile)
|
|
|
|
/******************* GROUP HANDLING START *****************************************************************/
|
|
|
|
var/should_share_air = FALSE
|
|
var/datum/gas_mixture/enemy_air = enemy_tile.air
|
|
|
|
//cache for sanic speed
|
|
var/datum/excited_group/enemy_excited_group = enemy_tile.excited_group
|
|
//If we are both in an excited group, and they aren't the same, merge.
|
|
//If we are both in an excited group, and you're active, share
|
|
//If we pass compare, and if we're not already both in a group, lets join up
|
|
//If we both pass compare, add to active and share
|
|
if(our_excited_group && enemy_excited_group)
|
|
if(our_excited_group != enemy_excited_group)
|
|
//combine groups (this also handles updating the excited_group var of all involved turfs)
|
|
our_excited_group.merge_groups(enemy_excited_group)
|
|
our_excited_group = excited_group //update our cache
|
|
if(our_excited_group && enemy_excited_group && enemy_tile.excited) //If you're both excited, no need to compare right?
|
|
should_share_air = TRUE
|
|
else if(our_air.compare(enemy_air, /*cmp_archive = */ TRUE)) //Lets see if you're up for it
|
|
SSair.add_to_active(enemy_tile) //Add yourself young man
|
|
var/datum/excited_group/existing_group = our_excited_group || enemy_excited_group || new
|
|
if(!our_excited_group)
|
|
existing_group.add_turf(src)
|
|
if(!enemy_excited_group)
|
|
existing_group.add_turf(enemy_tile)
|
|
our_excited_group = excited_group
|
|
should_share_air = TRUE
|
|
|
|
//air sharing
|
|
if(should_share_air)
|
|
var/difference = our_air.share(enemy_air, our_share_coeff, 1 / (LAZYLEN(enemy_tile.atmos_adjacent_turfs) + 1))
|
|
if(difference)
|
|
if(difference > 0)
|
|
consider_pressure_difference(enemy_tile, difference)
|
|
else
|
|
enemy_tile.consider_pressure_difference(src, -difference)
|
|
//This acts effectivly as a very slow timer, the max deltas of the group will slowly lower until it breaksdown, they then pop up a bit, and fall back down until irrelevant
|
|
LAST_SHARE_CHECK
|
|
|
|
|
|
/******************* GROUP HANDLING FINISH *********************************************************************/
|
|
|
|
if (planetary_atmos) //share our air with the "atmosphere" "above" the turf
|
|
var/datum/gas_mixture/planetary_mix = SSair.planetary[initial_gas_mix]
|
|
// archive ourself again so we don't accidentally share more gas than we currently have
|
|
LINDA_CYCLE_ARCHIVE(src)
|
|
if(our_air.compare(planetary_mix, /*cmp_archive = */ TRUE))
|
|
if(!our_excited_group)
|
|
var/datum/excited_group/new_group = new
|
|
new_group.add_turf(src)
|
|
our_excited_group = excited_group
|
|
// shares 4/5 of our difference in moles with the atmosphere
|
|
our_air.share(planetary_mix, 0.8, 0.8)
|
|
// temperature share with the atmosphere with an inflated heat capacity to simulate faster sharing with a large atmosphere
|
|
our_air.temperature_share(planetary_mix, OPEN_HEAT_TRANSFER_COEFFICIENT, planetary_mix.temperature_archived, planetary_mix.heat_capacity() * 5)
|
|
planetary_mix.garbage_collect()
|
|
PLANET_SHARE_CHECK
|
|
|
|
for(var/turf/open/enemy_tile as anything in share_end)
|
|
var/datum/gas_mixture/enemy_mix = enemy_tile.air
|
|
archive()
|
|
// We share 100% of our mix in this step. Let's jive
|
|
var/difference = our_air.share(enemy_mix, 1, 1)
|
|
LAST_SHARE_CHECK
|
|
if(!difference)
|
|
continue
|
|
if(difference > 0)
|
|
consider_pressure_difference(enemy_tile, difference)
|
|
else
|
|
enemy_tile.consider_pressure_difference(src, difference)
|
|
|
|
var/reacting = our_air.react(src)
|
|
if(our_excited_group)
|
|
our_excited_group.turf_reactions |= reacting //Adds the flag to turf_reactions so excited groups can check for them before dismantling.
|
|
|
|
update_visuals()
|
|
if(!consider_superconductivity(starting = TRUE) && !active_hotspot && !(reacting & (REACTING | STOP_REACTIONS)))
|
|
if(!our_excited_group) //If nothing of interest is happening, kill the active turf
|
|
SSair.remove_from_active(src) //This will kill any connected excited group, be careful (This broke atmos for 4 years)
|
|
if(cached_ticker > EXCITED_GROUP_DISMANTLE_CYCLES) //If you're stalling out, take a rest
|
|
SSair.sleep_active_turf(src)
|
|
|
|
significant_share_ticker = cached_ticker //Save our changes
|
|
temperature_expose(our_air, our_air.temperature)
|
|
|
|
//////////////////////////SPACEWIND/////////////////////////////
|
|
|
|
/turf/open/proc/consider_pressure_difference(turf/target_turf, difference)
|
|
SSair.high_pressure_delta |= src
|
|
if(difference > pressure_difference)
|
|
pressure_direction = get_dir(src, target_turf)
|
|
pressure_difference = difference
|
|
|
|
/turf/open/proc/high_pressure_movements()
|
|
var/atom/movable/moving_atom
|
|
for(var/thing in src)
|
|
moving_atom = thing
|
|
if (moving_atom.last_high_pressure_movement_air_cycle < SSair.times_fired)
|
|
if (!moving_atom.anchored && !moving_atom.pulledby)
|
|
moving_atom.experience_pressure_difference(pressure_difference, pressure_direction)
|
|
else
|
|
SEND_SIGNAL(moving_atom, COMSIG_MOVABLE_RESISTED_SPACEWIND, pressure_difference, pressure_direction)
|
|
|
|
/atom/movable
|
|
///How much delta pressure is needed for us to move
|
|
var/pressure_resistance = 10
|
|
var/last_high_pressure_movement_air_cycle = 0
|
|
|
|
/atom/movable/proc/experience_pressure_difference(pressure_difference, direction, pressure_resistance_prob_delta = 0)
|
|
set waitfor = FALSE
|
|
if(SEND_SIGNAL(src, COMSIG_ATOM_PRE_PRESSURE_PUSH) & COMSIG_ATOM_BLOCKS_PRESSURE)
|
|
return
|
|
var/const/PROBABILITY_OFFSET = 25
|
|
var/const/PROBABILITY_BASE_PRECENT = 75
|
|
var/max_force = sqrt(pressure_difference) * (MOVE_FORCE_DEFAULT / 5)
|
|
var/move_prob = 100
|
|
if (pressure_resistance > 0)
|
|
move_prob = (pressure_difference / pressure_resistance * PROBABILITY_BASE_PRECENT) - PROBABILITY_OFFSET
|
|
move_prob += pressure_resistance_prob_delta
|
|
if (move_prob > PROBABILITY_OFFSET && prob(move_prob) && (move_resist != INFINITY) && (!anchored && (max_force >= (move_resist * MOVE_FORCE_PUSH_RATIO))) || (anchored && (max_force >= (move_resist * MOVE_FORCE_FORCEPUSH_RATIO))))
|
|
step(src, direction)
|
|
last_high_pressure_movement_air_cycle = SSair.times_fired
|
|
|
|
///////////////////////////EXCITED GROUPS/////////////////////////////
|
|
|
|
/datum/excited_group
|
|
///Stores a reference to the turfs we are controlling
|
|
var/list/turf_list = list()
|
|
///If this is over EXCITED_GROUP_BREAKDOWN_CYCLES we call self_breakdown()
|
|
var/breakdown_cooldown = 0
|
|
///If this is over EXCITED_GROUP_DISMANTLE_CYCLES we call dismantle()
|
|
var/dismantle_cooldown = 0
|
|
///Used for debug to show the excited groups active and their turfs
|
|
var/should_display = FALSE
|
|
///Id of the index color of the displayed group
|
|
var/display_id = 0
|
|
///Wrapping loop of the index colors
|
|
var/static/wrapping_id = 0
|
|
///All turf reaction flags we have received.
|
|
var/turf_reactions = NONE
|
|
|
|
/datum/excited_group/New()
|
|
SSair.excited_groups += src
|
|
|
|
/datum/excited_group/proc/add_turf(turf/open/target_turf)
|
|
turf_list += target_turf
|
|
target_turf.excited_group = src
|
|
dismantle_cooldown = 0
|
|
if(should_display || SSair.display_all_groups)
|
|
display_turf(target_turf)
|
|
|
|
/datum/excited_group/proc/merge_groups(datum/excited_group/target_group)
|
|
if(turf_list.len > target_group.turf_list.len)
|
|
SSair.excited_groups -= target_group
|
|
for(var/turf/open/group_member as anything in target_group.turf_list)
|
|
group_member.excited_group = src
|
|
turf_list += group_member
|
|
should_display = target_group.should_display | should_display
|
|
if(should_display || SSair.display_all_groups)
|
|
target_group.hide_turfs()
|
|
display_turfs()
|
|
breakdown_cooldown = min(breakdown_cooldown, target_group.breakdown_cooldown) //Take the smaller of the two options
|
|
dismantle_cooldown = 0
|
|
else
|
|
SSair.excited_groups -= src
|
|
for(var/turf/open/group_member as anything in turf_list)
|
|
group_member.excited_group = target_group
|
|
target_group.turf_list += group_member
|
|
target_group.should_display = target_group.should_display | should_display
|
|
if(target_group.should_display || SSair.display_all_groups)
|
|
hide_turfs()
|
|
target_group.display_turfs()
|
|
target_group.breakdown_cooldown = min(breakdown_cooldown, target_group.breakdown_cooldown)
|
|
target_group.dismantle_cooldown = 0
|
|
|
|
/datum/excited_group/proc/reset_cooldowns()
|
|
breakdown_cooldown = 0
|
|
dismantle_cooldown = 0
|
|
|
|
/datum/excited_group/proc/self_breakdown(roundstart = FALSE, poke_turfs = FALSE)
|
|
var/datum/gas_mixture/shared_mix = new
|
|
|
|
//make local for sanic speed
|
|
var/list/shared_cached_moles = shared_mix.moles
|
|
var/list/turf_list = src.turf_list
|
|
var/turflen = turf_list.len
|
|
var/imumutable_in_group = FALSE
|
|
var/energy = 0
|
|
var/heat_cap = 0
|
|
|
|
for(var/turf/open/group_member as anything in turf_list)
|
|
//Cache?
|
|
var/datum/gas_mixture/turf/mix = group_member.air
|
|
if (roundstart)
|
|
if(istype(group_member.air, /datum/gas_mixture/immutable))
|
|
imumutable_in_group = TRUE
|
|
shared_mix.copy_from(group_member.air) //This had better be immutable young man
|
|
shared_cached_moles = shared_mix.moles //update the cache
|
|
break
|
|
// If we're planetary use THAT mix, and stop here
|
|
if(group_member.planetary_atmos)
|
|
imumutable_in_group = TRUE
|
|
var/datum/gas_mixture/planetary_mix = SSair.planetary[group_member.initial_gas_mix]
|
|
shared_mix.copy_from(planetary_mix)
|
|
shared_cached_moles = shared_mix.moles // Cache update
|
|
break
|
|
//"borrowing" this code from merge(), I need to play with the temp portion. Lets expand it out
|
|
//temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
|
|
var/capacity = mix.heat_capacity()
|
|
energy += mix.temperature * capacity
|
|
heat_cap += capacity
|
|
|
|
for(var/gas_id, amount in mix.moles)
|
|
shared_cached_moles[gas_id] += amount
|
|
|
|
if(!imumutable_in_group)
|
|
shared_mix.temperature = energy / heat_cap
|
|
for(var/gas_id in shared_cached_moles)
|
|
shared_cached_moles[gas_id] /= turflen
|
|
shared_mix.garbage_collect()
|
|
|
|
for(var/turf/open/group_member as anything in turf_list)
|
|
if(group_member.planetary_atmos) //We do this as a hack to try and minimize unneeded excited group spread over planetary turfs
|
|
group_member.air.copy_from(SSair.planetary[group_member.initial_gas_mix]) //Comes with a cost of "slower" drains, but it's worth it
|
|
else
|
|
group_member.air.copy_from(shared_mix) //Otherwise just set the mix to a copy of our equalized mix
|
|
group_member.update_visuals()
|
|
if(poke_turfs) //Because we only activate all these once every breakdown, in event of lag due to this code and slow space + vent things, increase the wait time for breakdowns
|
|
SSair.add_to_active(group_member)
|
|
group_member.significant_share_ticker = EXCITED_GROUP_DISMANTLE_CYCLES //Max out the ticker, if they don't share next tick, nuke em
|
|
|
|
breakdown_cooldown = 0
|
|
|
|
///Dismantles the excited group, puts allll the turfs to sleep
|
|
/datum/excited_group/proc/dismantle()
|
|
for(var/turf/open/current_turf as anything in turf_list)
|
|
current_turf.excited = FALSE
|
|
current_turf.significant_share_ticker = 0
|
|
SSair.active_turfs -= current_turf
|
|
#ifdef VISUALIZE_ACTIVE_TURFS //Use this when you want details about how the turfs are moving, display_all_groups should work for normal operation
|
|
current_turf.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, COLOR_VIBRANT_LIME)
|
|
#endif
|
|
garbage_collect()
|
|
|
|
//Breaks down the excited group, this doesn't sleep the turfs mind, just removes them from the group
|
|
/datum/excited_group/proc/garbage_collect()
|
|
if(display_id) //If we ever did make those changes
|
|
hide_turfs()
|
|
for(var/turf/open/current_turf as anything in turf_list)
|
|
current_turf.excited_group = null
|
|
turf_list.Cut()
|
|
SSair.excited_groups -= src
|
|
if(SSair.currentpart == SSAIR_EXCITEDGROUPS)
|
|
SSair.currentrun -= src
|
|
|
|
/datum/excited_group/proc/display_turfs()
|
|
if(display_id == 0) //Hasn't been shown before
|
|
wrapping_id = wrapping_id % GLOB.colored_turfs.len
|
|
wrapping_id++ //We do this after because lists index at 1
|
|
display_id = wrapping_id
|
|
for(var/thing in turf_list)
|
|
var/turf/display = thing
|
|
var/offset = GET_Z_PLANE_OFFSET(display.z) + 1
|
|
display.vis_contents += GLOB.colored_turfs[display_id][offset]
|
|
|
|
/datum/excited_group/proc/hide_turfs()
|
|
for(var/thing in turf_list)
|
|
var/turf/display = thing
|
|
var/offset = GET_Z_PLANE_OFFSET(display.z) + 1
|
|
display.vis_contents -= GLOB.colored_turfs[display_id][offset]
|
|
display_id = 0
|
|
|
|
/datum/excited_group/proc/display_turf(turf/thing)
|
|
if(display_id == 0) //Hasn't been shown before
|
|
wrapping_id = wrapping_id % GLOB.colored_turfs.len
|
|
wrapping_id++ //We do this after because lists index at 1
|
|
display_id = wrapping_id
|
|
var/offset = GET_Z_PLANE_OFFSET(thing.z) + 1
|
|
thing.vis_contents += GLOB.colored_turfs[display_id][offset]
|
|
|
|
////////////////////////SUPERCONDUCTIVITY/////////////////////////////
|
|
|
|
/**
|
|
ALLLLLLLLLLLLLLLLLLLLRIGHT HERE WE GOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
|
|
|
|
Read the code for more details, but first, a brief concept discussion/area
|
|
|
|
Our goal here is to "model" heat moving through solid objects, so walls, windows, and sometimes doors.
|
|
We do this by heating up the floor itself with the heat of the gasmix ontop of it, this is what the coeffs are for here, they slow that movement
|
|
Then we go through the process below.
|
|
|
|
If an active turf is fitting, we add it to processing, conduct with any covered tiles, (read windows and sometimes walls)
|
|
Then we space some of our heat, and think about if we should stop conducting.
|
|
**/
|
|
|
|
/turf/proc/conductivity_directions()
|
|
if(archived_cycle < SSair.times_fired)
|
|
archive()
|
|
return ALL_CARDINALS
|
|
|
|
///Returns a set of directions that we should be conducting in, NOTE, atmos_supeconductivity is ACTUALLY inversed, don't worrry about it
|
|
/turf/open/conductivity_directions()
|
|
if(blocks_air)
|
|
return ..()
|
|
for(var/direction in GLOB.cardinals)
|
|
var/turf/checked_turf = get_step(src, direction)
|
|
if(!(checked_turf in atmos_adjacent_turfs) && !(atmos_supeconductivity & direction))
|
|
. |= direction
|
|
|
|
///These two procs are a bit of a web, I belive in you
|
|
/turf/proc/neighbor_conduct_with_src(turf/open/other)
|
|
if(!other.blocks_air) //Solid but neighbor is open
|
|
other.temperature_share_open_to_solid(src)
|
|
else //Both tiles are solid
|
|
other.share_temperature_mutual_solid(src, thermal_conductivity)
|
|
temperature_expose(null, temperature)
|
|
|
|
/turf/open/neighbor_conduct_with_src(turf/other)
|
|
if(blocks_air)
|
|
return ..()
|
|
|
|
if(!other.blocks_air) //Both tiles are open
|
|
var/turf/open/open_other = other
|
|
open_other.air.temperature_share(air, WINDOW_HEAT_TRANSFER_COEFFICIENT)
|
|
else //Open but neighbor is solid
|
|
temperature_share_open_to_solid(other)
|
|
SSair.add_to_active(src)
|
|
|
|
/turf/proc/super_conduct()
|
|
var/conductivity_directions = conductivity_directions()
|
|
|
|
if(conductivity_directions)
|
|
//Conduct with tiles around me
|
|
for(var/direction in GLOB.cardinals)
|
|
if(!(conductivity_directions & direction))
|
|
continue
|
|
var/turf/neighbor = get_step(src, direction)
|
|
|
|
if(!neighbor.thermal_conductivity)
|
|
continue
|
|
|
|
if(neighbor.archived_cycle < SSair.times_fired)
|
|
neighbor.archive()
|
|
|
|
neighbor.neighbor_conduct_with_src(src)
|
|
|
|
neighbor.consider_superconductivity()
|
|
|
|
radiate_to_spess()
|
|
|
|
finish_superconduction()
|
|
|
|
/turf/proc/finish_superconduction(temp = temperature)
|
|
//Make sure still hot enough to continue conducting heat
|
|
if(temp < MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION)
|
|
SSair.active_super_conductivity -= src
|
|
return FALSE
|
|
|
|
/turf/open/finish_superconduction()
|
|
//Conduct with air on my tile if I have it
|
|
if(..((blocks_air ? temperature : air.temperature)) != FALSE && !blocks_air)
|
|
temperature = air.temperature_share(null, thermal_conductivity, temperature, heat_capacity)
|
|
|
|
///Should we attempt to superconduct?
|
|
/turf/proc/consider_superconductivity(starting)
|
|
if(!thermal_conductivity)
|
|
return FALSE
|
|
|
|
SSair.active_super_conductivity |= src
|
|
return TRUE
|
|
|
|
/turf/open/consider_superconductivity(starting)
|
|
if(air.temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
|
|
return FALSE
|
|
if(air.heat_capacity() < M_CELL_WITH_RATIO) // Was: MOLES_CELLSTANDARD*0.1*0.05 Since there are no variables here we can make this a constant.
|
|
return FALSE
|
|
return ..()
|
|
|
|
/turf/closed/consider_superconductivity(starting)
|
|
if(temperature < (starting?MINIMUM_TEMPERATURE_START_SUPERCONDUCTION:MINIMUM_TEMPERATURE_FOR_SUPERCONDUCTION))
|
|
return FALSE
|
|
return ..()
|
|
|
|
/// Radiate excess tile heat to space.
|
|
/turf/proc/radiate_to_spess()
|
|
if(temperature <= T0C) // Considering 0 degC as the break even point for radiation in and out.
|
|
return
|
|
// Because we keep losing energy, makes more sense for us to be the T2 here.
|
|
var/delta_temperature = temperature_archived - TCMB //hardcoded space temperature
|
|
if(heat_capacity <= 0 || abs(delta_temperature) <= MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
|
return
|
|
// Heat should be positive in most cases
|
|
// coefficient applied first because some turfs have very big heat caps.
|
|
var/heat = CALCULATE_CONDUCTION_ENERGY(thermal_conductivity * delta_temperature, HEAT_CAPACITY_VACUUM, heat_capacity)
|
|
temperature -= heat / heat_capacity
|
|
|
|
/turf/open/proc/temperature_share_open_to_solid(turf/sharer)
|
|
sharer.temperature = air.temperature_share(null, sharer.thermal_conductivity, sharer.temperature, sharer.heat_capacity)
|
|
|
|
/turf/proc/share_temperature_mutual_solid(turf/sharer, conduction_coefficient) //This is all just heat sharing, don't get freaked out
|
|
var/delta_temperature = sharer.temperature_archived - temperature_archived
|
|
if(abs(delta_temperature) <= MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER || !heat_capacity || !sharer.heat_capacity)
|
|
return
|
|
var/heat = conduction_coefficient * CALCULATE_CONDUCTION_ENERGY(delta_temperature, heat_capacity, sharer.heat_capacity)
|
|
temperature += heat / heat_capacity //The higher your own heat cap the less heat you get from this arrangement
|
|
sharer.temperature -= heat / sharer.heat_capacity
|
|
|
|
|
|
#undef LAST_SHARE_CHECK
|
|
#undef PLANET_SHARE_CHECK
|