Files
CHOMPStation2/code/ZAS/Fire.dm
CHOMPStation2 fe4cf73003 [MIRROR] fix division by 0 on fire effects (#7632)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com>
Co-authored-by: CHOMPStation2 <chompsation2@gmail.com>
2024-01-27 18:44:20 +01:00

434 lines
14 KiB
Plaintext

/*
Making Bombs with ZAS:
Get gas to react in an air tank so that it gains pressure. If it gains enough pressure, it goes boom.
The more pressure, the more boom.
If it gains pressure too slowly, it may leak or just rupture instead of exploding.
*/
//#define FIREDBG
/turf/var/obj/fire/fire = null
//Some legacy definitions so fires can be started.
/atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
return null
/turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0)
/turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh)
if(fire_protection > world.time-300)
return 0
if(locate(/obj/fire) in src)
return 1
var/datum/gas_mixture/air_contents = return_air()
if(!air_contents || exposed_temperature < PHORON_MINIMUM_BURN_TEMPERATURE)
return 0
var/igniting = 0
var/obj/effect/decal/cleanable/liquid_fuel/liquid = locate() in src
if(air_contents.check_combustability(liquid))
igniting = 1
create_fire(exposed_temperature)
return igniting
/zone/proc/process_fire()
var/datum/gas_mixture/burn_gas = air.remove_ratio(vsc.fire_consuption_rate, fire_tiles.len)
var/firelevel = burn_gas.zburn(src, fire_tiles, force_burn = 1, no_check = 1)
air.merge(burn_gas)
if(firelevel)
for(var/turf/T in fire_tiles)
if(T.fire)
T.fire.firelevel = firelevel
else
var/obj/effect/decal/cleanable/liquid_fuel/fuel = locate() in T
fire_tiles -= T
fuel_objs -= fuel
else
for(var/turf/simulated/T in fire_tiles)
if(istype(T.fire))
T.fire.RemoveFire()
T.fire = null
fire_tiles.Cut()
fuel_objs.Cut()
if(!fire_tiles.len)
air_master.active_fire_zones.Remove(src)
/zone/proc/remove_liquidfuel(var/used_liquid_fuel, var/remove_fire=0)
if(!fuel_objs.len)
return
//As a simplification, we remove fuel equally from all fuel sources. It might be that some fuel sources have more fuel,
//some have less, but whatever. It will mean that sometimes we will remove a tiny bit less fuel then we intended to.
var/fuel_to_remove = used_liquid_fuel/(fuel_objs.len*LIQUIDFUEL_AMOUNT_TO_MOL) //convert back to liquid volume units
for(var/obj/effect/decal/cleanable/liquid_fuel/fuel as anything in fuel_objs)
if(!istype(fuel))
fuel_objs -= fuel
continue
fuel.amount -= fuel_to_remove
if(fuel.amount <= 0)
fuel_objs -= fuel
if(remove_fire)
var/turf/T = fuel.loc
if(istype(T) && T.fire) qdel(T.fire)
qdel(fuel)
/turf/proc/create_fire(fl)
return 0
/turf/simulated/create_fire(fl)
if(fire)
fire.firelevel = max(fl, fire.firelevel)
return 1
if(!zone)
return 1
fire = new(src, fl)
air_master.active_fire_zones |= zone
var/obj/effect/decal/cleanable/liquid_fuel/fuel = locate() in src
zone.fire_tiles |= src
if(fuel) zone.fuel_objs += fuel
return 0
/obj/fire
//Icon for fire on turfs.
anchored = TRUE
mouse_opacity = 0
blend_mode = BLEND_ADD
icon = 'icons/effects/fire.dmi'
icon_state = "1"
light_color = "#ED9200"
layer = TURF_LAYER
var/firelevel = 1 //Calculated by gas_mixture.calculate_firelevel()
/obj/fire/process()
. = 1
var/turf/simulated/my_tile = loc
if(!istype(my_tile) || !my_tile.zone)
if(my_tile.fire == src)
my_tile.fire = null
RemoveFire()
return 1
var/datum/gas_mixture/air_contents = my_tile.return_air()
if(firelevel > 6)
icon_state = "3"
set_light(7, 3)
else if(firelevel > 2.5)
icon_state = "2"
set_light(5, 2)
else
icon_state = "1"
set_light(3, 1)
for(var/mob/living/L in loc)
L.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure()) //Burn the mobs!
loc.fire_act(air_contents, air_contents.temperature, air_contents.volume)
for(var/atom/A in loc)
A.fire_act(air_contents, air_contents.temperature, air_contents.volume)
//spread
for(var/direction in cardinal)
var/turf/simulated/enemy_tile = get_step(my_tile, direction)
if(istype(enemy_tile))
if(my_tile.open_directions & direction) //Grab all valid bordering tiles
if(!enemy_tile.zone || enemy_tile.fire)
continue
//if(!enemy_tile.zone.fire_tiles.len) TODO - optimize
var/datum/gas_mixture/acs = enemy_tile.return_air()
var/obj/effect/decal/cleanable/liquid_fuel/liquid = locate() in enemy_tile
if(!acs || !acs.check_combustability(liquid))
continue
//If extinguisher mist passed over the turf it's trying to spread to, don't spread and
//reduce firelevel.
if(enemy_tile.fire_protection > world.time-30)
firelevel -= 1.5
continue
//Spread the fire.
if(prob( 50 + 50 * (firelevel/vsc.fire_firelevel_multiplier) ) && my_tile.CanPass(src, enemy_tile) && enemy_tile.CanPass(src, my_tile))
enemy_tile.create_fire(firelevel)
else
enemy_tile.adjacent_fire_act(loc, air_contents, air_contents.temperature, air_contents.volume)
animate(src, color = fire_color(air_contents.temperature), 5)
set_light(l_color = color)
/obj/fire/New(newLoc,fl)
..()
if(!istype(loc, /turf))
qdel(src)
return
set_dir(pick(cardinal))
var/datum/gas_mixture/air_contents = loc.return_air()
color = fire_color(air_contents.temperature)
set_light(3, 1, color)
firelevel = fl
air_master.active_hotspots.Add(src)
/obj/fire/proc/fire_color(var/env_temperature)
var/temperature = max(4000*sqrt(firelevel/vsc.fire_firelevel_multiplier), env_temperature)
return heat2color(temperature)
/obj/fire/Destroy()
RemoveFire()
..()
/obj/fire/proc/RemoveFire()
var/turf/T = loc
if (istype(T))
set_light(0)
T.fire = null
loc = null
air_master.active_hotspots.Remove(src)
/turf/simulated/var/fire_protection = 0 //Protects newly extinguished tiles from being overrun again.
/turf/proc/apply_fire_protection()
/turf/simulated/apply_fire_protection()
fire_protection = world.time
//Returns the firelevel
/datum/gas_mixture/proc/zburn(zone/zone, force_burn, no_check = 0)
. = 0
if((temperature > PHORON_MINIMUM_BURN_TEMPERATURE || force_burn) && (no_check ||check_recombustability(zone? zone.fuel_objs : null)))
#ifdef FIREDBG
log_debug("***************** FIREDBG *****************")
log_debug("Burning [zone? zone.name : "zoneless gas_mixture"]!")
#endif
var/gas_fuel = 0
var/liquid_fuel = 0
var/total_fuel = 0
var/total_oxidizers = 0
//*** Get the fuel and oxidizer amounts
for(var/g in gas)
if(gas_data.flags[g] & XGM_GAS_FUEL)
gas_fuel += gas[g]
if(gas_data.flags[g] & XGM_GAS_OXIDIZER)
total_oxidizers += gas[g]
gas_fuel *= group_multiplier
total_oxidizers *= group_multiplier
//Liquid Fuel
var/fuel_area = 0
if(zone)
for(var/obj/effect/decal/cleanable/liquid_fuel/fuel in zone.fuel_objs)
liquid_fuel += fuel.amount*LIQUIDFUEL_AMOUNT_TO_MOL
fuel_area++
total_fuel = gas_fuel + liquid_fuel
if(total_fuel <= 0.005)
return 0
//*** Determine how fast the fire burns
//get the current thermal energy of the gas mix
//this must be taken here to prevent the addition or deletion of energy by a changing heat capacity
var/starting_energy = temperature * heat_capacity()
//determine how far the reaction can progress
var/reaction_limit = min(total_oxidizers*(FIRE_REACTION_FUEL_AMOUNT/FIRE_REACTION_OXIDIZER_AMOUNT), total_fuel) //stoichiometric limit
//vapour fuels are extremely volatile! The reaction progress is a percentage of the total fuel (similar to old zburn).)
var/gas_firelevel = calculate_firelevel(gas_fuel, total_oxidizers, reaction_limit, volume*group_multiplier) / vsc.fire_firelevel_multiplier
var/min_burn = 0.30*volume*group_multiplier/CELL_VOLUME //in moles - so that fires with very small gas concentrations burn out fast
var/gas_reaction_progress = min(max(min_burn, gas_firelevel*gas_fuel)*FIRE_GAS_BURNRATE_MULT, gas_fuel)
//liquid fuels are not as volatile, and the reaction progress depends on the size of the area that is burning. Limit the burn rate to a certain amount per area.
var/liquid_firelevel = calculate_firelevel(liquid_fuel, total_oxidizers, reaction_limit, 0) / vsc.fire_firelevel_multiplier
var/liquid_reaction_progress = min((liquid_firelevel*0.2 + 0.05)*fuel_area*FIRE_LIQUID_BURNRATE_MULT, liquid_fuel)
var/firelevel = (gas_fuel*gas_firelevel + liquid_fuel*liquid_firelevel)/total_fuel
var/total_reaction_progress = gas_reaction_progress + liquid_reaction_progress
var/used_fuel = min(total_reaction_progress, reaction_limit)
var/used_oxidizers = used_fuel*(FIRE_REACTION_OXIDIZER_AMOUNT/FIRE_REACTION_FUEL_AMOUNT)
#ifdef FIREDBG
log_debug("gas_fuel = [gas_fuel], liquid_fuel = [liquid_fuel], total_oxidizers = [total_oxidizers]")
log_debug("fuel_area = [fuel_area], total_fuel = [total_fuel], reaction_limit = [reaction_limit]")
log_debug("firelevel -> [firelevel] (gas: [gas_firelevel], liquid: [liquid_firelevel])")
log_debug("liquid_reaction_progress = [liquid_reaction_progress]")
log_debug("gas_reaction_progress = [gas_reaction_progress]")
log_debug("total_reaction_progress = [total_reaction_progress]")
log_debug("used_fuel = [used_fuel], used_oxidizers = [used_oxidizers]; ")
#endif
//if the reaction is progressing too slow then it isn't self-sustaining anymore and burns out
if(zone) //be less restrictive with canister and tank reactions
if((!liquid_fuel || used_fuel <= FIRE_LIQUD_MIN_BURNRATE) && (!gas_fuel || used_fuel <= FIRE_GAS_MIN_BURNRATE*zone.contents.len))
return 0
//*** Remove fuel and oxidizer, add carbon dioxide and heat
//remove and add gasses as calculated
var/used_gas_fuel = min(max(0.25, used_fuel*(gas_reaction_progress/total_reaction_progress)), gas_fuel) //remove in proportion to the relative reaction progress
var/used_liquid_fuel = min(max(0.25, used_fuel-used_gas_fuel), liquid_fuel)
//remove_by_flag() and adjust_gas() handle the group_multiplier for us.
remove_by_flag(XGM_GAS_OXIDIZER, used_oxidizers)
remove_by_flag(XGM_GAS_FUEL, used_gas_fuel)
adjust_gas("carbon_dioxide", used_oxidizers)
if(zone)
zone.remove_liquidfuel(used_liquid_fuel, !check_combustability())
//calculate the energy produced by the reaction and then set the new temperature of the mix
temperature = (starting_energy + vsc.fire_fuel_energy_release * (used_gas_fuel + used_liquid_fuel)) / heat_capacity()
update_values()
#ifdef FIREDBG
log_debug("used_gas_fuel = [used_gas_fuel]; used_liquid_fuel = [used_liquid_fuel]; total = [used_fuel]")
log_debug("new temperature = [temperature]; new pressure = [return_pressure()]")
#endif
return firelevel
/datum/gas_mixture/proc/check_recombustability(list/fuel_objs)
. = 0
for(var/g in gas)
if(gas_data.flags[g] & XGM_GAS_OXIDIZER && gas[g] >= 0.1)
. = 1
break
if(!.)
return 0
if(fuel_objs && fuel_objs.len)
return 1
. = 0
for(var/g in gas)
if(gas_data.flags[g] & XGM_GAS_FUEL && gas[g] >= 0.1)
. = 1
break
/datum/gas_mixture/proc/check_combustability(obj/effect/decal/cleanable/liquid_fuel/liquid=null)
. = 0
for(var/g in gas)
if(gas_data.flags[g] & XGM_GAS_OXIDIZER && QUANTIZE(gas[g] * vsc.fire_consuption_rate) >= 0.1)
. = 1
break
if(!.)
return 0
if(liquid)
return 1
. = 0
for(var/g in gas)
if(gas_data.flags[g] & XGM_GAS_FUEL && QUANTIZE(gas[g] * vsc.fire_consuption_rate) >= 0.005)
. = 1
break
//returns a value between 0 and vsc.fire_firelevel_multiplier
/datum/gas_mixture/proc/calculate_firelevel(total_fuel, total_oxidizers, reaction_limit, gas_volume)
//Calculates the firelevel based on one equation instead of having to do this multiple times in different areas.
var/firelevel = 0
var/total_combustables = (total_fuel + total_oxidizers)
var/active_combustables = (FIRE_REACTION_OXIDIZER_AMOUNT/FIRE_REACTION_FUEL_AMOUNT + 1)*reaction_limit
if(total_combustables > 0 && total_moles > 0)
//slows down the burning when the concentration of the reactants is low
var/damping_multiplier = min(1, active_combustables / (total_moles/group_multiplier))
//weight the damping mult so that it only really brings down the firelevel when the ratio is closer to 0
damping_multiplier = 2*damping_multiplier - (damping_multiplier*damping_multiplier)
//calculates how close the mixture of the reactants is to the optimum
//fires burn better when there is more oxidizer -- too much fuel will choke the fire out a bit, reducing firelevel.
var/mix_multiplier = 1 / (1 + (5 * ((total_fuel / total_combustables) ** 2)))
#ifdef FIREDBG
ASSERT(damping_multiplier <= 1)
ASSERT(mix_multiplier <= 1)
#endif
//toss everything together -- should produce a value between 0 and fire_firelevel_multiplier
firelevel = vsc.fire_firelevel_multiplier * mix_multiplier * damping_multiplier
return max( 0, firelevel)
/mob/living/proc/FireBurn(var/firelevel, var/last_temperature, var/pressure)
var/mx = 5 * firelevel/vsc.fire_firelevel_multiplier * min(pressure / ONE_ATMOSPHERE, 1)
apply_damage(2.5*mx, BURN)
/mob/living/carbon/human/FireBurn(var/firelevel, var/last_temperature, var/pressure)
//Burns mobs due to fire. Respects heat transfer coefficients on various body parts.
//Due to TG reworking how fireprotection works, this is kinda less meaningful.
var/head_exposure = 1
var/chest_exposure = 1
var/groin_exposure = 1
var/legs_exposure = 1
var/arms_exposure = 1
//Get heat transfer coefficients for clothing.
for(var/obj/item/clothing/C in src)
if(item_is_in_hands(C))
continue
if( C.max_heat_protection_temperature >= last_temperature )
if(C.body_parts_covered & HEAD)
head_exposure = 0
if(C.body_parts_covered & UPPER_TORSO)
chest_exposure = 0
if(C.body_parts_covered & LOWER_TORSO)
groin_exposure = 0
if(C.body_parts_covered & LEGS)
legs_exposure = 0
if(C.body_parts_covered & ARMS)
arms_exposure = 0
//minimize this for low-pressure enviroments
var/mx = 5 * firelevel/vsc.fire_firelevel_multiplier * min(pressure / ONE_ATMOSPHERE, 1)
//Always check these damage procs first if fire damage isn't working. They're probably what's wrong.
apply_damage(2.5*mx*head_exposure, BURN, BP_HEAD, 0, 0, "Fire")
apply_damage(2.5*mx*chest_exposure, BURN, BP_TORSO, 0, 0, "Fire")
apply_damage(2.0*mx*groin_exposure, BURN, BP_GROIN, 0, 0, "Fire")
apply_damage(0.6*mx*legs_exposure, BURN, BP_L_LEG, 0, 0, "Fire")
apply_damage(0.6*mx*legs_exposure, BURN, BP_R_LEG, 0, 0, "Fire")
apply_damage(0.4*mx*arms_exposure, BURN, BP_L_ARM, 0, 0, "Fire")
apply_damage(0.4*mx*arms_exposure, BURN, BP_R_ARM, 0, 0, "Fire")