diff --git a/code/modules/mod/mod_activation.dm b/code/modules/mod/mod_activation.dm
index 2935aeb0913..c3674801e36 100644
--- a/code/modules/mod/mod_activation.dm
+++ b/code/modules/mod/mod_activation.dm
@@ -50,11 +50,7 @@
balloon_alert(user, "currently [active ? "unsealing" : "sealing"]!")
playsound(src, 'sound/machines/scanner/scanbuzz.ogg', 25, TRUE, SILENCED_SOUND_EXTRARANGE)
return FALSE
- var/deploy = TRUE
- for(var/obj/item/part as anything in get_parts())
- if(part.loc != src)
- deploy = FALSE
- break
+ var/deploy = check_retracted()
wearer.visible_message(span_notice("[wearer]'s [src] [deploy ? "deploys" : "retracts"] its parts with a mechanical hiss."),
span_notice("[src] [deploy ? "deploys" : "retracts"] its parts with a mechanical hiss."),
span_hear("You hear a mechanical hiss."))
@@ -325,4 +321,12 @@
for(var/obj/item/part as anything in get_parts())
deploy(null, part, instant = TRUE)
+/// Checks if the suit is fully retracted, with no parts outside
+/obj/item/mod/control/proc/check_retracted()
+ for(var/obj/item/part as anything in get_parts())
+ if(part.loc != src)
+ return FALSE
+ return TRUE
+
+
#undef MOD_ACTIVATION_STEP_FLAGS
diff --git a/code/modules/mod/mod_control.dm b/code/modules/mod/mod_control.dm
index 397ca5c4d19..e2554be5646 100644
--- a/code/modules/mod/mod_control.dm
+++ b/code/modules/mod/mod_control.dm
@@ -100,8 +100,12 @@
locked = TRUE
new_core?.install(src)
update_speed()
+
RegisterSignal(src, COMSIG_ATOM_EXITED, PROC_REF(on_exit))
RegisterSignal(src, COMSIG_SPEED_POTION_APPLIED, PROC_REF(on_potion))
+ RegisterSignal(src, COMSIG_ITEM_GET_STRIPPABLE_ALT_ACTIONS, PROC_REF(get_strippable_alternate_actions))
+ RegisterSignal(src, COMSIG_ITEM_STRIPPABLE_ALT_ACTION, PROC_REF(do_strippable_action))
+
for(var/obj/item/mod/module/module as anything in theme.inbuilt_modules)
module = new module(src)
install(module)
@@ -402,6 +406,68 @@
icon_state = "[skin]-[base_icon_state][active ? "-sealed" : ""]"
return ..()
+/obj/item/mod/control/proc/get_strippable_alternate_actions(obj/item/source, atom/owner, mob/user, list/alt_actions)
+ SIGNAL_HANDLER
+ if(active)
+ alt_actions += "deactivate_mod"
+ else
+ alt_actions += "activate_mod"
+ if(check_retracted())
+ alt_actions += "deploy"
+ else
+ alt_actions += "undeploy"
+
+
+/obj/item/mod/control/proc/do_strippable_action(obj/item/source, atom/owner, mob/user, action_key)
+ SIGNAL_HANDLER
+ if(!isliving(user))
+ return NONE
+ switch(action_key)
+
+ if("deploy", "undeploy")
+ owner.visible_message(
+ span_warning("[user] tries to [action_key] [owner]'s [src]..."),
+ span_userdanger("[user] is trying to [action_key] your [src]!"),
+ blind_message = span_hear("You hear rustling."),
+ ignored_mobs = user,
+ )
+ INVOKE_ASYNC(src, PROC_REF(attempt_strip_deploy), owner, user, action_key)
+ return COMPONENT_ALT_ACTION_DONE
+
+ if("activate_mod", "deactivate_mod")
+ owner.visible_message(
+ span_warning("[user] tries to press [owner]'s [src]'s power button..."),
+ span_userdanger("[user] is trying to press your [src]'s power button!"),
+ blind_message = span_hear("You hear rustling."),
+ ignored_mobs = user,
+ )
+ INVOKE_ASYNC(src, PROC_REF(attempt_strip_activate), owner, user)
+ return COMPONENT_ALT_ACTION_DONE
+
+ else
+ return NONE
+
+/obj/item/mod/control/proc/attempt_strip_deploy(atom/owner, mob/user, message)
+ if(!do_after(user, strip_delay, owner))
+ return
+ owner.visible_message(
+ span_warning("[user] [message]s [owner]'s [src]."),
+ span_userdanger("[user] [message]s your [src]!"),
+ ignored_mobs = user,
+ )
+ quick_deploy(user)
+
+/obj/item/mod/control/proc/attempt_strip_activate(atom/owner, mob/user)
+ if(!do_after(user, strip_delay, owner))
+ return
+ owner.visible_message(
+ span_warning("[user] presses [owner]'s [src]'s power button."),
+ span_userdanger("[user] presses your [src]'s power button!"),
+ ignored_mobs = user,
+ )
+ toggle_activate(user)
+
+
/obj/item/mod/control/proc/get_parts(all = FALSE)
. = list()
for(var/key in mod_parts)
diff --git a/code/modules/mod/modules/modules_ninja.dm b/code/modules/mod/modules/modules_ninja.dm
index ae72a93df3a..04df80eb540 100644
--- a/code/modules/mod/modules/modules_ninja.dm
+++ b/code/modules/mod/modules/modules_ninja.dm
@@ -779,7 +779,7 @@
/obj/item/mod/module/dna_lock/reinforced/on_mod_activation(datum/source, mob/user)
. = ..()
- if(. != MOD_CANCEL_ACTIVATE || !isliving(user))
+ if(. != MOD_CANCEL_ACTIVATE || !isliving(user) || user != mod.wearer)
return
if(mod.ai_assistant == user)
to_chat(mod.ai_assistant, span_danger("fATaL EERRoR: 381200-*#00CODE BLUE\nAI INTErFERenCE DEtECted\nACTi0N DISrEGArdED"))
diff --git a/code/modules/mod/modules/modules_timeline.dm b/code/modules/mod/modules/modules_timeline.dm
index 3bc992ecfe6..bfca6e421b7 100644
--- a/code/modules/mod/modules/modules_timeline.dm
+++ b/code/modules/mod/modules/modules_timeline.dm
@@ -39,10 +39,12 @@
/obj/item/mod/module/eradication_lock/proc/on_mod_activation(datum/source, mob/user)
SIGNAL_HANDLER
- if(true_owner_ckey && user.ckey != true_owner_ckey)
+ if(!true_owner_ckey || user.ckey == true_owner_ckey)
+ return NONE
+ if(user == mod.wearer)
to_chat(mod.wearer, span_userdanger("\"MODsuit compromised by timeline inhabitant! Eradicating...\""))
new /obj/structure/chrono_field(user.loc, user)
- return MOD_CANCEL_ACTIVATE
+ return MOD_CANCEL_ACTIVATE
///Signal fired when the modsuit tries removing a module.
/obj/item/mod/module/eradication_lock/proc/on_mod_removal(datum/source, mob/user)
diff --git a/tgui/packages/tgui/interfaces/StripMenu.tsx b/tgui/packages/tgui/interfaces/StripMenu.tsx
index a0975730f63..480de878b40 100644
--- a/tgui/packages/tgui/interfaces/StripMenu.tsx
+++ b/tgui/packages/tgui/interfaces/StripMenu.tsx
@@ -88,6 +88,25 @@ const ALTERNATE_ACTIONS: Record = {
icon: 'ribbon',
text: 'Strip accessory',
},
+
+ deploy: {
+ icon: 'plus',
+ text: 'Deploy MOD',
+ },
+
+ undeploy: {
+ icon: 'minus',
+ text: 'Undeploy MOD',
+ },
+
+ activate_mod: {
+ icon: 'power-off',
+ text: 'Activate MOD',
+ },
+ deactivate_mod: {
+ icon: 'power-off',
+ text: 'Deactivate MOD',
+ },
};
const SLOTS: Record<