diff --git a/aurorastation.dme b/aurorastation.dme index 2eafcb23bdb..934ea706d8e 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -399,6 +399,7 @@ #include "code\controllers\subsystems\ticker.dm" #include "code\controllers\subsystems\timer.dm" #include "code\controllers\subsystems\trade.dm" +#include "code\controllers\subsystems\turf_fire.dm" #include "code\controllers\subsystems\verb_manager.dm" #include "code\controllers\subsystems\virtual_reality.dm" #include "code\controllers\subsystems\vis_contents.dm" @@ -1152,6 +1153,7 @@ #include "code\game\objects\effects\decals\posters\bs12.dm" #include "code\game\objects\effects\decals\posters\poster_subtypes.dm" #include "code\game\objects\effects\decals\posters\tgposters.dm" +#include "code\game\objects\effects\fire\fire.dm" #include "code\game\objects\effects\projectile\projectile_effects.dm" #include "code\game\objects\effects\projectile\projectile_impact.dm" #include "code\game\objects\effects\projectile\projectile_muzzle.dm" @@ -2254,6 +2256,7 @@ #include "code\modules\effects\maze_generation\loot_modules\trash_module.dm" #include "code\modules\effects\maze_generation\loot_modules\turf_painter.dm" #include "code\modules\effects\particles\native_particles.dm" +#include "code\modules\effects\particles\smoke.dm" #include "code\modules\effects\sparks\_spark_docs.dm" #include "code\modules\effects\sparks\procs.dm" #include "code\modules\effects\sparks\spawner.dm" diff --git a/code/ZAS/Fire.dm b/code/ZAS/Fire.dm index 2b607b5b626..b6af196d381 100644 --- a/code/ZAS/Fire.dm +++ b/code/ZAS/Fire.dm @@ -10,8 +10,10 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin #define FIRE_LIGHT_2 4 #define FIRE_LIGHT_3 5 +//The absolute minimum temperature, below which a fire will go out. In Kelvins +#define MINIMUM_HEAT_THRESHOLD 220 + /turf - var/tmp/obj/fire/fire = null //Some legacy definitions so fires can be started. /atom/proc/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume) @@ -19,6 +21,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin /turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0) + return /** * Create a temporary ignition source at turf. Think sparks/igniters. @@ -27,14 +30,13 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin * * * exposed_temperature - ignition source temp (K) * * exposed_volume - gas volume (passed to fire_act() for locs) - * * soh - they say no one knows * * Returns boolean and/or executes create_fire(exposed_temperature). */ -/turf/simulated/hotspot_expose(exposed_temperature, exposed_volume, soh) +/turf/simulated/hotspot_expose(exposed_temperature, exposed_volume) if(fire_protection > world.time-300) return FALSE - if(locate(/obj/fire) in src) + if(locate(/obj/hotspot) in src) return TRUE var/datum/gas_mixture/air_contents = return_air() if(!air_contents || exposed_temperature < PHORON_MINIMUM_BURN_TEMPERATURE) @@ -42,91 +44,62 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin var/igniting = FALSE var/obj/effect/decal/cleanable/liquid_fuel/liquid = locate() in src + if(liquid && air_contents.check_combustibility(liquid)) + IgniteTurf(liquid.amount * 10) + QDEL_NULL(liquid) - if(air_contents.check_combustibility(liquid)) + if(air_contents.check_combustibility()) igniting = TRUE - create_fire(exposed_temperature) + return igniting /zone/proc/process_fire() var/datum/gas_mixture/burn_gas = air.remove_ratio(GLOB.vsc.fire_consuption_rate, LAZYLEN(fire_tiles)) - var/firelevel = burn_gas.zburn(src, fire_tiles, force_burn = 1, no_check = 1) + var/firelevel = burn_gas.zburn(src, fire_tiles, force_burn = TRUE, no_check = TRUE) air.merge(burn_gas) if(firelevel) for(var/turf/T in fire_tiles) - if(T.fire) - T.fire.firelevel = firelevel + if(T.hotspot) + T.hotspot.firelevel = firelevel else - var/obj/effect/decal/cleanable/liquid_fuel/fuel = locate() in T LAZYREMOVE(fire_tiles, T) - LAZYREMOVE(fuel_objs, fuel) else for(var/turf/simulated/T in fire_tiles) - if(istype(T.fire)) - T.fire.RemoveFire() - T.fire = null + if(istype(T.hotspot)) + qdel(T.hotspot) LAZYCLEARLIST(fire_tiles) - LAZYCLEARLIST(fuel_objs) UNSETEMPTY(fire_tiles) - UNSETEMPTY(fuel_objs) if(!LAZYLEN(fire_tiles)) - SSair.active_fire_zones -= src - -/zone/proc/remove_liquidfuel(var/used_liquid_fuel, var/remove_fire=0) - if(!LAZYLEN(fuel_objs)) - 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/O in fuel_objs) - var/obj/effect/decal/cleanable/liquid_fuel/fuel = O - if(!istype(fuel)) - LAZYREMOVE(fuel_objs, fuel) - continue - - fuel.amount -= fuel_to_remove - if(fuel.amount <= 0) - LAZYREMOVE(fuel_objs, fuel) - if(remove_fire) - var/turf/T = fuel.loc - if(istype(T) && T.fire) qdel(T.fire) - qdel(fuel) + SSair.active_fire_zones.Remove(src) /turf/proc/create_fire(fl) - return 0 + return FALSE /turf/simulated/create_fire(fl) - if(fire) - fire.firelevel = max(fl, fire.firelevel) - return 1 + if(hotspot) + hotspot.firelevel = max(fl, hotspot.firelevel) + return TRUE if(!zone) - return 1 + return TRUE - fire = new(src, fl) + hotspot = new(src, fl) SSair.active_fire_zones |= zone - var/obj/effect/decal/cleanable/liquid_fuel/fuel = locate() in src - LAZYINITLIST(zone.fire_tiles) - zone.fire_tiles |= src - if(fuel) - LAZYADD(zone.fuel_objs, fuel) - var/obj/effect/decal/cleanable/foam/extinguisher_foam = locate() in src if(extinguisher_foam && extinguisher_foam.reagents) - fire.firelevel *= max(0,1 - (extinguisher_foam.reagents.total_volume*0.04)) + hotspot.firelevel *= max(0,1 - (extinguisher_foam.reagents.total_volume*0.04)) //25 units will eliminate the fire completely - return 0 + LAZYOR(zone.fire_tiles, src) + + return FALSE // Future low-quality heat effect /obj/heat @@ -136,8 +109,9 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin mouse_opacity = MOUSE_OPACITY_TRANSPARENT render_target = HEAT_EFFECT_PLATE_RENDER_TARGET -/obj/fire +/obj/hotspot //Icon for fire on turfs. + //Different from turf fires anchored = 1 mouse_opacity = MOUSE_OPACITY_TRANSPARENT @@ -150,15 +124,15 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin var/firelevel = 1 //Calculated by gas_mixture.calculate_firelevel() -/obj/fire/process() +/obj/hotspot/process() . = 1 var/turf/simulated/my_tile = loc if(!istype(my_tile) || !my_tile.zone) - if(my_tile && my_tile.fire == src) - my_tile.fire = null - RemoveFire() - return 1 + if(my_tile && my_tile.hotspot == src) + my_tile.hotspot = null + qdel(src) + return PROCESS_KILL var/datum/gas_mixture/air_contents = my_tile.return_air() @@ -169,8 +143,6 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin else set_light(5, FIRE_LIGHT_1) - air_contents.adjust_gas(GAS_CO2, firelevel * 0.07) - for(var/mob/living/L in loc) L.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure()) //Burn the mobs! @@ -184,13 +156,12 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin if(istype(enemy_tile)) if(my_tile.open_directions & direction) //Grab all valid bordering tiles - if(!enemy_tile.zone || enemy_tile.fire) + if(!enemy_tile.zone || enemy_tile.hotspot) 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_combustibility(liquid)) + if(!acs || !acs.check_combustibility()) continue //If extinguisher mist passed over the turf it's trying to spread to, don't spread and @@ -212,12 +183,11 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin var/atom/movable/AM = thing animate(AM, color = fire_color(air_contents.temperature), 5) -/obj/fire/New(newLoc,fl) - ..() +/obj/hotspot/Initialize(mapload, fl) + . = ..() - if(!istype(loc, /turf)) - qdel(src) - return + if(!isturf(loc)) + return INITIALIZE_HINT_QDEL set_dir(pick(GLOB.cardinals)) @@ -226,25 +196,25 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin set_light(3, 1, color) firelevel = fl - SSair.active_hotspots += src + SSair.active_hotspots.Add(src) -/obj/fire/proc/fire_color(var/env_temperature) + //If this is a turf and it can burn maybe it should be ignited too - Lingering fire effect of sorts + var/turf/simulated/floor/F = loc + if(istype(F) && F.flooring && F.flooring.flags & TURF_CAN_BURN) + if(prob(30)) + F.IgniteTurf(rand(8,22)) + +/obj/hotspot/proc/fire_color(var/env_temperature) var/temperature = max(4000*sqrt(firelevel/GLOB.vsc.fire_firelevel_multiplier), env_temperature) return heat2color(temperature) -/obj/fire/Destroy() - RemoveFire() - - return ..() - -/obj/fire/proc/RemoveFire() +/obj/hotspot/Destroy() var/turf/T = loc if (istype(T)) set_light(0) - - T.fire = null - loc = null - SSair.active_hotspots -= src + T.hotspot = null + SSair.active_hotspots.Remove(src) + . = ..() /turf/simulated var/tmp/fire_protection = 0 //Protects newly extinguished tiles from being overrun again. @@ -258,7 +228,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin //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))) + if((temperature > PHORON_MINIMUM_BURN_TEMPERATURE || force_burn) && (no_check || check_combustibility())) #ifdef ZASDBG log_subsystem_zas_debug("***************** FIREDBG *****************") @@ -266,7 +236,6 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin #endif var/gas_fuel = 0 - var/liquid_fuel = 0 var/total_fuel = 0 var/total_oxidizers = 0 @@ -279,14 +248,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin 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 + total_fuel = gas_fuel if(total_fuel <= 0.005) return 0 @@ -304,21 +266,16 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin 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) / GLOB.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)/total_fuel - var/firelevel = (gas_fuel*gas_firelevel + liquid_fuel*liquid_firelevel)/total_fuel - - var/total_reaction_progress = gas_reaction_progress + liquid_reaction_progress + var/total_reaction_progress = gas_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 ZASDBG - log_subsystem_zas_debug("gas_fuel = [gas_fuel], liquid_fuel = [liquid_fuel], total_oxidizers = [total_oxidizers]") - log_subsystem_zas_debug("fuel_area = [fuel_area], total_fuel = [total_fuel], reaction_limit = [reaction_limit]") - log_subsystem_zas_debug("firelevel -> [firelevel] (gas: [gas_firelevel], liquid: [liquid_firelevel])") - log_subsystem_zas_debug("liquid_reaction_progress = [liquid_reaction_progress]") + log_subsystem_zas_debug("gas_fuel = [gas_fuel], total_oxidizers = [total_oxidizers]") + log_subsystem_zas_debug("total_fuel = [total_fuel], reaction_limit = [reaction_limit]") + log_subsystem_zas_debug("firelevel -> [firelevel] (gas: [gas_firelevel])") log_subsystem_zas_debug("gas_reaction_progress = [gas_reaction_progress]") log_subsystem_zas_debug("total_reaction_progress = [total_reaction_progress]") log_subsystem_zas_debug("used_fuel = [used_fuel], used_oxidizers = [used_oxidizers]; ") @@ -326,7 +283,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin //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)) + if((!gas_fuel || used_fuel <= FIRE_GAS_MIN_BURNRATE*length(zone.contents))) return 0 @@ -334,25 +291,24 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin //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(GAS_CO2, used_oxidizers) - if(zone) - zone.remove_liquidfuel(used_liquid_fuel, !check_combustibility()) - //calculate the energy produced by the reaction and then set the new temperature of the mix - temperature = (starting_energy + GLOB.vsc.fire_fuel_energy_release * (used_gas_fuel + used_liquid_fuel)) / heat_capacity() + temperature = (starting_energy + GLOB.vsc.fire_fuel_energy_release * (used_gas_fuel)) / heat_capacity() update_values() #ifdef ZASDBG - log_subsystem_zas_debug("used_gas_fuel = [used_gas_fuel]; used_liquid_fuel = [used_liquid_fuel]; total = [used_fuel]") + log_subsystem_zas_debug("used_gas_fuel = [used_gas_fuel]; total = [used_fuel]") log_subsystem_zas_debug("new temperature = [temperature]; new pressure = [return_pressure()]") #endif + if (temperature < MINIMUM_HEAT_THRESHOLD) + firelevel = 0 + return firelevel /datum/gas_mixture/proc/check_recombustability(list/fuel_objs) @@ -365,7 +321,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin if(!.) return 0 - if(fuel_objs && fuel_objs.len) + if(fuel_objs && length(fuel_objs)) return 1 . = 0 @@ -431,7 +387,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin return max( 0, firelevel) -/mob/living/proc/FireBurn(var/firelevel, var/last_temperature, var/pressure) +/mob/living/proc/FireBurn(firelevel, last_temperature, pressure) var/mx = 5 * firelevel/GLOB.vsc.fire_firelevel_multiplier * min(pressure / ONE_ATMOSPHERE, 1) apply_damage(2.5*mx, DAMAGE_BURN) @@ -440,7 +396,7 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin * On the chopping block depending on how we feel. * We can decide its fate in future reworks. */ -/mob/living/carbon/human/FireBurn(var/firelevel, var/last_temperature, var/pressure) +/mob/living/carbon/human/FireBurn(firelevel, last_temperature, 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. @@ -483,3 +439,5 @@ If it gains pressure too slowly, it may leak or just rupture instead of explodin #undef FIRE_LIGHT_1 #undef FIRE_LIGHT_2 #undef FIRE_LIGHT_3 + +#undef MINIMUM_HEAT_THRESHOLD diff --git a/code/ZAS/Zone.dm b/code/ZAS/Zone.dm index db6e8b91623..aceac97e699 100644 --- a/code/ZAS/Zone.dm +++ b/code/ZAS/Zone.dm @@ -45,7 +45,6 @@ Class Procs: var/invalid = 0 var/list/contents = list() var/list/fire_tiles - var/list/fuel_objs var/needs_update = 0 @@ -72,12 +71,9 @@ Class Procs: add_tile_air(turf_air) T.zone = src contents += T - if(T.fire) - var/obj/effect/decal/cleanable/liquid_fuel/fuel = locate() in T + if(T.hotspot) LAZYADD(fire_tiles, T) SSair.active_fire_zones |= src - if (fuel) - LAZYADD(fuel_objs, fuel) T.update_graphic(air.graphic) /zone/proc/remove(turf/simulated/T) @@ -89,9 +85,6 @@ Class Procs: #endif contents -= T LAZYREMOVE(fire_tiles, T) - if(T.fire) - var/obj/effect/decal/cleanable/liquid_fuel/fuel = locate() in T - LAZYREMOVE(fuel_objs, fuel) T.zone = null T.update_graphic(graphic_remove = air.graphic) if(contents.len) diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 27f16fa2ce1..59007223416 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -99,6 +99,7 @@ #define MOB_EMISSIVE_LAYER 3.012 #define MOB_SHADOW_UPPER_LAYER 3.013 #define HOLOMAP_OVERLAY_LAYER 3.02 + #define MOB_LOWER_FIRE_OVERLAY 3.03 //LYING MOB AND HUMAN #define LYING_MOB_LAYER 3.07 #define LYING_HUMAN_LAYER 3.08 @@ -106,6 +107,7 @@ //HUMAN #define BASE_HUMAN_LAYER 3.10 //MOB_LAYER 4 + #define MOB_UPPER_FIRE_OVERLAY 4.01 #define UNDERDOOR 4.03 #define CLOSED_DOOR_LAYER 4.04 #define ABOVE_DOOR_LAYER 4.05 diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 3e0354689c2..4e8a7d8684d 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -116,6 +116,7 @@ #define SFX_ELECTRICAL_SPARK "electrical_sparm" #define SFX_EQUIP_SWORD "equip_sword" #define SFX_EXPLOSION "explosion" +#define SFX_FIRE "fire" #define SFX_FOOTSTEP_ASTEROID "footstep_asteroid" #define SFX_FOOTSTEP_BLANK "footstep_blank" #define SFX_FOOTSTEP_CARPET "footstep_carpet" diff --git a/code/__HELPERS/atmospherics.dm b/code/__HELPERS/atmospherics.dm index bdbe1714272..7287db40a2d 100644 --- a/code/__HELPERS/atmospherics.dm +++ b/code/__HELPERS/atmospherics.dm @@ -41,6 +41,3 @@ /obj/machinery/atmospherics/pipe/atmosanalyze(var/mob/user) return atmosanalyzer_scan(src, src.parent.air, user) - -/obj/item/flamethrower/atmosanalyze(var/mob/user) - if(gas_tank) return atmosanalyzer_scan(src, gas_tank.air_contents, user) diff --git a/code/controllers/subsystems/air.dm b/code/controllers/subsystems/air.dm index 876bfc8f4f8..c2c3f0ddd75 100644 --- a/code/controllers/subsystems/air.dm +++ b/code/controllers/subsystems/air.dm @@ -258,7 +258,7 @@ Total Unsimulated Turfs: [world.maxx*world.maxy*world.maxz - simulated_turf_coun return while (curr_hotspot.len) - var/obj/fire/F = curr_hotspot[curr_hotspot.len] + var/obj/hotspot/F = curr_hotspot[curr_hotspot.len] curr_hotspot.len-- F.process() diff --git a/code/controllers/subsystems/turf_fire.dm b/code/controllers/subsystems/turf_fire.dm new file mode 100644 index 00000000000..9732d2f9484 --- /dev/null +++ b/code/controllers/subsystems/turf_fire.dm @@ -0,0 +1,19 @@ +SUBSYSTEM_DEF(turf_fire) + name = "Turf Fire" + runlevels = RUNLEVELS_PLAYING + wait = 2 SECONDS + flags = SS_NO_INIT + var/list/fires = list() + +/datum/controller/subsystem/turf_fire/fire() + var/seconds_per_tick = (wait * 0.1) // Equivalent to wait / (1 SECOND) but micro-optimized + for(var/obj/turf_fire/fire as anything in fires) + fire.process(seconds_per_tick) + if(MC_TICK_CHECK) + return + +/turf + /// Reference to the turf fire on the turf + var/obj/turf_fire/turf_fire + /// Reference to the hotspot on the turf + var/obj/hotspot/hotspot = null diff --git a/code/defines/gases.dm b/code/defines/gases.dm index 2bb324645b8..5e773e23f74 100644 --- a/code/defines/gases.dm +++ b/code/defines/gases.dm @@ -112,7 +112,7 @@ overlay_limit = 0.5 specific_heat = 5 // J/(mol*K) molar_mass = 0.017 // kg/mol - flags = XGM_GAS_CONTAMINANT + flags = XGM_GAS_CONTAMINANT | XGM_GAS_OXIDIZER /singleton/xgm_gas/nitrogendioxide id = GAS_NO2 @@ -120,6 +120,7 @@ desc = "A byproduct of a dozen and more critical chemical industrial processes. Pollution." specific_heat = 33 // J/(mol*K) molar_mass = 0.046 // kg/mol + flags = XGM_GAS_OXIDIZER /singleton/xgm_gas/alium id = GAS_ALIEN diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index d59d6d28bc9..86fdd0dddc6 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -564,7 +564,7 @@ operating = FALSE //I shall not add a check every x ticks if a door has closed over some fire. - var/obj/fire/fire = locate() in loc + var/obj/hotspot/fire = locate() in loc if(fire) qdel(fire) return diff --git a/code/game/objects/effects/decals/Cleanable/fuel.dm b/code/game/objects/effects/decals/Cleanable/fuel.dm index 488d80a88d7..a5f610c6a24 100644 --- a/code/game/objects/effects/decals/Cleanable/fuel.dm +++ b/code/game/objects/effects/decals/Cleanable/fuel.dm @@ -47,29 +47,6 @@ new/obj/effect/decal/cleanable/liquid_fuel(target, amount*0.25,1) amount *= 0.75 -/obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel - anchored = 0 - -/obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel/Initialize(mapload, amt = 1, d = 0) - set_dir(d) //Setting this direction means you won't get torched by your own flamethrower. - . = ..() - -/obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel/Spread() - //The spread for flamethrower fuel is much more precise, to create a wide fire pattern. - if(amount < 1) return - var/turf/simulated/S = loc - if(!istype(S)) return - - for(var/d in list(turn(dir, 45), turn(dir, -45), dir)) - var/turf/simulated/O = get_step(S,d) - if(locate(/obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel) in O) - continue - if(O.CanPass(null, S, 0, 0) && S.CanPass(null, O, 0, 0)) - new/obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel(O,amount * 0.25,d) - O.hotspot_expose((T20C*2) + 400, 500) //Light flamethrower fuel on fire immediately. - - amount *= 0.25 - /obj/effect/decal/cleanable/liquid_fuel/napalm name = "napalm gel" diff --git a/code/game/objects/effects/fire/fire.dm b/code/game/objects/effects/fire/fire.dm new file mode 100644 index 00000000000..2a7839c513f --- /dev/null +++ b/code/game/objects/effects/fire/fire.dm @@ -0,0 +1,236 @@ +#define TURF_FIRE_REQUIRED_TEMP (T0C+10) +#define TURF_FIRE_TEMP_BASE (T0C+100) +#define TURF_FIRE_POWER_LOSS_ON_LOW_TEMP 3.5 +#define TURF_FIRE_TEMP_INCREMENT_PER_POWER 3 +#define TURF_FIRE_POWER_LOSS_BASE 0.5 +#define TURF_FIRE_VOLUME 150 +#define TURF_FIRE_MAX_POWER 50 + +#define TURF_FIRE_ENERGY_PER_BURNED_OXY_MOL 12000 +#define TURF_FIRE_BURN_RATE_BASE 0.06 +#define TURF_FIRE_BURN_RATE_PER_POWER 0.02 +#define TURF_FIRE_BURN_MINIMUM_OXYGEN_REQUIRED 0.5 +#define TURF_FIRE_BURN_PLAY_SOUND_EFFECT_CHANCE 6 + +#define TURF_FIRE_STATE_SMALL 1 +#define TURF_FIRE_STATE_MEDIUM 2 +#define TURF_FIRE_STATE_LARGE 3 +#define TURF_FIRE_STATE_MAX 4 + +/obj/turf_fire + icon = 'icons/effects/turf_fire.dmi' + layer = BELOW_DOOR_LAYER + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + /// How much power have we got. This is treated like fuel, be it flamethrower liquid or any random thing you could come up with + var/fire_power = 20 + /// If false, it does not interact with atmos. + var/interact_with_atmos = TRUE + + /// If false, it will not lose power by itself. Mainly for adminbus events or mapping. + var/passive_loss = TRUE + + /// Visual state of the fire. Kept track to not do too many updates. + var/current_fire_state + + light_color = COLOR_ORANGE + +///All the subtypes are for adminbussery and or mapping +/obj/turf_fire/magical + interact_with_atmos = FALSE + passive_loss = FALSE + color = COLOR_LIGHT_CYAN + +/obj/turf_fire/small + fire_power = 10 + +/obj/turf_fire/small/magical + interact_with_atmos = FALSE + passive_loss = FALSE + color = COLOR_SPRING_GREEN + +/obj/turf_fire/inferno + fire_power = 30 + +/obj/turf_fire/inferno/magical + interact_with_atmos = FALSE + passive_loss = FALSE + color = COLOR_VIOLET + +/obj/turf_fire/Initialize(mapload, power, fire_color) + . = ..() + var/turf/open_turf = loc + if(open_turf.turf_fire) + return INITIALIZE_HINT_QDEL + + if(fire_color && ((color != fire_color) && isnull(color))) //Take colour from proc unless base colour was already custom + color = fire_color + if(color != null) + set_color(color) + + particles = new /particles/smoke/turf_fire + + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + + AddElement(/datum/element/connect_loc, loc_connections) + + open_turf.turf_fire = src + SSturf_fire.fires += src + if(power) + fire_power = min(TURF_FIRE_MAX_POWER, power) + UpdateFireState() + +/obj/turf_fire/Destroy() + var/turf/T = loc + T.turf_fire = null + SSturf_fire.fires -= src + return ..() + +//Approximates colour. It will not change value or saturation, just hue. +/obj/turf_fire/proc/set_color(_color) + light_color = _color + var/base = rgb2num(COLOR_ORANGE, COLORSPACE_HSV) //Base sprite is red fire, but maybe other fires want to be different + var/target = rgb2num(_color, COLORSPACE_HSV) + var/baseAngle = base[1] + var/targetAngle= target[1] + + var/angle = (targetAngle - baseAngle) + filters = filter(type = "color", color = list(1,0,0,0, + 0,1,0,0, + 0,0,1,0, + 0,0,0,1, + angle/360,0,0,0), space = FILTER_COLOR_HSV) + color = null //The actual colour should remain white so transform makes sense + +/obj/turf_fire/proc/process_waste(seconds_per_tick) + var/total_oxidizer = 0 + var/turf/T = loc + var/datum/gas_mixture/env = T.return_air() + for (var/g in env.gas) + if(gas_data.flags[g] & XGM_GAS_OXIDIZER) + total_oxidizer += env.gas[g] + if (total_oxidizer < TURF_FIRE_BURN_MINIMUM_OXYGEN_REQUIRED) + return FALSE + var/burn_rate = (TURF_FIRE_BURN_RATE_BASE * seconds_per_tick) + fire_power * TURF_FIRE_BURN_RATE_PER_POWER + if(burn_rate > total_oxidizer) + burn_rate = total_oxidizer + + env.remove_by_flag(XGM_GAS_OXIDIZER, burn_rate) + + env.adjust_gas(GAS_CO2, burn_rate) + + var/energy_released = burn_rate * TURF_FIRE_ENERGY_PER_BURNED_OXY_MOL + env.add_thermal_energy(energy_released) + SSair.mark_for_update(T) + return TRUE + +/obj/turf_fire/process(seconds_per_tick) + var/turf/simulated/T = get_turf(src) + if(!T || T.density) + qdel(src) + return + if(prob(TURF_FIRE_BURN_PLAY_SOUND_EFFECT_CHANCE)) + playsound(T, SFX_FIRE, 40, TRUE) + if(T.hotspot) //If we have an active hotspot, let it do the damage instead and lets not loose power + return + if(interact_with_atmos && !process_waste(seconds_per_tick)) + qdel(src) + return + if(passive_loss) + if(T.air?.temperature < TURF_FIRE_REQUIRED_TEMP) + fire_power -= TURF_FIRE_POWER_LOSS_ON_LOW_TEMP * seconds_per_tick + fire_power -= TURF_FIRE_POWER_LOSS_BASE * seconds_per_tick + if(fire_power <= 0) + qdel(src) + return + var/effective_temperature = TURF_FIRE_TEMP_BASE + (TURF_FIRE_TEMP_INCREMENT_PER_POWER * fire_power) + T.hotspot_expose(effective_temperature) + //Nearby turfs may also trigger a fire (will only start fires if there's fuel, currently) + //Guaranteed fire spread in the last tick + if(prob(50 + fire_power) || fire_power == 1) + for(var/direction in GLOB.cardinals) + var/turf/simulated/other_tile = get_step(T, direction) + + // Exit the loop early if the checked tile is not a valid direction for the fire to spread. + if(!istype(other_tile) || (T.open_directions & direction) || other_tile.hotspot || other_tile.turf_fire) //Grab all valid bordering tiles + continue + + other_tile.hotspot_expose(effective_temperature) + + for(var/atom/movable/burning_atom as anything in T) + burning_atom.fire_act(exposed_temperature = effective_temperature, exposed_volume = TURF_FIRE_VOLUME) + + UpdateFireState() + if(!interact_with_atmos || prob(fire_power) || !istype(T, /turf/simulated/floor)) + return + + var/turf/simulated/floor/F = T + F.burn_tile(effective_temperature) + +/obj/turf_fire/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs) + SIGNAL_HANDLER + + if(!istype(arrived)) + return + + var/turf/T = loc + if(T.hotspot) //If we have an active hotspot, let it do the damage instead + return + + arrived.fire_act(exposed_temperature = TURF_FIRE_TEMP_BASE + (TURF_FIRE_TEMP_INCREMENT_PER_POWER*fire_power), exposed_volume = TURF_FIRE_VOLUME) + + return + +/obj/turf_fire/proc/AddPower(power) + fire_power = min(TURF_FIRE_MAX_POWER, fire_power + power) + UpdateFireState() + +/obj/turf_fire/proc/UpdateFireState() + var/new_state + switch(fire_power) + if(0 to 10) + new_state = TURF_FIRE_STATE_SMALL + if(11 to 24) + new_state = TURF_FIRE_STATE_MEDIUM + if(25 to 39) + new_state = TURF_FIRE_STATE_LARGE + if(40 to INFINITY) + new_state = TURF_FIRE_STATE_MAX + + if(new_state == current_fire_state) + return + current_fire_state = new_state + + switch(current_fire_state) + if(TURF_FIRE_STATE_SMALL) + icon_state = "small" + set_light(1.5, 0.5) + if(TURF_FIRE_STATE_MEDIUM) + icon_state = "medium" + set_light(2, 0.5) + if(TURF_FIRE_STATE_LARGE) + icon_state = "big" + set_light(2, 0.6) + if(TURF_FIRE_STATE_MAX) + icon_state = "max" + set_light(3, 0.7) + +#undef TURF_FIRE_REQUIRED_TEMP +#undef TURF_FIRE_TEMP_BASE +#undef TURF_FIRE_POWER_LOSS_ON_LOW_TEMP +#undef TURF_FIRE_TEMP_INCREMENT_PER_POWER +#undef TURF_FIRE_VOLUME +#undef TURF_FIRE_MAX_POWER + +#undef TURF_FIRE_ENERGY_PER_BURNED_OXY_MOL +#undef TURF_FIRE_BURN_RATE_BASE +#undef TURF_FIRE_BURN_RATE_PER_POWER +#undef TURF_FIRE_BURN_MINIMUM_OXYGEN_REQUIRED +#undef TURF_FIRE_BURN_PLAY_SOUND_EFFECT_CHANCE + +#undef TURF_FIRE_STATE_SMALL +#undef TURF_FIRE_STATE_MEDIUM +#undef TURF_FIRE_STATE_LARGE +#undef TURF_FIRE_STATE_MAX diff --git a/code/game/objects/items/weapons/flamethrower.dm b/code/game/objects/items/weapons/flamethrower.dm index ade97cfe6d6..f6e310c24a3 100644 --- a/code/game/objects/items/weapons/flamethrower.dm +++ b/code/game/objects/items/weapons/flamethrower.dm @@ -24,40 +24,60 @@ var/operating = FALSE //cooldown var/obj/item/weldingtool/welding_tool = null var/obj/item/device/assembly/igniter/igniter = null - var/obj/item/tank/gas_tank = null + var/obj/item/reagent_containers/fuel_container = null + /// How much range the flamethrower has. Upgraded welding tools increase this. + var/range = 4 + var/max_container = WEIGHT_CLASS_SMALL /obj/item/flamethrower/feedback_hints(mob/user, distance, is_adjacent) . += ..() if(is_adjacent) - if(gas_tank) - . += SPAN_NOTICE("Release pressure is set to [throw_amount] kPa. The tank has about [round(gas_tank.air_contents.return_pressure(), 10)] kPa left in it.") - else - . += SPAN_WARNING("It has no gas tank installed.") + if(fuel_container) + if(fuel_container.reagents) + . += SPAN_NOTICE("The loaded [fuel_container.name] has about [fuel_container.reagents.total_volume] unit\s left.") + else + . += SPAN_NOTICE("The loaded [fuel_container.name] is empty.") if(igniter) . += SPAN_NOTICE("It has \an [igniter] installed.") else . += SPAN_WARNING("It has no igniter installed.") + if(lit) + . += SPAN_WARNING("\The [src] is currently lit!") + +/obj/item/flamethrower/upgrade_hints(mob/user, distance, is_adjacent) + . = ..() + . += SPAN_INFO("Upgrading the welding tool increases the flamethrower's range.") + /obj/item/flamethrower/Initialize(mapload, var/welder) . = ..() icon_state = "flamethrower" // update to use the non-map version if(welder) welding_tool = welder welding_tool.forceMove(src) + processUpgrade() update_icon() /obj/item/flamethrower/Destroy() QDEL_NULL(welding_tool) QDEL_NULL(igniter) - QDEL_NULL(gas_tank) + QDEL_NULL(fuel_container) return ..() /obj/item/flamethrower/process() if(!lit) + STOP_PROCESSING(SSprocessing, src) + update_icon() + return null + else if (!fuel_container || !fuel_container.reagents || fuel_container.reagents.total_volume <= 0) + lit = FALSE + balloon_alert_to_viewers("*fffft*") + playsound(loc, 'sound/items/welder_deactivate.ogg', 50, TRUE) + update_icon() STOP_PROCESSING(SSprocessing, src) return null var/turf/location = loc - if(istype(location, /mob/)) + if(ismob(location)) var/mob/M = location if(M.l_hand == src || M.r_hand == src) location = M.loc @@ -71,10 +91,8 @@ if(igniter) AddOverlays("+igniter[secured]") - if(istype(gas_tank, /obj/item/tank/phoron)) + if(fuel_container) AddOverlays("+phoron_tank") - else if(istype(gas_tank, /obj/item/tank/hydrogen)) - AddOverlays("+hydro_tank") if(lit) AddOverlays("+lit") @@ -87,20 +105,23 @@ /obj/item/flamethrower/isFlameSource() return lit +/obj/item/flamethrower/proc/processUpgrade() + if(!welding_tool) + return + if(istype(welding_tool, /obj/item/weldingtool/emergency)) + range = 3 + else if(istype(welding_tool, /obj/item/weldingtool/largetank) || istype(welding_tool, /obj/item/weldingtool/experimental)) + range = 5 + else if(istype(welding_tool, /obj/item/weldingtool/hugetank)) + range = 6 + else + range = 4 + /obj/item/flamethrower/afterattack(atom/target, mob/user, proximity) - if(proximity) - return - if(!gas_tank) - return - if(gas_tank.air_contents.get_by_flag(XGM_GAS_FUEL) < 1) - to_chat(user, SPAN_WARNING("\The [src] doesn't have enough fuel left to throw!")) - return - // Make sure our user is still holding us - if(user && user.get_active_hand() == src) - var/turf/target_turf = get_turf(target) - if(target_turf) - var/turflist = get_turfs_in_cone(user, get_angle(user, target_turf), get_dist(user, target_turf), 30) - flame_turf(turflist) + var/turf/target_turf = get_turf(target) + if(target_turf) + var/turflist = get_line(user, target_turf) + flame_turf(turflist) /obj/item/flamethrower/attackby(obj/item/attacking_item, mob/user) if(use_check_and_message(user)) @@ -114,9 +135,9 @@ if(igniter) igniter.forceMove(T) igniter = null - if(gas_tank) - gas_tank.forceMove(T) - gas_tank = null + if(fuel_container) + fuel_container.forceMove(T) + fuel_container = null new /obj/item/stack/rods(T) qdel(src) return TRUE @@ -140,19 +161,16 @@ update_icon() return TRUE - else if(istype(attacking_item, /obj/item/tank/phoron) || istype(attacking_item, /obj/item/tank/hydrogen)) - if(gas_tank) - to_chat(user, SPAN_WARNING("There appears to already be a tank loaded in \the [src]!")) - return - user.drop_from_inventory(attacking_item, src) - gas_tank = attacking_item - update_icon() - return TRUE - - else if(istype(attacking_item, /obj/item/device/analyzer)) - var/obj/item/device/analyzer/A = attacking_item - A.analyze_gases(src, user) - return TRUE + else if(istype(attacking_item, /obj/item/reagent_containers) && attacking_item.is_open_container() && (attacking_item.w_class <= max_container)) + if(user.unEquip(attacking_item, target = src)) + if(fuel_container) + user.put_in_hands(fuel_container) + to_chat(user, SPAN_NOTICE("You swap out the [fuel_container] from \the [src]!")) + else + to_chat(user, SPAN_NOTICE("You load the [attacking_item] into \the [src]!")) + fuel_container = attacking_item + update_icon() + return TRUE else if(attacking_item.isFlameSource()) // you can light it with external input, even without an igniter attempt_lighting(user, TRUE) @@ -164,31 +182,22 @@ /obj/item/flamethrower/attack_self(mob/user) if(use_check_and_message(user)) return - if(!gas_tank) - to_chat(user, SPAN_WARNING("Attach a phoron tank first!")) - return var/list/options = list( - "Eject Tank" = image('icons/obj/tank.dmi', "phoron"), - "Light" = image('icons/effects/effects.dmi', "exhaust"), - "Lower Pressure" = image('icons/mob/screen/radial.dmi', "radial_sub"), - "Raise Pressure" = image('icons/mob/screen/radial.dmi', "radial_add") + "Eject Tank" = image('icons/obj/item/reagent_containers/glass.dmi', "beaker"), + "Light" = image('icons/effects/effects.dmi', "exhaust") ) var/handle = show_radial_menu(user, user, options, radius = 42, tooltips=TRUE) if(!handle) return switch(handle) if("Eject Tank") - if(!gas_tank) + if(!fuel_container) return - user.put_in_hands(gas_tank) - gas_tank = null + user.put_in_hands(fuel_container) + fuel_container = null lit = FALSE if("Light") attempt_lighting(user) - if("Lower Pressure") - change_pressure(-50, user) - if("Raise Pressure") - change_pressure(50, user) else return @@ -198,7 +207,11 @@ if(!external) // if it's external input, we can't unlight it, but we don't need to check for an igniter either if(lit) // you can extinguish the flamethrower without an igniter lit = FALSE - to_chat(user, SPAN_NOTICE("You extinguish \the [src].")) + balloon_alert_to_viewers("*fffft*") + STOP_PROCESSING(SSprocessing, src) + if(!external) + playsound(loc, 'sound/items/welder_deactivate.ogg', 50, TRUE) + update_icon() return if(!secured) // can't light via the flamethrower unless we have an igniter secured if(igniter) @@ -207,60 +220,92 @@ to_chat(user, SPAN_WARNING("\The [src] doesn't have a secured igniter installed.")) return if(lit) - to_chat(user, SPAN_WARNING("\The [src] is already lit.")) + balloon_alert(user, "already lit!") return - if(!gas_tank) - to_chat(user, SPAN_WARNING("\The [src] doesn't have a tank installed.")) + if(!fuel_container) + balloon_alert(user, "no fuel container loaded!") return - if(gas_tank.air_contents.get_by_flag(XGM_GAS_FUEL) < 1) - to_chat(user, SPAN_WARNING("\The [src] doesn't have any flammable fuel to light!")) + if(!fuel_container.reagents || fuel_container.reagents.total_volume <= 0) + balloon_alert(user, "fuel container is empty!") return lit = TRUE - to_chat(user, SPAN_NOTICE("You light \the [src].")) - if(lit) - START_PROCESSING(SSprocessing, src) + balloon_alert_to_viewers("*whoosh*") + update_icon() + START_PROCESSING(SSprocessing, src) + if(!external) + playsound(loc, 'sound/items/welder_activate.ogg', 50, TRUE) -/obj/item/flamethrower/proc/change_pressure(var/pressure, var/mob/user) - if(!pressure) - return - throw_amount += pressure - throw_amount = clamp(50, throw_amount, 5000) - if(ismob(user)) - to_chat(user, SPAN_NOTICE("Pressure has been adjusted to [throw_amount] kPa.")) +#define REQUIRED_POWER_TO_FIRE_FLAMETHROWER 10 +#define FLAMETHROWER_POWER_MULTIPLIER 1.5 +#define FLAMETHROWER_RELEASE_AMOUNT 5 //Called from turf.dm turf/dblclick -/obj/item/flamethrower/proc/flame_turf(turflist) +/obj/item/flamethrower/proc/flame_turf(list/turflist) + if(!fuel_container) + return if(!lit || operating) return - operating = TRUE - var/turf/operator_turf = get_turf(src) - for(var/turf/T in (turflist - operator_turf)) + turflist -= operator_turf //Don't flame the turf you're standing on + var/size = length(turflist) + if(!size) + return + LIST_RESIZE(turflist, min(size, range)) + + var/power = 0 + var/datum/reagents/fuel_reagents = fuel_container.reagents + var/datum/reagents/my_fraction = new(fuel_reagents.maximum_volume, src) + fuel_reagents.trans_to_holder(my_fraction, FLAMETHROWER_RELEASE_AMOUNT * length(turflist)) + var/fire_color = null + var/highest_amount = 0 + for(var/reagent in my_fraction.reagent_volumes) + var/singleton/reagent/fuel = GET_SINGLETON(reagent) + power += fuel.accelerant_quality * FLAMETHROWER_POWER_MULTIPLIER * (my_fraction.reagent_volumes[reagent] / length(turflist)) //Flamethrowers inflate flammability compared to a pool of fuel + if(my_fraction.reagent_volumes[reagent] > highest_amount && fuel.accelerant_quality > 0) + highest_amount = my_fraction.reagent_volumes[reagent] + fire_color = fuel.fire_color + + if(power < REQUIRED_POWER_TO_FIRE_FLAMETHROWER) + audible_message(SPAN_DANGER("\The [src] sputters.")) + balloon_alert_to_viewers("*pshhhh*") + playsound(src, 'sound/weapons/flamethrower/flamethrower_empty.ogg', 50, TRUE, -3) + my_fraction.remove_any(FLAMETHROWER_RELEASE_AMOUNT) //Wastes some fuel + return_fuel(fuel_reagents, my_fraction) + return + + playsound(src, pick('sound/weapons/flamethrower/flamethrower1.ogg','sound/weapons/flamethrower/flamethrower2.ogg','sound/weapons/flamethrower/flamethrower3.ogg' ), 50, TRUE, -3) + + operating = TRUE + + for(var/turf/T in (turflist)) if(T.density || istype(T, /turf/space)) continue if(LinkBlocked(operator_turf, T)) continue - ignite_turf(T) - sleep(1) + //Consume part of our fuel to create a fire spot + T.IgniteTurf(power, fire_color) + T.hotspot_expose(power * 3 + 380) + my_fraction.remove_any(FLAMETHROWER_RELEASE_AMOUNT) + sleep(0.1 SECONDS) + + return_fuel(fuel_reagents, my_fraction) operating = FALSE +#undef REQUIRED_POWER_TO_FIRE_FLAMETHROWER +#undef FLAMETHROWER_POWER_MULTIPLIER +#undef FLAMETHROWER_RELEASE_AMOUNT -/obj/item/flamethrower/proc/ignite_turf(turf/target) - var/datum/gas_mixture/air_transfer = gas_tank.air_contents.remove_ratio(0.02 * (throw_amount / 100)) - new /obj/effect/decal/cleanable/liquid_fuel/flamethrower_fuel(target, air_transfer.get_by_flag(XGM_GAS_FUEL) * 15, get_dir(loc, target)) - for(var/g in air_transfer.gas) - if(gas_data.flags[g] & XGM_GAS_FUEL) - air_transfer.gas[g] = 0 - target.assume_air(air_transfer) - target.hotspot_expose((gas_tank.air_contents.temperature*2) + 400, 500) - for(var/mob/living/M in target) - M.IgniteMob(1) +/obj/item/flamethrower/proc/return_fuel(datum/reagents/_return, datum/reagents/_fraction) + if(fuel_container) //In the event we earlied out that means some fuel goes back into tank + if(_fraction.total_volume > 0) + _fraction.trans_to_holder(_return, _fraction.total_volume) + QDEL_NULL(_fraction) /obj/item/flamethrower/full/Initialize() // slightly weird looking initialize cuz it has to do some stuff first welding_tool = new /obj/item/weldingtool(src) @@ -268,5 +313,7 @@ igniter = new /obj/item/device/assembly/igniter(src) igniter.secured = FALSE secured = TRUE - gas_tank = new /obj/item/tank/phoron(src) + fuel_container = new /obj/item/reagent_containers/glass/beaker/large(src) + fuel_container.reagents.add_reagent(/singleton/reagent/fuel, 120) + processUpgrade() return ..() diff --git a/code/game/objects/items/weapons/tanks/tank_types.dm b/code/game/objects/items/weapons/tanks/tank_types.dm index 14f9c846f84..e892470ffe5 100644 --- a/code/game/objects/items/weapons/tanks/tank_types.dm +++ b/code/game/objects/items/weapons/tanks/tank_types.dm @@ -99,17 +99,6 @@ /obj/item/tank/phoron/shuttle/adjust_initial_gas() air_contents.adjust_gas(GAS_PHORON, 4*(3*ONE_ATMOSPHERE)*70/(R_IDEAL_GAS_EQUATION*T20C)) -/obj/item/tank/phoron/attackby(obj/item/attacking_item, mob/user) - ..() - - if (istype(attacking_item, /obj/item/flamethrower)) - var/obj/item/flamethrower/F = attacking_item - if ((!F.secured)||(F.gas_tank)) return - src.master = F - F.gas_tank = src - user.remove_from_mob(src) - src.forceMove(F) - return /* *Hydrogen */ diff --git a/code/game/sound/sound_keys/sound_keys.dm b/code/game/sound/sound_keys/sound_keys.dm index b27892ca5b4..a980eb9ee92 100644 --- a/code/game/sound/sound_keys/sound_keys.dm +++ b/code/game/sound/sound_keys/sound_keys.dm @@ -205,6 +205,14 @@ 'sound/weapons/genhit3.ogg' ) +/datum/sound_effect/fire_ambience + key = SFX_FIRE + file_paths = list( + 'sound/ambience/fire/Fire_Ambient_01.ogg', + 'sound/ambience/fire/Fire_Ambient_02.ogg', + 'sound/ambience/fire/Fire_Ambient_03.ogg' + ) + /datum/sound_effect/hiss_sound key = SFX_HISS file_paths = list( diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm index b9e39583da5..ff981c97dbe 100644 --- a/code/game/turfs/simulated/floor.dm +++ b/code/game/turfs/simulated/floor.dm @@ -124,3 +124,9 @@ name = "hull plating" icon = 'icons/turf/flooring/tiles.dmi' icon_state = "reinforced_light" + +/turf/simulated/IgniteTurf(power, fire_color) + if(turf_fire) + turf_fire.AddPower(power) + return + new /obj/turf_fire(src, power, fire_color) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 72953f7bb1a..d0be60f5f19 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -806,3 +806,6 @@ if(istype(O,/obj/effect/rune) || istype(O,/obj/effect/decal/cleanable)) qdel(O) clean_blood() + +/turf/proc/IgniteTurf(power, fire_color) + return diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index 9badc58d1db..00733701fac 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -46,7 +46,8 @@ if(ispath(path, /turf/space) && GET_TURF_BELOW(src) && !ignore_override) path = openspace_override_type || /turf/simulated/open/airless - var/obj/fire/old_fire = fire + var/old_hotspot = hotspot + var/old_turf_fire = turf_fire var/old_baseturf = baseturf var/old_above = above var/list/old_blueprints = blueprints @@ -69,6 +70,11 @@ changing_turf = TRUE + if(isspaceturf(path) || isopenspace(path)) + QDEL_NULL(turf_fire) + else + old_turf_fire = turf_fire + if(connections) connections.erase_all() @@ -81,6 +87,11 @@ var/turf/new_turf = new path(src) + if(!density) + turf_fire = old_turf_fire + else if (old_turf_fire) + QDEL_NULL(old_turf_fire) + // If the area requires starlight, we need to fill it back in with starlight after the change. // Particularly necessary so shuttles don't leave dark patches after undocking with starlit turfs. update_starlight() @@ -129,12 +140,12 @@ new_turf.above = old_above if(ispath(path, /turf/simulated)) - if(old_fire) - fire = old_fire + if(old_hotspot) + hotspot = old_hotspot if (istype(new_turf, /turf/simulated/floor)) new_turf.RemoveLattice() - else if(old_fire) - old_fire.RemoveFire() + else if(hotspot) + qdel(hotspot) if(tell_universe) GLOB.universe.OnTurfChange(new_turf) diff --git a/code/modules/admin/verbs/clear_toxins.dm b/code/modules/admin/verbs/clear_toxins.dm index de50b51eb64..712ab877fe3 100644 --- a/code/modules/admin/verbs/clear_toxins.dm +++ b/code/modules/admin/verbs/clear_toxins.dm @@ -1,7 +1,7 @@ /client/proc/clear_toxins() set category = "Special Verbs" set name = "Clear Toxin/Fire in Zone" - set desc = "Remove fires from your current zone and reset the atmosphere to human-perfect gas mix, pressure, and temp." + set desc = "Remove hotspots & fires from your current zone and reset the atmosphere to human-perfect gas mix, pressure, and temp." if (!check_rights(R_ADMIN) && !isstoryteller(usr)) return @@ -23,7 +23,9 @@ if (location.zone) for (var/turf/T in location.zone.contents) - for (var/obj/fire/F in T.contents) + for (var/obj/hotspot/H in T.contents) + qdel(H) + for (var/obj/turf_fire/F in T.contents) qdel(F) log_and_message_admins("cleared air and fire in area [get_area(usr)].") diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 8958ca711e9..6e207974fe5 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -10,7 +10,7 @@ var/inactive_groups = SSair.zones.len - active_groups var/hotspots = 0 - for(var/obj/fire/hotspot in world) + for(var/obj/hotspot/hotspot in world) hotspots++ var/active_on_main_station = 0 diff --git a/code/modules/effects/particles/smoke.dm b/code/modules/effects/particles/smoke.dm new file mode 100644 index 00000000000..4ce9bbb3b3b --- /dev/null +++ b/code/modules/effects/particles/smoke.dm @@ -0,0 +1,20 @@ +// All the smoke variant particles. +/particles/smoke + icon = 'icons/effects/particles/smoke.dmi' + icon_state = list("smoke_1" = 1, "smoke_2" = 1, "smoke_3" = 2) + width = 100 + height = 100 + count = 1000 + spawning = 4 + lifespan = 1.5 SECONDS + fade = 1 SECONDS + velocity = list(0, 0.4, 0) + position = list(6, 0, 0) + drift = generator("sphere", 0, 2, NORMAL_RAND) + friction = 0.2 + gravity = list(0, 0.95) + grow = 0.05 + +/particles/smoke/turf_fire + spawning = 1 // don't turn this up or forest fires cause way too much lag + position = generator("sphere", 16, 24, NORMAL_RAND) diff --git a/code/modules/mob/living/simple_animal/hostile/adhomai.dm b/code/modules/mob/living/simple_animal/hostile/adhomai.dm index dafedd145cf..8d26e16e110 100644 --- a/code/modules/mob/living/simple_animal/hostile/adhomai.dm +++ b/code/modules/mob/living/simple_animal/hostile/adhomai.dm @@ -168,6 +168,12 @@ /mob/living/simple_animal/hostile/plasmageist/ex_act(severity) return +/mob/living/simple_animal/hostile/plasmageise/IgniteMob(fire_stacks_to_add) + return // Plasmageists can't catch fire + +/mob/living/simple_animal/hostile/plasmageist/ExtinguishMob(fire_stacks_to_remove) + return // Plasmageists can't catch fire + /obj/projectile/beam/tesla/plasmageist/on_hit(atom/target, blocked, def_zone) . = ..() if(isliving(target)) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index efda6e67ef3..61227d41fbe 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1032,6 +1032,41 @@ B.transform = M.Scale(scale) B.update_icon() + +/mob/living/simple_animal/handle_fire(seconds_per_tick) + if(..()) + return + + var/burn_temperature = fire_burn_temperature() + var/thermal_protection = get_heat_protection() + + if (thermal_protection < 1 && bodytemperature < burn_temperature) + bodytemperature += round(BODYTEMP_HEATING_MAX*(1-thermal_protection) * 20 * seconds_per_tick, 1) + + burn_temperature -= maxbodytemp + + if(burn_temperature < 1) + return + + adjustBruteLoss(log(10, (burn_temperature + 10))) + +/mob/living/simple_animal/update_fire() + . = ..() + var/image/upper_fire_overlay = image("icon" = 'icons/mob/burning/burning_generic.dmi', "icon_state" = "upper", layer = MOB_UPPER_FIRE_OVERLAY) + var/image/upper_fire_emissive = emissive_appearance("icon" = 'icons/mob/burning/burning_generic.dmi', "icon_state" = "upper", layer = MOB_UPPER_FIRE_OVERLAY) + var/image/lower_fire_overlay = image("icon" = 'icons/mob/burning/burning_generic.dmi', "icon_state" = "lower", layer = MOB_LOWER_FIRE_OVERLAY) + var/image/lower_fire_emissive = emissive_appearance("icon" = 'icons/mob/burning/burning_generic.dmi', "icon_state" = "lower", layer = MOB_LOWER_FIRE_OVERLAY) + CutOverlays(upper_fire_overlay) + CutOverlays(upper_fire_emissive) + CutOverlays(lower_fire_overlay) + CutOverlays(lower_fire_emissive) + if(on_fire) + AddOverlays(upper_fire_overlay) + AddOverlays(upper_fire_emissive) + AddOverlays(lower_fire_overlay) + AddOverlays(lower_fire_emissive) + + /mob/living/simple_animal/get_resist_power() return resist_mod diff --git a/code/modules/reagents/Chemistry-Reagents.dm b/code/modules/reagents/Chemistry-Reagents.dm index 724aed8f1d6..ba40bc225e9 100644 --- a/code/modules/reagents/Chemistry-Reagents.dm +++ b/code/modules/reagents/Chemistry-Reagents.dm @@ -57,6 +57,11 @@ /// Adds to the value of whatever container's holding it, value * units of reagents var/value = 1 + ///How effective this chemical is in a flamethrower. + var/accelerant_quality = 0 + ///Should this reagent burn a different color? + var/fire_color = null + /// Called when the reagent is created. /singleton/reagent/proc/initialize_data(var/newdata, var/datum/reagents/holder) if(!isnull(newdata)) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm index fe29cfb28c7..b97e994c742 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Core.dm @@ -139,7 +139,7 @@ var/datum/gas_mixture/environment = T.return_air() var/min_temperature = T0C + 100 // 100C, the boiling point of water - var/hotspot = (locate(/obj/fire) in T) + var/hotspot = (locate(/obj/hotspot) in T) if(hotspot && !istype(T, /turf/space)) var/datum/gas_mixture/lowertemp = T.remove_air(T.air.total_moles) lowertemp.temperature = max(lowertemp.temperature-2000, lowertemp.temperature / 2, T0C) @@ -164,6 +164,11 @@ if(istype(O, /obj/structure/bonfire)) var/obj/structure/bonfire/B = O B.fuel = max(0, B.fuel - (5 * amount)) + if(istype(O, /obj/turf_fire)) + var/obj/turf_fire/F = O + F.AddPower(-amount) + if(F.fire_power <= 0) + qdel(F) /singleton/reagent/water/touch_mob(var/mob/M, var/amount, var/datum/reagents/holder) . = ..() @@ -212,6 +217,8 @@ value = 6.8 + accelerant_quality = 10 + /singleton/reagent/fuel/touch_turf(var/turf/T, var/amount, var/datum/reagents/holder) new /obj/effect/decal/cleanable/liquid_fuel(T, amount) remove_self(amount, holder) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm index 36815a596f4..c4e99d86a8a 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Dispenser.dm @@ -8,6 +8,7 @@ fallback_specific_heat = 0.567 value = 0.27 + accelerant_quality = 3 /singleton/reagent/acetone/affect_blood(var/mob/living/carbon/M, var/alien, var/removed, var/datum/reagents/holder) M.adjustToxLoss(removed * 3) @@ -160,6 +161,9 @@ ABSTRACT_TYPE(/singleton/reagent/alcohol) glass_name = "glass of coder fuckups" glass_desc = "A glass of distilled maintainer tears." + accelerant_quality = 5 + fire_color = COLOR_CYAN_BLUE + var/hydration_factor = 1 //How much hydration to add per unit. var/nutriment_factor = 0.5 //How much nutrition to add per unit. var/strength = 100 // This is the Alcohol By Volume of the drink, value is in the range 0-100 unless you wanted to create some bizarre bluespace alcohol with <100 diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm index f4b8e6eb641..393fde24cc2 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Other.dm @@ -658,6 +658,7 @@ touch_met = 50 taste_description = "fiery death" taste_mult = 20 + accelerant_quality = 20 /singleton/reagent/fuel/napalm/touch_turf(var/turf/T, var/amount, var/datum/reagents/holder) new /obj/effect/decal/cleanable/liquid_fuel/napalm(T, amount/3) diff --git a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm index 9eda4bb9d53..664c5c6c319 100644 --- a/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm +++ b/code/modules/reagents/Chemistry-Reagents/Chemistry-Reagents-Toxins.dm @@ -409,7 +409,7 @@ if(!istype(T)) return - var/hotspot = (locate(/obj/fire) in T) + var/hotspot = (locate(/obj/hotspot) in T) if(hotspot && !istype(T, /turf/space)) var/datum/gas_mixture/lowertemp = T.return_air() lowertemp.temperature = max(lowertemp.temperature-2000, lowertemp.temperature / 2, T0C) @@ -443,6 +443,11 @@ if(istype(O, /obj/structure/bonfire)) var/obj/structure/bonfire/B = O B.fuel = max(0, B.fuel - (150 * amount)) + if(istype(O, /obj/turf_fire)) + var/obj/turf_fire/F = O + F.AddPower(-30 * amount) //Thirty times as effective as water + if (F.fire_power <= 0) + qdel(F) /singleton/reagent/toxin/plantbgone name = "Plant-B-Gone" diff --git a/html/changelogs/GeneralCamo - Turf Flames.yml b/html/changelogs/GeneralCamo - Turf Flames.yml new file mode 100644 index 00000000000..bca6a7a71cf --- /dev/null +++ b/html/changelogs/GeneralCamo - Turf Flames.yml @@ -0,0 +1,62 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# - (fixes bugs) +# wip +# - (work in progress) +# qol +# - (quality of life) +# soundadd +# - (adds a sound) +# sounddel +# - (removes a sound) +# rscadd +# - (adds a feature) +# rscdel +# - (removes a feature) +# imageadd +# - (adds an image or sprite) +# imagedel +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) +# experiment +# - (experimental change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) +################################# + +# Your name. +author: GeneralCamo + +# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again. +delete-after: True + +# Any changes you've made. See valid prefix list above. +# INDENT WITH TWO SPACES. NOT TABS. SPACES. +# SCREW THIS UP AND IT WON'T WORK. +# Also, this gets changed to [] after reading. Just remove the brackets when you add new shit. +# Please surround your changes in double quotes ("). It works without them, but if you use certain characters it screws up compiling. The quotes will not show up in the changelog. +changes: + - rscadd: "Added turf fires: Flamethrowers, fuel decals, and very hot atmos fires will now generate these fires." + - balance: "Simple Mobs can now take damage from burning." + - rscadd: "The flamethrower now works on liquid fuel (chems) instead of gasses. Several reagents now have accelerant values, determining how effective they are in a flamethrower." + - rscadd: "Nitrogen Dioxide and Chlorine are now oxidizers." + - bugfix: "ZAS Hotspots (renamed from fires) no longer generate CO2 from nothing. They solely use the reaction to do this." diff --git a/icons/effects/particles/smoke.dmi b/icons/effects/particles/smoke.dmi new file mode 100644 index 00000000000..99123beeb59 Binary files /dev/null and b/icons/effects/particles/smoke.dmi differ diff --git a/icons/effects/turf_fire.dmi b/icons/effects/turf_fire.dmi new file mode 100644 index 00000000000..f20bd64a4eb Binary files /dev/null and b/icons/effects/turf_fire.dmi differ diff --git a/icons/obj/item/flamethrower.dmi b/icons/obj/item/flamethrower.dmi index f0ac73a54ca..9b71c1095de 100644 Binary files a/icons/obj/item/flamethrower.dmi and b/icons/obj/item/flamethrower.dmi differ diff --git a/sound/ambience/fire/Fire_Ambient_01.ogg b/sound/ambience/fire/Fire_Ambient_01.ogg new file mode 100644 index 00000000000..670376d1dbf Binary files /dev/null and b/sound/ambience/fire/Fire_Ambient_01.ogg differ diff --git a/sound/ambience/fire/Fire_Ambient_02.ogg b/sound/ambience/fire/Fire_Ambient_02.ogg new file mode 100644 index 00000000000..b9c4936c258 Binary files /dev/null and b/sound/ambience/fire/Fire_Ambient_02.ogg differ diff --git a/sound/ambience/fire/Fire_Ambient_03.ogg b/sound/ambience/fire/Fire_Ambient_03.ogg new file mode 100644 index 00000000000..b771bde9b7e Binary files /dev/null and b/sound/ambience/fire/Fire_Ambient_03.ogg differ diff --git a/sound/weapons/flamethrower/flamethrower1.ogg b/sound/weapons/flamethrower/flamethrower1.ogg new file mode 100644 index 00000000000..447245d50b6 Binary files /dev/null and b/sound/weapons/flamethrower/flamethrower1.ogg differ diff --git a/sound/weapons/flamethrower/flamethrower2.ogg b/sound/weapons/flamethrower/flamethrower2.ogg new file mode 100644 index 00000000000..66cef56e15c Binary files /dev/null and b/sound/weapons/flamethrower/flamethrower2.ogg differ diff --git a/sound/weapons/flamethrower/flamethrower3.ogg b/sound/weapons/flamethrower/flamethrower3.ogg new file mode 100644 index 00000000000..0c0d75d93eb Binary files /dev/null and b/sound/weapons/flamethrower/flamethrower3.ogg differ diff --git a/sound/weapons/flamethrower/flamethrower_empty.ogg b/sound/weapons/flamethrower/flamethrower_empty.ogg new file mode 100644 index 00000000000..0b1f0c520b0 Binary files /dev/null and b/sound/weapons/flamethrower/flamethrower_empty.ogg differ