/* 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 //Unomos - global list inits for all of the meta gas lists. //This setup allows procs to only look at one list instead of trying to dig around in lists-within-lists GLOBAL_LIST_INIT(meta_gas_specific_heats, meta_gas_heat_list()) GLOBAL_LIST_INIT(meta_gas_names, meta_gas_name_list()) GLOBAL_LIST_INIT(meta_gas_visibility, meta_gas_visibility_list()) GLOBAL_LIST_INIT(meta_gas_overlays, meta_gas_overlay_list()) GLOBAL_LIST_INIT(meta_gas_dangers, meta_gas_danger_list()) GLOBAL_LIST_INIT(meta_gas_ids, meta_gas_id_list()) GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list()) /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 var/list/cached_gasheats = GLOB.meta_gas_specific_heats . = 0 for(var/id in cached_gases) . += cached_gases[id] * cached_gasheats[id] /datum/gas_mixture/turf/heat_capacity() // Same as above except vacuums return HEAT_CAPACITY_VACUUM var/list/cached_gases = gases var/list/cached_gasheats = GLOB.meta_gas_specific_heats for(var/id in cached_gases) . += cached_gases[id] * cached_gasheats[id] 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 //and also cache this shit rq because that results in sanic speed for reasons byond explanation var/list/cached_gasheats = GLOB.meta_gas_specific_heats //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 * cached_gasheats[id] 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(!length(cached_gases)) return var/list/reactions = list() for(var/I in cached_gases) reactions += SSair.gas_reactions[I] if(!length(reactions)) return reaction_results = new var/temp = temperature var/ener = THERMAL_ENERGY(src) reaction_loop: for(var/r in 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) //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 */