From 0918f8bddea175722ffc0a10607f48c40cf08f21 Mon Sep 17 00:00:00 2001 From: Mloc-Argent Date: Tue, 22 Jul 2014 17:50:57 +0100 Subject: [PATCH] new XGM gas mixture datum Gases are now represented in an associatiee list instead of harcoded variables. gas = list("oxygen" = 20, "nitrogen" = 80) New cleaned up share_ratio(), share_space() and equalise_gases() procs. Gas can be modified with adjust_gas() or adjust_gas_temp(), with variadic versions available for both. These are documented in gas_mixture_xgm.dm Signed-off-by: Mloc-Argent --- code/ZAS/Gas.dm | 45 +++++ code/ZAS/_gas_mixture_xgm.dm | 367 +++++++++++++++++++++++++++++++++++ code/ZAS/_xgm_gas_data.dm | 42 ++++ 3 files changed, 454 insertions(+) create mode 100644 code/ZAS/Gas.dm create mode 100644 code/ZAS/_gas_mixture_xgm.dm create mode 100644 code/ZAS/_xgm_gas_data.dm diff --git a/code/ZAS/Gas.dm b/code/ZAS/Gas.dm new file mode 100644 index 0000000000..66a996457a --- /dev/null +++ b/code/ZAS/Gas.dm @@ -0,0 +1,45 @@ +/xgm_gas/oxygen + id = "oxygen" + name = "Oxygen" + specific_heat = 20 + + flags = XGM_GAS_OXIDIZER + +/xgm_gas/nitrogen + id = "nitrogen" + name = "Nitrogen" + specific_heat = 20 + +/xgm_gas/carbon_dioxide + id = "carbon_dioxide" + name = "Carbon Dioxide" + specific_heat = 30 + +/xgm_gas/phoron + id = "phoron" + name = "Phoron" + specific_heat = 200 + + tile_overlay = "phoron" + overlay_limit = 0.7 + flags = XGM_GAS_FUEL | XGM_GAS_CONTAMINANT + +/xgm_gas/volatile_fuel + id = "volatile_fuel" + name = "Volatile Fuel" + specific_heat = 30 + + flags = XGM_GAS_FUEL + +/xgm_gas/sleeping_agent + id = "sleeping_agent" + name = "Sleeping Agent" + specific_heat = 40 + + tile_overlay = "sleeping_agent" + overlay_limit = 1 + +/xgm_gas/oxygen_agent_b + id = "oxygen_agent_b" + name = "Oxygen Agent-B" + specific_heat = 300 diff --git a/code/ZAS/_gas_mixture_xgm.dm b/code/ZAS/_gas_mixture_xgm.dm new file mode 100644 index 0000000000..a60fc0bc09 --- /dev/null +++ b/code/ZAS/_gas_mixture_xgm.dm @@ -0,0 +1,367 @@ +#define QUANTIZE(variable) (round(variable,0.0001)) + +/datum/gas_mixture + //Associative list of gas moles. + //Gases with 0 moles are not tracked and are pruned by update_values() + var/list/gas = list() + //Temperature in Kelvin of this gas mix. + var/temperature = 0 + + //Sum of all the gas moles in this mix. Updated by update_values() + var/total_moles = 0 + //Volume of this mix. + var/volume = CELL_VOLUME + //Size of the group this gas_mixture is representing. 1 for singletons. + var/group_multiplier = 1 + + //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. +/datum/gas_mixture/proc/adjust_gas(gasid, moles, update = 1) + if(moles == 0) + return + + gas[gasid] += moles + + 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) + return + + if(moles > 0 && abs(temperature - temp) > MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/self_heat_capacity = heat_capacity()*group_multiplier + var/giver_heat_capacity = gas_data.specific_heat[gasid] * moles + var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity + if(combined_heat_capacity != 0) + temperature = (temp * giver_heat_capacity + temperature * self_heat_capacity) / combined_heat_capacity + + gas[gasid] += moles + + if(update) + update_values() + +//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)) + + for(var/i = 1; i < args.len; i += 2) + adjust_gas(args[i], args[i+1], update = 0) + + update_values() + +//Variadic version of adjust_gas_temp(). Takes any number of gas, mole, and temperature tuples, and applies them. +/datum/gas_mixture/proc/adjust_multi_temp() + ASSERT(!(args.len % 3)) + + for(var/i = 1; i < args.len; i += 3) + adjust_gas_temp(args[i], args[i + 1], args[i + 2], update = 0) + + update_values() + +//Merges all the gas from another mixture into this one. Respects group_multiplies and adjusts temperature correctly. +/datum/gas_mixture/proc/merge(datum/gas_mixture/giver) + if(!giver) + return + + if(abs(temperature-giver.temperature)>MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + var/self_heat_capacity = heat_capacity()*group_multiplier + var/giver_heat_capacity = giver.heat_capacity()*giver.group_multiplier + var/combined_heat_capacity = giver_heat_capacity + self_heat_capacity + if(combined_heat_capacity != 0) + temperature = (giver.temperature*giver_heat_capacity + temperature*self_heat_capacity)/combined_heat_capacity + + if((group_multiplier != 1)||(giver.group_multiplier != 1)) + for(var/g in giver.gas) + gas[g] += giver.gas[g] * giver.group_multiplier / group_multiplier + else + for(var/g in giver.gas) + gas[g] += giver.gas[g] + + update_values() + +//Returns the heat capacity of the gas mix based on the specific heat of the gases. +/datum/gas_mixture/proc/heat_capacity() + . = 0 + for(var/g in gas) + . += gas_data.specific_heat[g] * gas[g] + +//Updates the total_moles count and trims any empty gases. +/datum/gas_mixture/proc/update_values() + total_moles = 0 + for(var/g in gas) + if(gas[g] <= 0) + gas -= g + 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) + var/sum = total_moles + amount = min(amount, sum) //Can not take more air than tile has! + if(amount <= 0) + return null + + var/datum/gas_mixture/removed = new + + for(var/g in gas) + removed.gas[g] = QUANTIZE((gas[g] / sum) * amount) + gas[g] -= removed.gas[g] / group_multiplier + + removed.temperature = temperature + update_values() + removed.update_values() + + 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) + return null + out_group_multiplier = max(1, min(group_multiplier, out_group_multiplier)) + + ratio = min(ratio, 1) + + var/datum/gas_mixture/removed = new + removed.group_multiplier = out_group_multiplier + + for(var/g in gas) + removed.gas[g] = QUANTIZE(gas[g] * ratio) + gas[g] = ((gas[g] * group_multiplier) - (removed.gas[g] * out_group_multiplier)) / group_multiplier + + removed.temperature = temperature + update_values() + removed.update_values() + + 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) + return + + var/sum = 0 + for(var/g in gas) + if(gas_data.flags[g] & flag) + sum += gas[g] + + var/datum/gas_mixture/removed = new + + for(var/g in gas) + if(gas_data.flags[g] & flag) + removed.gas[g] = QUANTIZE((gas[g] / sum) * amount) + gas[g] -= removed.gas[g] / group_multiplier + + removed.temperature = temperature + update_values() + removed.update_values() + + return removed + +//Copies gas and temperature from another gas_mixture. +/datum/gas_mixture/proc/copy_from(datum/gas_mixture/sample) + gas = sample.gas.Copy() + temperature = sample.temperature + + update_values() + + return 1 + +//Checks if we are within acceptable range of another gas_mixture to suspend processing. +/datum/gas_mixture/proc/compare(datum/gas_mixture/sample) + if(!sample) return 0 + + var/list/marked = list() + for(var/g in gas) + if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \ + ((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \ + (gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]))) + return 0 + marked[g] = 1 + + for(var/g in sample.gas) + if(!marked[g]) + if((abs(gas[g] - sample.gas[g]) > MINIMUM_AIR_TO_SUSPEND) && \ + ((gas[g] < (1 - MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]) || \ + (gas[g] > (1 + MINIMUM_AIR_RATIO_TO_SUSPEND) * sample.gas[g]))) + return 0 + + if(total_moles > MINIMUM_AIR_TO_SUSPEND) + if((abs(temperature - sample.temperature) > MINIMUM_TEMPERATURE_DELTA_TO_SUSPEND) && \ + ((temperature < (1 - MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature) || \ + (temperature > (1 + MINIMUM_TEMPERATURE_RATIO_TO_SUSPEND)*sample.temperature))) + return 0 + + return 1 + +/datum/gas_mixture/proc/react(atom/dump_location) + zburn(null) + +//Rechecks the gas_mixture and adjusts the graphic list if needed. +/datum/gas_mixture/proc/check_tile_graphic() + //List of new overlays that weren't valid before. + var/list/graphic_add = null + //List of overlays that need to be removed now that they're not valid. + var/list/graphic_remove = null + + for(var/g in gas_data.overlay_limit) + if(graphic.Find(gas_data.tile_overlay[g])) + //Overlay is already applied for this gas, check if it's still valid. + if(gas[g] <= gas_data.overlay_limit[g]) + if(!graphic_remove) + graphic_remove = list() + graphic_remove += gas_data.tile_overlay[g] + else + //Overlay isn't applied for this gas, check if it's valid and needs to be added. + if(gas[g] > gas_data.overlay_limit[g]) + if(!graphic_add) + graphic_add = list() + graphic_add += gas_data.tile_overlay[g] + + . = 0 + //Apply changes + if(graphic_add && graphic_add.len) + graphic += graphic_add + . = 1 + if(graphic_remove && graphic_remove.len) + 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) + gas[g] += right_side.gas[g] + + 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) + gas[g] -= right_side.gas[g] + + update_values() + return 1 + +//Multiply all gas amounts by a factor. +/datum/gas_mixture/proc/multiply(factor) + for(var/g in gas) + gas[g] *= factor + + update_values() + return 1 + +//Divide all gas amounts by a factor. +/datum/gas_mixture/proc/divide(factor) + for(var/g in gas) + gas[g] /= factor + + 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) + var/static/list/sharing_lookup_table = list(0.30, 0.40, 0.48, 0.54, 0.60, 0.66) + //Shares a specific ratio of gas between mixtures using simple weighted averages. + var/ratio = sharing_lookup_table[6] + + var/size = max(1, group_multiplier) + if(isnull(share_size)) share_size = max(1, other.group_multiplier) + + var/list/full_gas = list() + for(var/g in gas) + full_gas[g] = gas[g] * size + + var/full_heat_capacity = heat_capacity() * size + + var/list/s_full_gas = list() + for(var/g in other.gas) + s_full_gas[g] = other.gas[g] * share_size + + var/s_full_heat_capacity = other.heat_capacity() * share_size + + var/list/avg_gas = list() + + for(var/g in full_gas) + avg_gas[g] = (full_gas[g] + s_full_gas[g]) / (size + share_size) + + for(var/g in s_full_gas) + if(avg_gas[g] == null) + avg_gas[g] = (full_gas[g] + s_full_gas[g]) / (size + share_size) + + var/temp_avg = 0 + if(full_heat_capacity + s_full_heat_capacity) + temp_avg = (temperature * full_heat_capacity + other.temperature * s_full_heat_capacity) / (full_heat_capacity + s_full_heat_capacity) + + //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD. + if(sharing_lookup_table.len >= connecting_tiles) //6 or more interconnecting tiles will max at 42% of air moved per tick. + ratio = sharing_lookup_table[connecting_tiles] + //WOOT WOOT TOUCH THIS AND YOU ARE A RETARD + + for(var/g in avg_gas) + gas[g] = max(0, (gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g]) + other.gas[g] = max(0, (other.gas[g] - avg_gas[g]) * (1 - ratio) + avg_gas[g]) + + temperature = max(0, (temperature - temp_avg) * (1-ratio) + temp_avg) + other.temperature = max(0, (other.temperature - temp_avg) * (1-ratio) + temp_avg) + + update_values() + other.update_values() + + if(compare(other)) return 1 + else return 0 + +//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 + + var/old_pressure = return_pressure() + + share_ratio(unsim_air, unsim_air.group_multiplier, max(1, max(group_multiplier + 3, 1) + unsim_air.group_multiplier)) + + return abs(old_pressure - return_pressure()) + +//Equalizes a list of gas mixtures. Used for pipe networks. +/proc/equalize_gases(datum/gas_mixture/list/gases) + //Calculate totals from individual components + var/total_volume = 0 + var/total_thermal_energy = 0 + var/total_heat_capacity = 0 + + var/list/total_gas = list() + for(var/datum/gas_mixture/gasmix in gases) + total_volume += gasmix.volume + var/temp_heatcap = gasmix.heat_capacity() + total_thermal_energy += gasmix.temperature * temp_heatcap + total_heat_capacity += temp_heatcap + for(var/g in gasmix.gas) + total_gas[g] += gasmix.gas[g] + + if(total_volume > 0) + //Average out the gases + for(var/g in total_gas) + total_gas[g] /= total_volume + + //Calculate temperature + var/temperature = 0 + + if(total_heat_capacity > 0) + temperature = total_thermal_energy / total_heat_capacity + + //Update individual gas_mixtures + for(var/datum/gas_mixture/gasmix in gases) + gasmix.gas = total_gas.Copy() + gasmix.temperature = temperature + gasmix.multiply(gasmix.volume) + + return 1 diff --git a/code/ZAS/_xgm_gas_data.dm b/code/ZAS/_xgm_gas_data.dm new file mode 100644 index 0000000000..ee33a4d57f --- /dev/null +++ b/code/ZAS/_xgm_gas_data.dm @@ -0,0 +1,42 @@ +/var/xgm_gas_data/gas_data + +/xgm_gas_data + //Simple list of all the gas IDs. + var/list/gases = list() + //The friendly, human-readable name for the gas. + var/list/name = list() + //Specific heat of the gas. Used for calculating heat capacity. + var/list/specific_heat = list() + //Tile overlays. /images, created from references to 'icons/effects/tile_effects.dmi' + var/list/tile_overlay = list() + //Overlay limits. There must be at least this many moles for the overlay to appear. + var/list/overlay_limit = list() + //Flags. + var/list/flags = list() + +/xgm_gas + var/id = "" + var/name = "Unnamed Gas" + var/specific_heat = 20 + + var/tile_overlay = null + var/overlay_limit = null + + var/flags = 0 + +/hook/startup/proc/generateGasData() + gas_data = new + for(var/p in (typesof(/xgm_gas) - /xgm_gas)) + var/xgm_gas/gas = new p + + if(gas.id in gas_data.gases) + error("Duplicate gas id `[gas.id]` in `[p]`") + + gas_data.gases += gas.id + gas_data.name[gas.id] = gas.name + gas_data.specific_heat[gas.id] = gas.specific_heat + if(gas.tile_overlay) gas_data.tile_overlay[gas.id] = image('icons/effects/tile_effects.dmi', gas.tile_overlay, FLY_LAYER) + if(gas.overlay_limit) gas_data.overlay_limit[gas.id] = gas.overlay_limit + gas_data.flags[gas.id] = gas.flags + + return 1