Refactors effect_system (#94999)

## About The Pull Request

This PR refactors ``effect_system``s to be a bit easier to use by
getting rid of ``set_up``, allowing ``attach()`` to be chained into
``start()`` and refactoring most direct system usages in our code to use
helper procs.

``set_up`` was unnecessary and only existed to allow ``New``'s behavior
to be fully overriden, which is not required if we split
sparks/lightning/steam into a new ``/datum/effect_system/basic`` subtype
which houses the effect spreading behavior. This allows us to roll all
logic from ``set_up`` into ``New`` and cut down on code complexity.
Chaining setup as ``system.attach(src).start()`` also helps a bit in
case no helper method exists

I've added ``do_chem_smoke`` and ``do_foam`` helpers, which respectively
allow chemical smoke or foam to be spawned easily without having to
manually create effect datums and reagent holders.

Also turns out we've had some nonfunctional effect systems which either
never set themselves up, or never started, so I fixed those while I was
at it (mostly by moving them to aforementioned helper procs)

## Why It's Good For The Game

Cleaner code, makes it significantly easier for users to work with. Also
most of our effect system usage was copypasta which was passing booleans
as numbers, while perfectly fine helper procs existed in our code.

## Changelog
🆑
refactor: Refactored sparks, foam, smoke, and other miscellaneous effect
systems.
refactor: Vapes now have consistent rigging with cigs using the new
system.
fix: Fixed some effects never working.
/🆑
This commit is contained in:
SmArtKar
2026-02-03 22:23:09 -05:00
committed by GitHub
parent b1e6830ec5
commit f58b8511f0
134 changed files with 621 additions and 904 deletions
@@ -1,26 +0,0 @@
/obj/effect/shield
name = "shield"
icon = 'icons/effects/effects.dmi'
icon_state = "wave2"
layer = ABOVE_NORMAL_TURF_LAYER
flags_1 = PREVENT_CLICK_UNDER_1
anchored = TRUE
var/old_heat_capacity
/obj/effect/shield/Initialize(mapload)
. = ..()
var/turf/location = get_turf(src)
old_heat_capacity=location.heat_capacity
location.heat_capacity = INFINITY
/obj/effect/shield/Destroy()
var/turf/location = get_turf(src)
location.heat_capacity=old_heat_capacity
return ..()
/obj/effect/shield/singularity_act()
return
/obj/effect/shield/singularity_pull(atom/singularity, current_size)
return
@@ -1,10 +1,12 @@
/* This is an attempt to make some easily reusable "particle" type effect, to stop the code
constantly having to be rewritten. An item like the jetpack that uses the ion_trail_follow system, just has one
defined, then set up when it is created with New(). Then this same system can just be reused each time
it needs to create more trails.A beaker could have a steam_trail_follow system set up, then the steam
would spawn and follow the beaker, even if it is carried or thrown.
/*
* This is an attempt to make some easily reusable "particle" type effect, to stop the code
* constantly having to be rewritten. An item like the jetpack that uses the ion_trail_follow system, just has one
* defined, then set up when it is created with New(). Then this same system can just be reused each time
* it needs to create more trails.A beaker could have a steam_trail_follow system set up, then the steam
* would spawn and follow the beaker, even if it is carried or thrown.
*/
#define PER_SYSTEM_PARTICLE_CAP 20
/obj/effect/particle_effect
name = "particle effect"
@@ -17,36 +19,61 @@ would spawn and follow the beaker, even if it is carried or thrown.
return TRUE
/datum/effect_system
var/number = 3
var/cardinals_only = FALSE
var/turf/location
var/atom/holder
var/effect_type
var/total_effects = 0
var/autocleanup = FALSE //will delete itself after use
// Does not contain any behaviors and should not be used by itself
abstract_type = /datum/effect_system
/// Turf on which to spawn the effects
var/turf/location = null
/// Atom that is spawning the particles whose location we're following
var/atom/holder = null
/datum/effect_system/New(turf/location)
. = ..()
src.location = get_turf(location)
/datum/effect_system/Destroy()
holder = null
location = null
return ..()
/datum/effect_system/proc/set_up(number = 3, cardinals_only = FALSE, location)
src.number = min(number, 10)
src.cardinals_only = cardinals_only
src.location = get_turf(location)
/datum/effect_system/proc/attach(atom/atom)
holder = atom
/// Instruct the effect system to start following an atom. Can be chained into .start()
/datum/effect_system/proc/attach(atom/new_holder)
RETURN_TYPE(/datum/effect_system)
holder = new_holder
return src
/// Start the effect system
/datum/effect_system/proc/start()
return
/// Basic effect system which spawns a certain number of moving effects
/datum/effect_system/basic
/// Total number of particles to spawn
var/amount = 3
/// Should we pick among cardinals or all directions when deciding where the particle should move
var/cardinals_only = FALSE
/// Typepath of the effect to spawn
var/effect_type = null
/// Total amount of effects we currently have active
var/total_effects = 0
/// Should the system delete itself after finishing?
var/autocleanup = FALSE
/datum/effect_system/basic/New(turf/location, amount = null, cardinals_only = null)
. = ..()
if (!isnull(amount))
src.amount = amount
if (!isnull(cardinals_only))
src.cardinals_only = cardinals_only
/datum/effect_system/basic/start()
if(QDELETED(src))
return
for(var/i in 1 to number)
if(total_effects > 20)
for(var/i in 1 to amount)
if(total_effects > PER_SYSTEM_PARTICLE_CAP)
return
generate_effect()
/datum/effect_system/proc/generate_effect()
/datum/effect_system/basic/proc/generate_effect()
if(holder)
location = get_turf(holder)
var/obj/effect/effect = new effect_type(location)
@@ -56,15 +83,17 @@ would spawn and follow the beaker, even if it is carried or thrown.
direction = pick(GLOB.cardinals)
else
direction = pick(GLOB.alldirs)
var/step_amt = pick(1,2,3)
var/step_amt = rand(1, 3)
var/step_delay = 5
var/datum/move_loop/loop = GLOB.move_manager.move(effect, direction, step_delay, timeout = step_delay * step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(decrement_total_effect))
if (autocleanup)
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(decrement_total_effect))
/datum/effect_system/proc/decrement_total_effect(datum/source)
/datum/effect_system/basic/proc/decrement_total_effect(datum/source)
SIGNAL_HANDLER
total_effects--
if(!autocleanup || total_effects > 0)
return
QDEL_IN(src, 2 SECONDS)
if(total_effects == 0)
QDEL_IN(src, 2 SECONDS)
#undef PER_SYSTEM_PARTICLE_CAP
@@ -9,23 +9,20 @@
return INITIALIZE_HINT_LATELOAD
/obj/effect/particle_effect/expl_particles/LateInitialize()
var/step_amt = pick(25;1,50;2,100;3,200;4)
var/step_amt = pick(25;1, 50;2, 100;3, 200;4)
var/datum/move_loop/loop = GLOB.move_manager.move(src, pick(GLOB.alldirs), 1, timeout = step_amt, priority = MOVEMENT_ABOVE_SPACE_PRIORITY)
RegisterSignal(loop, COMSIG_QDELETING, PROC_REF(end_particle))
/obj/effect/particle_effect/expl_particles/proc/end_particle(datum/source)
SIGNAL_HANDLER
if(QDELETED(src))
return
qdel(src)
if (!QDELETED(src))
qdel(src)
/datum/effect_system/expl_particles
number = 10
/datum/effect_system/basic/expl_particles
amount = 10
/datum/effect_system/expl_particles/start()
for(var/i in 1 to number)
new /obj/effect/particle_effect/expl_particles(location)
/datum/effect_system/basic/expl_particles/generate_effect()
new /obj/effect/particle_effect/expl_particles(location)
/obj/effect/explosion
name = "fire"
@@ -45,21 +42,16 @@
/datum/effect_system/explosion
/datum/effect_system/explosion/set_up(location)
src.location = get_turf(location)
/datum/effect_system/explosion/start()
new/obj/effect/explosion( location )
var/datum/effect_system/expl_particles/P = new/datum/effect_system/expl_particles()
P.set_up(10, 0, location)
P.start()
new /obj/effect/explosion(location)
var/datum/effect_system/basic/expl_particles/boom_particles = new(location)
boom_particles.start()
/datum/effect_system/explosion/smoke
/datum/effect_system/explosion/smoke/proc/create_smoke()
var/datum/effect_system/fluid_spread/smoke/S = new
S.set_up(2, holder = holder, location = location)
S.start()
var/datum/effect_system/fluid_spread/smoke/smoke_system = new(location, range = 2)
smoke_system.attach(holder).start()
/datum/effect_system/explosion/smoke/start()
..()
@@ -1,113 +0,0 @@
/////////////////////////////////////////////
//////// Attach a trail to any object, that spawns when it moves (like for the jetpack)
/// just pass in the object to attach it to in set_up
/// Then do start() to start it and stop() to stop it, obviously
/// and don't call start() in a loop that will be repeated otherwise it'll get spammed!
/////////////////////////////////////////////
/datum/effect_system/trail_follow
var/turf/oldposition
var/active = FALSE
var/allow_overlap = FALSE
var/auto_process = TRUE
var/qdel_in_time = 10
var/fadetype = "ion_fade"
var/fade = TRUE
var/nograv_required = FALSE
/datum/effect_system/trail_follow/set_up(atom/atom)
attach(atom)
oldposition = get_turf(atom)
/datum/effect_system/trail_follow/Destroy()
oldposition = null
stop()
return ..()
/datum/effect_system/trail_follow/proc/stop()
oldposition = null
STOP_PROCESSING(SSfastprocess, src)
active = FALSE
return TRUE
/datum/effect_system/trail_follow/start()
oldposition = get_turf(holder)
if(!check_conditions())
return FALSE
if(auto_process)
START_PROCESSING(SSfastprocess, src)
active = TRUE
return TRUE
/datum/effect_system/trail_follow/process()
generate_effect()
/datum/effect_system/trail_follow/generate_effect()
if(!check_conditions())
return stop()
if(oldposition && !(oldposition == get_turf(holder)))
if(!oldposition.has_gravity() || !nograv_required)
var/obj/effect/E = new effect_type(oldposition)
set_dir(E)
if(fade)
flick(fadetype, E)
E.icon_state = ""
if(qdel_in_time)
QDEL_IN(E, qdel_in_time)
oldposition = get_turf(holder)
/datum/effect_system/trail_follow/proc/check_conditions()
if(!get_turf(holder))
return FALSE
return TRUE
/datum/effect_system/trail_follow/steam
effect_type = /obj/effect/particle_effect/steam
/obj/effect/particle_effect/ion_trails
name = "ion trails"
icon_state = "ion_trails"
anchored = TRUE
/obj/effect/particle_effect/ion_trails/flight
icon_state = "ion_trails_flight"
/datum/effect_system/trail_follow/ion
effect_type = /obj/effect/particle_effect/ion_trails
nograv_required = TRUE
qdel_in_time = 20
/datum/effect_system/trail_follow/proc/set_dir(obj/effect/particle_effect/ion_trails/I)
I.setDir(holder.dir)
/datum/effect_system/trail_follow/ion/grav_allowed
nograv_required = FALSE
//Reagent-based explosion effect
/datum/effect_system/reagents_explosion
var/amount // TNT equivalent
var/flashing_factor = null // factor of how powerful the flash effect relatively to the explosion
var/flaming_factor = null // factor of how powerful the flame effect is relatively to explosion
var/explosion_message = 1 //whether we show a message to mobs.
/datum/effect_system/reagents_explosion/set_up(amt, loca, flash_fact = null, flame_fact = null, message = TRUE)
amount = amt
explosion_message = message
if(isturf(loca))
location = loca
else
location = get_turf(loca)
flashing_factor = flash_fact
flaming_factor = flame_fact
/// Starts the explosion. The explosion_source is as part of logging and identifying the source of the explosion for logs.
/datum/effect_system/reagents_explosion/start(atom/explosion_source = null)
if(!explosion_source)
stack_trace("Reagent explosion triggered without a source atom. This explosion may have incomplete logging.")
if(explosion_message)
location.visible_message(span_danger("The solution violently explodes!"), span_hear("You hear an explosion!"))
dyn_explosion(location, amount, flash_range = flashing_factor, flame_range = flaming_factor, explosion_cause = explosion_source)
@@ -0,0 +1,30 @@
//Reagent-based explosion effect
/datum/effect_system/reagents_explosion
/// Explosive power
var/amount
/// Factor of how powerful the flash effect relatively to the explosion
var/flashing_factor = null
/// Factor of how powerful the flame effect is relatively to explosion
var/flaming_factor = null
/// Whether we show a message to mobs.
var/explosion_message = 1
/datum/effect_system/reagents_explosion/New(turf/location, amount, flash_fact = null, flame_fact = null, message = TRUE)
. = ..()
src.amount = amount
explosion_message = message
if (!isturf(location))
location = get_turf(location)
flashing_factor = flash_fact
flaming_factor = flame_fact
/// Starts the explosion. The explosion_source is as part of logging and identifying the source of the explosion for logs.
/datum/effect_system/reagents_explosion/start(atom/explosion_source = null)
if(!explosion_source)
stack_trace("Reagent explosion triggered without a source atom. This explosion may have incomplete logging.")
if(explosion_message)
location.visible_message(span_danger("The solution violently explodes!"), span_hear("You hear an explosion!"))
dyn_explosion(location, amount, flash_range = flashing_factor, flame_range = flaming_factor, explosion_cause = explosion_source)
@@ -5,13 +5,13 @@
// will always spawn at the items location.
/////////////////////////////////////////////
/proc/do_sparks(number, cardinal_only, datum/source)
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(number, cardinal_only, source)
/proc/do_sparks(number, cardinal_only, atom/source, atom/holder = null, spark_type = /datum/effect_system/basic/spark_spread)
var/datum/effect_system/basic/spark_spread/sparks = new spark_type(get_turf(source), number, cardinal_only)
if (holder)
sparks.attach(holder)
sparks.autocleanup = TRUE
sparks.start()
/obj/effect/particle_effect/sparks
name = "sparks"
icon_state = "sparks"
@@ -32,7 +32,7 @@
var/turf/location = loc
if(isturf(location))
affect_location(location, just_initialized = TRUE)
QDEL_IN(src, 20)
QDEL_IN(src, 2 SECONDS)
/obj/effect/particle_effect/sparks/Destroy()
var/turf/location = loc
@@ -41,11 +41,15 @@
return ..()
/obj/effect/particle_effect/sparks/Move()
..()
. = ..()
var/turf/location = loc
if(isturf(location))
affect_location(location)
/obj/effect/particle_effect/sparks/quantum
name = "quantum sparks"
icon_state = "quantum_sparks"
/*
* Apply the effects of this spark to its location.
*
@@ -56,7 +60,7 @@
* just_initialized - If the spark is just being created, and we need to manually affect everything in the location
*/
/obj/effect/particle_effect/sparks/proc/affect_location(turf/location, just_initialized = FALSE)
location.hotspot_expose(1000,100)
location.hotspot_expose(1000, 100)
SEND_SIGNAL(location, COMSIG_ATOM_TOUCHED_SPARKS, src) // for plasma floors; other floor types only have to worry about the mysterious HAZARDOUS sparks
if(just_initialized)
for(var/atom/movable/singed in location)
@@ -79,27 +83,23 @@
if(reagents && !(reagents.flags & SEALED_CONTAINER))
reagents.expose_temperature(1000) // we set this at 1000 because that's the max reagent temp for a chem heater, higher temps require more than sparks
return
if(ishuman(singed))
var/mob/living/carbon/human/singed_human = singed
for(var/obj/item/anything in singed_human.get_visible_items())
sparks_touched(src, anything)
/datum/effect_system/spark_spread
/datum/effect_system/basic/spark_spread
effect_type = /obj/effect/particle_effect/sparks
/datum/effect_system/spark_spread/quantum
/datum/effect_system/basic/spark_spread/quantum
effect_type = /obj/effect/particle_effect/sparks/quantum
//electricity
/obj/effect/particle_effect/sparks/electricity
name = "lightning"
icon_state = "electricity"
/obj/effect/particle_effect/sparks/quantum
name = "quantum sparks"
icon_state = "quantum_sparks"
/datum/effect_system/lightning_spread
/datum/effect_system/basic/lightning_spread
effect_type = /obj/effect/particle_effect/sparks/electricity
@@ -0,0 +1,103 @@
/////////////////////////////////////////////
//////// Attach a trail to any object, that spawns when it moves (like for the jetpack)
/// just pass in the object to attach it to in set_up
/// Then do start() to start it and stop() to stop it, obviously
/// and don't call start() in a loop that will be repeated otherwise it'll get spammed!
/////////////////////////////////////////////
/datum/effect_system/trail_follow
/// Previous position of the atom we're tracking
var/turf/oldposition
/// Are we currently spawning particles?
var/active = FALSE
/// Can the particles be spawned ontop of eachother?
var/allow_overlap = FALSE
/// Should we automatically start processing ourselves?
var/auto_process = TRUE
/// Delay before we delete the particles
var/qdel_in_time = 1 SECONDS
/// Typepath we should spawn
var/effect_type = null
/// Should we flick an icon state and blank out the particles afterwards?
var/fade = TRUE
/// icon_state to flick on our particles
var/fadetype = "ion_fade"
/// Are we restricted to zero-g only?
var/nograv_required = FALSE
/datum/effect_system/trail_follow/New(turf/location)
. = ..()
attach(location)
oldposition = location
/datum/effect_system/trail_follow/Destroy()
oldposition = null
stop()
return ..()
/datum/effect_system/trail_follow/proc/stop()
oldposition = null
STOP_PROCESSING(SSfastprocess, src)
active = FALSE
return TRUE
/datum/effect_system/trail_follow/start()
oldposition = get_turf(holder)
if(!check_conditions())
return FALSE
if(auto_process)
START_PROCESSING(SSfastprocess, src)
active = TRUE
return TRUE
/datum/effect_system/trail_follow/process()
generate_effect()
/datum/effect_system/trail_follow/proc/generate_effect()
if(!check_conditions())
return stop()
if(!oldposition || oldposition == get_turf(holder))
oldposition = get_turf(holder)
return
if(nograv_required && oldposition.has_gravity())
oldposition = get_turf(holder)
return
var/obj/effect/particle = new effect_type(oldposition)
set_dir(particle)
if(fade)
flick(fadetype, particle)
particle.icon_state = ""
if(qdel_in_time)
QDEL_IN(particle, qdel_in_time)
/datum/effect_system/trail_follow/proc/check_conditions()
if(!get_turf(holder))
return FALSE
return TRUE
/datum/effect_system/trail_follow/proc/set_dir(obj/effect/effect)
effect.setDir(holder.dir)
/datum/effect_system/trail_follow/steam
effect_type = /obj/effect/particle_effect/steam
/obj/effect/particle_effect/ion_trails
name = "ion trails"
icon_state = "ion_trails"
anchored = TRUE
/obj/effect/particle_effect/ion_trails/flight
icon_state = "ion_trails_flight"
/datum/effect_system/trail_follow/ion
effect_type = /obj/effect/particle_effect/ion_trails
nograv_required = TRUE
qdel_in_time = 2 SECONDS
/datum/effect_system/trail_follow/ion/grav_allowed
nograv_required = FALSE
@@ -4,15 +4,17 @@
name = "water"
icon_state = "extinguish"
pass_flags = PASSTABLE | PASSMACHINE | PASSSTRUCTURE | PASSGRILLE | PASSBLOB | PASSVEHICLE
var/life = 15
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/// Amount of turfs we pass before deleting ourselves
var/life = 15
/obj/effect/particle_effect/water/Initialize(mapload)
. = ..()
QDEL_IN(src, 70)
QDEL_IN(src, 7 SECONDS)
/obj/effect/particle_effect/water/Move(turf/newloc)
if (--src.life < 1)
life -= 1
if (life <= 0)
qdel(src)
return FALSE
return ..()
@@ -20,8 +22,7 @@
/obj/effect/particle_effect/water/Bump(atom/A)
if(reagents)
reagents.expose(A)
if(A.reagents)
A.reagents.expose_temperature(-25)
A.reagents?.expose_temperature(reagents.chem_temp)
return ..()
///Extinguisher snowflake
@@ -71,17 +72,11 @@
/////////////////////////////////////////////
// GENERIC STEAM SPREAD SYSTEM
//Usage: set_up(number of bits of steam, use North/South/East/West only, spawn location)
// Usage: set_up(number of bits of steam, use North/South/East/West only, spawn location)
// The attach(atom/atom) proc is optional, and can be called to attach the effect
// to something, like a smoking beaker, so then you can just call start() and the steam
// will always spawn at the items location, even if it's moved.
/* Example:
*var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread() -- creates new system
*steam.set_up(5, 0, mob.loc) -- sets up variables
*OPTIONAL: steam.attach(mob)
*steam.start() -- spawns the effect
*/
/////////////////////////////////////////////
/obj/effect/particle_effect/steam
name = "steam"
@@ -90,11 +85,7 @@
/obj/effect/particle_effect/steam/Initialize(mapload)
. = ..()
QDEL_IN(src, 20)
QDEL_IN(src, 2 SECONDS)
/datum/effect_system/steam_spread
/datum/effect_system/basic/steam_spread
effect_type = /obj/effect/particle_effect/steam
/obj/effect/particle_effect/water/Bump(atom/A)
if(A.reagents && reagents)
A.reagents.expose_temperature(reagents.chem_temp)
@@ -115,13 +115,16 @@
* A factory which produces fluid groups.
*/
/datum/effect_system/fluid_spread
effect_type = /obj/effect/particle_effect/fluid
/// The amount of smoke to produce.
/// The amount of fluid to produce.
var/amount = 10
/// Type of the effect we're spawning
var/effect_type = /obj/effect/particle_effect/fluid
/datum/effect_system/fluid_spread/set_up(range = 1, amount = DIAMOND_AREA(range), atom/holder, atom/location, ...)
src.holder = holder
src.location = location
/datum/effect_system/fluid_spread/New(turf/location, range = 1, amount = null, atom/holder = null)
. = ..()
attach(holder)
if (isnull(amount))
amount = DIAMOND_AREA(range)
src.amount = amount
/datum/effect_system/fluid_spread/start(log = FALSE)
@@ -144,7 +147,6 @@
var/blame_msg
if (holder)
holder.transfer_fingerprints_to(flood) // This is important. If this doesn't exist thermobarics are annoying to adjudicate.
source_msg = "from inside of [ismob(holder) ? ADMIN_LOOKUPFLW(holder) : ADMIN_VERBOSEJMP(holder)]"
var/lastkey = holder.fingerprintslast
if (lastkey)
@@ -178,31 +178,59 @@
if(prob(max(0, exposed_temperature - 475))) //foam dissolves when heated
kill_foam()
/// Proc to quickly spawn foam
/// reagent_type can accept a list of reagents, optionally as a key-value pair with values overriding reagent_volume if not null
/proc/do_foam(range = 1, atom/holder = null, turf/location = null, datum/reagent/reagent_type = null, reagent_volume = 10, datum/reagents/carry = null, amount = null, log = FALSE, datum/effect_system/fluid_spread/foam/foam_type = /datum/effect_system/fluid_spread/foam, result_type = null, stop_reactions = FALSE, reagent_scale = FOAM_REAGENT_SCALE)
if (carry || isnull(reagent_type))
var/datum/effect_system/fluid_spread/foam/foam = new foam_type(location, range, amount, holder || location, carry, result_type, stop_reactions, reagent_scale)
foam.start(log = log)
return
if (ispath(reagent_type, /datum/reagent))
var/datum/reagents/foam_reagents = new /datum/reagents(reagent_volume)
foam_reagents.add_reagent(reagent_type, reagent_volume)
var/datum/effect_system/fluid_spread/foam/foam = new foam_type(location, range, amount, holder || location, foam_reagents, result_type, stop_reactions, reagent_scale)
foam.start(log = log)
return
if (!islist(reagent_type))
CRASH("do_foam passed a non-reagent path, non-list reagent_type [reagent_type]!")
var/list/reagent_list = reagent_type
var/chem_volume = 0
for (var/chem_type in reagent_list)
chem_volume += reagent_list[chem_type] || reagent_volume
var/datum/reagents/foam_reagents = new /datum/reagents(chem_volume)
for (var/chem_type in reagent_list)
foam_reagents.add_reagent(chem_type, reagent_list[chem_type] || reagent_volume)
var/datum/effect_system/fluid_spread/foam/foam = new foam_type(location, range, amount, holder || location, foam_reagents, result_type, stop_reactions, reagent_scale)
foam.start(log = log)
/// A factory for foam fluid floods.
/datum/effect_system/fluid_spread/foam
effect_type = /obj/effect/particle_effect/fluid/foam
/// A container for all of the chemicals we distribute through the foam.
var/datum/reagents/chemholder
/// The amount that
var/datum/reagents/chemholder = null
/// The amount that we multiply the payload by
var/reagent_scale = FOAM_REAGENT_SCALE
/// What type of thing the foam should leave behind when it dissipates.
var/atom/movable/result_type = null
/datum/effect_system/fluid_spread/foam/New()
..()
/datum/effect_system/fluid_spread/foam/New(turf/location, range = 1, amount = null, atom/holder = null, datum/reagents/carry = null, result_type = null, stop_reactions = FALSE, reagent_scale = FOAM_REAGENT_SCALE)
. = ..()
chemholder = new(1000, NO_REACT)
carry?.trans_to(chemholder, carry.total_volume, no_react = stop_reactions, copy_only = TRUE)
if(!isnull(result_type))
src.result_type = result_type
src.reagent_scale = reagent_scale
/datum/effect_system/fluid_spread/foam/Destroy()
QDEL_NULL(chemholder)
return ..()
/datum/effect_system/fluid_spread/foam/set_up(range = 1, amount = DIAMOND_AREA(range), atom/holder, atom/location = null, datum/reagents/carry = null, result_type = null, stop_reactions = FALSE)
. = ..()
carry?.trans_to(chemholder, carry.total_volume, no_react = stop_reactions, copy_only = TRUE)
if(!isnull(result_type))
src.result_type = result_type
/datum/effect_system/fluid_spread/foam/start(log = FALSE)
var/obj/effect/particle_effect/fluid/foam/foam = new effect_type(location, new /datum/fluid_group(amount))
var/foamcolor = mix_color_from_reagents(chemholder.reagent_list)
@@ -235,7 +263,6 @@
effect_type = /obj/effect/particle_effect/fluid/foam/long_life
reagent_scale = FOAM_REAGENT_SCALE * (30 / 8)
// Firefighting foam
/// A variant of foam which absorbs plasma in the air if there is a fire.
/obj/effect/particle_effect/fluid/foam/firefighting
@@ -468,9 +495,7 @@
/obj/effect/spawner/foam_starter/Initialize(mapload)
. = ..()
var/datum/effect_system/fluid_spread/foam/foam = new foam_type()
foam.set_up(foam_size, holder = src, location = loc)
var/datum/effect_system/fluid_spread/foam/foam = new foam_type(loc, foam_size, holder = src)
foam.start()
/obj/effect/spawner/foam_starter/small
@@ -165,12 +165,13 @@
* - range: The amount of smoke to produce as number of steps from origin covered.
* - amount: The amount of smoke to produce as the total desired coverage area. Autofilled from the range arg if not set.
* - location: Where to produce the smoke cloud.
* - smoke_type: The smoke typepath to spawn.
* - smoke_type - Typepath for the effect system to use
* - effect_type: The smoke typepath to spawn.
* - log: Should the system log the smoke spawned?
*/
/proc/do_smoke(range = 0, amount = DIAMOND_AREA(range), atom/holder = null, location = null, smoke_type = /obj/effect/particle_effect/fluid/smoke, log = FALSE)
var/datum/effect_system/fluid_spread/smoke/smoke = new
smoke.effect_type = smoke_type
smoke.set_up(amount = amount, holder = holder, location = location)
/proc/do_smoke(range = 0, atom/holder = null, location = null, amount = null, smoke_type = /datum/effect_system/fluid_spread/smoke, effect_type = /obj/effect/particle_effect/fluid/smoke, log = FALSE)
var/datum/effect_system/fluid_spread/smoke/smoke = new smoke_type(location, range, amount, holder)
smoke.effect_type = effect_type
smoke.start(log = log)
/////////////////////////////////////////////
@@ -205,7 +206,6 @@
. = ..()
if(!.)
return
smoker.drop_all_held_items()
smoker.adjust_oxy_loss(1)
smoker.emote("cough")
@@ -275,6 +275,10 @@
/// Whether to make sure each affected turf is actually within range before cooling it.
var/distcheck = TRUE
/datum/effect_system/fluid_spread/smoke/freezing/New(turf/location, range = 1, amount = null, atom/holder = null, blast_radius = 0)
. = ..()
blast = blast_radius
/**
* Chills an open turf.
*
@@ -286,7 +290,7 @@
* Arguments:
* - [chilly][/turf/open]: The open turf to chill
*/
/datum/effect_system/fluid_spread/smoke/freezing/proc/Chilled(turf/open/chilly)
/datum/effect_system/fluid_spread/smoke/freezing/proc/chill_turf(turf/open/chilly)
if(!istype(chilly))
return
@@ -319,14 +323,10 @@
for(var/obj/item/potential_tinder in chilly)
potential_tinder.extinguish()
/datum/effect_system/fluid_spread/smoke/freezing/set_up(range = 5, amount = DIAMOND_AREA(range), atom/holder, atom/location, blast_radius = 0)
. = ..()
blast = blast_radius
/datum/effect_system/fluid_spread/smoke/freezing/start(log = FALSE)
if(blast)
for(var/turf/T in RANGE_TURFS(blast, location))
Chilled(T)
chill_turf(T)
return ..()
/// A variant of the base freezing smoke formerly used by the vent decontamination event.
@@ -398,13 +398,33 @@
return TRUE
/// Helper to quickly create a cloud of reagent smoke
/proc/do_chem_smoke(range = 0, amount = DIAMOND_AREA(range), atom/holder = null, location = null, reagent_type = /datum/reagent/water, reagent_volume = 10, log = FALSE, datum/effect_system/fluid_spread/smoke/chem/smoke_type = /datum/effect_system/fluid_spread/smoke/chem)
var/datum/reagents/smoke_reagents = new/datum/reagents(reagent_volume)
smoke_reagents.add_reagent(reagent_type, reagent_volume)
/// reagent_type can accept a list of reagents, optionally as a key-value pair with values overriding reagent_volume if not null
/proc/do_chem_smoke(range = 0, atom/holder = null, location = null, reagent_type = /datum/reagent/water, reagent_volume = 10, datum/reagents/carry = null, carry_limit = null, log = FALSE, amount = null, datum/effect_system/fluid_spread/smoke/chem/smoke_type = /datum/effect_system/fluid_spread/smoke/chem, silent = TRUE)
if (carry)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new smoke_type(location, range, amount, holder || location, carry, carry_limit, silent)
smoke.start(log = log)
return
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new smoke_type
smoke.attach(location)
smoke.set_up(amount = amount, holder = holder, location = location, carry = smoke_reagents, silent = TRUE)
if (ispath(reagent_type, /datum/reagent))
var/datum/reagents/smoke_reagents = new /datum/reagents(reagent_volume)
smoke_reagents.add_reagent(reagent_type, reagent_volume)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new smoke_type(location, range, amount, holder || location, smoke_reagents, carry_limit, silent)
smoke.start(log = log)
return
if (!islist(reagent_type))
CRASH("do_chem_smoke passed a non-reagent path, non-list reagent_type [reagent_type]!")
var/list/reagent_list = reagent_type
var/chem_volume = 0
for (var/chem_type in reagent_list)
chem_volume += reagent_list[chem_type] || reagent_volume
var/datum/reagents/smoke_reagents = new /datum/reagents(chem_volume)
for (var/chem_type in reagent_list)
smoke_reagents.add_reagent(chem_type, reagent_list[chem_type] || reagent_volume)
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new smoke_type(location, range, amount, holder || location, smoke_reagents, carry_limit, silent)
smoke.start(log = log)
/// A factory which produces clouds of chemical bearing smoke.
@@ -413,18 +433,10 @@
var/datum/reagents/chemholder
effect_type = /obj/effect/particle_effect/fluid/smoke/chem
/datum/effect_system/fluid_spread/smoke/chem/New()
..()
chemholder = new(1000, NO_REACT)
/datum/effect_system/fluid_spread/smoke/chem/Destroy()
QDEL_NULL(chemholder)
return ..()
/datum/effect_system/fluid_spread/smoke/chem/set_up(range = 1, amount = DIAMOND_AREA(range), atom/holder, atom/location = null, datum/reagents/carry = null, silent = FALSE)
/datum/effect_system/fluid_spread/smoke/chem/New(turf/location, range = 1, amount = null, atom/holder = null, datum/reagents/carry = null, carry_limit = null, silent = FALSE)
. = ..()
carry?.trans_to(chemholder, carry.total_volume, copy_only = TRUE)
chemholder = new(1000, NO_REACT)
carry?.trans_to(chemholder, isnull(carry_limit) ? carry.total_volume : carry_limit, copy_only = TRUE)
if(silent)
return
@@ -436,18 +448,22 @@
var/where = "[AREACOORD(location)]"
var/contained = length(contained_reagents) ? "\[[contained_reagents.Join(", ")]\] @ [chemholder.chem_temp]K" : null
var/area/fluid_area = get_area(location)
if(carry.my_atom?.fingerprintslast) //Some reagents don't have a my_atom in some cases
var/mob/M = get_mob_by_key(carry.my_atom.fingerprintslast)
var/more = ""
if(M)
more = "[ADMIN_LOOKUPFLW(M)] "
if(!istype(carry.my_atom, /obj/machinery/plumbing) && !(fluid_area.area_flags & QUIET_LOGS)) // I like to be able to see my logs thank you
message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. Key: [more ? more : carry.my_atom.fingerprintslast].")
log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last touched by [carry.my_atom.fingerprintslast].")
else
if(!istype(carry.my_atom, /obj/machinery/plumbing) && !(fluid_area.area_flags & QUIET_LOGS)) // Deathmatch has way too much smoke to log
// Some reagents don't have a my_atom in some cases
if(!carry.my_atom?.fingerprintslast)
// Deathmatch has way too much smoke to log
if(!istype(carry.my_atom, /obj/machinery/plumbing) && !(fluid_area.area_flags & QUIET_LOGS))
message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. No associated key.")
log_game("A chemical smoke reaction has taken place in ([where])[contained]. No associated key.")
return
var/mob/bomber = get_mob_by_key(carry.my_atom.fingerprintslast)
if(!istype(carry.my_atom, /obj/machinery/plumbing) && !(fluid_area.area_flags & QUIET_LOGS)) // I like to be able to see my logs thank you
message_admins("Smoke: ([ADMIN_VERBOSEJMP(location)])[contained]. Key: [bomber ? "[ADMIN_LOOKUPFLW(bomber)] " : carry.my_atom.fingerprintslast].")
log_game("A chemical smoke reaction has taken place in ([where])[contained]. Last touched by [carry.my_atom.fingerprintslast].")
/datum/effect_system/fluid_spread/smoke/chem/Destroy()
QDEL_NULL(chemholder)
return ..()
/datum/effect_system/fluid_spread/smoke/chem/start(log = FALSE)
var/start_loc = holder ? get_turf(holder) : src.location