diff --git a/code/__DEFINES/dcs/atom_signals.dm b/code/__DEFINES/dcs/atom_signals.dm
index 39bca29e114..17b7ce1200e 100644
--- a/code/__DEFINES/dcs/atom_signals.dm
+++ b/code/__DEFINES/dcs/atom_signals.dm
@@ -123,6 +123,10 @@
///called on /living, when pull is attempted, but before it completes, from base of [/mob/living/proc/start_pulling]: (atom/movable/thing, force)
#define COMSIG_LIVING_TRY_PULL "living_try_pull"
#define COMSIG_LIVING_CANCEL_PULL (1 << 0)
+#define COMSIG_ATOM_PULLED "atom_pulled"
+
+///from base of atom/Bumped(atom/bumped_atom)
+#define COMSIG_ATOM_BUMPED "atom_bumped"
///from base of atom/expose_reagents(): (/list, /datum/reagents, chemholder, volume_modifier)
#define COMSIG_ATOM_EXPOSE_REAGENTS "atom_expose_reagents"
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index 7d646927949..1c29b183f14 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -757,3 +757,6 @@ do { \
#define BRIDGE_SPAWN_TOO_WIDE 1
#define BRIDGE_SPAWN_TOO_NARROW 2
#define BRIDGE_SPAWN_BAD_TERRAIN 3
+
+#define DIRECT_EXPLOSIVE_TRAP_DEFUSE 1
+#define DIRECT_EXPLOSIVE_TRAP_IGNORE 2
diff --git a/code/datums/components/direct_explosive_trap.dm b/code/datums/components/direct_explosive_trap.dm
new file mode 100644
index 00000000000..b2ba85d7e02
--- /dev/null
+++ b/code/datums/components/direct_explosive_trap.dm
@@ -0,0 +1,99 @@
+/**
+ * Responds to certain signals and 'explodes' on the person using the item.
+ */
+/datum/component/direct_explosive_trap
+ /// An optional mob to inform about explosions
+ var/mob/living/saboteur
+ /// Amount of force to apply
+ var/explosive_force
+ /// Optional additional target checks before we go off
+ var/datum/callback/explosive_check
+ /// Signals which set off the bomb, must pass a mob as the first non-source argument
+ var/list/triggering_signals
+
+/datum/component/direct_explosive_trap/Initialize(
+ mob/living/saboteur_,
+ explosive_force_ = EXPLODE_HEAVY,
+ expire_time_ = 1 MINUTES,
+ glow_colour_ = COLOR_RED,
+ datum/callback/explosive_check_,
+ list/triggering_signals_ = list(
+ // TODO: This is where COMSIG_ATTACK_HAND would be
+ // if we could actually rely on it to call its parent
+ COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY,
+ COMSIG_INTERACT_USER,
+ COMSIG_ATTACK_BY,
+ COMSIG_ATOM_BUMPED,
+ COMSIG_CLICK_ALT,
+ COMSIG_ATOM_PULLED,
+ COMSIG_MOUSEDROP_ONTO,
+ )
+)
+ . = ..()
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+ saboteur = saboteur_
+ explosive_force = explosive_force_
+ explosive_check = explosive_check_
+ triggering_signals = triggering_signals_
+
+ if(expire_time_ > 0)
+ addtimer(CALLBACK(src, PROC_REF(bomb_expired)), expire_time_, TIMER_DELETE_ME)
+
+/datum/component/direct_explosive_trap/RegisterWithParent()
+ if(!(COMSIG_PARENT_EXAMINE in triggering_signals)) // Maybe you're being extra mean with this one
+ RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examined))
+ RegisterSignals(parent, triggering_signals, PROC_REF(explode))
+ if(!isnull(saboteur))
+ RegisterSignal(saboteur, COMSIG_PARENT_QDELETING, PROC_REF(on_bomber_deleted))
+
+/datum/component/direct_explosive_trap/UnregisterFromParent()
+ UnregisterSignal(parent, list(COMSIG_PARENT_EXAMINE) + triggering_signals)
+ if(!isnull(saboteur))
+ UnregisterSignal(saboteur, COMSIG_PARENT_QDELETING)
+
+/datum/component/direct_explosive_trap/Destroy(force)
+ if(isnull(saboteur))
+ return ..()
+ UnregisterSignal(saboteur, COMSIG_PARENT_QDELETING)
+ saboteur = null
+ return ..()
+
+/// Let people know something is up
+/datum/component/direct_explosive_trap/proc/on_examined(datum/source, mob/user, list/examine_text)
+ SIGNAL_HANDLER // COMSIG_PARENT_EXAMINE
+ if(get_dist(source, user) <= 2)
+ examine_text += "Looks odd!"
+
+/// Blow up
+/datum/component/direct_explosive_trap/proc/explode(atom/source, mob/living/victim)
+ SIGNAL_HANDLER // triggering_signals
+ if(!isliving(victim))
+ return
+ if(!isnull(explosive_check))
+ var/result = explosive_check.Invoke(source, victim)
+ if(result == DIRECT_EXPLOSIVE_TRAP_DEFUSE)
+ qdel(src)
+ return
+ else if(result == DIRECT_EXPLOSIVE_TRAP_IGNORE)
+ return
+ to_chat(victim, "[source] was boobytrapped!")
+ if(!isnull(saboteur))
+ to_chat(saboteur, "Success! Your trap on [source] caught [victim.name]!")
+ playsound(source, 'sound/effects/explosion2.ogg', 200, TRUE)
+ victim.ex_act(explosive_force)
+ // A bomb went off in your hands. Actually lets people follow up with it if
+ // they bait someone, right now it is unreliable.
+ victim.Stun(3 SECONDS)
+ qdel(src)
+
+/// Called if we sit too long without going off
+/datum/component/direct_explosive_trap/proc/bomb_expired()
+ if(!isnull(saboteur))
+ to_chat(saboteur, "Failure! Your trap on [parent] didn't catch anyone this time.")
+ qdel(src)
+
+/// Don't hang a reference to the person who placed the bomb
+/datum/component/direct_explosive_trap/proc/on_bomber_deleted()
+ SIGNAL_HANDLER
+ saboteur = null
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 3b4f7a781ca..c3a6cb33bc7 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -345,7 +345,7 @@
// want to get rid of this we need to move bumping in its entirety to signal
// handlers, which is scarier.
set waitfor = FALSE
- return
+ SEND_SIGNAL(src, COMSIG_ATOM_BUMPED, AM)
/// Convenience proc to see if a container is open for chemistry handling
/atom/proc/is_open_container()
diff --git a/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm b/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm
index 6f2574ad9b0..70121417183 100644
--- a/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm
+++ b/code/game/gamemodes/miniantags/guardian/types/explosive_guardian.dm
@@ -24,17 +24,24 @@
return
if(isobj(A) && can_plant(A))
if(bomb_cooldown <= world.time && stat == CONSCIOUS)
- var/obj/item/guardian_bomb/B = new /obj/item/guardian_bomb(get_turf(A))
+ A.AddComponent( \
+ /datum/component/direct_explosive_trap, \
+ saboteur_ = src, \
+ explosive_check_ = CALLBACK(src, PROC_REF(validate_target)))
add_attack_logs(src, A, "booby trapped (summoner: [summoner])")
to_chat(src, "Success! Bomb on [A] armed!")
if(summoner)
to_chat(summoner, "Your guardian has primed [A] to explode!")
bomb_cooldown = world.time + default_bomb_cooldown
- B.spawner = src
- B.disguise (A)
else
to_chat(src, "Your power is on cooldown! You must wait another [max(round((bomb_cooldown - world.time)*0.1, 0.1), 0)] seconds before you can place next bomb.")
+/mob/living/simple_animal/hostile/guardian/bomb/proc/validate_target(atom/source, mob/living/target)
+ if(target == summoner)
+ add_attack_logs(target, source, "booby trap defused")
+ to_chat(target, "You knew this because of your link with your guardian, so you smartly defuse the bomb.")
+ return DIRECT_EXPLOSIVE_TRAP_DEFUSE
+
/mob/living/simple_animal/hostile/guardian/bomb/proc/can_plant(atom/movable/A)
if(ismecha(A))
var/obj/mecha/target = A
@@ -45,76 +52,3 @@
to_chat(src, "You can't disguise disposal units as a bomb!")
return FALSE
return TRUE
-
-/obj/item/guardian_bomb
- name = "bomb"
- desc = "You shouldn't be seeing this!"
- var/obj/stored_obj
- var/mob/living/spawner
-
-/obj/item/guardian_bomb/proc/disguise(obj/A)
- A.forceMove(src)
- stored_obj = A
- opacity = A.opacity
- anchored = A.anchored
- density = A.density
- appearance = A.appearance
- dir = A.dir
- move_resist = A.move_resist
- addtimer(CALLBACK(src, PROC_REF(disable)), 600)
-
-/obj/item/guardian_bomb/CanPass(atom/movable/mover, border_dir)
- return stored_obj.CanPass(mover, border_dir)
-
-/obj/item/guardian_bomb/proc/disable()
- add_attack_logs(null, stored_obj, "booby trap expired")
- stored_obj.forceMove(get_turf(src))
- if(spawner)
- to_chat(spawner, "Failure! Your trap on [stored_obj] didn't catch anyone this time.")
- qdel(src)
-
-/obj/item/guardian_bomb/proc/detonate(mob/living/user)
- if(!istype(user))
- return
- to_chat(user, "[src] was boobytrapped!")
- if(isguardian(spawner))
- var/mob/living/simple_animal/hostile/guardian/G = spawner
- if(user == G.summoner)
- add_attack_logs(user, stored_obj, "booby trap defused")
- to_chat(user, "You knew this because of your link with your guardian, so you smartly defuse the bomb.")
- stored_obj.forceMove(get_turf(loc))
- qdel(src)
- return
- add_attack_logs(user, stored_obj, "booby trap TRIGGERED (spawner: [spawner])")
- to_chat(spawner, "Success! Your trap on [src] caught [user]!")
- stored_obj.forceMove(get_turf(loc))
- playsound(get_turf(src),'sound/effects/explosion2.ogg', 200, 1)
- user.ex_act(EXPLODE_HEAVY)
- user.Stun(3 SECONDS)//A bomb went off in your hands. Actually lets people follow up with it if they bait someone, right now it is unreliable.
- qdel(src)
-
-/obj/item/guardian_bomb/attackby__legacy__attackchain(obj/item/W, mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/attack_hand(mob/user)
- detonate(user)
-
-/obj/item/guardian_bomb/MouseDrop_T(obj/item/I, mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/AltClick(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/MouseDrop(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/Bumped(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/can_be_pulled(mob/living/user)
- detonate(user)
-
-/obj/item/guardian_bomb/examine(mob/user)
- . = stored_obj.examine(user)
- if(get_dist(user, src) <= 2)
- . += "Looks odd!"
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 143de7aee6e..a18bf84f09b 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -106,6 +106,7 @@
return ..()
/obj/machinery/door/Bumped(atom/AM)
+ . = ..()
if(operating || emagged || foam_level)
return
if(ismob(AM))
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index eb8bbd4ea32..e5cedcf2abe 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1018,6 +1018,7 @@
AM.pulledby = src
if(pullin)
pullin.update_icon(UPDATE_ICON_STATE)
+ SEND_SIGNAL(AM, COMSIG_ATOM_PULLED, src)
for(var/log_pulltype in GLOB.log_pulltypes)
if(istype(AM, log_pulltype))
diff --git a/paradise.dme b/paradise.dme
index 09a306d4dcf..be15c1007bd 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -497,6 +497,7 @@
#include "code\datums\components\deadchat_control.dm"
#include "code\datums\components\debris.dm"
#include "code\datums\components\defibrillator.dm"
+#include "code\datums\components\direct_explosive_trap.dm"
#include "code\datums\components\drift.dm"
#include "code\datums\components\ducttape.dm"
#include "code\datums\components\edit_complainer.dm"