diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 7da895a593..87c226b839 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -77,6 +77,10 @@
#define COMSIG_MACHINE_PROCESS "machine_process" //from machinery subsystem fire(): ()
#define COMSIG_MACHINE_PROCESS_ATMOS "machine_process_atmos" //from air subsystem process_atmos_machinery(): ()
+
// /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 CALTROP_BYPASS_SHOES 1
+#define CALTROP_IGNORE_WALKERS 2
diff --git a/code/datums/components/caltrop.dm b/code/datums/components/caltrop.dm
new file mode 100644
index 0000000000..5b8adf0175
--- /dev/null
+++ b/code/datums/components/caltrop.dm
@@ -0,0 +1,60 @@
+/datum/component/caltrop
+ var/min_damage
+ var/max_damage
+ var/probability
+ var/flags
+
+ var/cooldown = 0
+
+/datum/component/caltrop/Initialize(_min_damage = 0, _max_damage = 0, _probability = 100, _flags = NONE)
+ min_damage = _min_damage
+ max_damage = max(_min_damage, _max_damage)
+ probability = _probability
+ flags = _flags
+
+ RegisterSignal(list(COMSIG_MOVABLE_CROSSED), .proc/Crossed)
+
+/datum/component/caltrop/proc/Crossed(atom/movable/AM)
+ var/atom/A = parent
+ if(!A.has_gravity())
+ return
+
+ if(!prob(probability))
+ return
+
+ if(ishuman(AM))
+ var/mob/living/carbon/human/H = AM
+ if(PIERCEIMMUNE in H.dna.species.species_traits)
+ return
+
+ if((flags & CALTROP_IGNORE_WALKERS) && H.m_intent == MOVE_INTENT_WALK)
+ return
+
+ var/picked_def_zone = pick("l_leg", "r_leg")
+ var/obj/item/bodypart/O = H.get_bodypart(picked_def_zone)
+ if(!istype(O))
+ return
+ if(O.status == BODYPART_ROBOTIC)
+ return
+
+ var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET))
+
+ if(!(flags & CALTROP_BYPASS_SHOES) && (H.shoes || feetCover))
+ return
+
+ if((H.movement_type & FLYING) || H.buckled)
+ return
+
+ var/damage = rand(min_damage, max_damage)
+ H.apply_damage(damage, BRUTE, picked_def_zone)
+
+ if(cooldown < world.time - 10) //cooldown to avoid message spam.
+ if(!H.incapacitated(ignore_restraints = TRUE))
+ H.visible_message("[H] steps on [A].", \
+ "You step on [A]!")
+ else
+ H.visible_message("[H] slides on [A]!", \
+ "You slide on [A]!")
+
+ cooldown = world.time
+ H.Knockdown(60)
diff --git a/code/game/objects/items/dice.dm b/code/game/objects/items/dice.dm
index 4fe27d1320..334be4a645 100644
--- a/code/game/objects/items/dice.dm
+++ b/code/game/objects/items/dice.dm
@@ -73,6 +73,10 @@
icon_state = "d4"
sides = 4
+/obj/item/dice/d4/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/caltrop, 4)
+
/obj/item/dice/d6
name = "d6"
@@ -182,14 +186,6 @@
else if(!src.throwing) //Dice was thrown and is coming to rest
visible_message("[src] rolls to a stop, landing on [result]. [comment]")
-/obj/item/dice/d4/Crossed(mob/living/carbon/human/H)
- if(istype(H) && !H.shoes)
- if(PIERCEIMMUNE in H.dna.species.species_traits)
- return 0
- to_chat(H, "You step on the D4!")
- H.apply_damage(4,BRUTE,(pick("l_leg", "r_leg")))
- H.Knockdown(60)
-
/obj/item/dice/update_icon()
cut_overlays()
add_overlay("[src.icon_state][src.result]")
diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm
index c6a5d074e7..a70692a07d 100644
--- a/code/game/objects/items/stacks/sheets/glass.dm
+++ b/code/game/objects/items/stacks/sheets/glass.dm
@@ -205,6 +205,7 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \
/obj/item/shard/Initialize()
. = ..()
+ AddComponent(/datum/component/caltrop, force)
icon_state = pick("large", "medium", "small")
switch(icon_state)
if("small")
@@ -257,27 +258,4 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \
/obj/item/shard/Crossed(mob/AM)
if(istype(AM) && has_gravity(loc))
playsound(loc, 'sound/effects/glass_step.ogg', 50, 1)
- if(ishuman(AM))
- var/mob/living/carbon/human/H = AM
- if(PIERCEIMMUNE in H.dna.species.species_traits)
- return
- var/picked_def_zone = pick("l_leg", "r_leg")
- var/obj/item/bodypart/O = H.get_bodypart(picked_def_zone)
- if(!istype(O))
- return
- if(O.status == BODYPART_ROBOTIC)
- return
- var/feetCover = (H.wear_suit && H.wear_suit.body_parts_covered & FEET) || (H.w_uniform && H.w_uniform.body_parts_covered & FEET)
- if(H.shoes || feetCover || H.movement_type & FLYING || H.buckled)
- return
- H.apply_damage(5, BRUTE, picked_def_zone)
- if(cooldown < world.time - 10) //cooldown to avoid message spam.
- if(!H.incapacitated())
- H.visible_message("[H] steps in the broken glass!", \
- "You step in the broken glass!")
- else
- H.visible_message("[H] slides on the broken glass!", \
- "You slide on the broken glass!")
-
- cooldown = world.time
- H.Knockdown(60)
+ . = ..()
diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm
index 6d6587a73a..6ecdc91635 100644
--- a/code/modules/mining/lavaland/ash_flora.dm
+++ b/code/modules/mining/lavaland/ash_flora.dm
@@ -137,18 +137,10 @@
regrowth_time_low = 4800
regrowth_time_high = 7200
-/obj/structure/flora/ash/cacti/Crossed(mob/AM)
- if(ishuman(AM) && has_gravity(loc) && prob(70))
- var/mob/living/carbon/human/H = AM
- if(!H.shoes && !H.lying) //ouch, my feet.
- var/picked_def_zone = pick("l_leg", "r_leg")
- var/obj/item/bodypart/O = H.get_bodypart(picked_def_zone)
- if(!istype(O) || (PIERCEIMMUNE in H.dna.species.species_traits))
- return
- H.apply_damage(rand(3, 6), BRUTE, picked_def_zone)
- H.Knockdown(40)
- H.visible_message("[H] steps on a cactus!", \
- "You step on a cactus!")
+/obj/structure/flora/ash/cacti/Initialize(mapload)
+ . = ..()
+ // min dmg 3, max dmg 6, prob(70)
+ AddComponent(/datum/component/caltrop, 3, 6, 70)
/obj/item/reagent_containers/food/snacks/grown/ash_flora
name = "mushroom shavings"
diff --git a/tgstation.dme b/tgstation.dme
index 0e23640d22..b95733097c 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -334,6 +334,7 @@
#include "code\datums\antagonists\wizard.dm"
#include "code\datums\components\_component.dm"
#include "code\datums\components\archaeology.dm"
+#include "code\datums\components\caltrop.dm"
#include "code\datums\components\chasm.dm"
#include "code\datums\components\decal.dm"
#include "code\datums\components\infective.dm"