diff --git a/code/ZAS/ConnectionGroup.dm b/code/ZAS/ConnectionGroup.dm index 94731228a6..f377efe74f 100644 --- a/code/ZAS/ConnectionGroup.dm +++ b/code/ZAS/ConnectionGroup.dm @@ -62,6 +62,7 @@ Class Procs: /connection_edge/var/list/connecting_turfs = list() /connection_edge/var/direct = 0 +/connection_edge/var/sleeping = 1 /connection_edge/var/coefficient = 0 @@ -88,6 +89,8 @@ Class Procs: /connection_edge/proc/tick() +/connection_edge/proc/recheck() + /connection_edge/proc/flow(list/movable, differential, repelled) for(var/i = 1; i <= movable.len; i++) var/atom/movable/M = movable[i] @@ -147,35 +150,38 @@ Class Procs: if(A.invalid || B.invalid) erase() return - //world << "[id]: Tick [air_master.current_cycle]: \..." - if(direct) - if(air_master.equivalent_pressure(A, B)) - //world << "merged." - erase() - air_master.merge(A, B) - //world << "zones merged." - return - //air_master.equalize(A, B) - A.air.share_ratio(B.air, coefficient) - air_master.mark_zone_update(A) - air_master.mark_zone_update(B) - //world << "equalized." + var/equiv = A.air.share_ratio(B.air, coefficient) var/differential = A.air.return_pressure() - B.air.return_pressure() - if(abs(differential) < vsc.airflow_lightest_pressure) return + if(abs(differential) >= vsc.airflow_lightest_pressure) + var/list/attracted + var/list/repelled + if(differential > 0) + attracted = A.movables() + repelled = B.movables() + else + attracted = B.movables() + repelled = A.movables() - var/list/attracted - var/list/repelled - if(differential > 0) - attracted = A.movables() - repelled = B.movables() - else - attracted = B.movables() - repelled = A.movables() + flow(attracted, abs(differential), 0) + flow(repelled, abs(differential), 1) - flow(attracted, abs(differential), 0) - flow(repelled, abs(differential), 1) + if(equiv) + if(direct) + erase() + air_master.merge(A, B) + return + else + A.air.equalize(B.air) + air_master.mark_edge_sleeping(src) + + air_master.mark_zone_update(A) + air_master.mark_zone_update(B) + +/connection_edge/zone/recheck() + if(!A.air.compare(B.air)) + air_master.mark_edge_active(src) //Helper proc to get connections for a zone. /connection_edge/zone/proc/get_connected_zone(zone/from) @@ -214,20 +220,27 @@ Class Procs: if(A.invalid) erase() return - //world << "[id]: Tick [air_master.current_cycle]: To [B]!" - //A.air.mimic(B, coefficient) - A.air.share_space(air, dbg_out) - air_master.mark_zone_update(A) + + var/equiv = A.air.share_space(air) var/differential = A.air.return_pressure() - air.return_pressure() - if(abs(differential) < vsc.airflow_lightest_pressure) return + if(abs(differential) >= vsc.airflow_lightest_pressure) + var/list/attracted = A.movables() + flow(attracted, abs(differential), differential < 0) - var/list/attracted = A.movables() - flow(attracted, abs(differential), differential < 0) + if(equiv) + A.air.copy_from(air) + air_master.mark_edge_sleeping(src) + + air_master.mark_zone_update(A) + +/connection_edge/unsimulated/recheck() + if(!A.air.compare(air)) + air_master.mark_edge_active(src) proc/ShareHeat(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles) //This implements a simplistic version of the Stefan-Boltzmann law. - var/energy_delta = ((A.temperature - B.temperature) ** 4) * 5.6704e-8 * connecting_tiles * 2.5 + var/energy_delta = ((A.temperature - B.temperature) ** 4) * STEFAN_BOLTZMANN_CONSTANT * connecting_tiles * 2.5 var/maximum_energy_delta = max(0, min(A.temperature * A.heat_capacity() * A.group_multiplier, B.temperature * B.heat_capacity() * B.group_multiplier)) if(maximum_energy_delta > abs(energy_delta)) if(energy_delta < 0) @@ -235,4 +248,4 @@ proc/ShareHeat(datum/gas_mixture/A, datum/gas_mixture/B, connecting_tiles) energy_delta = maximum_energy_delta A.temperature -= energy_delta / (A.heat_capacity() * A.group_multiplier) - B.temperature += energy_delta / (B.heat_capacity() * B.group_multiplier) \ No newline at end of file + B.temperature += energy_delta / (B.heat_capacity() * B.group_multiplier) diff --git a/code/ZAS/Controller.dm b/code/ZAS/Controller.dm index 756887cc09..4ba30f47b3 100644 --- a/code/ZAS/Controller.dm +++ b/code/ZAS/Controller.dm @@ -75,6 +75,7 @@ Class Procs: /datum/controller/air_system/var/list/zones_to_update = list() /datum/controller/air_system/var/list/active_fire_zones = list() /datum/controller/air_system/var/list/active_hotspots = list() +/datum/controller/air_system/var/list/active_edges = list() /datum/controller/air_system/var/active_zones = 0 @@ -97,7 +98,7 @@ Class Procs: set background = 1 #endif - world << "\red \b Processing Geometry..." + world << "Processing Geometry..." sleep(-1) var/start_time = world.timeofday @@ -108,10 +109,15 @@ Class Procs: simulated_turf_count++ S.update_air_properties() - world << {"Geometry initialized in [round(0.1*(world.timeofday-start_time),0.1)] seconds. + world << {"Geometry initialized in [round(0.1*(world.timeofday-start_time),0.1)] seconds. + Total Simulated Turfs: [simulated_turf_count] Total Zones: [zones.len] -Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_count]"} +Total Edges: [edges.len] +Total Active Edges: [active_edges.len ? "[active_edges.len]" : "None"] +Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_count] +"} + // spawn Start() @@ -169,7 +175,7 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun if(.) tick_progress = "processing edges" - for(var/connection_edge/edge in edges) + for(var/connection_edge/edge in active_edges) edge.tick() //Process fire zones. @@ -299,6 +305,22 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun zones_to_update.Add(Z) Z.needs_update = 1 +/datum/controller/air_system/proc/mark_edge_sleeping(connection_edge/E) + #ifdef ZASDBG + ASSERT(istype(E) + #endif + if(E.sleeping) return + active_edges.Remove(E) + E.sleeping = 1 + +/datum/controller/air_system/proc/mark_edge_active(connection_edge/E) + #ifdef ZASDBG + ASSERT(istype(E) + #endif + if(!E.sleeping) return + active_edges.Add(E) + E.sleeping = 0 + /datum/controller/air_system/proc/equivalent_pressure(zone/A, zone/B) return A.air.compare(B.air) @@ -309,12 +331,14 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun if(edge.contains_zone(B)) return edge var/connection_edge/edge = new/connection_edge/zone(A,B) edges.Add(edge) + edge.recheck() return edge else for(var/connection_edge/unsimulated/edge in A.edges) if(has_same_air(edge.B,B)) return edge var/connection_edge/edge = new/connection_edge/unsimulated(A,B) edges.Add(edge) + edge.recheck() return edge /datum/controller/air_system/proc/has_same_air(turf/A, turf/B) @@ -325,5 +349,6 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun if(A.temperature != B.temperature) return 0 return 1 -/datum/controller/air_system/proc/remove_edge(connection/c) - edges.Remove(c) \ No newline at end of file +/datum/controller/air_system/proc/remove_edge(connection_edge/E) + edges.Remove(E) + if(!E.sleeping) active_edges.Remove(E) \ No newline at end of file diff --git a/code/ZAS/Zone.dm b/code/ZAS/Zone.dm index 1f2cbee8d3..7c9c5f32ed 100644 --- a/code/ZAS/Zone.dm +++ b/code/ZAS/Zone.dm @@ -138,6 +138,10 @@ Class Procs: graphic_add.len = 0 graphic_remove.len = 0 + for(var/connection_edge/E in edges) + if(E.sleeping) + E.recheck() + /zone/proc/dbg_data(mob/M) M << name for(var/g in air.gas) diff --git a/code/ZAS/_gas_mixture_xgm.dm b/code/ZAS/_gas_mixture_xgm.dm index 01f95ee9a5..3390cb9fd3 100644 --- a/code/ZAS/_gas_mixture_xgm.dm +++ b/code/ZAS/_gas_mixture_xgm.dm @@ -17,7 +17,8 @@ //List of active tile overlays for this gas_mixture. Updated by check_tile_graphic() var/list/graphic = list() -//Takes a gas string, and the amount of moles to adjust by. Calls update_values() if update isn't 0. + +//Takes a gas string and the amount of moles to adjust by. Calls update_values() if update isn't 0. /datum/gas_mixture/proc/adjust_gas(gasid, moles, update = 1) if(moles == 0) return @@ -30,6 +31,7 @@ if(update) update_values() + //Same as adjust_gas(), but takes a temperature which is mixed in with the gas. /datum/gas_mixture/proc/adjust_gas_temp(gasid, moles, temp, update = 1) if(moles == 0) @@ -50,7 +52,8 @@ if(update) update_values() -//Variadic version of adjust_gas(). Takes any number of gas and mole pairs, and applies them. + +//Variadic version of adjust_gas(). Takes any number of gas and mole pairs and applies them. /datum/gas_mixture/proc/adjust_multi() ASSERT(!(args.len % 2)) @@ -59,7 +62,8 @@ update_values() -//Variadic version of adjust_gas_temp(). Takes any number of gas, mole, and temperature tuples, and applies them. + +//Variadic version of adjust_gas_temp(). Takes any number of gas, mole and temperature associations and applies them. /datum/gas_mixture/proc/adjust_multi_temp() ASSERT(!(args.len % 3)) @@ -68,8 +72,10 @@ update_values() + //Merges all the gas from another mixture into this one. Respects group_multipliers and adjusts temperature correctly. -/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) +//Does not modify giver in any way. +/datum/gas_mixture/proc/merge(const/datum/gas_mixture/giver) if(!giver) return @@ -89,6 +95,25 @@ update_values() + +/datum/gas_mixture/proc/equalize(datum/gas_mixture/sharer) + for(var/g in sharer.gas) + var/comb = gas[g] + sharer.gas[g] + comb /= volume + sharer.volume + gas[g] = comb * volume + sharer.gas[g] = comb * sharer.volume + + var/our_heatcap = heat_capacity() + var/share_heatcap = sharer.heat_capacity() + + temperature = 0 + if(our_heatcap + share_heatcap) + temperature = ((temperature * our_heatcap) + (sharer.temperature * share_heatcap)) / (our_heatcap + share_heatcap) + sharer.temperature = temperature + + return 1 + + //Returns the heat capacity of the gas mix based on the specific heat of the gases. /datum/gas_mixture/proc/heat_capacity() . = 0 @@ -96,11 +121,12 @@ . += gas_data.specific_heat[g] * gas[g] . *= group_multiplier + //Adds or removes thermal energy. Returns the actual thermal energy change, as in the case of removing energy we can't go below TCMB. /datum/gas_mixture/proc/add_thermal_energy(var/thermal_energy) if (total_moles == 0) return 0 - + var/heat_capacity = heat_capacity() if (thermal_energy < 0) if (temperature < TCMB) @@ -114,43 +140,47 @@ /datum/gas_mixture/proc/get_thermal_energy_change(var/new_temperature) return heat_capacity()*(max(new_temperature, 0) - temperature) + //Technically vacuum doesn't have a specific entropy. Just use a really big number (infinity would be ideal) here so that it's easy to add gas to vacuum and hard to take gas out. #define SPECIFIC_ENTROPY_VACUUM 150000 + //Returns the ideal gas specific entropy of the whole mix. This is the entropy per mole of /mixed/ gas. /datum/gas_mixture/proc/specific_entropy() if (!gas.len || total_moles == 0) return SPECIFIC_ENTROPY_VACUUM - + . = 0 for(var/g in gas) . += gas[g] * specific_entropy_gas(g) . /= total_moles + /* It's arguable whether this should even be called entropy anymore. It's more "based on" entropy than actually entropy now. - + Returns the ideal gas specific entropy of a specific gas in the mix. This is the entropy due to that gas per mole of /that/ gas in the mixture, not the entropy due to that gas per mole of gas mixture. - + For the purposes of SS13, the specific entropy is just a number that tells you how hard it is to move gas. You can replace this with whatever you want. Just remember that returning a SMALL number == adding gas to this gas mix is HARD, taking gas away is EASY, and that returning a LARGE number means the opposite (so a vacuum should approach infinity). - So returning a constant/(partial pressure) would probably do what most players expect. Although the version I have implemented below is a bit more nuanced than simply 1/P in that it scales in a way + So returning a constant/(partial pressure) would probably do what most players expect. Although the version I have implemented below is a bit more nuanced than simply 1/P in that it scales in a way which is bit more realistic (natural log), and returns a fairly accurate entropy around room temperatures and pressures. */ /datum/gas_mixture/proc/specific_entropy_gas(var/gasid) if (!(gasid in gas) || gas[gasid] == 0) return SPECIFIC_ENTROPY_VACUUM //that gas isn't here - + //group_multiplier gets divided out in volume/gas[gasid] - also, V/(m*T) = R/(partial pressure) var/molar_mass = gas_data.molar_mass[gasid] var/specific_heat = gas_data.specific_heat[gasid] return R_IDEAL_GAS_EQUATION * ( log( (IDEAL_GAS_ENTROPY_CONSTANT*volume/(gas[gasid] * temperature)) * (molar_mass*specific_heat*temperature)**(2/3) + 1 ) + 15 ) - + //alternative, simpler equation //var/partial_pressure = gas[gasid] * R_IDEAL_GAS_EQUATION * temperature / volume //return R_IDEAL_GAS_EQUATION * ( log (1 + IDEAL_GAS_ENTROPY_CONSTANT/partial_pressure) + 20 ) + //Updates the total_moles count and trims any empty gases. /datum/gas_mixture/proc/update_values() total_moles = 0 @@ -160,12 +190,14 @@ else total_moles += gas[g] + //Returns the pressure of the gas mix. Only accurate if there have been no gas modifications since update_values() has been called. /datum/gas_mixture/proc/return_pressure() if(volume) return total_moles * R_IDEAL_GAS_EQUATION * temperature / volume return 0 + //Removes moles from the gas mixture and returns a gas_mixture containing the removed air. /datum/gas_mixture/proc/remove(amount) amount = min(amount, total_moles * group_multiplier) //Can not take more air than the gas mixture has! @@ -184,6 +216,7 @@ return removed + //Removes a ratio of gas from the mixture and returns a gas_mixture containing the removed air. /datum/gas_mixture/proc/remove_ratio(ratio, out_group_multiplier = 1) if(ratio <= 0) @@ -205,6 +238,7 @@ return removed + //Removes moles from the gas mixture, limited by a given flag. Returns a gax_mixture containing the removed air. /datum/gas_mixture/proc/remove_by_flag(flag, amount) if(!flag || amount <= 0) @@ -228,8 +262,9 @@ return removed + //Copies gas and temperature from another gas_mixture. -/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) +/datum/gas_mixture/proc/copy_from(const/datum/gas_mixture/sample) gas = sample.gas.Copy() temperature = sample.temperature @@ -237,8 +272,9 @@ return 1 + //Checks if we are within acceptable range of another gas_mixture to suspend processing or merge. -/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) +/datum/gas_mixture/proc/compare(const/datum/gas_mixture/sample) if(!sample) return 0 var/list/marked = list() @@ -264,9 +300,11 @@ return 1 + /datum/gas_mixture/proc/react(atom/dump_location) zburn(null) + //Rechecks the gas_mixture and adjusts the graphic list if needed. //Two lists can be passed by reference if you need know specifically which graphics were added and removed. /datum/gas_mixture/proc/check_tile_graphic(list/graphic_add = null, list/graphic_remove = null) @@ -293,6 +331,7 @@ graphic -= graphic_remove . = 1 + //Simpler version of merge(), adjusts gas amounts directly and doesn't account for temperature or group_multiplier. /datum/gas_mixture/proc/add(datum/gas_mixture/right_side) for(var/g in right_side.gas) @@ -301,6 +340,7 @@ update_values() return 1 + //Simpler version of remove(), adjusts gas amounts directly and doesn't account for group_multiplier. /datum/gas_mixture/proc/subtract(datum/gas_mixture/right_side) for(var/g in right_side.gas) @@ -309,6 +349,7 @@ update_values() return 1 + //Multiply all gas amounts by a factor. /datum/gas_mixture/proc/multiply(factor) for(var/g in gas) @@ -317,6 +358,7 @@ update_values() return 1 + //Divide all gas amounts by a factor. /datum/gas_mixture/proc/divide(factor) for(var/g in gas) @@ -325,6 +367,7 @@ update_values() return 1 + //Shares gas with another gas_mixture based on the amount of connecting tiles and a fixed lookup table. /datum/gas_mixture/proc/share_ratio(datum/gas_mixture/other, connecting_tiles, share_size = null, one_way = 0) var/static/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66) @@ -369,19 +412,13 @@ update_values() other.update_values() - if(compare(other)) return 1 - else return 0 + return compare(other) + //A wrapper around share_ratio for spacing gas at the same rate as if it were going into a large airless room. /datum/gas_mixture/proc/share_space(datum/gas_mixture/unsim_air) - if(!unsim_air) - return 0 + return share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier), one_way = 1) - var/old_pressure = return_pressure() - - share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier), one_way = 1) - - return abs(old_pressure - return_pressure()) //Equalizes a list of gas mixtures. Used for pipe networks. /proc/equalize_gases(datum/gas_mixture/list/gases) diff --git a/code/setup.dm b/code/setup.dm index ab3b065ae0..5ebde2b9a3 100644 --- a/code/setup.dm +++ b/code/setup.dm @@ -9,7 +9,7 @@ #define IDEAL_GAS_ENTROPY_CONSTANT 1164 //(mol^3 * s^3) / (kg^3 * L). Equal to (4*pi/(avrogadro's number * planck's constant)^2)^(3/2) / (avrogadro's number * 1000 Liters per m^3). //radiation constants -#define STEFAN_BOLTZMANN_CONSTANT 0.0000000567 //W/(m^2*K^4) +#define STEFAN_BOLTZMANN_CONSTANT 5.6704e-8 //W/(m^2*K^4) #define COSMIC_RADIATION_TEMPERATURE 3.15 //K #define AVERAGE_SOLAR_RADIATION 200 //W/m^2. Kind of arbitrary. Really this should depend on the sun position much like solars. #define RADIATOR_OPTIMUM_PRESSURE 110 //kPa at 20 C