Rewrites plasma rigging code to be much more flexible and dynamic, and cover more cases (#94861)

## About The Pull Request
Instead of being snowflake interactions on objects themselves, rigging
things with plasma or welding fuel is now handled on reagents' side via
``on_spark_act``, allowing each reagent to decide how they want to
handle exposure to electric currents or violent heating.
- Obviously plasma and welding fuel cause explosions, but former gains
additional power from electric current being passed through it (same
math as powercells), while latter doesn't explode unless in an enclosed
space, or a high enough current/temperature has been reached, and merely
creates a hotspot instead.
- Napalm and phlogiston create hotspots, or (try to) damage the holder
if they're enclosed
- Sorium, liquid dark matter, TATP, nitroglycerin and flash/smoke/sonic
powders trigger their usual detonation effects, albeit slightly weaker.
- Teslium creates a ZAP, with additional power for a BIG ZAP if it has
been triggered by electric current.
- RDX becomes spicier with temperature and current, similarly to how it
becomes more dangerous when mixed with LE or teslium.
- Gunpowder produces a delayed explosion effect (unless its hot or the
current is high enough, in which case its instant) and causes sparks as
the message claims it does (normal gunpowder explosions also do that)

Plasma and gunpowder rigged items can now be controlled using
stabilizing agent - in its presence, plasma will not explode unless the
used charge is higher than 0.2% of a standard cell's per unit of agent,
while gunpowder will instead spend 0.1 + (charge / 10% of a standard
cell's charge) units of agent to delay its detonation.

Smoke (powder) now produces a visible message and slightly damages lungs
of whoever it reacted inside of, if the container was a mob.
The numbers on plasma/welding fuel rigs should be ***roughly*** the
same. Also lighters can now be rigged in a fashion similar to welding
tools.

Also it turns out we broke light rigging at some point, so now it works
again.

## Why It's Good For The Game

This makes the system much more flexible and allows players to make more
creative IEDs, expanding upon the sandbox aspect of the game. Also I
have an upcoming project bringing IEDs (kind of, in a new form) back,
which is why I wrote this in the first place (but decided to atomize the
PRs).
As for smoke damage, it feels weird to not have any tells when someone
suddenly starts hacking up volumetric amounts of smoke, it makes sense
that they'd struggle a bit after coughing a few dozen cubic meters of
thick vape clouds.

## Changelog
🆑
add: Multiple new reagents such as napalm, sorium, TATP,
smoke/sonic/flash powders and other explosives can now be used to rig
cells or tools.
add: Plasma and gunpowder rigged items can now have controlled current
requirement/delay by adding some stabilizing agent alongside the main
reagent.
balance: Creating chemical smoke inside of a mob now makes them
violently cough it up, damaging their lungs a bit.
fix: Chemically-rigged lightbulbs now once again explode.
refactor: Refactored how power cells, welding tools, lightbulbs,
cigarettes (and now, lighters) handle being rigged with chemicals.
/🆑
This commit is contained in:
SmArtKar
2026-01-21 19:21:06 +00:00
committed by GitHub
parent 61f6e7d22c
commit c74f104c89
24 changed files with 374 additions and 140 deletions
@@ -788,6 +788,34 @@
set_temperature(round(chem_temp))
handle_reactions()
/*
* Call in case of electrical current exposure, rapid heating or blunt force, things that would set off explosives and alike
* Arguments:
* * power_charge - If we were triggered from electric current, how much power was dumped into us?
* * enclosed - Is the reaction happening in an enclosed container or not? Doesn't use reagent holder's flags as it might be called on reagents "exiting" the container
* * banned_reagents - List of reagent types which we may want to have custom handling for and should avoid checking in here
*/
/datum/reagents/proc/spark_act(power_charge, enclosed, list/banned_reagents)
if (!islist(banned_reagents))
banned_reagents = list(banned_reagents)
var/result = NONE
var/update = FALSE
for (var/datum/reagent/reagent as anything in reagent_list)
if (is_type_in_list(reagent, banned_reagents))
continue
var/reagent_result = reagent.on_spark_act(power_charge, enclosed)
if (!reagent_result)
continue
result |= (reagent_result & SPARK_ACT_RETURNS)
if (!(reagent_result & SPARK_ACT_KEEP_REAGENT))
reagent.volume = 0
update = TRUE
if (result & SPARK_ACT_CLEAR_ALL)
clear_reagents()
else if (update)
update_total()
return result
//===============================Logging==========================================
/// Outputs a log-friendly list of reagents based on the internal reagent_list.
@@ -236,6 +236,17 @@
/datum/reagent/proc/on_mob_dead(mob/living/carbon/affected_mob, seconds_per_tick)
SHOULD_CALL_PARENT(TRUE)
/*
* Called when a reagent is exposed to electric current, rapidly heated or smashed, something that would cause explosives to get easily set off
* Returning a SPARK_ACT_ flag will signal that an action has occurred as a result, for parent behavior and logging purposes
* Probably shouldn't be called from within a mob's bloodstream, unless you're ready for some very explosive results
* Arguments:
* * power_charge - If we were triggered from electric current, how much power was dumped into us?
* * enclosed - Is the reaction happening in an enclosed container or not? Doesn't use reagent holder's flags as it might be called on reagents "exiting" the container
*/
/datum/reagent/proc/on_spark_act(power_charge = 0, enclosed = TRUE)
return NONE
/**
* Called after add_reagents creates a new reagent.
*
@@ -1298,7 +1298,6 @@
/datum/reagent/fuel/expose_turf(turf/exposed_turf, reac_volume)
. = ..()
if(!istype(exposed_turf) || isspaceturf(exposed_turf))
return
@@ -1309,6 +1308,16 @@
if(pool)
pool.burn_amount = max(min(round(reac_volume / 5), 10), 1)
/datum/reagent/fuel/on_spark_act(power_charge, enclosed)
// Doesn't go boom in open air unless we pass a REALLY high current or if we're hot enough
if (!enclosed && power_charge < STANDARD_BATTERY_VALUE && holder.chem_temp < 474)
var/turf/our_turf = get_turf(holder.my_atom)
our_turf?.hotspot_expose(holder.chem_temp * 10, volume)
return NONE
reagent_explode(holder, volume, strengthdiv = 10, clear_holder_reagents = FALSE)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/space_cleaner
name = "Space Cleaner"
description = "A compound used to clean things. Now with 50% more sodium hypochlorite! Can be used to clean wounds, but it's not really meant for that."
@@ -29,6 +29,10 @@
if(affected_mob.adjust_organ_loss(ORGAN_SLOT_HEART, -0.5 * metabolization_ratio * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags))
return UPDATE_MOB_HEALTH
/datum/reagent/nitroglycerin/on_spark_act(power_charge, enclosed)
reagent_explode(holder, volume, strengthdiv = 2, clear_holder_reagents = FALSE)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/stabilizing_agent
name = "Stabilizing Agent"
description = "Keeps unstable chemicals stable. This does not work on everything."
@@ -85,6 +89,11 @@
taste_description = "air and bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/sorium/on_spark_act(power_charge, enclosed)
var/range = clamp(sqrt(volume), 1, 6)
goonchem_vortex(get_turf(holder.my_atom), 1, range)
return SPARK_ACT_NON_DESTRUCTIVE
/datum/reagent/liquid_dark_matter
name = "Liquid Dark Matter"
description = "Sucks everything into the detonation point."
@@ -92,6 +101,11 @@
taste_description = "compressed bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/liquid_dark_matter/on_spark_act(power_charge, enclosed)
var/range = clamp(sqrt(volume / 2), 1, 6)
goonchem_vortex(get_turf(holder.my_atom), 0, range)
return SPARK_ACT_NON_DESTRUCTIVE
/datum/reagent/gunpowder
name = "Gunpowder"
description = "Explodes. Violently."
@@ -116,10 +130,29 @@
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, 0, 0, message = 0)
e.set_up(1 + round(volume / 6, 1), location, message = FALSE)
e.start(holder.my_atom)
holder.clear_reagents()
/datum/reagent/gunpowder/on_spark_act(power_charge, enclosed)
// Gunpowder doesn't blow in presence of stabilizing agent but instead consumes it every time it'd get triggered
var/agent_volume = holder.get_reagent_amount(/datum/reagent/stabilizing_agent)
if (agent_volume)
var/sapped_amt = 0.1 + round(power_charge / (0.1 * STANDARD_CELL_CHARGE), 0.01)
holder.remove_reagent(/datum/reagent/stabilizing_agent, sapped_amt)
if (agent_volume > sapped_amt)
return
if (power_charge >= STANDARD_CELL_CHARGE || holder.chem_temp >= 474)
reagent_explode(holder, volume, modifier = 5, strengthdiv = 10, clear_holder_reagents = FALSE)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
holder.my_atom.visible_message(span_boldnotice("Sparks start flying around the gunpowder!"))
if (!enclosed)
do_sparks(2, TRUE, get_turf(holder.my_atom))
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reagent_explode), holder, volume, 5, 10), rand(5 SECONDS, 10 SECONDS))
return SPARK_ACT_NON_DESTRUCTIVE // just wait a bit...
/datum/reagent/rdx
name = "RDX"
description = "Military grade explosive"
@@ -127,6 +160,17 @@
taste_description = "salt"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/rdx/on_spark_act(power_charge, enclosed)
if (power_charge)
// Okay but what if we made a REALLY big boom?
var/power_coeff = log(2, power_charge / STANDARD_CELL_CHARGE)
reagent_explode(holder, volume, modifier = min(2 * power_coeff, 8), strengthdiv = 7 / min(power_coeff, 4), clear_holder_reagents = FALSE)
else if (holder.chem_temp >= 474)
reagent_explode(holder, volume, modifier = 2, strengthdiv = 7, clear_holder_reagents = FALSE)
else
reagent_explode(holder, volume, strengthdiv = 8, clear_holder_reagents = FALSE)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/tatp
name = "TaTP"
description = "Suicide grade explosive"
@@ -134,6 +178,10 @@
taste_description = "death"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/tatp/on_spark_act(power_charge, enclosed)
reagent_explode(holder, volume, strengthdiv = 1.5 + rand() * 1.5, clear_holder_reagents = FALSE)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/flash_powder
name = "Flash Powder"
description = "Makes a very bright flash."
@@ -141,6 +189,26 @@
taste_description = "salt"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/flash_powder/on_spark_act(power_charge, enclosed)
// Even weaker version of the normal flash effect
var/turf/location = get_turf(holder.my_atom)
if (!enclosed)
do_sparks(2, TRUE, location)
var/range = round(volume / 15, 1)
holder.my_atom.flash_lighting_fx(range = (range + 2))
for(var/mob/living/victim in get_hearers_in_view(range, location))
if(!victim.flash_act(affect_silicon = TRUE))
continue
var/distance = get_dist(location, victim)
if(distance <= 4 || issilicon(victim))
victim.Paralyze(max(2 SECONDS / max(1, distance), 0.5 SECONDS))
victim.Knockdown(max(20 SECONDS / max(1, distance), 6 SECONDS))
else
victim.adjust_dizzy_up_to(max(20 SECONDS / max(1, distance), 0.5 SECONDS), 20 SECONDS)
victim.dropItemToGround(victim.get_active_held_item())
victim.dropItemToGround(victim.get_inactive_held_item())
return SPARK_ACT_NON_DESTRUCTIVE
/datum/reagent/smoke_powder
name = "Smoke Powder"
description = "Makes a large cloud of smoke that can carry reagents."
@@ -148,6 +216,23 @@
taste_description = "smoke"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/smoke_powder/on_spark_act(power_charge, enclosed)
// Can't really make a cloud of smoke if we're inside of an enclosed container
// ...unless we're a mob, in which case this is pretty cursed
if (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)
return SPARK_ACT_NON_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/sonic_powder
name = "Sonic Powder"
description = "Makes a deafening noise."
@@ -155,6 +240,14 @@
taste_description = "loud noises"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/sonic_powder/on_spark_act(power_charge, enclosed)
// Even weaker version of the normal flash effect
var/turf/location = get_turf(holder.my_atom)
var/range = round(volume / 15, 1)
for(var/mob/living/victim in get_hearers_in_view(range, location))
victim.soundbang_act(1, 10 SECONDS, rand(0, 0.5 SECONDS))
return SPARK_ACT_NON_DESTRUCTIVE
/datum/reagent/phlogiston
name = "Phlogiston"
description = "Catches you on fire and makes you ignite."
@@ -166,7 +259,7 @@
/datum/reagent/phlogiston/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume, show_message = TRUE, touch_protection = 0)
. = ..()
exposed_mob.adjust_fire_stacks(1)
var/burndmg = max(0.3*exposed_mob.fire_stacks * (1 - touch_protection), 0.3)
var/burndmg = max(0.3 * exposed_mob.fire_stacks * (1 - touch_protection), 0.3)
if(burndmg)
exposed_mob.adjust_fire_loss(burndmg, 0)
exposed_mob.ignite_mob()
@@ -177,6 +270,18 @@
if(metabolizer.adjust_fire_loss(0.15 * max(metabolizer.fire_stacks, 0.15) * metabolization_ratio * seconds_per_tick, updating_health = FALSE))
return UPDATE_MOB_HEALTH
/datum/reagent/phlogiston/on_spark_act(power_charge, enclosed)
if (enclosed && !ismob(holder.my_atom))
if (!holder.my_atom.uses_integrity)
return
// Spicy!
holder.my_atom.take_damage(volume / 3, BURN, FIRE, FALSE)
return SPARK_ACT_NON_DESTRUCTIVE | SPARK_ACT_KEEP_REAGENT
var/turf/our_turf = get_turf(holder.my_atom)
our_turf?.hotspot_expose(holder.chem_temp * 50, volume)
return SPARK_ACT_NON_DESTRUCTIVE
/datum/reagent/napalm
name = "Napalm"
description = "Very flammable."
@@ -201,7 +306,19 @@
/datum/reagent/napalm/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
. = ..()
if(istype(exposed_mob) && (methods & (TOUCH|VAPOR|PATCH)))
exposed_mob.adjust_fire_stacks(min(reac_volume/4, 20))
exposed_mob.adjust_fire_stacks(min(reac_volume / 4, 20))
/datum/reagent/napalm/on_spark_act(power_charge, enclosed)
if (enclosed && !ismob(holder.my_atom))
if (!holder.my_atom.uses_integrity)
return
// Spicy!
holder.my_atom.take_damage(volume / 5, BURN, FIRE, FALSE)
return SPARK_ACT_NON_DESTRUCTIVE | SPARK_ACT_KEEP_REAGENT
var/turf/our_turf = get_turf(holder.my_atom)
our_turf?.hotspot_expose(round(holder.chem_temp * 30 * (1 + sqrt(power_charge / STANDARD_CELL_CHARGE)), 1), volume)
return SPARK_ACT_NON_DESTRUCTIVE
#define CRYO_SPEED_PREFACTOR 0.4
#define CRYO_SPEED_CONSTANT 0.1
@@ -328,6 +445,11 @@
var/mob/living/carbon/human/affected_human = affected_mob
affected_human.physiology.siemens_coeff *= 0.5
/datum/reagent/teslium/on_spark_act(power_charge, enclosed)
tesla_zap(source = holder.my_atom, zap_range = round(volume / 5, 1), power = volume * 20 + power_charge, cutoff = 1 KILO JOULES, zap_flags = ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN | ZAP_LOW_POWER_GEN)
playsound(holder.my_atom, 'sound/machines/defib/defib_zap.ogg', 50, TRUE)
return SPARK_ACT_NON_DESTRUCTIVE
/datum/reagent/teslium/energized_jelly
name = "Energized Jelly"
description = "Electrically-charged jelly. Boosts jellypeople's nervous system, but only shocks other lifeforms."
@@ -81,6 +81,7 @@
#define LIQUID_PLASMA_BP (50+T0C)
#define LIQUID_PLASMA_IG (325+T0C)
#define LIQUID_PLASMA_CHARGE_COEFF 2.7
/datum/reagent/toxin/plasma
name = "Plasma"
@@ -141,15 +142,30 @@
exposed_turf.atmos_spawn_air("[GAS_PLASMA]=[reac_volume];[TURF_TEMPERATURE(temp)]")
return ..()
#undef LIQUID_PLASMA_BP
#undef LIQUID_PLASMA_IG
/datum/reagent/toxin/plasma/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)//Splashing people with plasma is stronger than fuel!
// Splashing people with plasma is stronger than fuel!
/datum/reagent/toxin/plasma/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
. = ..()
if(methods & (TOUCH|VAPOR))
exposed_mob.adjust_fire_stacks(reac_volume / 5)
return
/datum/reagent/toxin/plasma/on_spark_act(power_charge, enclosed)
// If we have any stabilizing agent in the mix, we need 0.2% of a standard cell value per mol of agent to be spent at once to blow
// This should allow for some more creative traps to be made with plasma
var/agent_volume = holder.get_reagent_amount(/datum/reagent/stabilizing_agent)
if (agent_volume && power_charge < agent_volume * 0.002 * STANDARD_CELL_CHARGE)
return NONE
// Plasma explosions become stronger with higher current, and don't care about if they're enclosed or not
var/power_modifier = max(0, round(power_charge / STANDARD_CELL_CHARGE * LIQUID_PLASMA_CHARGE_COEFF, 1) - 1)
reagent_explode(holder, volume, modifier = power_modifier, strengthdiv = 5, clear_holder_reagents = FALSE)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
#undef LIQUID_PLASMA_BP
#undef LIQUID_PLASMA_IG
#undef LIQUID_PLASMA_CHARGE_COEFF
/datum/reagent/toxin/hot_ice
name = "Hot Ice Slush"
description = "Frozen plasma, worth its weight in gold, to the right people."
+48 -32
View File
@@ -278,52 +278,68 @@
*
* arguments:
* * holder - the reagents datum that it is being used on
* * created_volume - the volume of reacting elements
* * volume - the volume of reacting elements
* * modifier - a flat additive numeric to the size of the explosion - set this if you want a minimum range
* * strengthdiv - the divisional factor of the explosion, a larger number means a smaller range - This is the part that modifies an explosion's range with volume (i.e. it divides it by this number)
* * clear_mob_reagents - should our mob be purged of their reagents?
* * clear_holder_reagents - should the reagent holder be fully purged? Heavily recommended to be TRUE, unless you have code handling that elsewhere down the line for whatever reason
* * flame_factor - multiplier to flame range of the explosion
* * flash_factor - multiplier to flash range of the explosion
*/
/datum/chemical_reaction/proc/default_explode(datum/reagents/holder, created_volume, modifier = 0, strengthdiv = 10, clear_mob_reagents)
var/power = modifier + round(created_volume/strengthdiv, 1)
if(power > 0)
var/turf/T = get_turf(holder.my_atom)
/proc/reagent_explode(datum/reagents/holder, volume, modifier = 0, strengthdiv = 10, clear_mob_reagents = FALSE, clear_holder_reagents = TRUE, flash_factor = null, flame_factor = null)
if (QDELETED(holder))
return
var/power = modifier + round(volume / strengthdiv, 1)
if (power > 0)
var/turf/our_turf = get_turf(holder.my_atom)
var/inside_msg
if(ismob(holder.my_atom))
var/mob/M = holder.my_atom
inside_msg = " inside [ADMIN_LOOKUPFLW(M)]"
if (ismob(holder.my_atom))
var/mob/owner = holder.my_atom
inside_msg = " inside [ADMIN_LOOKUPFLW(owner)]"
var/lastkey = holder.my_atom.fingerprintslast //This can runtime (null.fingerprintslast) - due to plumbing?
var/touch_msg = "N/A"
if(lastkey)
if (lastkey)
var/mob/toucher = get_mob_by_key(lastkey)
touch_msg = "[ADMIN_LOOKUPFLW(toucher)]"
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, 0, 0)
e.start(holder.my_atom)
if (ismob(holder.my_atom))
if(!clear_mob_reagents)
return
// Only clear reagents if they use a special explosive reaction to do it; it shouldn't apply
// to any explosion inside a person
holder.clear_reagents()
if(iscarbon(holder.my_atom))
var/mob/living/carbon/victim = holder.my_atom
var/vomit_flags = MOB_VOMIT_MESSAGE | MOB_VOMIT_FORCE
// The vomiting here is for effect, not meant to help with purging
victim.vomit(vomit_flags, distance = 5)
// Not quite the same if the reaction is in their stomach; they'll throw up
// from any explosion, but it'll only make them puke up everything in their
// stomach
else if (istype(holder.my_atom, /obj/item/organ/stomach))
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)
explosion_system.start(holder.my_atom)
if (istype(holder.my_atom, /obj/item/organ/stomach))
var/obj/item/organ/stomach/indigestion = holder.my_atom
if(power < 1)
return
indigestion.owner?.vomit(MOB_VOMIT_MESSAGE | MOB_VOMIT_FORCE, lost_nutrition = 150, distance = 5, purge_ratio = 1)
if (clear_mob_reagents)
indigestion.owner?.vomit(MOB_VOMIT_MESSAGE | MOB_VOMIT_FORCE, lost_nutrition = 150, distance = 5, purge_ratio = 1)
if (clear_holder_reagents)
holder.clear_reagents()
return
if (!ismob(holder.my_atom))
holder.clear_reagents()
return
else
// Not quite the same if the reaction is in their stomach; they'll throw up
// from any explosion, but it'll only make them puke up everything in their
// stomach
if (!clear_mob_reagents)
return
// Only clear reagents if they use a special explosive reaction to do it; it shouldn't apply
// to any explosion inside a person
if (clear_holder_reagents)
holder.clear_reagents()
if (iscarbon(holder.my_atom))
var/mob/living/carbon/victim = holder.my_atom
var/vomit_flags = MOB_VOMIT_MESSAGE | MOB_VOMIT_FORCE
// The vomiting here is for effect, not meant to help with purging
victim.vomit(vomit_flags, distance = 5)
/*
*Creates a flash effect only - less expensive than explode()
*
@@ -73,7 +73,7 @@
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, 0, 0)
e.set_up(power, T)
e.start(holder.my_atom)
holder.clear_reagents()
@@ -222,10 +222,10 @@
purity_min = 0.32
/datum/chemical_reaction/medicine/ephedrine/overheated(datum/reagents/holder, datum/equilibrium/equilibrium, vol_added)
default_explode(holder, equilibrium.reacted_vol, 0, 25)
reagent_explode(holder, equilibrium.reacted_vol, 0, 25)
/datum/chemical_reaction/medicine/ephedrine/overly_impure(datum/reagents/holder, datum/equilibrium/equilibrium, vol_added)
default_explode(holder, equilibrium.reacted_vol, 0, 20)
reagent_explode(holder, equilibrium.reacted_vol, 0, 20)
/datum/chemical_reaction/medicine/diphenhydramine
results = list(/datum/reagent/medicine/diphenhydramine = 4)
@@ -17,30 +17,39 @@
var/clear_mob_reagents = FALSE
/datum/chemical_reaction/reagent_explosion/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume, clear_mob_reagents)
if (!ismob(holder.my_atom))
reagent_explode(holder, created_volume, modifier, strengthdiv, clear_mob_reagents)
return
// If an explosive reaction clears mob reagents, it should always be a minimum power
if(ismob(holder.my_atom) && clear_mob_reagents)
if(clear_mob_reagents)
if(round((created_volume / strengthdiv) + modifier, 1) < 1)
modifier += 1 - ((created_volume / strengthdiv) + modifier)
reagent_explode(holder, created_volume, modifier, strengthdiv, clear_mob_reagents)
return
// If this particular explosion doesn't automatically clear mob reagents as an inherent quality,
// then we can still clear mob reagents with some mad science malpractice that shouldn't work but
// does because omnizine is magic and also it's the future or whatever
if(ismob(holder.my_atom) && !clear_mob_reagents)
// The explosion needs to be a minimum power to clear reagents: see above
var/purge_power = round((created_volume / strengthdiv) + modifier, 1)
if(purge_power >= 1)
var/has_purging_chemical = FALSE
// They need one of the purge reagents in them
for(var/purging_chem in PURGING_REAGENTS)
if(holder.has_reagent(purging_chem))
// We have a purging chemical
has_purging_chemical = TRUE
break
// Then we need omnizine! MAGIC!
var/has_omnizine = holder.has_reagent(/datum/reagent/medicine/omnizine)
if(has_purging_chemical && has_omnizine)
// With all this medical "science" combined, we can clear mob reagents
clear_mob_reagents = TRUE
default_explode(holder, created_volume, modifier, strengthdiv, clear_mob_reagents)
// The explosion needs to be a minimum power to clear reagents: see above
var/purge_power = round((created_volume / strengthdiv) + modifier, 1)
if(purge_power < 1)
reagent_explode(holder, created_volume, modifier, strengthdiv, clear_mob_reagents)
return
var/has_purging_chemical = FALSE
// They need one of the purge reagents in them
for(var/purging_chem in PURGING_REAGENTS)
if(holder.has_reagent(purging_chem))
// We have a purging chemical
has_purging_chemical = TRUE
break
// Then we need omnizine! MAGIC!
var/has_omnizine = holder.has_reagent(/datum/reagent/medicine/omnizine)
if(has_purging_chemical && has_omnizine)
// With all this medical "science" combined, we can clear mob reagents
clear_mob_reagents = TRUE
reagent_explode(holder, created_volume, modifier, strengthdiv, clear_mob_reagents)
#undef PURGING_REAGENTS
/datum/chemical_reaction/reagent_explosion/nitroglycerin
@@ -62,7 +71,7 @@
/datum/chemical_reaction/reagent_explosion/rdx
results = list(/datum/reagent/rdx= 2)
required_reagents = list(/datum/reagent/phenol = 2, /datum/reagent/toxin/acid/nitracid = 1, /datum/reagent/acetone_oxide = 1 )
required_reagents = list(/datum/reagent/phenol = 2, /datum/reagent/toxin/acid/nitracid = 1, /datum/reagent/acetone_oxide = 1)
required_catalysts = list(/datum/reagent/gold) //royal explosive
required_temp = 404
strengthdiv = 8
@@ -199,7 +208,8 @@
mix_message = span_boldnotice("Sparks start flying around the gunpowder!")
/datum/chemical_reaction/reagent_explosion/gunpowder_explosion/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume)
addtimer(CALLBACK(src, PROC_REF(default_explode), holder, created_volume, modifier, strengthdiv), rand(5 SECONDS, 10 SECONDS))
do_sparks(2, TRUE, get_turf(holder.my_atom))
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reagent_explode), holder, created_volume, modifier, strengthdiv), rand(5 SECONDS, 10 SECONDS))
/datum/chemical_reaction/thermite
results = list(/datum/reagent/thermite = 3)
@@ -391,6 +401,12 @@
S.start(log = TRUE)
if(holder?.my_atom)
holder.clear_reagents()
if (!iscarbon(holder?.my_atom))
return
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, created_volume / 5)
/datum/chemical_reaction/smoke_powder_smoke
required_reagents = list(/datum/reagent/smoke_powder = 1)
@@ -409,6 +425,14 @@
S.start(log = TRUE)
if(holder?.my_atom)
holder.clear_reagents()
if(holder?.my_atom)
holder.clear_reagents()
if (!iscarbon(holder?.my_atom))
return
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, created_volume / 10)
/datum/chemical_reaction/sonic_powder
results = list(/datum/reagent/sonic_powder = 3)
@@ -603,7 +627,7 @@
added_delay += 1.5 SECONDS
if(created_volume >= 10) //10 units minimum for lightning, 40 units for secondary blast, 75 units for tertiary blast.
addtimer(CALLBACK(src, PROC_REF(zappy_zappy), holder, T3), added_delay)
addtimer(CALLBACK(src, PROC_REF(default_explode), holder, created_volume, modifier, strengthdiv), added_delay)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reagent_explode), holder, created_volume, modifier, strengthdiv), added_delay)
/datum/chemical_reaction/reagent_explosion/teslium_lightning/proc/zappy_zappy(datum/reagents/holder, power)
var/atom/holder_atom = holder.my_atom