Re-refactors batons / Refactors attack chain force modifiers (#90809)

## About The Pull Request

Melee attack chain now has a list passed along with it,
`attack_modifiers`, which you can stick force modifiers to change the
resulting attack

This is basically a soft implementation of damage packets until a more
definitive pr, but one that only applies to item attack chain, and not
unarmed attacks.

This change was done to facilitate a baton refactor - batons no longer
hack together their own attack chain, and are now integrated straight
into the real attack chain. This refactor itself was done because batons
don't send any attack signals, which has been annoying in the past (for
swing combat).

## Changelog

🆑 Melbert
refactor: Batons have been refactored again. Baton stuns now properly
count as an attack, when before it was a nothing. Report any oddities,
particularly in regards to harmbatonning vs normal batonning.
refactor: The method of adjusting item damage mid-attack has been
refactored - some affected items include the Nullblade and knives.
Report any strange happenings with damage numbers.
refactor: A few objects have been moved to the new interaction chain -
records consoles, mawed crucible, alien weeds and space vines, hedges,
restaurant portals, and some mobs - to name a few.
fix: Spears only deal bonus damage against secure lockers, not all
closet types (including crates)
/🆑
This commit is contained in:
MrMelbert
2025-05-19 13:32:12 +10:00
committed by GitHub
parent 225300a189
commit 5261efb67f
483 changed files with 1088 additions and 1082 deletions
+132 -159
View File
@@ -41,12 +41,15 @@
var/on_stun_sound = 'sound/effects/woodhit.ogg'
/// The volume of the above.
var/on_stun_volume = 75
/// Do we animate the "hit" when stunning something?
var/stun_animation = TRUE
/// Whether the stun attack is logged. Only relevant for abductor batons, which have different modes.
var/log_stun_attack = TRUE
/// Boolean on whether people with chunky fingers can use this baton.
var/chunky_finger_usable = FALSE
/// Boolean, if TRUE when we harmbaton someone we will also try to stun if the baton is active / not on cooldown.
var/stun_on_harmbaton = FALSE
/// Text shown when trying to stun someone while the baton is on cooldown.
var/wait_desc = ""
/// What term do we use to describe our baton being 'ready', or the phrase to use when var/active is TRUE.
var/activated_word = "ready"
@@ -96,37 +99,83 @@
readout += "\nIt has a stun armor-piercing capability of [span_warning("[get_stun_penetration_value()]%")]."
return readout.Join("\n")
/**
* Ok, think of baton attacks like a melee attack chain:
*
* [/baton_attack()] comes first. It checks if the user is clumsy, if the target parried the attack and handles some messages and sounds.
* * Depending on its return value, it'll either do a normal attack, continue to the next step or stop the attack.
*
* [/finalize_baton_attack()] is then called. It handles logging stuff, sound effects and calls baton_effect().
* * The proc is also called in other situations such as stunbatons right clicking or throw impact. Basically when baton_attack()
* * checks are either redundant or unnecessary.
*
* [/baton_effect()] is third in the line. It knockdowns targets, along other effects called in additional_effects_cyborg() and
* * additional_effects_non_cyborg().
*
* Last but not least [/set_batoned()], which gives the target the IWASBATONED trait with REF(user) as source and then removes it
* * after a cooldown has passed. Basically, it stops users from cheesing the cooldowns by dual wielding batons.
*
* TL;DR: [/baton_attack()] -> [/finalize_baton_attack()] -> [/baton_effect()] -> [/set_batoned()]
*/
/obj/item/melee/baton/attack(mob/living/target, mob/living/user, list/modifiers)
add_fingerprint(user)
switch(baton_attack(target, user, modifiers))
if(BATON_DO_NORMAL_ATTACK)
return ..()
if(BATON_ATTACKING)
finalize_baton_attack(target, user, modifiers)
/// Checks if we can actually USE the baton. Impure
/obj/item/melee/baton/proc/can_baton(mob/living/target, mob/living/user)
PROTECTED_PROC(TRUE)
if(clumsy_check(user, target))
return FALSE
if(!chunky_finger_usable && ishuman(user))
var/mob/living/carbon/human/human_user = user
if(human_user.check_chunky_fingers() && user.is_holding(src) && !HAS_MIND_TRAIT(user, TRAIT_CHUNKYFINGERS_IGNORE_BATON))
balloon_alert(human_user, "fingers are too big!")
return FALSE
return TRUE
// Stun attack
/obj/item/melee/baton/pre_attack(atom/target, mob/living/user, list/modifiers, list/attack_modifiers)
. = ..()
if(. || !isliving(target))
return .
if(!can_baton(target, user))
return TRUE
if(!COOLDOWN_FINISHED(src, cooldown_check))
if(wait_desc)
balloon_alert(user, wait_desc)
return TRUE
if(HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user))) //no doublebaton abuse anon!
target.balloon_alert(user, "can't stun yet!")
return TRUE
if(active)
// when we continue to attack, deal 0 (brute) damage (just stun)
SET_ATTACK_FORCE(attack_modifiers, 0)
MUTE_ATTACK_HITSOUND(attack_modifiers)
HIDE_ATTACK_MESSAGES(attack_modifiers)
return .
// Harm attack
/obj/item/melee/baton/pre_attack_secondary(atom/target, mob/living/user, list/modifiers, list/attack_modifiers)
. = ..()
if(. != SECONDARY_ATTACK_CALL_NORMAL || !isliving(target))
return .
if(!can_baton(target, user))
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
// call attack, NOT pre_attack
// (so we go directly to harming)
return SECONDARY_ATTACK_CONTINUE_CHAIN
// This is where stun gets applied
/obj/item/melee/baton/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers)
if(!active || !COOLDOWN_FINISHED(src, cooldown_check) || HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user)))
return
// worst check in the chain but - right click = harmbaton
if(LAZYACCESS(modifiers, RIGHT_CLICK) && !stun_on_harmbaton)
return
finalize_baton_attack(target, user, modifiers)
var/list/desc
if(iscyborg(target))
desc = get_cyborg_stun_description(target, user)
if(!affect_cyborg)
playsound(src, 'sound/effects/bang.ogg', 10, TRUE) //bonk
else
desc = get_stun_description(target, user)
if(desc)
target.visible_message(desc["visible"], desc["local"], visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE)
/obj/item/melee/baton/apply_fantasy_bonuses(bonus)
. = ..()
stamina_damage = modify_fantasy_variable("stamina_damage", stamina_damage, bonus * 4)
/obj/item/melee/baton/remove_fantasy_bonuses(bonus)
stamina_damage = reset_fantasy_variable("stamina_damage", stamina_damage)
return ..()
@@ -155,74 +204,19 @@
return CONTEXTUAL_SCREENTIP_SET
/obj/item/melee/baton/proc/baton_attack(mob/living/target, mob/living/user, modifiers)
. = BATON_ATTACKING
if(clumsy_check(user, target))
return BATON_ATTACK_DONE
if(!chunky_finger_usable && ishuman(user))
var/mob/living/carbon/human/potential_chunky_finger_human = user
if(potential_chunky_finger_human.check_chunky_fingers() && user.is_holding(src) && !HAS_MIND_TRAIT(user, TRAIT_CHUNKYFINGERS_IGNORE_BATON))
balloon_alert(potential_chunky_finger_human, "fingers are too big!")
return BATON_ATTACK_DONE
if(!active || LAZYACCESS(modifiers, RIGHT_CLICK))
return BATON_DO_NORMAL_ATTACK
if(cooldown_check > world.time)
var/wait_desc = get_wait_description()
if (wait_desc)
to_chat(user, wait_desc)
return BATON_ATTACK_DONE
if(check_parried(target, user))
return BATON_ATTACK_DONE
if(HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user))) //no doublebaton abuse anon!
to_chat(user, span_danger("You fumble and miss [target]!"))
return BATON_ATTACK_DONE
if(stun_animation)
user.do_attack_animation(target)
var/list/desc
if(iscyborg(target))
if(affect_cyborg)
desc = get_cyborg_stun_description(target, user)
else
desc = get_unga_dunga_cyborg_stun_description(target, user)
playsound(get_turf(src), 'sound/effects/bang.ogg', 10, TRUE) //bonk
. = BATON_ATTACK_DONE
else
desc = get_stun_description(target, user)
if(desc)
target.visible_message(desc["visible"], desc["local"])
/obj/item/melee/baton/proc/check_parried(mob/living/carbon/human/human_target, mob/living/user)
if (human_target.check_block(src, 0, "[user]'s [name]", MELEE_ATTACK))
playsound(human_target, 'sound/items/weapons/genhit.ogg', 50, TRUE)
return TRUE
return FALSE
/obj/item/melee/baton/proc/finalize_baton_attack(mob/living/target, mob/living/user, modifiers, in_attack_chain = TRUE)
if(!in_attack_chain && HAS_TRAIT_FROM(target, TRAIT_IWASBATONED, REF(user)))
return BATON_ATTACK_DONE
cooldown_check = world.time + cooldown
/// Wrapper for calling "stun()" and doing relevant vfx/sfx
/obj/item/melee/baton/proc/finalize_baton_attack(mob/living/target, mob/living/user, list/modifiers)
PROTECTED_PROC(TRUE)
COOLDOWN_START(src, cooldown_check, cooldown)
if(on_stun_sound)
playsound(get_turf(src), on_stun_sound, on_stun_volume, TRUE, -1)
if(user)
target.lastattacker = user.real_name
target.lastattackerckey = user.ckey
if(log_stun_attack)
log_combat(user, target, "stun attacked", src)
playsound(src, on_stun_sound, on_stun_volume, TRUE, -1)
if(baton_effect(target, user, modifiers) && user)
set_batoned(target, user, cooldown)
log_combat(user, target, "stunned", src.name)
/obj/item/melee/baton/proc/baton_effect(mob/living/target, mob/living/user, modifiers, stun_override)
/// The actual "stun()" of the stun baton
/obj/item/melee/baton/proc/baton_effect(mob/living/target, mob/living/user, list/modifiers, stun_override)
PROTECTED_PROC(TRUE)
var/trait_check = HAS_TRAIT(target, TRAIT_BATON_RESISTANCE)
if(iscyborg(target))
if(!affect_cyborg)
@@ -244,46 +238,37 @@
SEND_SIGNAL(target, COMSIG_MOB_BATONED, user, src)
return TRUE
/// Description for trying to stun when still on cooldown.
/obj/item/melee/baton/proc/get_wait_description()
return
/// Default message for stunning a living, non-cyborg mob.
/obj/item/melee/baton/proc/get_stun_description(mob/living/target, mob/living/user)
PROTECTED_PROC(TRUE)
. = list()
.["visible"] = span_danger("[user] knocks [target] down with [src]!")
.["local"] = span_userdanger("[user] knocks you down with [src]!")
return .
/// Default message for stunning a cyborg.
/obj/item/melee/baton/proc/get_cyborg_stun_description(mob/living/target, mob/living/user)
PROTECTED_PROC(TRUE)
. = list()
.["visible"] = span_danger("[user] pulses [target]'s sensors with the baton!")
.["local"] = span_danger("You pulse [target]'s sensors with the baton!")
return .
/// Default message for trying to stun a cyborg with a baton that can't stun cyborgs.
/obj/item/melee/baton/proc/get_unga_dunga_cyborg_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] tries to knock down [target] with [src], and predictably fails!") //look at this duuuuuude
.["local"] = span_userdanger("[user] tries to... knock you down with [src]?") //look at the top of his head!
return .
if(affect_cyborg)
.["visible"] = span_danger("[user] pulses [target]'s sensors with the baton!")
.["local"] = span_danger("You pulse [target]'s sensors with the baton!")
else
.["visible"] = span_danger("[user] tries to knock down [target] with [src], and predictably fails!") //look at this duuuuuude
.["local"] = span_userdanger("[user] tries to... knock you down with [src]?") //look at the top of his head!
/// Contains any special effects that we apply to living, non-cyborg mobs we stun. Does not include applying a knockdown, dealing stamina damage, etc.
/obj/item/melee/baton/proc/additional_effects_non_cyborg(mob/living/target, mob/living/user)
PROTECTED_PROC(TRUE)
return
/// Contains any special effects that we apply to cyborgs we stun. Does not include flashing the cyborg's screen, hardstunning them, etc.
/obj/item/melee/baton/proc/additional_effects_cyborg(mob/living/target, mob/living/user)
PROTECTED_PROC(TRUE)
return
/// Used in marking a target as being hit by a baton
/obj/item/melee/baton/proc/set_batoned(mob/living/target, mob/living/user, cooldown)
PRIVATE_PROC(TRUE)
if(!cooldown)
return
var/user_ref = REF(user) // avoids harddels.
@@ -293,7 +278,7 @@
/obj/item/melee/baton/proc/clumsy_check(mob/living/user, mob/living/intented_target)
if(!active || !HAS_TRAIT(user, TRAIT_CLUMSY) || prob(50))
return FALSE
user.visible_message(span_danger("[user] accidentally hits [user.p_them()]self over the head with [src]! What a doofus!"), span_userdanger("You accidentally hit yourself over the head with [src]!"))
user.visible_message(span_danger("[user] accidentally hits [user.p_them()]self over the head with [src]! What a doofus!"), span_userdanger("You accidentally hit yourself over the head with [src]!"), visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE)
if(iscyborg(user))
if(affect_cyborg)
@@ -301,9 +286,9 @@
user.Paralyze(clumsy_knockdown_time)
additional_effects_cyborg(user, user) // user is the target here
if(on_stun_sound)
playsound(get_turf(src), on_stun_sound, on_stun_volume, TRUE, -1)
playsound(src, on_stun_sound, on_stun_volume, TRUE, -1)
else
playsound(get_turf(src), 'sound/effects/bang.ogg', 10, TRUE)
playsound(src, 'sound/effects/bang.ogg', 10, TRUE)
else
//straight up always force say for clumsy humans
if(ishuman(user))
@@ -313,14 +298,11 @@
user.apply_damage(stamina_damage, STAMINA)
additional_effects_non_cyborg(user, user) // user is the target here
if(on_stun_sound)
playsound(get_turf(src), on_stun_sound, on_stun_volume, TRUE, -1)
user.apply_damage(2*force, BRUTE, BODY_ZONE_HEAD, attacking_item = src)
playsound(src, on_stun_sound, on_stun_volume, TRUE, -1)
user.apply_damage(2 * force, BRUTE, BODY_ZONE_HEAD, attacking_item = src)
log_combat(user, user, "accidentally stun attacked [user.p_them()]self due to their clumsiness", src)
if(stun_animation)
user.do_attack_animation(user)
return
user.do_attack_animation(user)
/// Handles the penetration value of our baton, called during baton_effect()
/obj/item/melee/baton/proc/get_stun_penetration_value()
@@ -461,6 +443,7 @@
stun_armour_penetration = 40
clumsy_knockdown_time = 24 SECONDS
affect_cyborg = TRUE
wait_desc = "still charging!"
on_stun_sound = 'sound/items/weapons/contractor_baton/contractorbatonhit.ogg'
unfolded_drop_sound = 'sound/items/baton/contractor_baton_unfolded_pickup.ogg'
unfolded_pickup_sound = 'sound/items/baton/contractor_baton_unfolded_pickup.ogg'
@@ -469,9 +452,6 @@
on_sound = 'sound/items/weapons/contractorbatonextend.ogg'
active_force = 16
/obj/item/melee/baton/telescopic/contractor_baton/get_wait_description()
return span_danger("The baton is still charging!")
/obj/item/melee/baton/telescopic/contractor_baton/additional_effects_non_cyborg(mob/living/target, mob/living/user)
. = ..()
target.set_jitter_if_lower(40 SECONDS)
@@ -504,6 +484,8 @@
on_stun_sound = 'sound/items/weapons/egloves.ogg'
on_stun_volume = 50
active = FALSE
stun_on_harmbaton = TRUE
wait_desc = "still charging!"
activated_word = "activated"
context_living_rmb_active = "Harmful Stun"
light_range = 1.5
@@ -553,7 +535,7 @@
/obj/item/melee/baton/security/suicide_act(mob/living/user)
if(cell?.charge && active)
user.visible_message(span_suicide("[user] is putting the live [name] in [user.p_their()] mouth! It looks like [user.p_theyre()] trying to commit suicide!"))
attack(user, user)
finalize_baton_attack(user, user)
return FIRELOSS
else
user.visible_message(span_suicide("[user] is shoving \the [src] down their throat! It looks like [user.p_theyre()] trying to commit suicide!"))
@@ -624,7 +606,7 @@
tool.play_tool_sound(src)
return TRUE
/obj/item/melee/baton/security/attackby(obj/item/item, mob/user, list/modifiers)
/obj/item/melee/baton/security/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers)
if(istype(item, /obj/item/stock_parts/power_store/cell))
var/obj/item/stock_parts/power_store/cell/active_cell = item
if(cell)
@@ -727,18 +709,20 @@
SEND_SIGNAL(user, COMSIG_LIVING_MINOR_SHOCK)
deductcharge(cell_hit_cost)
/// Handles prodding targets with turned off stunbatons and right clicking stun'n'bash
/obj/item/melee/baton/security/baton_attack(mob/living/target, mob/living/user, modifiers)
/obj/item/melee/baton/security/pre_attack(atom/target, mob/living/user, list/modifiers, list/attack_modifiers)
. = ..()
if(. != BATON_DO_NORMAL_ATTACK)
return
if(LAZYACCESS(modifiers, RIGHT_CLICK))
if(active && cooldown_check <= world.time && !check_parried(target, user))
finalize_baton_attack(target, user, modifiers, in_attack_chain = FALSE)
else if(!user.combat_mode)
target.visible_message(span_warning("[user] prods [target] with [src]. Luckily it was off."), \
span_warning("[user] prods you with [src]. Luckily it was off."))
return BATON_ATTACK_DONE
if(. || !isliving(target))
return .
if(!active && !user.combat_mode)
target.visible_message(
span_warning("[user] prods [target] with [src]. Luckily it was off."),
span_warning("[user] prods you with [src]. Luckily it was off."),
visible_message_flags = ALWAYS_SHOW_SELF_MESSAGE,
)
return TRUE
return .
/obj/item/melee/baton/security/baton_effect(mob/living/target, mob/living/user, modifiers, stun_override)
if(iscyborg(loc))
@@ -778,25 +762,22 @@
if(!trait_check)
target.Knockdown(knockdown_time)
/obj/item/melee/baton/security/get_wait_description()
return span_danger("The baton is still charging!")
/obj/item/melee/baton/security/get_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] stuns [target] with [src]!")
.["local"] = span_userdanger("[user] stuns you with [src]!")
/obj/item/melee/baton/security/get_unga_dunga_cyborg_stun_description(mob/living/target, mob/living/user)
. = list()
.["visible"] = span_danger("[user] tries to stun [target] with [src], and predictably fails!")
.["local"] = span_userdanger("[user] tries to... stun you with [src]?")
/obj/item/melee/baton/security/get_cyborg_stun_description(mob/living/target, mob/living/user)
. = ..()
if(!affect_cyborg)
.["visible"] = span_danger("[user] tries to stun [target] with [src], and predictably fails!")
.["local"] = span_userdanger("[user] tries to... stun you with [src]?")
/obj/item/melee/baton/security/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
if(!. && active && prob(throw_stun_chance) && isliving(hit_atom))
finalize_baton_attack(hit_atom, throwingdatum.get_thrower(), in_attack_chain = FALSE)
finalize_baton_attack(hit_atom, throwingdatum.get_thrower())
/obj/item/melee/baton/security/emp_act(severity)
. = ..()
@@ -857,7 +838,7 @@
. = ..()
sparkler = new (src)
/obj/item/melee/baton/security/cattleprod/attackby(obj/item/item, mob/user, list/modifiers)//handles sticking a crystal onto a stunprod to make an improved cattleprod
/obj/item/melee/baton/security/cattleprod/attackby(obj/item/item, mob/user, list/modifiers, list/attack_modifiers)//handles sticking a crystal onto a stunprod to make an improved cattleprod
if(!istype(item, /obj/item/stack))
return ..()
@@ -889,9 +870,9 @@
var/obj/item/melee/baton/security/cattleprod/brand_new_prod = new our_prod(user.loc)
user.put_in_hands(brand_new_prod)
/obj/item/melee/baton/security/cattleprod/baton_effect()
/obj/item/melee/baton/security/cattleprod/can_baton(mob/living/target, mob/living/user)
if(!sparkler.activate())
return BATON_ATTACK_DONE
return FALSE
return ..()
/obj/item/melee/baton/security/cattleprod/Destroy()
@@ -919,15 +900,7 @@
/obj/item/melee/baton/security/boomerang/Initialize(mapload)
. = ..()
AddComponent(/datum/component/boomerang, throw_range+2, TRUE)
/obj/item/melee/baton/security/boomerang/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(!active)
return ..()
var/caught = hit_atom.hitby(src, skipcatch = FALSE, hitpush = FALSE, throwingdatum = throwingdatum)
var/mob/thrown_by = throwingdatum.get_thrower()
if(isliving(hit_atom) && !iscyborg(hit_atom) && !caught && prob(throw_stun_chance))//if they are a living creature and they didn't catch it
finalize_baton_attack(hit_atom, thrown_by, in_attack_chain = FALSE)
AddComponent(/datum/component/boomerang, throw_range + 2, TRUE)
/obj/item/melee/baton/security/boomerang/loaded //Same as above, comes with a cell.
preload_cell_type = /obj/item/stock_parts/power_store/cell/high
+3 -3
View File
@@ -243,7 +243,7 @@
final_block_chance = 0 //Don't bring a sword to a gunfight, and also you aren't going to really block someone full body tackling you with a sword
return ..()
/obj/item/melee/beesword/afterattack(atom/target, mob/user, list/modifiers)
/obj/item/melee/beesword/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers)
if(iscarbon(target))
var/mob/living/carbon/carbon_target = target
carbon_target.reagents.add_reagent(/datum/reagent/toxin, 4)
@@ -292,7 +292,7 @@
if(!isspaceturf(turf))
consume_turf(turf)
/obj/item/melee/supermatter_sword/pre_attack(atom/target, mob/living/user, list/modifiers)
/obj/item/melee/supermatter_sword/pre_attack(atom/target, mob/living/user, list/modifiers, list/attack_modifiers)
. = ..()
if(.)
return .
@@ -386,7 +386,7 @@
attack_verb_simple = list("flog", "whip", "lash", "discipline")
hitsound = 'sound/items/weapons/whip.ogg'
/obj/item/melee/curator_whip/afterattack(atom/target, mob/user, list/modifiers)
/obj/item/melee/curator_whip/afterattack(atom/target, mob/user, list/modifiers, list/attack_modifiers)
if(ishuman(target))
var/mob/living/carbon/human/human_target = target
human_target.drop_all_held_items()