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
+2 -5
View File
@@ -79,11 +79,8 @@
*/
/proc/spread_reagents(datum/reagents/source, atom/epicenter, spread_range)
spread_range = min(spread_range, 20) // Fuck off with trying to do more then this
var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread()
steam.set_up(10, 0, epicenter)
steam.attach(epicenter)
steam.start()
var/datum/effect_system/basic/steam_spread/steam = new /datum/effect_system/basic/steam_spread(epicenter, 10, FALSE)
steam.attach(epicenter).start()
// This is a basic floodfill algorithm of atmos connected tiles
// Turfs will be stored in the form turf -> TRUE
var/chem_temp = source.chem_temp
@@ -30,14 +30,6 @@
opacity = FALSE
alpha = 100
/datum/effect_system/fluid_spread/smoke/chem/smoke_machine/set_up(range = 1, amount = DIAMOND_AREA(range), atom/holder, atom/location, datum/reagents/carry, efficiency = 10, silent = FALSE)
src.holder = holder
src.location = get_turf(location)
src.amount = amount
if(carry)
carry.trans_to(chemholder, 20, copy_only = TRUE)
carry.remove_all(amount / efficiency)
/obj/machinery/smoke_machine/Initialize(mapload)
create_reagents(REAGENTS_BASE_VOLUME, INJECTABLE)
@@ -163,12 +155,14 @@
return PROCESS_KILL
var/turf/location = get_turf(src)
if(!(locate(/obj/effect/particle_effect/fluid/smoke) in location))
var/datum/effect_system/fluid_spread/smoke/chem/smoke_machine/smoke = new()
smoke.set_up(setting * 3, holder = src, location = location, carry = reagents, efficiency = efficiency)
smoke.start()
use_energy(active_power_usage * (setting / max_range))
update_appearance(UPDATE_ICON_STATE)
if(locate(/obj/effect/particle_effect/fluid/smoke) in location)
return
var/smoke_amount = DIAMOND_AREA(setting * 3)
do_chem_smoke(amount = smoke_amount, holder = src, location = location, carry = reagents, carry_limit = 20, smoke_type = /datum/effect_system/fluid_spread/smoke/chem/smoke_machine)
reagents.remove_all(smoke_amount / efficiency)
use_energy(active_power_usage * (setting / max_range))
update_appearance(UPDATE_ICON_STATE)
/obj/machinery/smoke_machine/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -129,9 +129,8 @@
if(source.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)
return
var/location = get_turf(holder.my_atom)
var/datum/effect_system/reagents_explosion/e = new()
e.set_up(1 + round(volume / 6, 1), location, message = FALSE)
e.start(holder.my_atom)
var/datum/effect_system/reagents_explosion/expl = new(location, 1 + round(volume / 6, 1), message = FALSE)
expl.start(holder.my_atom)
holder.clear_reagents()
/datum/reagent/gunpowder/on_spark_act(power_charge, spark_flags)
@@ -225,15 +224,13 @@
if ((spark_flags & SPARK_ACT_ENCLOSED) && !ismob(holder.my_atom))
return
var/location = get_turf(holder.my_atom)
var/datum/effect_system/fluid_spread/smoke/chem/smoke_system = new()
playsound(location, 'sound/effects/smoke.ogg', 50, TRUE, -3)
if (iscarbon(holder.my_atom))
var/mob/living/carbon/victim = holder.my_atom
if (victim.stat != DEAD)
victim.visible_message(span_warning("[victim] starts violently coughing up smoke!"))
victim.adjust_organ_loss(ORGAN_SLOT_LUNGS, volume / 15)
smoke_system.set_up(amount = volume / 1.5, holder = holder.my_atom, location = location, carry = holder, silent = FALSE)
smoke_system.start(log = TRUE)
do_chem_smoke(amount = volume / 1.5, holder = holder.my_atom, location = location, carry = holder, silent = FALSE, log = TRUE)
return SPARK_ACT_NON_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/sonic_powder
+3 -8
View File
@@ -305,8 +305,7 @@
if (!istype(holder.my_atom, /obj/machinery/plumbing)) //excludes standard plumbing equipment from spamming admins with this shit
message_admins("Reagent explosion reaction occurred at [ADMIN_VERBOSEJMP(our_turf)][inside_msg]. Last Fingerprint: [touch_msg].")
log_game("Reagent explosion reaction occurred at [AREACOORD(our_turf)]. Last Fingerprint: [lastkey ? lastkey : "N/A"]." )
var/datum/effect_system/reagents_explosion/explosion_system = new()
explosion_system.set_up(power, our_turf, flash_fact = flash_factor, flame_fact = flame_factor)
var/datum/effect_system/reagents_explosion/explosion_system = new(our_turf, power, flash_fact = flash_factor, flame_fact = flame_factor)
explosion_system.start(holder.my_atom)
if (istype(holder.my_atom, /obj/item/organ/stomach))
@@ -370,7 +369,6 @@
//Spews out the inverse of the chems in the beaker of the products/reactants only
/datum/chemical_reaction/proc/explode_invert_smoke(datum/reagents/holder, datum/equilibrium/equilibrium, force_range = 0, clear_products = TRUE, clear_reactants = TRUE, accept_impure = TRUE)
var/datum/reagents/invert_reagents = new (2100, NO_REACT)//I think the biggest size we can get is 2100?
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new()
var/sum_volume = 0
invert_reagents.my_atom = holder.my_atom //Give the gas a fingerprint
for(var/datum/reagent/reagent as anything in holder.reagent_list) //make gas for reagents, has to be done this way, otherwise it never stops Exploding
@@ -386,8 +384,7 @@
if(!force_range)
force_range = (sum_volume/6) + 3
if(invert_reagents.reagent_list)
smoke.set_up(force_range, holder = holder.my_atom, location = holder.my_atom, carry = invert_reagents)
smoke.start(log = TRUE)
do_chem_smoke(force_range, holder.my_atom, holder.my_atom, carry = invert_reagents, log = TRUE)
holder.my_atom.audible_message("The [holder.my_atom] suddenly explodes, launching the aerosolized reagents into the air!")
if(clear_reactants)
clear_reactants(holder)
@@ -397,7 +394,6 @@
//Spews out the corrisponding reactions reagents (products/required) of the beaker in a smokecloud. Doesn't spew catalysts
/datum/chemical_reaction/proc/explode_smoke(datum/reagents/holder, datum/equilibrium/equilibrium, force_range = 0, clear_products = TRUE, clear_reactants = TRUE)
var/datum/reagents/reagents = new/datum/reagents(2100, NO_REACT)//Lets be safe first
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new()
reagents.my_atom = holder.my_atom //fingerprint
var/sum_volume = 0
for (var/datum/reagent/reagent as anything in holder.reagent_list)
@@ -407,8 +403,7 @@
if(!force_range)
force_range = (sum_volume/6) + 3
if(reagents.reagent_list)
smoke.set_up(force_range, holder = holder.my_atom, location = holder.my_atom, carry = reagents)
smoke.start(log = TRUE)
do_chem_smoke(force_range, holder = holder.my_atom, location = holder.my_atom, carry = reagents, log = TRUE)
holder.my_atom.audible_message("The [holder.my_atom] suddenly explodes, launching the aerosolized reagents into the air!")
if(clear_reactants)
clear_reactants(holder)
@@ -72,9 +72,8 @@
if(!istype(holder.my_atom, /obj/machinery/plumbing)) //excludes standard plumbing equipment from spamming admins with this shit
message_admins("Reagent explosion reaction occurred at [ADMIN_VERBOSEJMP(T)][inside_msg]. Last Fingerprint: [touch_msg].")
log_game("Reagent explosion reaction occurred at [AREACOORD(T)]. Last Fingerprint: [lastkey ? lastkey : "N/A"]." )
var/datum/effect_system/reagents_explosion/e = new()
e.set_up(power, T)
e.start(holder.my_atom)
var/datum/effect_system/reagents_explosion/expl = new(T, power)
expl.start(holder.my_atom)
holder.clear_reagents()
///Amount of meth required to make a crystal
@@ -393,12 +393,8 @@
return
holder.remove_reagent(/datum/reagent/smoke_powder, created_volume * 3)
var/location = get_turf(holder.my_atom)
var/datum/effect_system/fluid_spread/smoke/chem/S = new
S.attach(location)
do_chem_smoke(amount = created_volume * 3, holder = holder.my_atom, location = location, carry = holder, silent = FALSE, log = TRUE)
playsound(location, 'sound/effects/smoke.ogg', 50, TRUE, -3)
if(S)
S.set_up(amount = created_volume * 3, holder = holder.my_atom, location = location, carry = holder, silent = FALSE)
S.start(log = TRUE)
if(holder?.my_atom)
holder.clear_reagents()
if (!iscarbon(holder?.my_atom))
@@ -417,12 +413,8 @@
/datum/chemical_reaction/smoke_powder_smoke/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume)
var/location = get_turf(holder.my_atom)
var/datum/effect_system/fluid_spread/smoke/chem/S = new
S.attach(location)
do_chem_smoke(amount = created_volume, holder = holder.my_atom, location = location, carry = holder, log = TRUE, silent = FALSE)
playsound(location, 'sound/effects/smoke.ogg', 50, TRUE, -3)
if(S)
S.set_up(amount = created_volume, holder = holder.my_atom, location = location, carry = holder, silent = FALSE)
S.start(log = TRUE)
if(holder?.my_atom)
holder.clear_reagents()
if(holder?.my_atom)
@@ -260,21 +260,11 @@
/obj/item/reagent_containers/inhaler_canister/handle_deconstruct(disassembled)
if (!reagents?.total_volume)
visible_message(span_warning("[src] breaks open - but is empty!"))
return ..()
var/datum/reagents/smoke_reagents = new/datum/reagents() // Lets be safe first, our own reagents may be qdelled if we get deleted
var/datum/effect_system/fluid_spread/smoke/chem/smoke_machine/smoke = new()
smoke_reagents.my_atom = src
for (var/datum/reagent/reagent as anything in reagents.reagent_list)
smoke_reagents.add_reagent(reagent.type, reagent.volume, added_purity = reagent.purity)
reagents.remove_reagent(reagent.type, reagent.volume)
if (smoke_reagents.reagent_list)
smoke.set_up(1, holder = src, location = get_turf(src), carry = smoke_reagents)
smoke.start(log = TRUE)
visible_message(span_warning("[src] breaks open and sprays its aerosilized contents everywhere!"))
else
visible_message(span_warning("[src] breaks open - but is empty!"))
do_chem_smoke(1, src, get_turf(src), carry = reagents, log = TRUE)
visible_message(span_warning("[src] breaks open and sprays its aerosilized contents everywhere!"))
return ..()
/obj/item/inhaler/medical
+1 -4
View File
@@ -211,11 +211,8 @@
return FALSE
/obj/structure/reagent_dispensers/proc/knock_down()
var/datum/effect_system/fluid_spread/smoke/chem/smoke = new ()
var/range = reagents.total_volume / REAGENT_SPILL_DIVISOR
smoke.attach(drop_location())
smoke.set_up(round(range), holder = drop_location(), location = drop_location(), carry = reagents, silent = FALSE)
smoke.start(log = TRUE)
do_chem_smoke(round(range), drop_location(), drop_location(), carry = reagents, silent = FALSE, log = TRUE)
reagents.clear_reagents()
qdel(src)