diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 0b2a3e06d4..4bb27d56d6 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -82,6 +82,7 @@
// /mob/living/carbon/human signals
#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target)
#define COMSIG_HUMAN_MELEE_UNARMED_ATTACKBY "human_melee_unarmed_attackby" //from mob/living/carbon/human/UnarmedAttack(): (mob/living/carbon/human/attacker)
+#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted)
#define CALTROP_BYPASS_SHOES 1
#define CALTROP_IGNORE_WALKERS 2
diff --git a/code/datums/components/knockoff.dm b/code/datums/components/knockoff.dm
new file mode 100644
index 0000000000..35d1e5423e
--- /dev/null
+++ b/code/datums/components/knockoff.dm
@@ -0,0 +1,53 @@
+//Items with these will have a chance to get knocked off when disarming
+/datum/component/knockoff
+ var/knockoff_chance = 100 //Chance to knockoff
+ var/list/target_zones //Aiming for these zones will cause the knockoff, null means all zones allowed
+ var/list/slots_knockoffable //Can be only knocked off from these slots, null means all slots allowed
+ var/datum/component/redirect/disarm_redirect
+
+/datum/component/knockoff/Initialize(knockoff_chance,zone_override,slots_knockoffable)
+ if(!isitem(parent))
+ . = COMPONENT_INCOMPATIBLE
+ CRASH("Knockoff component misuse")
+ RegisterSignal(COMSIG_ITEM_EQUIPPED,.proc/OnEquipped)
+ RegisterSignal(COMSIG_ITEM_DROPPED,.proc/OnDropped)
+
+ src.knockoff_chance = knockoff_chance
+
+ if(zone_override)
+ target_zones = zone_override
+
+ if(slots_knockoffable)
+ src.slots_knockoffable = slots_knockoffable
+
+/datum/component/knockoff/proc/Knockoff(mob/living/attacker,zone)
+ var/obj/item/I = parent
+ var/mob/living/carbon/human/wearer = I.loc
+ if(!istype(wearer))
+ return
+ if(target_zones && !(zone in target_zones))
+ return
+ if(!prob(knockoff_chance))
+ return
+ if(!wearer.dropItemToGround(I))
+ return
+
+ wearer.visible_message("[attacker] knocks off [wearer]'s [I.name]!","[attacker] knocks off your [I.name]!")
+
+/datum/component/knockoff/proc/OnEquipped(mob/living/carbon/human/H,slot)
+ if(!istype(H))
+ return
+ if(slots_knockoffable && !(slot in slots_knockoffable))
+ if(disarm_redirect)
+ QDEL_NULL(disarm_redirect)
+ return
+ if(!disarm_redirect)
+ disarm_redirect = H.AddComponent(/datum/component/redirect,list(COMSIG_HUMAN_DISARM_HIT),CALLBACK(src,.proc/Knockoff))
+
+/datum/component/knockoff/proc/OnDropped(mob/living/M)
+ if(disarm_redirect)
+ QDEL_NULL(disarm_redirect)
+
+/datum/component/knockoff/Destroy()
+ QDEL_NULL(disarm_redirect)
+ . = ..()
\ No newline at end of file
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index d4e5240642..823a91cead 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -406,6 +406,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
if(DROPDEL_1 & flags_1)
qdel(src)
in_inventory = FALSE
+ SendSignal(COMSIG_ITEM_DROPPED,user)
// called just as an item is picked up (loc is not yet changed)
/obj/item/proc/pickup(mob/user)
@@ -436,6 +437,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/datum/action/A = X
if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot.
A.Grant(user)
+ SendSignal(COMSIG_ITEM_EQUIPPED,user,slot)
in_inventory = TRUE
//sometimes we only want to grant the item's action if it's equipped in a specific slot.
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index 9c7c34510c..fd414b8d9a 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -129,6 +129,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM
reagents.add_reagent_list(list_reagents)
if(starts_lit)
light()
+ AddComponent(/datum/component/knockoff,90,list("mouth"),list(slot_wear_mask))//90% to knock off when wearing a mask
/obj/item/clothing/mask/cigarette/Destroy()
STOP_PROCESSING(SSobj, src)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 0b02a2c711..a3ebf5ab2f 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1366,8 +1366,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else if(target.lying)
target.forcesay(GLOB.hit_appends)
-
-
/datum/species/proc/disarm(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
var/aim_for_mouth = user.zone_selected == "mouth"
var/target_on_help_and_unarmed = target.a_intent == INTENT_HELP && !target.get_active_held_item()
@@ -1387,10 +1385,12 @@ GLOBAL_LIST_EMPTY(roundstart_races)
return 1
else
user.do_attack_animation(target, ATTACK_EFFECT_DISARM)
-
+
if(target.w_uniform)
target.w_uniform.add_fingerprint(user)
- var/obj/item/bodypart/affecting = target.get_bodypart(ran_zone(user.zone_selected))
+ var/randomized_zone = ran_zone(user.zone_selected)
+ target.SendSignal(COMSIG_HUMAN_DISARM_HIT, user, user.zone_selected)
+ var/obj/item/bodypart/affecting = target.get_bodypart(randomized_zone)
var/randn = rand(1, 100)
if(randn <= 25)
playsound(target, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
diff --git a/tgstation.dme b/tgstation.dme
index 2852a1d295..7f9f4707e1 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -344,6 +344,7 @@
#include "code\datums\components\caltrop.dm"
#include "code\datums\components\chasm.dm"
#include "code\datums\components\decal.dm"
+#include "code\datums\components\knockoff.dm"
#include "code\datums\components\infective.dm"
#include "code\datums\components\jousting.dm"
#include "code\datums\components\material_container.dm"