mirror of
https://github.com/SPLURT-Station/S.P.L.U.R.T-Station-13.git
synced 2025-12-10 01:49:19 +00:00
396 lines
15 KiB
Plaintext
396 lines
15 KiB
Plaintext
/*
|
|
What are the archived variables for?
|
|
Calculations are done using the archived variables with the results merged into the regular variables.
|
|
This prevents race conditions that arise based on the order of tile processing.
|
|
*/
|
|
#define MINIMUM_HEAT_CAPACITY 0.0003
|
|
#define MINIMUM_MOLE_COUNT 0.01
|
|
GLOBAL_LIST_INIT(meta_gas_info, meta_gas_list()) //see ATMOSPHERICS/gas_types.dm
|
|
/datum/gas_mixture
|
|
var/list/gases = list()
|
|
var/temperature = 0 //kelvins
|
|
var/tmp/temperature_archived = 0
|
|
var/volume = CELL_VOLUME //liters
|
|
var/last_share = 0
|
|
var/list/reaction_results = list()
|
|
var/list/analyzer_results //used for analyzer feedback - not initialized until its used
|
|
var/gc_share = FALSE // Whether to call garbage_collect() on the sharer during shares, used for immutable mixtures
|
|
|
|
/datum/gas_mixture/New(volume)
|
|
if (!isnull(volume))
|
|
src.volume = volume
|
|
|
|
//PV = nRT
|
|
|
|
/datum/gas_mixture/proc/heat_capacity() //joules per kelvin
|
|
var/list/cached_gases = gases
|
|
. = 0
|
|
for(var/id in cached_gases)
|
|
. += cached_gases[id] * GLOB.meta_gas_info[id][META_GAS_SPECIFIC_HEAT]
|
|
|
|
/datum/gas_mixture/turf/heat_capacity()
|
|
. = ..()
|
|
if(!.)
|
|
. += HEAT_CAPACITY_VACUUM //we want vacuums in turfs to have the same heat capacity as space
|
|
|
|
/datum/gas_mixture/proc/total_moles()
|
|
var/cached_gases = gases
|
|
TOTAL_MOLES(cached_gases, .)
|
|
|
|
/datum/gas_mixture/proc/return_pressure() //kilopascals
|
|
if(volume > 0) // to prevent division by zero
|
|
var/cached_gases = gases
|
|
TOTAL_MOLES(cached_gases, .)
|
|
. *= R_IDEAL_GAS_EQUATION * temperature / volume
|
|
return
|
|
return 0
|
|
|
|
/datum/gas_mixture/proc/return_temperature() //kelvins
|
|
return temperature
|
|
|
|
/datum/gas_mixture/proc/return_volume() //liters
|
|
return max(0, volume)
|
|
|
|
/datum/gas_mixture/proc/thermal_energy() //joules
|
|
return THERMAL_ENERGY(src) //see code/__DEFINES/atmospherics.dm; use the define in performance critical areas
|
|
|
|
/datum/gas_mixture/proc/merge(datum/gas_mixture/giver)
|
|
//Merges all air from giver into self. Deletes giver.
|
|
//Returns: 1 if we are mutable, 0 otherwise
|
|
|
|
/datum/gas_mixture/proc/remove(amount)
|
|
//Proportionally removes amount of gas from the gas_mixture
|
|
//Returns: gas_mixture with the gases removed
|
|
|
|
/datum/gas_mixture/proc/remove_ratio(ratio)
|
|
//Proportionally removes amount of gas from the gas_mixture
|
|
//Returns: gas_mixture with the gases removed
|
|
|
|
/datum/gas_mixture/proc/copy()
|
|
//Creates new, identical gas mixture
|
|
//Returns: duplicate gas mixture
|
|
|
|
/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample)
|
|
//Copies variables from sample
|
|
//Returns: 1 if we are mutable, 0 otherwise
|
|
|
|
/datum/gas_mixture/proc/copy_from_turf(turf/model)
|
|
//Copies all gas info from the turf into the gas list along with temperature
|
|
//Returns: 1 if we are mutable, 0 otherwise
|
|
|
|
/datum/gas_mixture/proc/parse_gas_string(gas_string)
|
|
//Copies variables from a particularly formatted string.
|
|
//Returns: 1 if we are mutable, 0 otherwise
|
|
|
|
/datum/gas_mixture/proc/share(datum/gas_mixture/sharer)
|
|
//Performs air sharing calculations between two gas_mixtures assuming only 1 boundary length
|
|
//Returns: amount of gas exchanged (+ if sharer received)
|
|
|
|
/datum/gas_mixture/proc/temperature_share(datum/gas_mixture/sharer, conduction_coefficient)
|
|
//Performs temperature sharing calculations (via conduction) between two gas_mixtures assuming only 1 boundary length
|
|
//Returns: new temperature of the sharer
|
|
|
|
/datum/gas_mixture/proc/compare(datum/gas_mixture/sample)
|
|
//Compares sample to self to see if within acceptable ranges that group processing may be enabled
|
|
//Returns: a string indicating what check failed, or "" if check passes
|
|
|
|
/datum/gas_mixture/proc/react(turf/open/dump_location)
|
|
//Performs various reactions such as combustion or fusion (LOL)
|
|
//Returns: 1 if any reaction took place; 0 otherwise
|
|
|
|
|
|
/datum/gas_mixture/merge(datum/gas_mixture/giver)
|
|
if(!giver)
|
|
return 0
|
|
|
|
//heat transfer
|
|
if(abs(temperature - giver.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
|
var/self_heat_capacity = heat_capacity()
|
|
var/giver_heat_capacity = giver.heat_capacity()
|
|
var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity
|
|
if(combined_heat_capacity)
|
|
temperature = (giver.temperature * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity
|
|
|
|
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
|
|
var/list/giver_gases = giver.gases
|
|
//gas transfer
|
|
for(var/giver_id in giver_gases)
|
|
cached_gases[giver_id] += giver_gases[giver_id]
|
|
|
|
return 1
|
|
|
|
/datum/gas_mixture/remove(amount)
|
|
var/sum
|
|
var/list/cached_gases = gases
|
|
TOTAL_MOLES(cached_gases, sum)
|
|
amount = min(amount, sum) //Can not take more air than tile has!
|
|
if(amount <= 0)
|
|
return null
|
|
var/datum/gas_mixture/removed = new type
|
|
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
|
|
|
|
removed.temperature = temperature
|
|
for(var/id in cached_gases)
|
|
removed_gases[id] = QUANTIZE((cached_gases[id] / sum) * amount)
|
|
cached_gases[id] -= removed_gases[id]
|
|
GAS_GARBAGE_COLLECT(gases)
|
|
|
|
return removed
|
|
|
|
/datum/gas_mixture/remove_ratio(ratio)
|
|
if(ratio <= 0)
|
|
return null
|
|
ratio = min(ratio, 1)
|
|
|
|
var/list/cached_gases = gases
|
|
var/datum/gas_mixture/removed = new type
|
|
var/list/removed_gases = removed.gases //accessing datum vars is slower than proc vars
|
|
|
|
removed.temperature = temperature
|
|
for(var/id in cached_gases)
|
|
removed_gases[id] = QUANTIZE(cached_gases[id] * ratio)
|
|
cached_gases[id] -= removed_gases[id]
|
|
|
|
GAS_GARBAGE_COLLECT(gases)
|
|
|
|
return removed
|
|
|
|
/datum/gas_mixture/copy()
|
|
var/list/cached_gases = gases
|
|
var/datum/gas_mixture/copy = new type
|
|
var/list/copy_gases = copy.gases
|
|
|
|
copy.temperature = temperature
|
|
for(var/id in cached_gases)
|
|
copy_gases[id] = cached_gases[id]
|
|
|
|
return copy
|
|
|
|
|
|
/datum/gas_mixture/copy_from(datum/gas_mixture/sample)
|
|
var/list/cached_gases = gases //accessing datum vars is slower than proc vars
|
|
var/list/sample_gases = sample.gases
|
|
|
|
temperature = sample.temperature
|
|
for(var/id in sample_gases)
|
|
cached_gases[id] = sample_gases[id]
|
|
|
|
//remove all gases not in the sample
|
|
cached_gases &= sample_gases
|
|
|
|
return 1
|
|
|
|
/datum/gas_mixture/copy_from_turf(turf/model)
|
|
parse_gas_string(model.initial_gas_mix)
|
|
|
|
//acounts for changes in temperature
|
|
var/turf/model_parent = model.parent_type
|
|
if(model.temperature != initial(model.temperature) || model.temperature != initial(model_parent.temperature))
|
|
temperature = model.temperature
|
|
|
|
return 1
|
|
|
|
/datum/gas_mixture/parse_gas_string(gas_string)
|
|
var/list/gases = src.gases
|
|
var/list/gas = params2list(gas_string)
|
|
if(gas["TEMP"])
|
|
temperature = text2num(gas["TEMP"])
|
|
gas -= "TEMP"
|
|
gases.Cut()
|
|
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
|
|
gases[path] = text2num(gas[id])
|
|
return 1
|
|
|
|
/datum/gas_mixture/share(datum/gas_mixture/sharer, atmos_adjacent_turfs = 4)
|
|
|
|
var/list/cached_gases = gases
|
|
var/list/sharer_gases = sharer.gases
|
|
|
|
var/temperature_delta = temperature_archived - sharer.temperature_archived
|
|
var/abs_temperature_delta = abs(temperature_delta)
|
|
|
|
var/old_self_heat_capacity = 0
|
|
var/old_sharer_heat_capacity = 0
|
|
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
|
old_self_heat_capacity = heat_capacity()
|
|
old_sharer_heat_capacity = sharer.heat_capacity()
|
|
|
|
var/heat_capacity_self_to_sharer = 0 //heat capacity of the moles transferred from us to the sharer
|
|
var/heat_capacity_sharer_to_self = 0 //heat capacity of the moles transferred from the sharer to us
|
|
|
|
var/moved_moles = 0
|
|
var/abs_moved_moles = 0
|
|
|
|
//we're gonna define these vars outside of this for loop because as it turns out, var declaration is pricy
|
|
var/delta
|
|
var/gas_heat_capacity
|
|
//GAS TRANSFER
|
|
for(var/id in cached_gases | sharer_gases) // transfer gases
|
|
|
|
delta = QUANTIZE(cached_gases[id] - sharer_gases[id])/(atmos_adjacent_turfs+1) //the amount of gas that gets moved between the mixtures
|
|
|
|
if(delta && abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
|
gas_heat_capacity = delta * GLOB.meta_gas_info[id][META_GAS_SPECIFIC_HEAT]
|
|
if(delta > 0)
|
|
heat_capacity_self_to_sharer += gas_heat_capacity
|
|
else
|
|
heat_capacity_sharer_to_self -= gas_heat_capacity //subtract here instead of adding the absolute value because we know that delta is negative.
|
|
|
|
cached_gases[id] -= delta
|
|
sharer_gases[id] += delta
|
|
moved_moles += delta
|
|
abs_moved_moles += abs(delta)
|
|
|
|
last_share = abs_moved_moles
|
|
|
|
//THERMAL ENERGY TRANSFER
|
|
if(abs_temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
|
var/new_self_heat_capacity = old_self_heat_capacity + heat_capacity_sharer_to_self - heat_capacity_self_to_sharer
|
|
var/new_sharer_heat_capacity = old_sharer_heat_capacity + heat_capacity_self_to_sharer - heat_capacity_sharer_to_self
|
|
|
|
//transfer of thermal energy (via changed heat capacity) between self and sharer
|
|
if(new_self_heat_capacity > MINIMUM_HEAT_CAPACITY)
|
|
temperature = (old_self_heat_capacity*temperature - heat_capacity_self_to_sharer*temperature_archived + heat_capacity_sharer_to_self*sharer.temperature_archived)/new_self_heat_capacity
|
|
|
|
if(new_sharer_heat_capacity > MINIMUM_HEAT_CAPACITY)
|
|
sharer.temperature = (old_sharer_heat_capacity*sharer.temperature-heat_capacity_sharer_to_self*sharer.temperature_archived + heat_capacity_self_to_sharer*temperature_archived)/new_sharer_heat_capacity
|
|
//thermal energy of the system (self and sharer) is unchanged
|
|
|
|
if(abs(old_sharer_heat_capacity) > MINIMUM_HEAT_CAPACITY)
|
|
if(abs(new_sharer_heat_capacity/old_sharer_heat_capacity - 1) < 0.1) // <10% change in sharer heat capacity
|
|
temperature_share(sharer, OPEN_HEAT_TRANSFER_COEFFICIENT)
|
|
|
|
if (initial(sharer.gc_share))
|
|
GAS_GARBAGE_COLLECT(sharer.gases)
|
|
if(temperature_delta > MINIMUM_TEMPERATURE_TO_MOVE || abs(moved_moles) > MINIMUM_MOLES_DELTA_TO_MOVE)
|
|
var/our_moles
|
|
TOTAL_MOLES(cached_gases,our_moles)
|
|
var/their_moles
|
|
TOTAL_MOLES(sharer_gases,their_moles)
|
|
return (temperature_archived*(our_moles + moved_moles) - sharer.temperature_archived*(their_moles - moved_moles)) * R_IDEAL_GAS_EQUATION / volume
|
|
|
|
/datum/gas_mixture/temperature_share(datum/gas_mixture/sharer, conduction_coefficient, sharer_temperature, sharer_heat_capacity)
|
|
//transfer of thermal energy (via conduction) between self and sharer
|
|
if(sharer)
|
|
sharer_temperature = sharer.temperature_archived
|
|
var/temperature_delta = temperature_archived - sharer_temperature
|
|
if(abs(temperature_delta) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER)
|
|
var/self_heat_capacity = heat_capacity()
|
|
sharer_heat_capacity = sharer_heat_capacity || sharer.heat_capacity()
|
|
|
|
if((sharer_heat_capacity > MINIMUM_HEAT_CAPACITY) && (self_heat_capacity > MINIMUM_HEAT_CAPACITY))
|
|
var/heat = conduction_coefficient*temperature_delta* \
|
|
(self_heat_capacity*sharer_heat_capacity/(self_heat_capacity+sharer_heat_capacity))
|
|
|
|
temperature = max(temperature - heat/self_heat_capacity, TCMB)
|
|
sharer_temperature = max(sharer_temperature + heat/sharer_heat_capacity, TCMB)
|
|
if(sharer)
|
|
sharer.temperature = sharer_temperature
|
|
return sharer_temperature
|
|
//thermal energy of the system (self and sharer) is unchanged
|
|
|
|
/datum/gas_mixture/compare(datum/gas_mixture/sample)
|
|
var/list/sample_gases = sample.gases //accessing datum vars is slower than proc vars
|
|
var/list/cached_gases = gases
|
|
|
|
for(var/id in cached_gases | sample_gases) // compare gases from either mixture
|
|
var/gas_moles = cached_gases[id]
|
|
var/sample_moles = sample_gases[id]
|
|
var/delta = abs(gas_moles - sample_moles)
|
|
if(delta > MINIMUM_MOLES_DELTA_TO_MOVE && \
|
|
delta > gas_moles * MINIMUM_AIR_RATIO_TO_MOVE)
|
|
return id
|
|
|
|
var/our_moles
|
|
TOTAL_MOLES(cached_gases, our_moles)
|
|
if(our_moles > MINIMUM_MOLES_DELTA_TO_MOVE)
|
|
var/temp = temperature
|
|
var/sample_temp = sample.temperature
|
|
|
|
var/temperature_delta = abs(temp - sample_temp)
|
|
if(temperature_delta > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND)
|
|
return "temp"
|
|
|
|
return ""
|
|
|
|
/datum/gas_mixture/react(datum/holder)
|
|
. = NO_REACTION
|
|
var/list/cached_gases = gases
|
|
if(!cached_gases.len)
|
|
return
|
|
var/possible
|
|
for(var/I in cached_gases)
|
|
if(GLOB.nonreactive_gases[I])
|
|
continue
|
|
possible = TRUE
|
|
break
|
|
if(!possible)
|
|
return
|
|
reaction_results = new
|
|
var/temp = temperature
|
|
var/ener = THERMAL_ENERGY(src)
|
|
|
|
reaction_loop:
|
|
for(var/r in SSair.gas_reactions)
|
|
var/datum/gas_reaction/reaction = r
|
|
|
|
var/list/min_reqs = reaction.min_requirements
|
|
if((min_reqs["TEMP"] && temp < min_reqs["TEMP"]) \
|
|
|| (min_reqs["ENER"] && ener < min_reqs["ENER"]))
|
|
continue
|
|
|
|
for(var/id in min_reqs)
|
|
if (id == "TEMP" || id == "ENER")
|
|
continue
|
|
if(cached_gases[id] < min_reqs[id])
|
|
continue reaction_loop
|
|
//at this point, all minimum requirements for the reaction are satisfied.
|
|
|
|
/* currently no reactions have maximum requirements, so we can leave the checks commented out for a slight performance boost
|
|
PLEASE DO NOT REMOVE THIS CODE. the commenting is here only for a performance increase.
|
|
enabling these checks should be as easy as possible and the fact that they are disabled should be as clear as possible
|
|
|
|
var/list/max_reqs = reaction.max_requirements
|
|
if((max_reqs["TEMP"] && temp > max_reqs["TEMP"]) \
|
|
|| (max_reqs["ENER"] && ener > max_reqs["ENER"]))
|
|
continue
|
|
for(var/id in max_reqs)
|
|
if(id == "TEMP" || id == "ENER")
|
|
continue
|
|
if(cached_gases[id] && cached_gases[id][MOLES] > max_reqs[id])
|
|
continue reaction_loop
|
|
//at this point, all requirements for the reaction are satisfied. we can now react()
|
|
*/
|
|
|
|
. |= reaction.react(src, holder)
|
|
if (. & STOP_REACTIONS)
|
|
break
|
|
if(.)
|
|
GAS_GARBAGE_COLLECT(gases)
|
|
if(temperature < TCMB) //just for safety
|
|
temperature = TCMB
|
|
|
|
//Takes the amount of the gas you want to PP as an argument
|
|
//So I don't have to do some hacky switches/defines/magic strings
|
|
//eg:
|
|
//Tox_PP = get_partial_pressure(gas_mixture.toxins)
|
|
//O2_PP = get_partial_pressure(gas_mixture.oxygen)
|
|
|
|
/datum/gas_mixture/proc/get_breath_partial_pressure(gas_pressure)
|
|
return (gas_pressure * R_IDEAL_GAS_EQUATION * temperature) / BREATH_VOLUME
|
|
//inverse
|
|
/datum/gas_mixture/proc/get_true_breath_pressure(partial_pressure)
|
|
return (partial_pressure * BREATH_VOLUME) / (R_IDEAL_GAS_EQUATION * temperature)
|
|
|
|
//Mathematical proofs:
|
|
/*
|
|
get_breath_partial_pressure(gas_pp) --> gas_pp/total_moles()*breath_pp = pp
|
|
get_true_breath_pressure(pp) --> gas_pp = pp/breath_pp*total_moles()
|
|
|
|
10/20*5 = 2.5
|
|
10 = 2.5/5*20
|
|
*/
|