diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 288604434e34..1aaf3714182e 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -127,6 +127,8 @@
#define TRAIT_INTENT_EYES "t_intent_eyes"
/// Masked synthetic biology. Basic medHUDs will percieve the mob as human. (Infiltrator Synths)
#define TRAIT_INFILTRATOR_SYNTH "t_infiltrator_synth"
+/// Makes it impossible to strip the inventory of this mob.
+#define TRAIT_UNSTRIPPABLE "t_unstrippable"
// HIVE TRAITS
/// If the Hive is a Xenonid Hive
@@ -251,6 +253,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_FOREIGN_BIO" = TRAIT_FOREIGN_BIO,
"TRAIT_INTENT_EYES" = TRAIT_INTENT_EYES,
"TRAIT_INFILTRATOR_SYNTH" = TRAIT_INFILTRATOR_SYNTH,
+ "TRAIT_UNSTRIPPABLE" = TRAIT_UNSTRIPPABLE,
"TRAIT_NESTED" = TRAIT_NESTED,
"TRAIT_CRAWLER" = TRAIT_CRAWLER,
"TRAIT_SIMPLE_DESC" = TRAIT_SIMPLE_DESC,
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index d01416b2443f..4ed9795b1fc0 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -509,8 +509,11 @@
if(U == w_uniform)
U.remove_accessory(usr, A)
else
+ if(HAS_TRAIT(src, TRAIT_UNSTRIPPABLE) && !is_mob_incapacitated()) //Can't strip the unstrippable!
+ to_chat(usr, SPAN_DANGER("[src] has an unbreakable grip on their equipment!"))
+ return
visible_message(SPAN_DANGER("[usr] is trying to take off \a [A] from [src]'s [U]!"), null, null, 5)
- if(do_after(usr, HUMAN_STRIP_DELAY, INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
+ if(do_after(usr, get_strip_delay(usr, src), INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
if(U == w_uniform)
U.remove_accessory(usr, A)
@@ -529,7 +532,7 @@
else
var/oldsens = U.has_sensor
visible_message(SPAN_DANGER("[usr] is trying to modify [src]'s sensors!"), null, null, 4)
- if(do_after(usr, HUMAN_STRIP_DELAY, INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
+ if(do_after(usr, get_strip_delay(usr, src), INTERRUPT_ALL, BUSY_ICON_GENERIC, src, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
if(U == w_uniform)
if(U.has_sensor >= UNIFORM_FORCED_SENSORS)
to_chat(usr, "The controls are locked.")
diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm
index 835e03c8e042..45fb65771069 100644
--- a/code/modules/mob/living/carbon/human/inventory.dm
+++ b/code/modules/mob/living/carbon/human/inventory.dm
@@ -486,63 +486,82 @@
return WEAR_LEGCUFFS
return ..()
-
-
-/mob/living/carbon/human/stripPanelUnequip(obj/item/I, mob/M, slot_to_process)
- if(I.flags_item & ITEM_ABSTRACT)
+/mob/living/carbon/human/proc/get_strip_delay(mob/living/carbon/human/user, mob/living/carbon/human/target)
+ /// Default delay
+ var/target_delay = HUMAN_STRIP_DELAY
+ /// Multiplier for how quickly the user can strip things.
+ var/user_speed = user.get_skill_duration_multiplier(SKILL_CQC)
+ /// The total skill level of CQC & Police
+ var/target_skills = (target.skills.get_skill_level(SKILL_CQC) + target.skills.get_skill_level(SKILL_POLICE))
+
+ /// Delay then gets + 0.5s per skill level, so long as not dead or cuffed.
+ if(!(target.stat || target.handcuffed))
+ target_delay += (target_skills * 5)
+
+ /// Final result is overall delay * speed multiplier
+ return target_delay * user_speed
+
+/mob/living/carbon/human/stripPanelUnequip(obj/item/interact_item, mob/target_mob, slot_to_process)
+ if(HAS_TRAIT(target_mob, TRAIT_UNSTRIPPABLE) && !target_mob.is_mob_incapacitated()) //Can't strip the unstrippable!
+ to_chat(src, SPAN_DANGER("[target_mob] has an unbreakable grip on their equipment!"))
+ return
+ if(interact_item.flags_item & ITEM_ABSTRACT)
+ return
+ if(interact_item.flags_item & NODROP)
+ to_chat(src, SPAN_WARNING("You can't remove \the [interact_item.name], it appears to be stuck!"))
return
- if(I.flags_item & NODROP)
- to_chat(src, SPAN_WARNING("You can't remove \the [I.name], it appears to be stuck!"))
+ if(interact_item.flags_inventory & CANTSTRIP)
+ to_chat(src, SPAN_WARNING("You're having difficulty removing \the [interact_item.name]."))
return
- if(I.flags_inventory & CANTSTRIP)
- to_chat(src, SPAN_WARNING("You're having difficulty removing \the [I.name]."))
+ target_mob.attack_log += "\[[time_stamp()]\] Has had their [interact_item.name] ([slot_to_process]) attempted to be removed by [key_name(src)]"
+ attack_log += "\[[time_stamp()]\] Attempted to remove [key_name(target_mob)]'s [interact_item.name] ([slot_to_process])"
+ log_interact(src, target_mob, "[key_name(src)] tried to remove [key_name(target_mob)]'s [interact_item.name] ([slot_to_process]).")
+
+ src.visible_message(SPAN_DANGER("[src] tries to remove [target_mob]'s [interact_item.name]."), \
+ SPAN_DANGER("You are trying to remove [target_mob]'s [interact_item.name]."), null, 5)
+ interact_item.add_fingerprint(src)
+ if(do_after(src, get_strip_delay(src, target_mob), INTERRUPT_ALL, BUSY_ICON_GENERIC, target_mob, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
+ if(interact_item && Adjacent(target_mob) && interact_item == target_mob.get_item_by_slot(slot_to_process))
+ target_mob.drop_inv_item_on_ground(interact_item)
+ log_interact(src, target_mob, "[key_name(src)] removed [key_name(target_mob)]'s [interact_item.name] ([slot_to_process]) successfully.")
+
+ if(target_mob)
+ if(interactee == target_mob && Adjacent(target_mob))
+ target_mob.show_inv(src)
+
+
+/mob/living/carbon/human/stripPanelEquip(obj/item/interact_item, mob/target_mob, slot_to_process)
+ if(HAS_TRAIT(target_mob, TRAIT_UNSTRIPPABLE) && !target_mob.is_mob_incapacitated())
+ to_chat(src, SPAN_DANGER("[target_mob] is too strong to force [interact_item.name] onto them!"))
return
- M.attack_log += "\[[time_stamp()]\] Has had their [I.name] ([slot_to_process]) attempted to be removed by [key_name(src)]"
- attack_log += "\[[time_stamp()]\] Attempted to remove [key_name(M)]'s [I.name] ([slot_to_process])"
- log_interact(src, M, "[key_name(src)] tried to remove [key_name(M)]'s [I.name] ([slot_to_process]).")
-
- src.visible_message(SPAN_DANGER("[src] tries to remove [M]'s [I.name]."), \
- SPAN_DANGER("You are trying to remove [M]'s [I.name]."), null, 5)
- I.add_fingerprint(src)
- if(do_after(src, HUMAN_STRIP_DELAY * src.get_skill_duration_multiplier(SKILL_CQC), INTERRUPT_ALL, BUSY_ICON_GENERIC, M, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
- if(I && Adjacent(M) && I == M.get_item_by_slot(slot_to_process))
- M.drop_inv_item_on_ground(I)
- log_interact(src, M, "[key_name(src)] removed [key_name(M)]'s [I.name] ([slot_to_process]) successfully.")
-
- if(M)
- if(interactee == M && Adjacent(M))
- M.show_inv(src)
-
-
-/mob/living/carbon/human/stripPanelEquip(obj/item/I, mob/M, slot_to_process)
- if(I && !(I.flags_item & ITEM_ABSTRACT))
- if(I.flags_item & NODROP)
- to_chat(src, SPAN_WARNING("You can't put \the [I.name] on [M], it's stuck to your hand!"))
+ if(interact_item && !(interact_item.flags_item & ITEM_ABSTRACT))
+ if(interact_item.flags_item & NODROP)
+ to_chat(src, SPAN_WARNING("You can't put \the [interact_item.name] on [target_mob], it's stuck to your hand!"))
return
- if(I.flags_inventory & CANTSTRIP)
- to_chat(src, SPAN_WARNING("You're having difficulty putting \the [I.name] on [M]."))
+ if(interact_item.flags_inventory & CANTSTRIP)
+ to_chat(src, SPAN_WARNING("You're having difficulty putting \the [interact_item.name] on [target_mob]."))
return
- if(I.flags_item & WIELDED)
- I.unwield(src)
- if(!I.mob_can_equip(M, slot_to_process, TRUE))
- to_chat(src, SPAN_WARNING("You can't put \the [I.name] on [M]!"))
+ if(interact_item.flags_item & WIELDED)
+ interact_item.unwield(src)
+ if(!interact_item.mob_can_equip(target_mob, slot_to_process, TRUE))
+ to_chat(src, SPAN_WARNING("You can't put \the [interact_item.name] on [target_mob]!"))
return
- visible_message(SPAN_NOTICE("[src] tries to put \the [I.name] on [M]."), null, null, 5)
- if(do_after(src, HUMAN_STRIP_DELAY * src.get_skill_duration_multiplier(SKILL_CQC), INTERRUPT_ALL, BUSY_ICON_GENERIC, M, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
- if(I == get_active_hand() && !M.get_item_by_slot(slot_to_process) && Adjacent(M))
- if(I.flags_item & WIELDED) //to prevent re-wielding it during the do_after
- I.unwield(src)
- if(I.mob_can_equip(M, slot_to_process, TRUE))//Placing an item on the mob
- drop_inv_item_on_ground(I)
- if(I && !QDELETED(I)) //Might be self-deleted?
- M.equip_to_slot_if_possible(I, slot_to_process, 1, 0, 1, 1)
- if(ishuman(M) && M.stat == DEAD)
- var/mob/living/carbon/human/H = M
- H.disable_lights() // take that powergamers -spookydonut
-
- if(M)
- if(interactee == M && Adjacent(M))
- M.show_inv(src)
+ visible_message(SPAN_NOTICE("[src] tries to put \the [interact_item.name] on [target_mob]."), null, null, 5)
+ if(do_after(src, get_strip_delay(src, target_mob), INTERRUPT_ALL, BUSY_ICON_GENERIC, target_mob, INTERRUPT_MOVED, BUSY_ICON_GENERIC))
+ if(interact_item == get_active_hand() && !target_mob.get_item_by_slot(slot_to_process) && Adjacent(target_mob))
+ if(interact_item.flags_item & WIELDED) //to prevent re-wielding it during the do_after
+ interact_item.unwield(src)
+ if(interact_item.mob_can_equip(target_mob, slot_to_process, TRUE))//Placing an item on the mob
+ drop_inv_item_on_ground(interact_item)
+ if(interact_item && !QDELETED(interact_item)) //Might be self-deleted?
+ target_mob.equip_to_slot_if_possible(interact_item, slot_to_process, 1, 0, 1, 1)
+ if(ishuman(target_mob) && target_mob.stat == DEAD)
+ var/mob/living/carbon/human/human_target = target_mob
+ human_target.disable_lights() // take that powergamers -spookydonut
+
+ if(target_mob)
+ if(interactee == target_mob && Adjacent(target_mob))
+ target_mob.show_inv(src)
/mob/living/carbon/human/drop_inv_item_on_ground(obj/item/I, nomoveupdate, force)
remember_dropped_object(I)
diff --git a/code/modules/mob/living/carbon/human/species/working_joe/_species.dm b/code/modules/mob/living/carbon/human/species/working_joe/_species.dm
index 139b339fbfd7..292c302f9317 100644
--- a/code/modules/mob/living/carbon/human/species/working_joe/_species.dm
+++ b/code/modules/mob/living/carbon/human/species/working_joe/_species.dm
@@ -4,7 +4,7 @@
death_message = "violently gargles fluid and seizes up, the glow in their eyes dimming..."
uses_ethnicity = FALSE
burn_mod = 0.65 // made for hazardous environments, withstanding temperatures up to 1210 degrees
- mob_inherent_traits = list(TRAIT_SUPER_STRONG, TRAIT_INTENT_EYES, TRAIT_EMOTE_CD_EXEMPT, TRAIT_CANNOT_EAT)
+ mob_inherent_traits = list(TRAIT_SUPER_STRONG, TRAIT_INTENT_EYES, TRAIT_EMOTE_CD_EXEMPT, TRAIT_CANNOT_EAT, TRAIT_UNSTRIPPABLE)
slowdown = 0.45
hair_color = "#000000"