Readds sovlful IEDs and adds assembly beakerbombs (#94965)

## About The Pull Request

This PR sort-of brings back old IEDs, albeit much more freeform compared
to their previous iteration. Using #94861's ``spark_act`` interactions
they can now be made by filling up a soda can with a variety of
chemicals (welding fuel being the easiest to obtain), dunking in a piece
of wire, taping it up (optional, required if using welding fuel or
plasma) and lighting it on fire with a lighter or welding tool (anything
that's hot enough works, really)
They have a random delay of 2-4 seconds, and can be disarmed by snipping
the fuse with wirecutters in time before they detonate (or don't,
depending on the mixture)

There's also a new, more "professional" improvised chemical explosive in
the form of beakerbombs. These can be assembled by putting a lid on a
beaker (alt-click, prevents the beaker from spilling its contents when
thrown) and attaching an assembly with an igniter or a condenser to it.

<img width="150" height="90" alt="dreamseeker_2OKpZeN7ay"
src="https://github.com/user-attachments/assets/599e75ea-9653-4db9-a915-b2ca6307635f"
/>

When triggered, igniters will heat the reagents up by a bit while
condensers will cool them down. However, you can also attach and wire up
a power cell, which will cause it to dump all of its power into the
beaker when the igniter fires off, triggering ``spark_act`` interactions
potentially causing a larger explosion.

<img width="534" height="447" alt="dreamseeker_k9Uzf18Eoa"
src="https://github.com/user-attachments/assets/4cfed5b4-3875-4522-9bfa-5f6a3c8e0864"
/>

Decently sized boom from The Contraption.

Plasma and welding fuel's potency inside of beakerbombs/soda cans is
significantly reduced compared to other riggable objects (strengthdiv is
3 times higher) as they're very easy to obtain and would make for very
powerful explosives, considering their very strong strengthdiv as most
rigging interactions use very little fuel/plasma to cause a large
explosion, which was carried over to the new system. (Don't worry, this
still leaves them at sensible values with welding fuel being about as
strong as old IEDs on average)

Closes #94990 via having plasma's electrical power modifier have a
decline past a certain point based on its volume

## Why It's Good For The Game

#81529 didn't justify IED removal whatsoever and I think with new
mechanics these can be used as easier (but weaker) to make improvised
bombs, being much less clunky to make and use than pipebombs.
Beakerbombs are essentially a somewhat weaker (even with metamat
beakers, you're still 20u short of a normal large beaker grenade at
200u) form of grenades, but have access to new interactions involving
charged up explosions, which could make for some variety among chemists'
weaponry.

## Changelog
🆑
add: Added back IEDs made by attaching some wire and tape to a soda can
filled with fuel, plasma, or any other explosive of your choice. They
need to be lit on fire with a lighter or a welding tool.
add: You can now attach a lid to beakers with alt-click, preventing them
from being spilled when thrown.
add: Added beakerbombs, made by attaching an assembly (with optional
power cell and wiring) to a lidded beaker.
balance: Rigged explosions now create flames.
balance: Plasma explosions now limit their explosion potency past a
certain point based on their volume
/🆑
This commit is contained in:
SmArtKar
2026-01-28 09:11:50 +13:00
committed by GitHub
parent c2e7290580
commit dbadf01a35
23 changed files with 460 additions and 84 deletions
@@ -792,10 +792,10 @@
* 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
* * spark_flags - Set of flags describing the interaction
* * 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)
/datum/reagents/proc/spark_act(power_charge, spark_flags, list/banned_reagents)
if (!islist(banned_reagents))
banned_reagents = list(banned_reagents)
var/result = NONE
@@ -803,7 +803,7 @@
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)
var/reagent_result = reagent.on_spark_act(power_charge, spark_flags)
if (!reagent_result)
continue
result |= (reagent_result & SPARK_ACT_RETURNS)
+2 -2
View File
@@ -242,9 +242,9 @@
* 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
* * spark_flags - Flags specific to the interaction, is it in an enclosed space, should we nerf common reagents, etc.
*/
/datum/reagent/proc/on_spark_act(power_charge = 0, enclosed = TRUE)
/datum/reagent/proc/on_spark_act(power_charge = 0, spark_flags = NONE)
return NONE
/**
@@ -1308,14 +1308,18 @@
if(pool)
pool.burn_amount = max(min(round(reac_volume / 5), 10), 1)
/datum/reagent/fuel/on_spark_act(power_charge, enclosed)
/datum/reagent/fuel/on_spark_act(power_charge, spark_flags)
// 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)
if (!(spark_flags & SPARK_ACT_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)
var/strengthdiv = 10
if (spark_flags & SPARK_ACT_WEAKEN_COMMON)
strengthdiv *= 3 // Noticeably weaker than waterpot, at least put some effort in, cmon
reagent_explode(holder, volume, strengthdiv = strengthdiv, clear_holder_reagents = FALSE, flame_factor = 1)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/space_cleaner
@@ -29,8 +29,8 @@
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)
/datum/reagent/nitroglycerin/on_spark_act(power_charge, spark_flags)
reagent_explode(holder, volume, strengthdiv = 2, clear_holder_reagents = FALSE, flame_factor = 1)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/stabilizing_agent
@@ -89,7 +89,7 @@
taste_description = "air and bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/sorium/on_spark_act(power_charge, enclosed)
/datum/reagent/sorium/on_spark_act(power_charge, spark_flags)
var/range = clamp(sqrt(volume), 1, 6)
goonchem_vortex(get_turf(holder.my_atom), 1, range)
return SPARK_ACT_NON_DESTRUCTIVE
@@ -101,7 +101,7 @@
taste_description = "compressed bitterness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/liquid_dark_matter/on_spark_act(power_charge, enclosed)
/datum/reagent/liquid_dark_matter/on_spark_act(power_charge, spark_flags)
var/range = clamp(sqrt(volume / 2), 1, 6)
goonchem_vortex(get_turf(holder.my_atom), 0, range)
return SPARK_ACT_NON_DESTRUCTIVE
@@ -134,7 +134,7 @@
e.start(holder.my_atom)
holder.clear_reagents()
/datum/reagent/gunpowder/on_spark_act(power_charge, enclosed)
/datum/reagent/gunpowder/on_spark_act(power_charge, spark_flags)
// 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)
@@ -144,11 +144,11 @@
return
if (power_charge >= STANDARD_CELL_CHARGE || holder.chem_temp >= 474)
reagent_explode(holder, volume, modifier = 5, strengthdiv = 10, clear_holder_reagents = FALSE)
reagent_explode(holder, volume, modifier = 5, strengthdiv = 10, clear_holder_reagents = FALSE, flame_factor = 1)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
holder.my_atom.visible_message(span_boldnotice("Sparks start flying around the gunpowder!"))
if (!enclosed)
if (!(spark_flags & SPARK_ACT_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...
@@ -160,15 +160,15 @@
taste_description = "salt"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/rdx/on_spark_act(power_charge, enclosed)
/datum/reagent/rdx/on_spark_act(power_charge, spark_flags)
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)
reagent_explode(holder, volume, modifier = min(2 * power_coeff, 8), strengthdiv = 7 / min(power_coeff, 4), clear_holder_reagents = FALSE, flame_factor = 1)
else if (holder.chem_temp >= 474)
reagent_explode(holder, volume, modifier = 2, strengthdiv = 7, clear_holder_reagents = FALSE)
reagent_explode(holder, volume, modifier = 2, strengthdiv = 7, clear_holder_reagents = FALSE, flame_factor = 1)
else
reagent_explode(holder, volume, strengthdiv = 8, clear_holder_reagents = FALSE)
reagent_explode(holder, volume, strengthdiv = 8, clear_holder_reagents = FALSE, flame_factor = 1)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/tatp
@@ -178,8 +178,8 @@
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)
/datum/reagent/tatp/on_spark_act(power_charge, spark_flags)
reagent_explode(holder, volume, strengthdiv = 1.5 + rand() * 1.5, clear_holder_reagents = FALSE, flame_factor = 1)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
/datum/reagent/flash_powder
@@ -189,11 +189,14 @@
taste_description = "salt"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/flash_powder/on_spark_act(power_charge, enclosed)
/datum/reagent/flash_powder/on_spark_act(power_charge, spark_flags)
// Even weaker version of the normal flash effect
var/turf/location = get_turf(holder.my_atom)
if (!enclosed)
if (!(spark_flags & SPARK_ACT_ENCLOSED))
do_sparks(2, TRUE, location)
else if (!(holder.flags & TRANSPARENT) || (!isturf(holder.my_atom.loc) && !ismob(holder.my_atom.loc))) // nope
return SPARK_ACT_NON_DESTRUCTIVE
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))
@@ -216,10 +219,10 @@
taste_description = "smoke"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/smoke_powder/on_spark_act(power_charge, enclosed)
/datum/reagent/smoke_powder/on_spark_act(power_charge, spark_flags)
// 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))
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()
@@ -240,7 +243,7 @@
taste_description = "loud noises"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
/datum/reagent/sonic_powder/on_spark_act(power_charge, enclosed)
/datum/reagent/sonic_powder/on_spark_act(power_charge, spark_flags)
// Even weaker version of the normal flash effect
var/turf/location = get_turf(holder.my_atom)
var/range = round(volume / 15, 1)
@@ -270,8 +273,8 @@
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))
/datum/reagent/phlogiston/on_spark_act(power_charge, spark_flags)
if ((spark_flags & SPARK_ACT_ENCLOSED) && !ismob(holder.my_atom))
if (!holder.my_atom.uses_integrity)
return
// Spicy!
@@ -308,8 +311,8 @@
if(istype(exposed_mob) && (methods & (TOUCH|VAPOR|PATCH)))
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))
/datum/reagent/napalm/on_spark_act(power_charge, spark_flags)
if ((spark_flags & SPARK_ACT_ENCLOSED) && !ismob(holder.my_atom))
if (!holder.my_atom.uses_integrity)
return
// Spicy!
@@ -445,7 +448,7 @@
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)
/datum/reagent/teslium/on_spark_act(power_charge, spark_flags)
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
@@ -82,6 +82,7 @@
#define LIQUID_PLASMA_BP (50+T0C)
#define LIQUID_PLASMA_IG (325+T0C)
#define LIQUID_PLASMA_CHARGE_COEFF 2.7
#define LIQUID_PLASMA_VOLUME_POWER_CAP 7
/datum/reagent/toxin/plasma
name = "Plasma"
@@ -150,7 +151,16 @@
exposed_mob.adjust_fire_stacks(reac_volume / 5)
return
/datum/reagent/toxin/plasma/on_spark_act(power_charge, enclosed)
/datum/reagent/toxin/plasma/on_spark_act(power_charge, spark_flags)
// Tape up your plasma IEDs
if ((spark_flags & SPARK_ACT_WEAKEN_COMMON) && !(spark_flags & SPARK_ACT_ENCLOSED))
if(holder.chem_temp < LIQUID_PLASMA_BP)
return NONE
var/turf/holder_turf = get_turf(holder)
if (holder_turf)
holder_turf.atmos_spawn_air("[GAS_PLASMA]=[volume];[TURF_TEMPERATURE(holder.chem_temp)]")
return SPARK_ACT_NON_DESTRUCTIVE
// 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)
@@ -158,13 +168,21 @@
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)
var/power_modifier = max(0, round(sqrt(power_charge / STANDARD_CELL_CHARGE) * LIQUID_PLASMA_CHARGE_COEFF, 1) - 1)
var/strengthdiv = 5
if (spark_flags & SPARK_ACT_WEAKEN_COMMON)
strengthdiv *= 3 // Stronger than waterpot, weaker than methbombs
var/current_limit = round(volume / LIQUID_PLASMA_VOLUME_POWER_CAP, 1)
// High current can only get you so far before you get a sharp dropoff
if (power_modifier > current_limit)
power_modifier = round(current_limit + log(power_modifier - current_limit + 1), 1)
reagent_explode(holder, volume, modifier = power_modifier, strengthdiv = strengthdiv, clear_holder_reagents = FALSE, flame_factor = 1)
return SPARK_ACT_DESTRUCTIVE | SPARK_ACT_CLEAR_ALL
#undef LIQUID_PLASMA_BP
#undef LIQUID_PLASMA_IG
#undef LIQUID_PLASMA_CHARGE_COEFF
#undef LIQUID_PLASMA_VOLUME_POWER_CAP
/datum/reagent/toxin/hot_ice
name = "Hot Ice Slush"
@@ -11,31 +11,53 @@
righthand_file = 'icons/mob/inhands/items/drinks_righthand.dmi'
reagent_container_liquid_sound = SFX_DEFAULT_LIQUID_SLOSH
///Like Edible's food type, what kind of drink is this?
/// Like Edible's food type, what kind of drink is this?
var/drink_type = NONE
///The last time we have checked for taste.
/// The last time we have checked for taste.
var/last_check_time
///How much we drink at once, shot glasses drink more.
/// How much we drink at once, shot glasses drink more.
var/gulp_size = 5
///Whether the 'bottle' is made of glass or not so that milk cartons dont shatter when someone gets hit by it.
/// Whether the 'bottle' is made of glass or not so that milk cartons dont shatter when someone gets hit by it.
var/isGlass = FALSE
///What kind of chem transfer method does this cup use. Defaults to INGEST
/// What kind of chem transfer method does this cup use. Defaults to INGEST
var/reagent_consumption_method = INGEST
///What sound does our consumption play on consuming from the container?
/// What sound does our consumption play on consuming from the container?
var/consumption_sound = 'sound/items/drink.ogg'
///Whether to allow heating up the contents with a source of flame.
/// Whether to allow heating up the contents with a source of flame.
var/heatable = TRUE
/// Can we put a lid on this container?
var/can_lid = FALSE
/// Does this container have a lid on right now?
var/has_lid = FALSE
/// Assembly attached to our lid
var/obj/item/assembly_holder/lid_assembly = null
/// Power cell duct-taped to the side of the beaker
var/obj/item/stock_parts/power_store/cell/attached_cell = null
/// Have we added wiring to the cell?
var/cell_wired = FALSE
/// Visual y-offset for the assembly on our lid
var/assembly_pixel_y = 0
/obj/item/reagent_containers/cup/Initialize(mapload, vol)
. = ..()
if(heatable)
AddElement(/datum/element/reagents_item_heatable)
/obj/item/reagent_containers/cup/Destroy(force)
QDEL_NULL(lid_assembly)
QDEL_NULL(attached_cell)
return ..()
/obj/item/reagent_containers/cup/examine(mob/user)
. = ..()
if(drink_type)
var/list/types = bitfield_to_list(drink_type, FOOD_FLAGS)
. += span_notice("The label says it contains [LOWER_TEXT(english_list(types))] ingredients.")
if(can_lid)
if(has_lid)
. += span_notice("Its sealed with a bright orange rubber lid[!isnull(lid_assembly) ? "with an assembly attached ontop of it" : ""].")
else
. += span_notice("It can be sealed with a lid using [EXAMINE_HINT("Alt-Click")].")
/**
* Checks if the mob actually liked drinking this cup.
@@ -141,8 +163,58 @@
return NONE
/obj/item/reagent_containers/cup/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if(has_lid && istype(tool, /obj/item/assembly_holder))
if (lid_assembly)
to_chat(user, span_warning("[src]'s lid already has an assembly attached to it!"))
return ITEM_INTERACT_BLOCKING
if (attach_assembly(tool, user))
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
return ITEM_INTERACT_SUCCESS
return ITEM_INTERACT_BLOCKING
if (lid_assembly && istype(tool, /obj/item/stock_parts/power_store/cell))
if (attached_cell)
to_chat(user, span_warning("[src] already has \a [attached_cell] attached to it!"))
return ITEM_INTERACT_BLOCKING
if (isnull(locate(/obj/item/assembly/igniter) in lid_assembly))
to_chat(user, span_warning("[lid_assembly] doesn't have an igniter to connect [src] to!"))
return ITEM_INTERACT_BLOCKING
if (!user.transferItemToLoc(tool, src))
to_chat(user, span_warning("[tool] is stuck to your hand!"))
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You attach [tool] underneath [src]'s lid."))
add_fingerprint(user)
log_bomber(user, "attached [tool.name] to", src)
attached_cell = tool
attached_cell.pixel_y = 0
attached_cell.pixel_z = -4
update_appearance()
playsound(src, 'sound/machines/click.ogg', 50, TRUE)
return ITEM_INTERACT_SUCCESS
if (attached_cell && istype(tool, /obj/item/stack/cable_coil))
if (cell_wired)
to_chat(user, span_warning("[attached_cell] is already wired to [lid_assembly]!"))
return ITEM_INTERACT_BLOCKING
var/obj/item/stack/cable_coil/cable = tool
if (!cable.use(5))
to_chat(user, span_warning("You need at least 5 cable pieces to wire [attached_cell]!"))
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You wire [attached_cell] to [lid_assembly]."))
add_fingerprint(user)
cell_wired = TRUE
update_appearance()
return ITEM_INTERACT_SUCCESS
if(!is_open_container())
return NONE
if(istype(tool, /obj/item/food/egg)) //breaking eggs
if(reagents.holder_full())
to_chat(user, span_notice("[src] is full."))
@@ -172,6 +244,125 @@
/obj/item/reagent_containers/cup/proc/on_cup_reset()
drink_type = NONE
/obj/item/reagent_containers/cup/update_overlays()
. = ..()
if (has_lid)
. += mutable_appearance(icon, "[icon_state]_lid")
if (lid_assembly)
. += lid_assembly
if (attached_cell)
. += attached_cell
if (cell_wired)
. += mutable_appearance('icons/obj/machines/cell_charger.dmi', "ccharger-[attached_cell.connector_type]-on")
// For player convinience, assume that the lids are rubber and can be pierced with a syringe
/obj/item/reagent_containers/cup/is_refillable()
return ..() && !has_lid
/obj/item/reagent_containers/cup/is_drainable()
return ..() && !has_lid
/obj/item/reagent_containers/cup/is_dunkable()
return ..() && !has_lid
/obj/item/reagent_containers/cup/attack_self(mob/user)
if (!lid_assembly)
return ..()
lid_assembly.attack_self(user)
return TRUE
/obj/item/reagent_containers/cup/click_alt(mob/user)
if (!can_lid)
return NONE
if (cell_wired)
balloon_alert(user, "cut the wiring first!")
return CLICK_ACTION_BLOCKING
if (attached_cell)
var/obj/item/our_cell = attached_cell
// Exited() automatically clears it
our_cell.forceMove(drop_location())
user.put_in_hands(our_cell)
balloon_alert(user, "cell detached")
update_appearance()
return CLICK_ACTION_SUCCESS
if (lid_assembly)
var/obj/item/our_assembly = lid_assembly
our_assembly.forceMove(drop_location())
user.put_in_hands(our_assembly)
balloon_alert(user, "assembly detached")
update_appearance()
return CLICK_ACTION_SUCCESS
has_lid = !has_lid
update_appearance()
balloon_alert(user, "lid [has_lid ? "sealed" : "unsealed"]")
if (has_lid)
add_container_flags(SEALED_CONTAINER)
else
reset_container_flags()
return CLICK_ACTION_SUCCESS
/obj/item/reagent_containers/cup/wirecutter_act(mob/living/user, obj/item/tool)
if (user.combat_mode || !cell_wired)
return NONE
new /obj/item/stack/cable_coil(drop_location(), 5)
cell_wired = FALSE
update_appearance()
balloon_alert(user, "wiring cut")
tool.play_tool_sound(src, 50)
return ITEM_INTERACT_SUCCESS
/obj/item/reagent_containers/cup/proc/attach_assembly(obj/item/assembly_holder/assembly, mob/living/user)
if (!user.transferItemToLoc(assembly, src))
to_chat(user, span_warning("[assembly] is stuck to your hand!"))
return FALSE
to_chat(user, span_notice("You attach [assembly] to [src]'s lid."))
add_fingerprint(user)
lid_assembly = assembly
lid_assembly.master = src
lid_assembly.pixel_y = 0
lid_assembly.pixel_z = assembly_pixel_y
lid_assembly.on_attach()
RegisterSignal(src, COMSIG_IGNITER_ACTIVATE, PROC_REF(on_igniter_activate))
log_bomber(user, "attached [lid_assembly.name] to", src)
update_appearance()
return TRUE
/obj/item/reagent_containers/cup/Exited(atom/movable/gone, direction)
. = ..()
if (gone == lid_assembly)
lid_assembly = null
UnregisterSignal(src, COMSIG_IGNITER_ACTIVATE)
update_appearance()
else if (gone == attached_cell)
attached_cell = null
cell_wired = FALSE
update_appearance()
/obj/item/reagent_containers/cup/proc/on_igniter_activate(datum/source, obj/item/assembly/igniter/igniter)
SIGNAL_HANDLER
// We've got an attached cell wired up, so we'll try to spend all of its current first
if (attached_cell && cell_wired)
var/power_spent = attached_cell.use(attached_cell.charge())
// Power cell was rigged
if (QDELETED(src))
return
// We'll be nerfing plasma and welding fuel as they're very easy to get and make for boring bombs
if (power_spent > 0 && (reagents.spark_act(power_spent, SPARK_ACT_ENCLOSED | SPARK_ACT_WEAKEN_COMMON) & SPARK_ACT_DESTRUCTIVE))
qdel(src)
return
// Igniters heat, condensers chill
var/igniter_temp = igniter.get_temperature()
if (igniter_temp > 0)
reagents.expose_temperature(igniter_temp)
/obj/item/reagent_containers/cup/beaker
name = "beaker"
desc = "A beaker. It can hold up to 50 units."
@@ -186,6 +377,8 @@
pickup_sound = 'sound/items/handling/beaker_pickup.ogg'
drop_sound = 'sound/items/handling/beaker_place.ogg'
sound_vary = TRUE
can_lid = TRUE
assembly_pixel_y = 4
/obj/item/reagent_containers/cup/beaker/Initialize(mapload)
. = ..()
@@ -199,6 +392,7 @@
desc = "A jar for honey. It can hold up to 50 units of sweet delight."
icon = 'icons/obj/medical/chemical.dmi'
icon_state = "vapour"
can_lid = FALSE
/obj/item/reagent_containers/cup/beaker/large
name = "large beaker"
@@ -209,6 +403,7 @@
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,50,100)
fill_icon_thresholds = list(0, 1, 20, 40, 60, 80, 100)
assembly_pixel_y = 8
/obj/item/reagent_containers/cup/beaker/plastic
name = "x-large beaker"
@@ -220,6 +415,7 @@
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,60,120)
fill_icon_thresholds = list(0, 1, 10, 20, 40, 60, 80, 100)
assembly_pixel_y = 8
/obj/item/reagent_containers/cup/beaker/meta
name = "metamaterial beaker"
@@ -231,6 +427,7 @@
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,60,120,180)
fill_icon_thresholds = list(0, 1, 10, 25, 35, 50, 60, 80, 100)
assembly_pixel_y = 10
/obj/item/reagent_containers/cup/beaker/noreact
name = "cryostasis beaker"
@@ -242,6 +439,7 @@
initial_reagent_flags = OPENCONTAINER | NO_REACT
volume = 50
amount_per_transfer_from_this = 10
can_lid = FALSE
/obj/item/reagent_containers/cup/beaker/bluespace
name = "bluespace beaker"
@@ -254,6 +452,7 @@
volume = 300
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300)
can_lid = FALSE
/obj/item/reagent_containers/cup/beaker/meta/omnizine
list_reagents = list(/datum/reagent/medicine/omnizine = 180)
@@ -528,3 +727,8 @@
possible_transfer_amounts = list(5, 10, 15, 30)
volume = 30
fill_icon_thresholds = list(0, 1, 20, 40, 60, 80, 100)
can_lid = TRUE
/obj/item/reagent_containers/cup/tube/attach_assembly(obj/item/assembly_holder/assembly, mob/living/user)
to_chat(user, span_warning("[src]'s lid is too small to fit [assembly]!"))
return FALSE
@@ -11,6 +11,8 @@
possible_transfer_amounts = list(5, 10, 15, 25, 50)
volume = 50
fill_icon_thresholds = list(0, 1, 20, 40, 60, 80, 100)
can_lid = TRUE
assembly_pixel_y = 4
/obj/item/reagent_containers/cup/bottle/Initialize(mapload)
. = ..()
@@ -505,6 +507,7 @@
fill_icon_thresholds = list(0, 20, 40, 60, 80, 100)
possible_transfer_amounts = list(5, 10)
amount_per_transfer_from_this = 5
can_lid = FALSE
/obj/item/reagent_containers/cup/bottle/syrup_bottle/Initialize(mapload)
. = ..()
@@ -12,6 +12,7 @@
possible_transfer_amounts = list(20, 40, 60, 120)
fill_icon_thresholds = list(0, 1, 20, 40, 60, 80, 100)
w_class = WEIGHT_CLASS_SMALL // Organs are small by default, so the jar should be at least small as well
can_lid = FALSE
/// The organ that is currently inside the jar
var/obj/item/organ/held_organ = null
/// Whether the jar is filled to capacity with formaldehyde, preserving any organ inside
@@ -22,6 +22,12 @@
throwforce = 12 // set to 0 upon being opened. Have you ever been domed by a soda can? Those things fucking hurt
/// If the can hasn't been opened yet, this is the measure of how fizzed up it is from being shaken or thrown around. When opened, this is rolled as a percentage chance to burst
var/fizziness = 0
/// Have we been sealed with tape? And if so, what color is it?
var/tape_color = null
/// Color of our fuse, if any
var/fuse_color = null
/// Timer for our explosion
var/fuse_timer = null
/obj/item/reagent_containers/cup/soda_cans/Initialize(mapload, vol)
. = ..()
@@ -39,7 +45,7 @@
H.visible_message(span_warning("[H] is trying to take a big sip from [src]... The can is empty!"))
return SHAME
if(!is_drainable())
open_soda()
open_soda(H)
sleep(1 SECONDS)
H.visible_message(span_suicide("[H] takes a big sip from [src]! It looks like [H.p_theyre()] trying to commit suicide!"))
playsound(H,'sound/items/drink.ogg', 80, TRUE)
@@ -67,24 +73,24 @@
return TOXLOSS
/obj/item/reagent_containers/cup/soda_cans/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(iscarbon(target) && !reagents.total_volume && user.combat_mode && user.zone_selected == BODY_ZONE_HEAD)
if(target == user)
user.visible_message(
span_warning("[user] crushes the can of [src] on [user.p_their()] forehead!"),
span_notice("You crush the can of [src] on your forehead."),
)
else
user.visible_message(
span_warning("[user] crushes the can of [src] on [target]'s forehead!"),
span_notice("You crush the can of [src] on [target]'s forehead."),
)
playsound(src, 'sound/items/weapons/pierce.ogg', rand(10, 50), TRUE)
var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(target.drop_location())
crushed_can.icon_state = icon_state
qdel(src)
return ITEM_INTERACT_SUCCESS
if(!iscarbon(target) || reagents.total_volume || !user.combat_mode || user.zone_selected != BODY_ZONE_HEAD)
return ..()
return ..()
if(target == user)
user.visible_message(
span_warning("[user] crushes the can of [src] on [user.p_their()] forehead!"),
span_notice("You crush the can of [src] on your forehead."),
)
else
user.visible_message(
span_warning("[user] crushes the can of [src] on [target]'s forehead!"),
span_notice("You crush the can of [src] on [target]'s forehead."),
)
playsound(src, 'sound/items/weapons/pierce.ogg', rand(10, 50), TRUE)
var/obj/item/trash/can/crushed_can = new /obj/item/trash/can(target.drop_location())
crushed_can.icon_state = icon_state
qdel(src)
return ITEM_INTERACT_SUCCESS
/obj/item/reagent_containers/cup/soda_cans/bullet_act(obj/projectile/proj)
. = ..()
@@ -99,6 +105,14 @@
qdel(src)
/obj/item/reagent_containers/cup/soda_cans/proc/open_soda(mob/user)
if(tape_color)
to_chat(user, "You rip off the tape covering [src]'s hole.")
playsound(user, 'sound/items/duct_tape/duct_tape_rip.ogg', 50, TRUE)
tape_color = null
add_container_flags(OPENCONTAINER)
update_appearance()
return
if(prob(fizziness))
user.visible_message(span_danger("[user] opens [src], and is suddenly sprayed by the fizzing contents!"), span_danger("You pull back the tab of [src], and are suddenly sprayed with a torrent of liquid! Ahhh!!"))
burst_soda(user)
@@ -135,9 +149,120 @@
reagents.clear_reagents()
throwforce = 0
/obj/item/reagent_containers/cup/soda_cans/wirecutter_act(mob/living/user, obj/item/tool)
if (!fuse_color)
return NONE
to_chat(user, span_notice("You snip [src]'s fuse off."))
tool.play_tool_sound(src, 50)
add_fingerprint(user)
fuse_color = null
if (!isnull(fuse_timer))
deltimer(fuse_timer)
fuse_timer = null
log_bomber(user, "has disarmed", src)
if (heatable)
AddElement(/datum/element/reagents_item_heatable)
update_appearance()
/obj/item/reagent_containers/cup/soda_cans/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
if (istype(tool, /obj/item/stack/cable_coil))
if (fuse_color)
to_chat(user, span_warning("[src] already has a fuse attached to it!"))
return ITEM_INTERACT_BLOCKING
if (tape_color)
to_chat(user, span_warning("[src]'s hole is covered up with tape!"))
return ITEM_INTERACT_BLOCKING
if (!is_drainable())
to_chat(user, span_warning("[src] hasn't been opened yet!"))
return ITEM_INTERACT_BLOCKING
var/obj/item/stack/cable_coil/coil = tool
var/coil_color = GLOB.cable_colors[coil.cable_color]
add_fingerprint(user)
if (!coil.use(1))
return ITEM_INTERACT_BLOCKING
fuse_color = coil_color
// Heating replaced with lighting the fuse
RemoveElement(/datum/element/reagents_item_heatable)
to_chat(user, span_notice("You attach a fuse to [src]."))
log_bomber(user, "attached a fuse to", src)
update_appearance()
return ITEM_INTERACT_SUCCESS
if (istype(tool, /obj/item/stack/sticky_tape))
if (tape_color)
to_chat(user, span_warning("[src]'s hole is already covered up with tape!"))
return ITEM_INTERACT_BLOCKING
if (!is_drainable())
to_chat(user, span_warning("[src] hasn't been opened yet!"))
return ITEM_INTERACT_BLOCKING
var/obj/item/stack/sticky_tape/tape = tool
var/list/tape_colors = SSgreyscale.ParseColorString(tape.greyscale_colors)
add_fingerprint(user)
if (!tape.use(1))
return ITEM_INTERACT_BLOCKING
tape_color = tape_colors[1]
to_chat(user, span_notice("You wrap [src] up in [tape]."))
reset_container_flags()
update_appearance()
return ITEM_INTERACT_SUCCESS
if (!fuse_color || tool.get_temperature() < FIRE_MINIMUM_TEMPERATURE_TO_EXIST)
return ..()
if (fuse_timer)
to_chat(user, span_warning("[src] is already lit!"))
return ITEM_INTERACT_BLOCKING
add_fingerprint(user)
log_bomber(user, "has primed a rigged", src)
to_chat(user, span_warning("You light [src]'s fuse!"))
fuse_timer = addtimer(CALLBACK(src, PROC_REF(try_detonate)), rand(2 SECONDS, 4 SECONDS))
update_appearance()
return ITEM_INTERACT_SUCCESS
/obj/item/reagent_containers/cup/soda_cans/proc/try_detonate()
var/spark_flags = SPARK_ACT_WEAKEN_COMMON
if (tape_color)
spark_flags |= SPARK_ACT_ENCLOSED
playsound(src, 'sound/effects/sparks/sparks1.ogg', 50, TRUE)
if (reagents.spark_act(0, spark_flags) & SPARK_ACT_DESTRUCTIVE)
qdel(src)
return
// Was a dud
fuse_color = null
tape_color = null
add_container_flags(OPENCONTAINER)
if (heatable)
AddElement(/datum/element/reagents_item_heatable)
update_appearance()
/obj/item/reagent_containers/cup/soda_cans/update_overlays()
. = ..()
if (fuse_color)
var/mutable_appearance/fuse_overlay = mutable_appearance('icons/obj/weapons/grenade.dmi', "improvised_grenade_fuse")
fuse_overlay.color = fuse_color
. += fuse_overlay
if (tape_color)
var/mutable_appearance/tape_overlay = mutable_appearance('icons/obj/weapons/grenade.dmi', "improvised_grenade_tape")
tape_overlay.color = tape_color
. += tape_overlay
if (fuse_timer)
. += mutable_appearance('icons/obj/weapons/grenade.dmi', "improvised_grenade_active")
/obj/item/reagent_containers/cup/soda_cans/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(. || is_open_container() || !reagents.total_volume) // if it was caught, already opened, or has nothing in it
if(. || is_open_container() || !reagents.total_volume || tape_color) // if it was caught, already opened, or has nothing in it
return
fizziness += SODA_FIZZINESS_THROWN
@@ -152,9 +277,14 @@
QDEL_IN(src, 1 SECONDS) // give it a second so it can still be logged for the throw impact
/obj/item/reagent_containers/cup/soda_cans/attack_self(mob/user)
if(fuse_timer)
balloon_alert(user, "the fuse is on fire!")
return
if(!is_drainable())
open_soda(user)
return
return ..()
/obj/item/reagent_containers/cup/soda_cans/attack_self_secondary(mob/user)