diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm
index 83ba539baef..dd16f7ee66e 100644
--- a/code/__HELPERS/traits.dm
+++ b/code/__HELPERS/traits.dm
@@ -230,6 +230,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define STAT_TRAIT "stat_trait"
#define TRANSFORMING_TRAIT "transforming"
#define BUCKLING_TRAIT "buckled"
+#define TRAIT_WAS_BATONNED "batonged"
//quirk traits
#define TRAIT_ALCOHOL_TOLERANCE "alcohol_tolerance"
diff --git a/code/game/objects/items/weapons/batons.dm b/code/game/objects/items/weapons/batons.dm
index ada35397da3..33a3b306bf2 100644
--- a/code/game/objects/items/weapons/batons.dm
+++ b/code/game/objects/items/weapons/batons.dm
@@ -16,8 +16,12 @@
// Settings
/// Whether the baton can stun silicon mobs
var/affect_silicon = FALSE
+ /// The amount of stamina damage the baton does per swing
+ var/stamina_damage = 30
+ /// How much melee armour is ignored by the stamina damage
+ var/stamina_armour_pen = 0
/// The stun time (in seconds) for non-silicons
- var/stun_time = 6 SECONDS
+ var/knockdown_duration = 6 SECONDS
/// The stun time (in seconds) for silicons
var/stun_time_silicon = 10 SECONDS
/// Cooldown in seconds between two knockdowns
@@ -38,7 +42,7 @@
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
user.visible_message("[user] accidentally clubs [user.p_them()]self with [src]!", \
"You accidentally club yourself with [src]!")
- user.Weaken(stun_time)
+ user.KnockDown(knockdown_duration)
if(ishuman(user))
var/mob/living/carbon/human/H = user
H.apply_damage(force * 2, BRUTE, "head")
@@ -53,7 +57,7 @@
if(issilicon(target) && !affect_silicon)
return ..()
else
- stun(target, user)
+ baton_knockdown(target, user)
/**
* Called when a target is about to be hit non-lethally.
@@ -62,7 +66,7 @@
* * target - The mob about to be hit
* * user - The attacking user
*/
-/obj/item/melee/classic_baton/proc/stun(mob/living/target, mob/living/user)
+/obj/item/melee/classic_baton/proc/baton_knockdown(mob/living/target, mob/living/user)
if(issilicon(target))
user.visible_message("[user] pulses [target]'s sensors with [src]!",\
"You pulse [target]'s sensors with [src]!")
@@ -81,12 +85,12 @@
// Visuals and sound
user.do_attack_animation(target)
playsound(target, stun_sound, 75, TRUE, -1)
- add_attack_logs(user, target, "Stunned with [src]")
+ add_attack_logs(user, target, "Knocked down with [src]")
// Hit 'em
target.LAssailant = iscarbon(user) ? user : null
- target.Weaken(stun_time)
+ target.KnockDown(knockdown_duration)
on_cooldown = TRUE
- addtimer(CALLBACK(src, .proc/cooldown_finished), cooldown)
+ addtimer(VARSET_CALLBACK(src, on_cooldown, FALSE), cooldown)
return TRUE
/**
@@ -108,13 +112,9 @@
* * user - The attacking user
*/
/obj/item/melee/classic_baton/proc/on_non_silicon_stun(mob/living/target, mob/living/user)
- return
-
-/**
- * Called some time after a non-lethal attack
- */
-/obj/item/melee/classic_baton/proc/cooldown_finished()
- on_cooldown = FALSE
+ var/armour = target.run_armor_check("chest", armour_penetration = stamina_armour_pen) // returns a % of their chest melee armour
+ var/factor = (100 - armour) / 100 // converts the % into a decimal
+ target.adjustStaminaLoss(stamina_damage * factor)
/**
* # Fancy Cane
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index d3c39d48170..080b3b87f1a 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -11,15 +11,19 @@
origin_tech = "combat=2"
attack_verb = list("beaten")
armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 50, BIO = 0, RAD = 0, FIRE = 80, ACID = 80)
- /// How many life ticks does the stun last for
- var/stunforce = 14 SECONDS
+ /// How many seconds does the knockdown last for?
+ var/knockdown_duration = 10 SECONDS
+ /// how much stamina damage does this baton do?
+ var/stam_damage = 60 // 2 hits or 1 hit + 2 disabler shots
/// Is the baton currently turned on
var/turned_on = FALSE
/// How much power does it cost to stun someone
var/hitcost = 1000
- /// Chance for the baton to stun when thrown at someone
- var/throw_hit_chance = 35
var/obj/item/stock_parts/cell/high/cell = null
+ /// the initial cooldown tracks the time between swings. tracks the world.time when the baton is usable again.
+ var/cooldown = 3.5 SECONDS
+ /// the time it takes before the target falls over
+ var/knockdown_delay = 2.5 SECONDS
/obj/item/melee/baton/Initialize(mapload)
. = ..()
@@ -80,11 +84,6 @@
/obj/item/melee/baton/get_cell()
return cell
-/obj/item/melee/baton/throw_impact(atom/hit_atom)
- ..()
- if(prob(throw_hit_chance) && turned_on && isliving(hit_atom) && !issilicon(hit_atom))
- baton_stun(hit_atom)
-
/**
* Removes the specified amount of charge from the batons power cell.
*
@@ -153,10 +152,9 @@
/obj/item/melee/baton/attack(mob/M, mob/living/user)
if(turned_on && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
- user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!",
+ if(baton_stun(user, user, skip_cooldown = TRUE)) // for those super edge cases where you clumsy baton yourself in quick succession
+ user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!",
"You accidentally hit yourself with [src]!")
- user.Weaken(stunforce * 3)
- deductcharge(hitcost)
return
if(issilicon(M)) // Can't stunbaton borgs and AIs
@@ -181,23 +179,34 @@
"[L == user ? "You prod yourself" : "[user] has prodded you"] with [src]. Luckily it was off.")
return
- baton_stun(L, user)
- user.do_attack_animation(L)
+ if(baton_stun(L, user))
+ user.do_attack_animation(L)
-/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user)
+/// returning false results in no baton attack animation, returning true results in an animation.
+/obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user, skip_cooldown = FALSE)
+ if(cooldown > world.time && !skip_cooldown)
+ return FALSE
+
+ var/user_UID = user.UID()
+ if(HAS_TRAIT_FROM(L, TRAIT_WAS_BATONNED, user_UID)) // prevents double baton cheese.
+ return FALSE
+
+ cooldown = world.time + initial(cooldown) // tracks the world.time when hitting will be next available.
if(ishuman(L))
var/mob/living/carbon/human/H = L
if(H.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that
playsound(L, 'sound/weapons/genhit.ogg', 50, TRUE)
- return
+ return FALSE
H.forcesay(GLOB.hit_appends)
+ H.Confused(10 SECONDS)
+ H.Jitter(10 SECONDS)
+ H.adjustStaminaLoss(stam_damage)
+
+ ADD_TRAIT(L, TRAIT_WAS_BATONNED, user.UID()) // so one person cannot hit the same person with two separate batons
+ addtimer(CALLBACK(src, .proc/baton_knockdown, L, user_UID, knockdown_duration), knockdown_delay)
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK, 33)
- L.Weaken(stunforce)
- L.Stuttering(stunforce)
-
-
if(user)
L.lastattacker = user.real_name
L.lastattackerckey = user.ckey
@@ -206,7 +215,11 @@
add_attack_logs(user, L, "stunned")
playsound(src, 'sound/weapons/egloves.ogg', 50, TRUE, -1)
deductcharge(hitcost)
+ return TRUE
+/obj/item/melee/baton/proc/baton_knockdown(mob/living/target, user_UID, knockdown_duration)
+ target.KnockDown(knockdown_duration)
+ REMOVE_TRAIT(target, TRAIT_WAS_BATONNED, user_UID)
/obj/item/melee/baton/emp_act(severity)
. = ..()
@@ -216,13 +229,9 @@
/obj/item/melee/baton/wash(mob/living/user, atom/source)
if(turned_on && cell?.charge)
flick("baton_active", source)
- user.Stun(stunforce)
- user.Weaken(stunforce)
- user.SetStuttering(stunforce)
- deductcharge(hitcost)
+ baton_stun(user, user, skip_cooldown = TRUE)
user.visible_message("[user] shocks [user.p_them()]self while attempting to wash the active [src]!",
"You unwisely attempt to wash [src] while it's still on.")
- playsound(src, "sparks", 50, TRUE)
return TRUE
..()
@@ -235,9 +244,9 @@
item_state = "prod"
force = 3
throwforce = 5
- stunforce = 10 SECONDS
+ knockdown_duration = 6 SECONDS
+ stam_damage = 40 // three hits to crit
hitcost = 2000
- throw_hit_chance = 10
slot_flags = SLOT_BACK
var/obj/item/assembly/igniter/sparkler = null
@@ -249,6 +258,6 @@
QDEL_NULL(sparkler)
return ..()
-/obj/item/melee/baton/cattleprod/baton_stun()
+/obj/item/melee/baton/cattleprod/baton_stun(mob/living/L, mob/user, skip_cooldown = FALSE)
if(sparkler.activate())
return ..()
diff --git a/code/game/objects/items/weapons/teleprod.dm b/code/game/objects/items/weapons/teleprod.dm
index b15d843c98c..e1bb74ff273 100644
--- a/code/game/objects/items/weapons/teleprod.dm
+++ b/code/game/objects/items/weapons/teleprod.dm
@@ -13,7 +13,6 @@
if(HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50))
user.visible_message("[user] accidentally hits [user.p_them()]self with [src]!",
"You accidentally hit yourself with [src]!")
- user.Weaken(stunforce * 3)
deductcharge(hitcost)
do_teleport(user, get_turf(user), 50)//honk honk
else if(iscarbon(M) && !M.anchored)
diff --git a/code/modules/antagonists/traitor/contractor/items/contractor_baton.dm b/code/modules/antagonists/traitor/contractor/items/contractor_baton.dm
index 30fc73ac222..fc94e31373b 100644
--- a/code/modules/antagonists/traitor/contractor/items/contractor_baton.dm
+++ b/code/modules/antagonists/traitor/contractor/items/contractor_baton.dm
@@ -3,8 +3,10 @@
desc = "A compact, specialised baton issued to Syndicate contractors. Applies light electrical shocks to targets."
// Overrides
affect_silicon = TRUE
- stun_time = 2 SECONDS
+ knockdown_duration = 4 SECONDS
cooldown = 2.5 SECONDS
+ stamina_damage = 70
+ stamina_armour_pen = 100
force_off = 5
force_on = 15
item_state_on = "contractor_baton"
@@ -13,15 +15,12 @@
stun_sound = 'sound/weapons/contractorbatonhit.ogg'
extend_sound = 'sound/weapons/contractorbatonextend.ogg'
// Settings
- /// Stamina damage to deal on stun.
- var/stamina_damage = 70
/// Jitter to deal on stun.
var/jitter_amount = 5 SECONDS
/// Stutter to deal on stun.
var/stutter_amount = 10 SECONDS
-/obj/item/melee/classic_baton/telescopic/contractor/stun(mob/living/target, mob/living/user)
+/obj/item/melee/classic_baton/telescopic/contractor/baton_knockdown(mob/living/target, mob/living/user)
. = ..()
- target.adjustStaminaLoss(stamina_damage)
target.Jitter(jitter_amount)
target.AdjustStuttering(stutter_amount)