Attack Chain Migration: Grenades & Plastic Explosives (#31874)

* Update bomb.dm

* migration

* Fix CI

* Update mining_charges.dm

* Update deployable.dm
This commit is contained in:
CRUNCH
2026-04-21 05:58:44 +01:00
committed by GitHub
parent fd45dc8718
commit 3c1d985e31
10 changed files with 252 additions and 202 deletions
+66 -53
View File
@@ -8,7 +8,8 @@
det_time = 10
display_timer = FALSE
origin_tech = "syndicate=1"
var/atom/target = null
custom_activation = TRUE
var/atom/target_atom = null
var/image_overlay = null
var/obj/item/assembly/nadeassembly = null
var/assemblyattacher
@@ -28,30 +29,34 @@
/obj/item/grenade/plastic/Destroy()
QDEL_NULL(nadeassembly)
target = null
target_atom = null
plastic_overlay_target = null
return ..()
/obj/item/grenade/plastic/attackby__legacy__attackchain(obj/item/I, mob/user, params)
if(!nadeassembly && istype(I, /obj/item/assembly))
var/obj/item/assembly/A = I
/obj/item/grenade/plastic/wirecutter_act(mob/living/user, obj/item/used)
if(nadeassembly)
playsound(src, used.usesound, 20, 1)
nadeassembly.loc = get_turf(src)
nadeassembly.master = null
nadeassembly = null
update_icon(UPDATE_ICON_STATE)
return TRUE
/obj/item/grenade/plastic/item_interaction(mob/living/user, obj/item/used, list/modifiers)
if(!nadeassembly && istype(used, /obj/item/assembly))
var/obj/item/assembly/A = used
if(!user.transfer_item_to(A, src))
return ..()
nadeassembly = A
A.master = src
assemblyattacher = user.ckey
to_chat(user, SPAN_NOTICE("You add [A] to [src]."))
playsound(src, 'sound/weapons/tap.ogg', 20, 1)
update_icon(UPDATE_ICON_STATE)
return
if(nadeassembly && istype(I, /obj/item/wirecutters))
playsound(src, I.usesound, 20, 1)
nadeassembly.loc = get_turf(src)
nadeassembly.master = null
nadeassembly = null
update_icon(UPDATE_ICON_STATE)
return
..()
return ITEM_INTERACT_COMPLETE
return ..()
/obj/item/grenade/plastic/proc/on_atom_entered(datum/source, atom/movable/entered)
if(nadeassembly)
@@ -71,36 +76,43 @@
var/obj/item/assembly/voice/voice_analyzer = nadeassembly
voice_analyzer.hear_input(M, msg, 1)
/obj/item/grenade/plastic/attack_self__legacy__attackchain(mob/user)
/obj/item/grenade/plastic/activate_self(mob/user)
if(..())
return ITEM_INTERACT_COMPLETE
if(nadeassembly)
nadeassembly.attack_self__legacy__attackchain(user)
return
return ITEM_INTERACT_COMPLETE
var/newtime = input(usr, "Please set the timer.", "Timer", det_time) as num
if(user.is_in_active_hand(src))
newtime = clamp(newtime, 10, 60000)
det_time = newtime
to_chat(user, "Timer set for [det_time] seconds.")
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/plastic/interact_with_atom(atom/target, mob/living/user, list/modifiers)
if(isstorage(target) || ismodcontrol(target))
return ..() // Let us not have people C4 themselves. Especially with a now 1.5 second do_after().
if(ismob(target))
if(isobserver(target))
to_chat(user, SPAN_WARNING("Your hand just phases through [target]!"))
return ITEM_INTERACT_COMPLETE
var/mob/M = target
if(M.stat == CONSCIOUS)
to_chat(user, SPAN_WARNING("You can't get the [src] to stick to [M]! Perhaps if [M] was asleep or dead you could attach it?"))
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/plastic/afterattack__legacy__attackchain(mob/AM, mob/user, flag)
if(!flag)
return
if(ismob(AM) && AM.stat == CONSCIOUS)
to_chat(user, SPAN_WARNING("You can't get the [src] to stick to [AM]! Perhaps if [AM] was asleep or dead you could attach it?"))
return
if(isstorage(AM) || ismodcontrol(AM))
return ..() //Let us not have people c4 themselfs. Especially with a now 1.5 second do_after
if(isobserver(AM))
to_chat(user, SPAN_WARNING("Your hand just phases through [AM]!"))
return
to_chat(user, SPAN_NOTICE("You start planting [src].[isnull(nadeassembly) ? " The timer is set to [det_time]..." : ""]"))
if(do_after(user, 1.5 SECONDS * toolspeed, target = AM))
if(do_after(user, 1.5 SECONDS * toolspeed, target = target))
if(!user.unequip(src))
return
to_chat(user, SPAN_DANGER("[src] is stuck to your hand!"))
return ITEM_INTERACT_COMPLETE
target = AM
target_atom = target
loc = null
if(notify_admins)
message_admins("[ADMIN_LOOKUPFLW(user)] planted [name] on [target.name] at ([target.x],[target.y],[target.z] - <a href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[target.x];Y=[target.y];Z=[target.z]'>JMP</a>) with [det_time] second fuse", 0, 1)
log_game("[key_name(user)] planted [name] on [target.name] at ([target.x],[target.y],[target.z]) with [det_time] second fuse")
@@ -126,8 +138,9 @@
plastic_overlay_target.add_overlay(plastic_overlay)
if(!nadeassembly)
to_chat(user, SPAN_NOTICE("You plant the bomb. Timer counting down from [det_time]."))
to_chat(user, SPAN_DANGER("You plant the bomb. Timer counting down from [det_time]."))
addtimer(CALLBACK(src, PROC_REF(prime)), det_time SECONDS)
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/plastic/suicide_act(mob/user)
message_admins("[key_name_admin(user)]([ADMIN_QUE(user,"?")]) ([ADMIN_FLW(user,"FLW")]) suicided with [src.name] at ([user.x],[user.y],[user.z] - <A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[user.x];Y=[user.y];Z=[user.z]'>JMP</a>)",0,1)
@@ -150,7 +163,7 @@
else if(role == SPECIAL_ROLE_DEATHSQUAD || role == ROLE_ERT)
message_say = "FOR NANOTRASEN!"
user.say(message_say)
target = user
target_atom = user
sleep(10)
prime()
user.gib()
@@ -184,9 +197,9 @@
. = ..()
desc += SPAN_NOTICE("<br><b>Use [src] in-hand</b> to adjust the detonation timer.")
/obj/item/grenade/plastic/c4/afterattack__legacy__attackchain(atom/movable/AM, mob/user, flag)
aim_dir = get_dir(user, AM)
..()
/obj/item/grenade/plastic/c4/interact_with_atom(atom/target, mob/living/user, list/modifiers)
aim_dir = get_dir(user, target)
return ..()
/obj/item/grenade/plastic/c4/prime()
var/turf/location
@@ -194,14 +207,14 @@
plastic_overlay_target.cut_overlay(plastic_overlay, TRUE)
if(istype(plastic_overlay_target, /obj/effect/plastic))
qdel(plastic_overlay_target)
if(target)
if(!QDELETED(target))
location = get_turf(target)
if(!ex_breach && iswallturf(target)) //Walls get dismantled instead of destroyed to avoid making unwanted holes to space.
var/turf/simulated/wall/W = target
if(target_atom)
if(!QDELETED(target_atom))
location = get_turf(target_atom)
if(!ex_breach && iswallturf(target_atom)) //Walls get dismantled instead of destroyed to avoid making unwanted holes to space.
var/turf/simulated/wall/W = target_atom
W.dismantle_wall(TRUE, TRUE)
else
target.ex_act(EXPLODE_DEVASTATE)
target_atom.ex_act(EXPLODE_DEVASTATE)
else
location = get_turf(src)
if(location)
@@ -238,10 +251,10 @@
/obj/item/grenade/plastic/c4/shaped/flash/prime()
var/turf/T
if(target && target.density)
T = get_step(get_turf(target), aim_dir)
else if(target)
T = get_turf(target)
if(target_atom && target_atom.density)
T = get_step(get_turf(target_atom), aim_dir)
else if(target_atom)
T = get_turf(target_atom)
else
T = get_turf(src)
@@ -263,15 +276,15 @@
plastic_overlay_target.cut_overlay(plastic_overlay, TRUE)
if(istype(plastic_overlay_target, /obj/effect/plastic))
qdel(plastic_overlay_target)
if(target)
if(!QDELETED(target))
location = get_turf(target)
if(target_atom)
if(!QDELETED(target_atom))
location = get_turf(target_atom)
else
location = get_turf(src)
if(location)
var/datum/effect_system/smoke_spread/smoke = new
smoke.set_up(8, FALSE, location, aim_dir)
if(target && target.density)
if(target_atom && target_atom.density)
var/turf/T = get_step(location, aim_dir)
for(var/turf/simulated/wall/W in range(1, location))
W.thermitemelt(speed = 30)
@@ -282,8 +295,8 @@
addtimer(CALLBACK(null, GLOBAL_PROC_REF(explosion), T, 0, 0, 2), 3)
addtimer(CALLBACK(smoke, TYPE_PROC_REF(/datum/effect_system/smoke_spread, start)), 3)
if(isliving(target))
var/mob/living/M = target
if(isliving(target_atom))
var/mob/living/M = target_atom
M.adjust_fire_stacks(2)
M.IgniteMob()
qdel(src)
@@ -30,27 +30,22 @@
desc = "A grenade casing made of bananium."
icon_state = "banana_casing"
deliveryamt = 0
/obj/item/grenade/bananade/casing/attack_hand()
return // No activating an empty grenade
/obj/item/grenade/bananade/casing/attack_self__legacy__attackchain()
return // Stop trying to break stuff
custom_activation = TRUE // Used to make it unprimable.
/obj/item/grenade/bananade/casing/prime()
return // The grenade isnt completed yet, dont even try to blow it up
return // The grenade isnt completed yet, dont even try to blow it up!
/obj/item/grenade/bananade/casing/attackby__legacy__attackchain(obj/item/I, mob/user, params)
if(istype(I, /obj/item/grown/bananapeel))
if(deliveryamt < 9)
to_chat(user, "<span class='notice'>You add another banana peel to the assembly.</span>")
deliveryamt += 1
qdel(I)
else
to_chat(user, SPAN_NOTICE("The bananade is full, screwdriver it shut to ready it."))
return
/obj/item/grenade/bananade/casing/item_interaction(mob/living/user, obj/item/used, list/modifiers)
if(!istype(used, /obj/item/grown/bananapeel))
return ..()
return ..()
if(deliveryamt < 9)
to_chat(user, SPAN_NOTICE("You add another banana peel to the assembly"))
deliveryamt += 1
qdel(used)
else
to_chat(user, SPAN_WARNING("[src] is full. Screwdriver it shut to ready it."))
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/bananade/casing/screwdriver_act(mob/living/user, obj/item/I)
if(!deliveryamt)
@@ -7,6 +7,7 @@
desc = "A do it yourself grenade casing!"
icon_state = "chemg"
materials = list(MAT_METAL = 2000)
custom_activation = TRUE
var/bomb_state = "chembomb"
var/payload_name = null // used for spawned grenades
force = 2
@@ -93,7 +94,10 @@
underlays += "[O]_r"
/obj/item/grenade/chem_grenade/attack_self__legacy__attackchain(mob/user)
/obj/item/grenade/chem_grenade/activate_self(mob/user)
if(..())
return ITEM_INTERACT_COMPLETE
if(stage == READY && !active)
var/turf/bombturf = get_turf(src)
var/area/A = get_area(bombturf)
@@ -128,43 +132,45 @@
return TRUE
return ..()
/obj/item/grenade/chem_grenade/attackby__legacy__attackchain(obj/item/I, mob/user, params)
if(istype(I,/obj/item/hand_labeler))
var/obj/item/hand_labeler/HL = I
/obj/item/grenade/chem_grenade/item_interaction(mob/living/user, obj/item/used, list/modifiers)
if(istype(used, /obj/item/hand_labeler))
var/obj/item/hand_labeler/HL = used
if(length(HL.label))
label = " ([HL.label])"
return 0
else
if(label)
label = null
update_icon(UPDATE_ICON_STATE)
to_chat(user, "You remove the label from [src].")
return 1
else if(stage == WIRED && is_type_in_list(I, allowed_containers))
return ITEM_INTERACT_COMPLETE
if(label)
label = null
update_icon(UPDATE_ICON_STATE)
to_chat(user, "You remove the label from [src].")
return ITEM_INTERACT_COMPLETE
if(stage == WIRED && is_type_in_list(used, allowed_containers))
if(length(beakers) == 0)
container_type = TRANSPARENT // Allows to see reagents in player's made bombs
if(length(beakers) == 2)
if(length(beakers) >= 2)
to_chat(user, SPAN_NOTICE("[src] can not hold more containers."))
return
else
if(I.reagents.total_volume)
to_chat(user, SPAN_NOTICE("You add [I] to the assembly."))
user.drop_item()
I.forceMove(src)
beakers += I
// Saving reagents to show them via scanners
reagents.reagent_list.Add(I.reagents.reagent_list)
reagents.update_total()
else
to_chat(user, SPAN_NOTICE("[I] is empty."))
return ITEM_INTERACT_COMPLETE
else if(stage == EMPTY && istype(I, /obj/item/assembly_holder))
var/obj/item/assembly_holder/A = I
if(used.reagents.total_volume)
to_chat(user, SPAN_NOTICE("You add [used] to the assembly."))
user.drop_item()
used.forceMove(src)
beakers += used
// Saving reagents to show them via scanners
reagents.reagent_list.Add(used.reagents.reagent_list)
reagents.update_total()
else
to_chat(user, SPAN_NOTICE("[used] is empty."))
return ITEM_INTERACT_COMPLETE
if(stage == EMPTY && istype(used, /obj/item/assembly_holder))
var/obj/item/assembly_holder/A = used
if(!A.secured)
return
if(isigniter(A.a_left) == isigniter(A.a_right)) //Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it
return
return ITEM_INTERACT_COMPLETE
if(isigniter(A.a_left) == isigniter(A.a_right)) // Check if either part of the assembly has an igniter, but if both parts are igniters, then fuck it.
return ITEM_INTERACT_COMPLETE
user.drop_item()
nadeassembly = A
@@ -174,34 +180,17 @@
stage = WIRED
to_chat(user, SPAN_NOTICE("You add [A] to [src]!"))
update_icon(UPDATE_ICON_STATE)
return ITEM_INTERACT_COMPLETE
else if(stage == EMPTY && istype(I, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/C = I
if(stage == EMPTY && istype(used, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/C = used
C.use(1)
stage = WIRED
to_chat(user, SPAN_NOTICE("You rig [src]."))
update_icon(UPDATE_ICON_STATE)
return ITEM_INTERACT_COMPLETE
else if(stage == READY && istype(I, /obj/item/wirecutters))
to_chat(user, SPAN_NOTICE("You unlock the assembly."))
stage = WIRED
update_icon(UPDATE_ICON_STATE)
else if(stage == WIRED && iswrench(I))
to_chat(user, SPAN_NOTICE("You open the grenade and remove the contents."))
stage = EMPTY
payload_name = null
label = null
if(nadeassembly)
nadeassembly.forceMove(get_turf(src))
nadeassembly.master = null
nadeassembly = null
if(length(beakers))
for(var/obj/O in beakers)
O.forceMove(get_turf(src))
beakers = list()
update_icon(UPDATE_ICON_STATE)
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/chem_grenade/screwdriver_act(mob/living/user, obj/item/I)
if(stage == WIRED)
@@ -242,6 +231,34 @@
to_chat(user, SPAN_NOTICE("You need to add an activation mechanism."))
return TRUE
/obj/item/grenade/chem_grenade/wrench_act(mob/living/user, obj/item/I)
if(stage != WIRED)
return TRUE
to_chat(user, SPAN_NOTICE("You open the grenade and remove the contents."))
stage = EMPTY
payload_name = null
label = null
if(nadeassembly)
nadeassembly.forceMove(get_turf(src))
nadeassembly.master = null
nadeassembly = null
if(length(beakers))
for(var/obj/O in beakers)
O.forceMove(get_turf(src))
beakers = list()
update_icon(UPDATE_ICON_STATE)
return TRUE
/obj/item/grenade/chem_grenade/wirecutter_act(mob/living/user, obj/item/I)
if(stage != READY)
return TRUE
to_chat(user, SPAN_NOTICE("You unlock the assembly."))
stage = WIRED
update_icon(UPDATE_ICON_STATE)
return TRUE
/obj/item/grenade/chem_grenade/HasProximity(atom/movable/AM)
if(nadeassembly)
nadeassembly.HasProximity(AM)
@@ -371,14 +388,15 @@
//I tried to just put it in the allowed_containers list but
//if you do that it must have reagents. If you're going to
//make a special case you might as well do it explicitly. -Sayu
/obj/item/grenade/chem_grenade/large/attackby__legacy__attackchain(obj/item/I, mob/user, params)
if(istype(I, /obj/item/slime_extract) && stage == WIRED)
to_chat(user, SPAN_NOTICE("You add [I] to the assembly."))
/obj/item/grenade/chem_grenade/large/item_interaction(mob/living/user, obj/item/used, list/modifiers)
if(istype(used, /obj/item/slime_extract) && stage == WIRED)
to_chat(user, SPAN_NOTICE("You add [used] to the assembly."))
user.drop_item()
I.loc = src
beakers += I
else
return ..()
used.loc = src
beakers += used
return ITEM_INTERACT_COMPLETE
return ..()
/// Intended for rare cryogenic mixes. Cools the area moderately upon detonation.
/obj/item/grenade/chem_grenade/cryo
@@ -7,6 +7,7 @@
throw_range = 7
display_timer = FALSE
modifiable_timer = FALSE
custom_activation = TRUE
var/list/times
/obj/item/grenade/iedcasing/examine(mob/user)
@@ -33,26 +34,31 @@
can_underlay.plane = FLOAT_PLANE
underlays += can_underlay
/obj/item/grenade/iedcasing/activate_self(mob/user)
if(..())
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/iedcasing/attack_self__legacy__attackchain(mob/user) //
if(!active)
if(clown_check(user))
to_chat(user, SPAN_WARNING("You light [src]!"))
active = TRUE
overlays -= "improvised_grenade_filled"
icon_state = initial(icon_state) + "_active"
add_fingerprint(user)
var/turf/bombturf = get_turf(src)
var/area/A = get_area(bombturf)
if(active)
return ITEM_INTERACT_COMPLETE
if(clown_check(user))
to_chat(user, SPAN_WARNING("You light [src]!"))
active = TRUE
overlays -= "improvised_grenade_filled"
icon_state = initial(icon_state) + "_active"
add_fingerprint(user)
var/turf/bombturf = get_turf(src)
var/area/A = get_area(bombturf)
message_admins("[key_name_admin(user)] has primed a [name] for detonation at [A.name] [ADMIN_JMP(bombturf)]")
log_game("[key_name(user)] has primed a [name] for detonation at [A.name] [COORD(bombturf)]")
investigate_log("[key_name(user)] has primed a [name] for detonation at [A.name] [COORD(bombturf)])", INVESTIGATE_BOMB)
add_attack_logs(user, src, "has primed for detonation", ATKLOG_FEW)
if(iscarbon(user))
var/mob/living/carbon/C = user
C.throw_mode_on()
addtimer(CALLBACK(src, PROC_REF(prime)), det_time)
message_admins("[key_name_admin(user)] has primed a [name] for detonation at [A.name] [ADMIN_JMP(bombturf)]")
log_game("[key_name(user)] has primed a [name] for detonation at [A.name] [COORD(bombturf)]")
investigate_log("[key_name(user)] has primed a [name] for detonation at [A.name] [COORD(bombturf)])", INVESTIGATE_BOMB)
add_attack_logs(user, src, "has primed for detonation", ATKLOG_FEW)
if(iscarbon(user))
var/mob/living/carbon/C = user
C.throw_mode_on()
addtimer(CALLBACK(src, PROC_REF(prime)), det_time)
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/iedcasing/prime() //Blowing that can up
update_mob()
@@ -13,6 +13,7 @@
resistance_flags = FLAMMABLE
max_integrity = 40
flags_2 = RANDOM_BLOCKER_2
new_attack_chain = TRUE
/// Has the pin been pulled?
var/active = FALSE
/// Time between the pin being pulled and detonation.
@@ -21,6 +22,8 @@
var/display_timer = TRUE
/// Can the grenade's fuze time be changed?
var/modifiable_timer = TRUE
/// Allows the default `activate_self()` behavour to be fully overriden.
var/custom_activation = FALSE
/obj/item/grenade/examine(mob/user)
. = ..()
@@ -62,11 +65,18 @@
return FALSE
return TRUE
/obj/item/grenade/attack_self__legacy__attackchain(mob/user as mob)
/obj/item/grenade/activate_self(mob/user)
if(..())
return ITEM_INTERACT_COMPLETE
if(custom_activation)
return // Let the child proc cook.
if(active)
return
return ITEM_INTERACT_COMPLETE
if(!clown_check(user))
return
return ITEM_INTERACT_COMPLETE
to_chat(user, SPAN_DANGER("You prime [src]! [det_time / 10] seconds!"))
active = TRUE
@@ -83,6 +93,7 @@
C.throw_mode_on()
spawn(det_time)
prime()
return ITEM_INTERACT_COMPLETE
/obj/item/grenade/proc/prime()
return
@@ -19,12 +19,12 @@
if(HAS_TRAIT(user, TRAIT_CLUMSY))
. += SPAN_SANS("There are small glue ejectors all over the bomb.")
/obj/item/grenade/syndieminibomb/fake/attack_self__legacy__attackchain(mob/user)
/obj/item/grenade/syndieminibomb/fake/activate_self(mob/user)
if(!active)
set_nodrop(TRUE, user)
to_chat(user, SPAN_USERDANGER("As you activate the bomb, it emits a substance that sticks to your hand! It won't come off!"))
to_chat(user, SPAN_SANS("Uh oh."))
. = ..()
return ..()
/obj/item/grenade/syndieminibomb/pen
name = "pen"
@@ -35,7 +35,8 @@
inhand_icon_state = "pen"
explosion_cause = "Syndicate minibomb (disguised as a black pen)"
/obj/item/grenade/syndieminibomb/pen/attack_self__legacy__attackchain(mob/user)
/obj/item/grenade/syndieminibomb/pen/activate_self(mob/user)
if(!active)
..()
visible_message(SPAN_NOTICE("[user] fumbles with [src]!"))
. = ..()
return ITEM_INTERACT_COMPLETE