From c74f104c899b026d830ffcb0120a372fc66896ef Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Wed, 21 Jan 2026 20:21:06 +0100 Subject: [PATCH] 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 :cl: 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. /:cl: --- _maps/map_files/wawastation/wawastation.dmm | 2 +- code/__DEFINES/reagents.dm | 14 ++ code/controllers/subsystem/explosions.dm | 5 +- code/game/machinery/_machinery.dm | 4 +- .../effects/effect_system/effects_other.dm | 11 +- code/game/objects/items/cigarettes.dm | 20 +-- code/game/objects/items/lighter.dm | 6 +- .../items/tools/engineering/weldingtool.dm | 9 +- .../modules/antagonists/heretic/influences.dm | 2 +- .../heretic/knowledge/moon_lore.dm | 2 +- code/modules/hydroponics/plant_genes.dm | 4 - code/modules/mob/living/living_defense.dm | 2 +- code/modules/power/lighting/light.dm | 9 ++ code/modules/power/power_store.dm | 64 +++++---- .../reagents/chemistry/holder/holder.dm | 28 ++++ code/modules/reagents/chemistry/reagents.dm | 11 ++ .../chemistry/reagents/other_reagents.dm | 11 +- .../reagents/pyrotechnic_reagents.dm | 128 +++++++++++++++++- .../chemistry/reagents/toxin_reagents.dm | 22 ++- code/modules/reagents/chemistry/recipes.dm | 80 ++++++----- .../reagents/chemistry/recipes/drugs.dm | 2 +- .../reagents/chemistry/recipes/medicine.dm | 4 +- .../chemistry/recipes/pyrotechnics.dm | 66 ++++++--- code/modules/research/part_replacer.dm | 8 +- 24 files changed, 374 insertions(+), 140 deletions(-) diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index d1d71be9d4b..9fd655eda86 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -62053,7 +62053,7 @@ /obj/item/stock_parts/power_store/cell/bluespace{ pixel_x = -5; pixel_y = -8; - rigged = 1 + corrupted = 1 }, /turf/open/misc/asteroid, /area/station/asteroid) diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm index f0629886ef2..80413080462 100644 --- a/code/__DEFINES/reagents.dm +++ b/code/__DEFINES/reagents.dm @@ -296,3 +296,17 @@ /// Cooldown between patch reagent messages #define PATCH_MESSAGE_COOLDOWN 10 SECONDS + +// on_spark_act returns +/// We caused a non-destructive reaction +#define SPARK_ACT_NON_DESTRUCTIVE (1 << 0) +/// We caused a destructive action or an explosion, the holder should probably delete itself +#define SPARK_ACT_DESTRUCTIVE (1 << 1) +/// Don't remove this reagent, even if it reacted to spark_act +#define SPARK_ACT_KEEP_REAGENT (1 << 2) +/// Remove all reagents even if they didn't react and stop the processing +/// Usually means an explosion or a destroyed parent +#define SPARK_ACT_CLEAR_ALL (1 << 3) + +/// Valid values to be returned from reagent holder's spark_act +#define SPARK_ACT_RETURNS (SPARK_ACT_NON_DESTRUCTIVE | SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL) diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm index f8062a8a224..4ca6932cb3c 100644 --- a/code/controllers/subsystem/explosions.dm +++ b/code/controllers/subsystem/explosions.dm @@ -169,6 +169,8 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec * 5 explosion power is a (0, 1, 3) explosion. * 1 explosion power is a (0, 0, 1) explosion. * + * The easy formula for defaults is sqrt(power * 2) * (0.25 | 0.5 | 1) + * * Arguments: * * epicenter: Turf the explosion is centered at. * * power - Dyn explosion power. See reference above. @@ -176,7 +178,6 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec * * flash_range: The range at which the explosion flashes people. Equal to the equivalent of the light impact range multiplied by this value. * * adminlog: Whether to log the explosion/report it to the administration. * * ignorecap: Whether to ignore the relevant bombcap. Defaults to FALSE. - * * flame_range: The range at which the explosion should produce hotspots. * * silent: Whether to generate/execute sound effects. * * smoke: Whether to generate a smoke cloud provided the explosion is powerful enough to warrant it. * * explosion_cause: [Optional] The atom that caused the explosion, when different to the origin. Used for logging. @@ -186,7 +187,7 @@ ADMIN_VERB(check_bomb_impacts, R_DEBUG, "Check Bomb Impact", "See what the effec return var/range = 0 range = round((2 * power)**GLOB.DYN_EX_SCALE) - explosion(epicenter, devastation_range = round(range * 0.25), heavy_impact_range = round(range * 0.5), light_impact_range = round(range), flame_range = flame_range*range, flash_range = flash_range*range, adminlog = adminlog, ignorecap = ignorecap, silent = silent, smoke = smoke, explosion_cause = explosion_cause) + explosion(epicenter, devastation_range = round(range * 0.25), heavy_impact_range = round(range * 0.5), light_impact_range = round(range), flame_range = isnull(flame_range) ? null : flame_range * range, flash_range = isnull(flash_range) ? null : flash_range * range, adminlog = adminlog, ignorecap = ignorecap, silent = silent, smoke = smoke, explosion_cause = explosion_cause) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 2b2d1dd7918..fdf6806046a 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -1092,9 +1092,7 @@ if(istype(secondary_part, /obj/item/stock_parts/power_store/cell) && works_from_distance) var/obj/item/stock_parts/power_store/cell/checked_cell = secondary_part // If it's rigged or corrupted, max the charge. Then explode it. - if(checked_cell.rigged || checked_cell.corrupted) - checked_cell.charge = checked_cell.maxcharge - checked_cell.explode() + if(checked_cell.try_explode(max_charge = TRUE)) break if(secondary_part.get_part_rating() > current_rating) //store name of part incase we qdel it below diff --git a/code/game/objects/effects/effect_system/effects_other.dm b/code/game/objects/effects/effect_system/effects_other.dm index fd41f739e33..c47f4684283 100644 --- a/code/game/objects/effects/effect_system/effects_other.dm +++ b/code/game/objects/effects/effect_system/effects_other.dm @@ -88,20 +88,19 @@ /datum/effect_system/reagents_explosion var/amount // TNT equivalent - var/flashing = FALSE // does explosion creates flash effect? - var/flashing_factor = 0 // factor of how powerful the flash effect relatively to the explosion + 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 = FALSE, flash_fact = 0, message = TRUE) +/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 = flash 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) @@ -111,4 +110,4 @@ 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, explosion_cause = explosion_source) + dyn_explosion(location, amount, flash_range = flashing_factor, flame_range = flaming_factor, explosion_cause = explosion_source) diff --git a/code/game/objects/items/cigarettes.dm b/code/game/objects/items/cigarettes.dm index 5275f69df20..7e008a750d3 100644 --- a/code/game/objects/items/cigarettes.dm +++ b/code/game/objects/items/cigarettes.dm @@ -426,6 +426,12 @@ CIGARETTE PACKETS ARE IN FANCY.DM damtype = BURN force = 4 + if(reagents?.spark_act(0, FALSE, banned_reagents = /datum/reagent/flash_powder) & SPARK_ACT_DESTRUCTIVE) + usr?.log_message("lit a rigged cigarette", LOG_VICTIM) + qdel(src) + return + + // Custom handling for the hallucination effect if(reagents?.has_reagent(/datum/reagent/flash_powder)) if(!isliving(loc)) loc.visible_message(span_hear("\The [src] burns up!")) @@ -442,18 +448,6 @@ CIGARETTE PACKETS ARE IN FANCY.DM if(reagents?.has_reagent(/datum/reagent/drug/methamphetamine)) reagents.flags |= NO_REACT - if(reagents?.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) - e.start(src) - qdel(src) - return - if(reagents?.get_reagent_amount(/datum/reagent/fuel)) // the fuel explodes, too, but much less violently - var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/fuel) / 5, 1), get_turf(src), 0, 0) - e.start(src) - qdel(src) - return // allowing reagents to react after being lit update_appearance(UPDATE_ICON) if(flavor_text) @@ -1178,7 +1172,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire var/datum/effect_system/reagents_explosion/e = new() - e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0, 0) + e.set_up(round(reagents.get_reagent_amount(/datum/reagent/toxin/plasma) / 2.5, 1), get_turf(src), 0) e.start(src) qdel(src) diff --git a/code/game/objects/items/lighter.dm b/code/game/objects/items/lighter.dm index bd87f67ea22..1bf94097f32 100644 --- a/code/game/objects/items/lighter.dm +++ b/code/game/objects/items/lighter.dm @@ -245,6 +245,10 @@ if(!lit) return FALSE + if (reagents.spark_act(0, TRUE, banned_reagents = /datum/reagent/fuel) & SPARK_ACT_DESTRUCTIVE) + qdel(src) + return FALSE + if(used > 0) burned_fuel_for = 0 @@ -257,7 +261,7 @@ ///Returns the amount of fuel /obj/item/lighter/proc/get_fuel() - return reagents.get_reagent_amount(/datum/reagent/fuel) + return reagents.get_reagent_amount(/datum/reagent/fuel) + reagents.get_reagent_amount(/datum/reagent/toxin/plasma) /obj/item/lighter/greyscale name = "cheap lighter" diff --git a/code/game/objects/items/tools/engineering/weldingtool.dm b/code/game/objects/items/tools/engineering/weldingtool.dm index 142bf0be111..ec2412ca835 100644 --- a/code/game/objects/items/tools/engineering/weldingtool.dm +++ b/code/game/objects/items/tools/engineering/weldingtool.dm @@ -119,11 +119,6 @@ . = ..() update_appearance() -/obj/item/weldingtool/proc/explode() - var/plasmaAmount = reagents.get_reagent_amount(/datum/reagent/toxin/plasma) - dyn_explosion(src, plasmaAmount/5, explosion_cause = src) // 20 plasma in a standard welder has a 4 power explosion. no breaches, but enough to kill/dismember holder - qdel(src) - /obj/item/weldingtool/cyborg_unequip(mob/user) if(!isOn()) return @@ -189,10 +184,10 @@ user.log_message("set [key_name(attacked_mob)] on fire with [src].", LOG_ATTACK) /obj/item/weldingtool/attack_self(mob/user) - if(reagents.has_reagent(/datum/reagent/toxin/plasma)) + if(reagents.spark_act(0, TRUE, banned_reagents = /datum/reagent/fuel) & SPARK_ACT_DESTRUCTIVE) message_admins("[ADMIN_LOOKUPFLW(user)] activated a rigged welder at [AREACOORD(user)].") user.log_message("activated a rigged welder", LOG_VICTIM) - explode() + qdel(src) return switched_on(user) diff --git a/code/modules/antagonists/heretic/influences.dm b/code/modules/antagonists/heretic/influences.dm index 3c9af66ca24..659ed72854b 100644 --- a/code/modules/antagonists/heretic/influences.dm +++ b/code/modules/antagonists/heretic/influences.dm @@ -148,7 +148,7 @@ human_user.gib(DROP_ALL_REMAINS) human_user.investigate_log("has died from using telekinesis on a heretic influence.", INVESTIGATE_DEATHS) var/datum/effect_system/reagents_explosion/explosion = new() - explosion.set_up(1, get_turf(human_user), TRUE, 0) + explosion.set_up(1, get_turf(human_user), 1) explosion.start(src) /obj/effect/visible_heretic_influence/examine(mob/living/user) diff --git a/code/modules/antagonists/heretic/knowledge/moon_lore.dm b/code/modules/antagonists/heretic/knowledge/moon_lore.dm index 5ceb818ded5..f099050ecfb 100644 --- a/code/modules/antagonists/heretic/knowledge/moon_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/moon_lore.dm @@ -304,7 +304,7 @@ if(!head?.dismember()) carbon_view.gib(DROP_ALL_REMAINS) var/datum/effect_system/reagents_explosion/explosion = new() - explosion.set_up(1, get_turf(carbon_view), TRUE, 0) + explosion.set_up(1, get_turf(carbon_view), 1) explosion.start(src) else attempt_conversion(carbon_view, source) diff --git a/code/modules/hydroponics/plant_genes.dm b/code/modules/hydroponics/plant_genes.dm index 76892cd2870..4437353b1d3 100644 --- a/code/modules/hydroponics/plant_genes.dm +++ b/code/modules/hydroponics/plant_genes.dm @@ -624,10 +624,6 @@ pocell.charge = pocell.maxcharge pocell.name = "[our_plant.name] battery" pocell.desc = "A rechargeable plant-based power cell. This one has a rating of [display_energy(pocell.maxcharge)], and you should not swallow it." - - if(our_plant.reagents.has_reagent(/datum/reagent/toxin/plasma, 2)) - pocell.rigged = TRUE - qdel(our_plant) /* diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 52cd8ec6ad1..bafaeafe7d4 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -668,7 +668,7 @@ return TRUE //called when the mob receives a loud bang -/mob/living/proc/soundbang_act(intensity = SOUNDBANG_NORMAL, stun_pwr = 20, damage_pwr = 5, deafen_pwr = 15, ignore_deafness = FALSE, send_sound = TRUE) +/mob/living/proc/soundbang_act(intensity = SOUNDBANG_NORMAL, stun_pwr = 2 SECONDS, damage_pwr = 5, deafen_pwr = 1.5 SECONDS, ignore_deafness = FALSE, send_sound = TRUE) var/protection = get_ear_protection(ignore_deafness) if(protection >= intensity) return FALSE diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm index c4265c35e95..2f268d5746f 100644 --- a/code/modules/power/lighting/light.dm +++ b/code/modules/power/lighting/light.dm @@ -231,6 +231,7 @@ if(LIGHT_BROKEN,LIGHT_BURNED,LIGHT_EMPTY) on = FALSE low_power_mode = FALSE + if(on) var/brightness_set = brightness var/power_set = bulb_power @@ -239,6 +240,10 @@ color_set = color if(reagents || !is_full_charge()) START_PROCESSING(SSmachines, src) + if (reagents.spark_act(active_power_usage, TRUE) & SPARK_ACT_DESTRUCTIVE) + message_admins("A rigged lightbulb at [AREACOORD(src)] has exploded.") + qdel(src) + return var/area/local_area = get_room_area() if (flickering) brightness_set = brightness * bulb_low_power_brightness_mul @@ -274,6 +279,10 @@ else if(has_emergency_power(LIGHT_EMERGENCY_POWER_USE * SSMACHINES_DT) && !turned_off()) use_power = IDLE_POWER_USE low_power_mode = TRUE + if (reagents?.spark_act(idle_power_usage, TRUE) & SPARK_ACT_DESTRUCTIVE) + message_admins("A rigged lightbulb at [AREACOORD(src)] has exploded.") + qdel(src) + return START_PROCESSING(SSmachines, src) else use_power = IDLE_POWER_USE diff --git a/code/modules/power/power_store.dm b/code/modules/power/power_store.dm index dee7151f523..adca94543e0 100644 --- a/code/modules/power/power_store.dm +++ b/code/modules/power/power_store.dm @@ -18,9 +18,7 @@ var/rating_base = STANDARD_CELL_CHARGE ///Maximum charge in cell units var/maxcharge = STANDARD_CELL_CHARGE - ///If the cell has been booby-trapped by injecting it with plasma. Chance on use() to explode. - var/rigged = FALSE - ///If the power cell was damaged by an explosion, chance for it to become corrupted and function the same as rigged. + ///If the power cell was damaged by an explosion, chance for it to become corrupted and function the same as if it was rigged with plasma. var/corrupted = FALSE ///How much power is given per second in a recharger. var/chargerate = STANDARD_CELL_RATE * 0.05 @@ -43,7 +41,7 @@ /obj/item/stock_parts/power_store/get_save_vars() . = ..() . += NAMEOF(src, charge) - . += NAMEOF(src, rigged) + . += NAMEOF(src, corrupted) return . /obj/item/stock_parts/power_store/Initialize(mapload, override_maxcharge) @@ -107,10 +105,6 @@ return . -/obj/item/stock_parts/power_store/create_reagents(max_vol, flags) - . = ..() - RegisterSignal(reagents, COMSIG_REAGENTS_HOLDER_UPDATED, PROC_REF(on_reagent_change)) - /obj/item/stock_parts/power_store/update_overlays() . = ..() if(grown_battery) @@ -163,8 +157,7 @@ /// Returns: The power used from the cell in joules. /obj/item/stock_parts/power_store/use(used, force = FALSE) var/power_used = min(used, charge) - if(rigged && power_used > 0) - explode() + if(power_used > 0 && try_explode()) return 0 // The cell decided to explode so we won't be able to use it. if(!force && charge < used) return 0 @@ -180,8 +173,8 @@ /obj/item/stock_parts/power_store/proc/give(amount) var/power_used = min(maxcharge-charge,amount) charge += power_used - if(rigged && amount > 0) - explode() + if (amount) + try_explode() return power_used /** @@ -193,46 +186,51 @@ /obj/item/stock_parts/power_store/proc/change(amount) var/energy_used = clamp(amount, -charge, maxcharge - charge) charge += energy_used - if(rigged && energy_used) - explode() + if(energy_used) + try_explode() return energy_used /obj/item/stock_parts/power_store/examine(mob/user) . = ..() - if(rigged) + if(corrupted) . += span_danger("This [name] seems to be faulty!") else if(!isnull(charge_light_type)) . += "The charge meter reads [CEILING(percent(), 0.1)]%." //so it doesn't say 0% charge when the overlay indicates it still has charge -/obj/item/stock_parts/power_store/proc/on_reagent_change(datum/reagents/holder) - SIGNAL_HANDLER +/obj/item/stock_parts/power_store/proc/try_explode(max_charge = FALSE) + var/check_charge = charge + if (max_charge) + check_charge = maxcharge - rigged = corrupted || !!holder.has_reagent(/datum/reagent/toxin/plasma, 5) //has_reagent returns the reagent datum + if(!check_charge) + return FALSE -/obj/item/stock_parts/power_store/proc/explode() - if(!charge) - return - var/range_devastation = -1 - var/range_heavy = round(sqrt(charge / (3.6 * rating_base))) - var/range_light = round(sqrt(charge / (0.9 * rating_base))) - var/range_flash = range_light + if (!corrupted) + if (!(reagents?.spark_act(check_charge, TRUE) & SPARK_ACT_DESTRUCTIVE)) + return FALSE + message_admins("[ADMIN_LOOKUPFLW(usr)] has triggered a rigged power cell explosion at [AREACOORD(loc)].") + usr?.log_message("triggered a rigged power cell explosion", LOG_GAME) + usr?.log_message("triggered a rigged power cell explosion", LOG_VICTIM, log_globally = FALSE) + qdel(src) + return TRUE + + var/range_heavy = round(sqrt(check_charge / (3.6 * rating_base))) + var/range_light = round(sqrt(check_charge / (0.9 * rating_base))) if(!range_light) - rigged = FALSE corrupt() - return + return FALSE - message_admins("[ADMIN_LOOKUPFLW(usr)] has triggered a rigged/corrupted power cell explosion at [AREACOORD(loc)].") - usr?.log_message("triggered a rigged/corrupted power cell explosion", LOG_GAME) - usr?.log_message("triggered a rigged/corrupted power cell explosion", LOG_VICTIM, log_globally = FALSE) - - explosion(src, devastation_range = range_devastation, heavy_impact_range = range_heavy, light_impact_range = range_light, flash_range = range_flash) + message_admins("[ADMIN_LOOKUPFLW(usr)] has triggered a corrupted power cell explosion at [AREACOORD(loc)].") + usr?.log_message("triggered a corrupted power cell explosion", LOG_GAME) + usr?.log_message("triggered a corrupted power cell explosion", LOG_VICTIM, log_globally = FALSE) + explosion(src, devastation_range = -1, heavy_impact_range = range_heavy, light_impact_range = range_light, flash_range = range_light) qdel(src) + return TRUE /obj/item/stock_parts/power_store/proc/corrupt(force) charge /= 2 maxcharge = max(maxcharge/2, chargerate) if (force || prob(10)) - rigged = TRUE //broken batterys are dangerous corrupted = TRUE /obj/item/stock_parts/power_store/emp_act(severity) diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm index 3d50d5c1e28..a03c360e328 100644 --- a/code/modules/reagents/chemistry/holder/holder.dm +++ b/code/modules/reagents/chemistry/holder/holder.dm @@ -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. diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm index b2bf032d208..273268f1c30 100644 --- a/code/modules/reagents/chemistry/reagents.dm +++ b/code/modules/reagents/chemistry/reagents.dm @@ -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. * diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 0fec8f117c9..e55e1b33b32 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -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." diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index cc45a67b64a..d2895ccb683 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -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." diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index 1c49c7bd06b..0d6988abb69 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -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." diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index c6ecac2eb64..e07c14d0484 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -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() * diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm index ad73329cf15..f4f89f6af36 100644 --- a/code/modules/reagents/chemistry/recipes/drugs.dm +++ b/code/modules/reagents/chemistry/recipes/drugs.dm @@ -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() diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index 66cc645924c..9ebad8f2900 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -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) diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 3e3e7d3e3e9..876bfc6d78e 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -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 diff --git a/code/modules/research/part_replacer.dm b/code/modules/research/part_replacer.dm index fea7c8211d0..2ac29877ead 100644 --- a/code/modules/research/part_replacer.dm +++ b/code/modules/research/part_replacer.dm @@ -90,10 +90,10 @@ if(istype(inserted_component, /obj/item/stock_parts/power_store)) var/obj/item/stock_parts/power_store/inserted_cell = inserted_component - if(inserted_cell.rigged || inserted_cell.corrupted) - message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].") - usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME) - usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK) + if(inserted_cell.corrupted) + message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted corrupted [inserted_cell] into [src].") + usr.log_message("has inserted corrupted [inserted_cell] into [src].", LOG_GAME) + usr.log_message("inserted corrupted [inserted_cell] into [src]", LOG_ATTACK) return var/datum/reagents/target_holder = inserted_component.reagents