From ef520472740e57f253545c24c7526e45e47b5ec2 Mon Sep 17 00:00:00 2001 From: necromanceranne <40847847+necromanceranne@users.noreply.github.com> Date: Thu, 23 Nov 2023 04:36:04 +1100 Subject: [PATCH] [READY] The Tackleling: Unarmed bonuses and features contribute to tackle success and failure, significant outcome overhaul, among other things (#79721) ## About The Pull Request ### Tackling Outcomes Tackling now determines success based on outcome categories. These are derived from the typical attacker/defender roll that would have previously determined the outcome on its own. A negative roll results in a negative outcome, a positive roll a positive outcome, and a result of exactly 0 resulting in a neutral outcome. The result of your roll are then passed along to the relevant proc to determine severity. The derived roll is multiplied by 10 (or -10 for the negative roll to get a positive value to roll with). Then we see if our final roll fits a severity bracket. Negative outcomes will roll to determine their outcome, and potentially could roll a less severe outcome than what our first roll would suggest. For positive outcomes, the defender's melee armor reduces the severity of the outcome. For negative outcomes, the attacker's melee armor improves the potential outcome and at least prevents more severe backlash. It'll still be negative, you can't move from a negative outcome to a positive outcome just from good armor. Most of the outcomes are fairly similar to the current outcomes, but with the inclusion of staggering one or both parties to make the subsequent potential grabs _stickier_, if that makes sense. Neutral is now a mutual stagger, but also the tackler being left upright. It's effectively net zero. ### Blocking Blocking is checked on impact, and results in a neutral outcome if the defender successfully blocks. This means our tackler isn't too severely impacted from an unsuccessful tackle ### Additional Changes Your arms ``unarmed_effectiveness`` now contributes to the attack mod and defense mod of tackles. For humans tackling humans, this often results in a net neutral result. But if you have a better arm, or the tackle target has worse arms, this can alter the outcome significantly. Any tackler with the trait TRAIT_NOGUNS (like bezerkers, Sleeping Carp users or the very unlikely chance ninjas are tackling while wearing their armor) gains a bonus to their tackles. Any suit that prevents shove knockdowns grants an attack bonus, and not just riot armor. This now includes Mk.1 Swat suits, the ones from the SWAT crate in cargo. Settlers are vulnerable to tackles, much like their dwarf cousins. They're also just as bad at tackles. Security lockers come with gripper gloves, and the sec vendor has 5 sets of gripper gloves as standard items. They also have a +1 skill bonus. This should encourage people to use tackling a bit more without having to always seek out the best gear to accomplish the task. (particularly since security is inherently pretty good at tackling with the outcome changes). The HoS gets a pair of gorilla gloves in his garment bag. If he wants them. The shove slowdown is now a new status effect, Staggered. This is just better functionality overall. Any instance of adding the shove slowdown now makes our target staggered. ## Why It's Good For The Game Tackling is a bit outdated, to say the least. Not much content has been added for a while that isn't strictly meme content. With these changes, tackling should be slightly more nuanced, considering elements such as unarmed effectiveness, the presence of martial arts, and actually properly checking block rather than notionally checking block. There is also more opportunity to protect yourself from tackle outcomes, both positive and negative. It also should be a little fairer to be on the receiving end of tackles if you have taken the time to layer up defenses against it. Attackers often overwhelmed defenders due to numbers favoring attackers more than defenders. Closes some really outdated design that was resulting in some really bizarre behaviour with regards to layered defenses against attack not having the same meaning against tackles, if only because it was looking for the wrong things and not even the correct parts of what it was looking for. Namely, blocking and shielding. The inclusion of more gripper gloves and a good outcome from using them will hopefully incentivize people to consider tacking as a useful tool, if a bit risky still due to the splat mechanics. ## Changelog :cl: balance: Judo Joe, archnemesis of Maint Khan, has begun re-airing his midnight infomercials shilling his extremely expensive Tackle Supreme Judo Karate Training video tapes. Unable to pass up a 'bargain', Nanotrasen has purchased these tapes en masse. Tackling techniques have started to improve, as well as Nanotrasen's tackling instructional algorithms within tackle gloves. balance: The outcomes for tackling are more equalized. It isn't as feast or famine, and should be somewhat more controllable without becoming too severe. add: Blocking successfully against a tackle will force the tackle to be a neutral outcome. add: Unarmed effectiveness from arms now contributes to attacking with and defending from tackles. add: Those who refuse to use firearms (like Sleeping Carp users and insane unholy berzerkers) are better at tackling others. add: Riot specialized armor, and not just riot armor, now contributes meaningfully to tackling effectiveness. balance: MK.1 Swat Suits, the ones that come in SWAT crates, now functions similarly to riot armor. add: Settlers from the outer rims have noticed they aren't very good at protecting themselves against Judo Joe's clearly discriminatory tackling techniques. add: Security lockers come with gripper gloves, security vendors now sell them as standard items, and the HoS' garment bag now has a pair of gorilla gloves. Gripper gloves have a positive skill bonus to tackling. add: Being insane also makes you INSANELY good at tackling but also INSANELY likely to eat shit on a whiff. DO OR DIE, BITCH. refactor: Shoving slowdown and all its implementations now use a status effect, Staggered. /:cl: --- code/__DEFINES/combat.dm | 6 +- code/__DEFINES/status_effects.dm | 5 + code/datums/components/tackle.dm | 233 +++++++++++++----- .../status_effects/debuffs/staggered.dm | 41 +++ code/game/objects/items/storage/garment.dm | 1 + .../objects/structures/beds_chairs/chair.dm | 5 +- .../crates_lockers/closets/secure/security.dm | 1 + code/modules/clothing/gloves/tacklers.dm | 2 +- code/modules/clothing/shoes/_shoes.dm | 4 +- code/modules/clothing/suits/armor.dm | 5 +- .../mob/living/carbon/carbon_defense.dm | 19 +- .../mob/living/carbon/human/_species.dm | 14 +- code/modules/mob/living/living.dm | 4 +- code/modules/movespeed/modifiers/mobs.dm | 4 +- code/modules/vending/security.dm | 2 +- tgstation.dme | 1 + 16 files changed, 248 insertions(+), 99 deletions(-) create mode 100644 code/datums/status_effects/debuffs/staggered.dm diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index d619da64d48..5df4128be62 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -171,9 +171,9 @@ DEFINE_BITFIELD(status_flags, list( #define SHOVE_KNOCKDOWN_TABLE 20 #define SHOVE_KNOCKDOWN_COLLATERAL 1 #define SHOVE_CHAIN_PARALYZE 30 -//Shove slowdown -#define SHOVE_SLOWDOWN_LENGTH 30 -#define SHOVE_SLOWDOWN_STRENGTH 0.85 //multiplier +//Staggered slowdown, an effect caused by shoving and a few other features, such as tackling +#define STAGGERED_SLOWDOWN_LENGTH 30 +#define STAGGERED_SLOWDOWN_STRENGTH 0.85 //multiplier //Shove disarming item list GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( /obj/item/gun))) diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 03a3bf49ebb..768f1faa514 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -114,6 +114,11 @@ #define set_dizzy(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness) #define set_dizzy_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness, TRUE) +#define adjust_staggered(duration) adjust_timed_status_effect(duration, /datum/status_effect/staggered) +#define adjust_staggered_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/staggered, up_to) +#define set_staggered(duration) set_timed_status_effect(duration, /datum/status_effect/staggered) +#define set_staggered_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/staggered, TRUE) + #define adjust_jitter(duration) adjust_timed_status_effect(duration, /datum/status_effect/jitter) #define adjust_jitter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/jitter, up_to) #define set_jitter(duration) set_timed_status_effect(duration, /datum/status_effect/jitter) diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 1faae99d459..185d7123237 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -96,7 +96,7 @@ to_chat(user, span_warning("You're not ready to tackle!")) return - if(user.has_movespeed_modifier(/datum/movespeed_modifier/shove)) // can't tackle if you just got shoved + if(user.get_timed_status_effect_duration(/datum/status_effect/staggered)) // can't tackle if you're staggered to_chat(user, span_warning("You're too off balance to tackle!")) return @@ -131,12 +131,9 @@ * Check [rollTackle()][/datum/component/tackler/proc/rollTackle] for a more thorough explanation on the modifiers at play. * * Then, we figure out what effect we want, and we get to work! Note that with standard gripper gloves and no modifiers, the range of rolls is (-3, 3). The results are as follows, based on what we rolled: - * * -inf to -5: Seriously botched tackle, tackler suffers a concussion, brute damage, and a 3 second paralyze, target suffers nothing - * * -4 to -2: weak tackle, tackler gets 3 second knockdown, target gets shove slowdown but is otherwise fine - * * -1 to 0: decent tackle, tackler gets up a bit quicker than the target - * * 1: solid tackle, tackler has more of an advantage getting up quicker - * * 2 to 4: expert tackle, tackler has sizeable advantage and lands on their feet with a free passive grab - * * 5 to inf: MONSTER tackle, tackler gets up immediately and gets a free aggressive grab, target takes sizeable stamina damage from the hit and is paralyzed for one and a half seconds and knocked down for three seconds + * * -inf to -1: We have a negative roll result, which means something unfortunate or less than ideal happens to our sacker! Could mean just getting knocked down, but it could also mean they get a concussion. Ouch. + * * 0: We get a relatively neutral result, mildly favouring the tackler. + * * 1 to inf: We get a positive roll result, which means we get a reasonable to significant advantage against the target! * * Finally, we return a bitflag to [COMSIG_MOVABLE_IMPACT] that forces the hitpush to false so that we don't knock them away. */ @@ -155,53 +152,74 @@ return var/mob/living/carbon/target = hit - var/mob/living/carbon/human/T = target - var/mob/living/carbon/human/S = user var/tackle_word = isfelinid(user) ? "pounce" : "tackle" //If cat, "pounce" instead of "tackle". + if(target.check_block(user, 0, user.name, attack_type = LEAP_ATTACK)) + user.visible_message(span_danger("[user]'s tackle is blocked by [target], softening the effect!"), span_userdanger("Your tackle is blocked by [target], softening the effect!"), ignored_mobs = target) + to_chat(target, span_userdanger("[target] blocks [user]'s tackle attempt, softening the effect!")) + neutral_outcome(user, target, tackle_word) //Forces a neutral outcome so you're not screwed too much from being blocked while tackling + return + var/roll = rollTackle(target) tackling = FALSE tackle.gentle = TRUE switch(roll) - if(-INFINITY to -5) - user.visible_message(span_danger("[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into [target], knocking [user.p_them()]self silly!"), span_userdanger("You botch your [tackle_word] and slam your head into [target], knocking yourself silly!"), ignored_mobs = target) - to_chat(target, span_userdanger("[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into you, knocking [user.p_them()]self silly!")) + if(-INFINITY to -1) + negative_outcome(user, target, roll, tackle_word) //OOF - user.Paralyze(3 SECONDS) - var/obj/item/bodypart/head/hed = user.get_bodypart(BODY_ZONE_HEAD) - if(hed) - hed.receive_damage(brute = 15, updating_health = TRUE, wound_bonus = CANT_WOUND) - user.gain_trauma(/datum/brain_trauma/mild/concussion) + if(0) //nothing good, nothing bad + neutral_outcome(user, target, tackle_word) - if(-4 to -2) // glancing blow at best - user.visible_message(span_warning("[user] lands a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!"), span_userdanger("You land a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!"), ignored_mobs = target) - to_chat(target, span_userdanger("[user] lands a weak [tackle_word] on you, briefly knocking you off-balance!")) + if(1 to INFINITY) + positive_outcome(user, target, roll, tackle_word) - user.Knockdown(30) - if(ishuman(target) && !T.has_movespeed_modifier(/datum/movespeed_modifier/shove)) - T.add_movespeed_modifier(/datum/movespeed_modifier/shove) // maybe define a slightly more severe/longer slowdown for this - addtimer(CALLBACK(T, TYPE_PROC_REF(/mob/living/carbon, clear_shove_slowdown)), SHOVE_SLOWDOWN_LENGTH * 2) + return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH - if(-1 to 0) // decent hit, both parties are about equally inconvenienced - user.visible_message(span_warning("[user] lands a passable [tackle_word] on [target], sending them both tumbling!"), span_userdanger("You land a passable [tackle_word] on [target], sending you both tumbling!"), ignored_mobs = target) - to_chat(target, span_userdanger("[user] lands a passable [tackle_word] on you, sending you both tumbling!")) +/** + * Our positive tackling outcomes. + * + * We pass our tackle result here to determine the potential outcome of the tackle. Typically, this results in a very poor state for the tackled, and a positive outcome for the tackler. + * + * First, we determine severity by taking our roll result, multiplying it by 10, and then rolling within that value. + * + * If our target is human, their armor will reduce the severity of the roll. We pass along any MELEE armor as a percentage reduction. + * If they're not human (such as a carbon), we give them a small grace of a 10% reduction. + * + * Finally, we figure out what effect our target receives. Note that all positive outcomes inflict staggered, resulting in a much harder time escaping the potential grab: + * * 1 to 20: Our target is briefly stunned and knocked down. suffers 30 stamina damage, and our tackler is also knocked down. + * * 21 to 49: Our target is knocked down, dealt 40 stamina damage, and put into a passive grab. Given they are staggered, this means the target must resist to escape! + * * 50 to inf: Our target is hit with a significant chunk of stamina damage, put into an aggressive grab, and knocked down. They're probably not escaping after this. If our tackler is stamcrit when they land this, so is our target. +*/ - target.adjustStaminaLoss(stamina_cost) - target.Paralyze(0.5 SECONDS) - user.Knockdown(2 SECONDS) - target.Knockdown(2.5 SECONDS) +/datum/component/tackler/proc/positive_outcome(mob/living/carbon/user, mob/living/carbon/target, roll = 1, tackle_word = "tackle") + var/potential_outcome = (roll * 10) - if(1 to 2) // solid hit, tackler has a slight advantage + if(ishuman(target)) + var/mob/living/carbon/human/human_target = target + var/target_armor = human_target.run_armor_check(BODY_ZONE_CHEST, MELEE) + potential_outcome *= ((100 - target_armor) /100) + else + potential_outcome *= 0.9 + + var/mob/living/carbon/human/human_target = target + var/mob/living/carbon/human/human_sacker = user + + switch(potential_outcome) + if(-INFINITY to 0) //I don't want to know how this has happened, okay? + neutral_outcome(user, target, roll, tackle_word) //Default to neutral + + if(1 to 20) user.visible_message(span_warning("[user] lands a solid [tackle_word] on [target], knocking them both down hard!"), span_userdanger("You land a solid [tackle_word] on [target], knocking you both down hard!"), ignored_mobs = target) to_chat(target, span_userdanger("[user] lands a solid [tackle_word] on you, knocking you both down hard!")) - target.adjustStaminaLoss(30) + target.apply_damage(30, STAMINA) target.Paralyze(0.5 SECONDS) user.Knockdown(1 SECONDS) target.Knockdown(2 SECONDS) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) - if(3 to 4) // really good hit, the target is definitely worse off here. Without positive modifiers, this is as good a tackle as you can land + if(21 to 49) // really good hit, the target is definitely worse off here. Without positive modifiers, this is as good a tackle as you can land user.visible_message(span_warning("[user] lands an expert [tackle_word] on [target], knocking [target.p_them()] down hard while landing on [user.p_their()] feet with a passive grip!"), span_userdanger("You land an expert [tackle_word] on [target], knocking [target.p_them()] down hard while landing on your feet with a passive grip!"), ignored_mobs = target) to_chat(target, span_userdanger("[user] lands an expert [tackle_word] on you, knocking you down hard and maintaining a passive grab!")) @@ -209,22 +227,24 @@ user.SetKnockdown(0, ignore_canstun = TRUE) user.get_up(TRUE) user.forceMove(get_turf(target)) - target.adjustStaminaLoss(40) + target.apply_damage(40, STAMINA) target.Paralyze(0.5 SECONDS) target.Knockdown(3 SECONDS) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(S.dna.species, TYPE_PROC_REF(/datum/species, grab), S, T) - S.setGrabState(GRAB_PASSIVE) + INVOKE_ASYNC(human_sacker.dna.species, TYPE_PROC_REF(/datum/species, grab), human_sacker, human_target) + human_sacker.setGrabState(GRAB_PASSIVE) - if(5 to INFINITY) // absolutely BODIED + if(50 to INFINITY) // absolutely BODIED var/stamcritted_user = HAS_TRAIT_FROM(user, TRAIT_INCAPACITATED, STAMINA) - if(stamcritted_user) // in case the user went into stamcrit from the tackle itself and cannot actually aggro grab (since they will be crit) we make the tackle a bit more effective on the target + if(stamcritted_user) // in case the user went into stamcrit from the tackle itself and cannot actually aggro grab (since they will be crit) we make the tackle effectivelly mutually assured...stamina crit user.visible_message(span_warning("[user] lands a monsterly reckless [tackle_word] on [target], knocking both of them senseless!"), span_userdanger("You land a monsterly reckless [tackle_word] on [target], knocking both of you senseless!"), ignored_mobs = target) to_chat(target, span_userdanger("[user] lands a monsterly reckless [tackle_word] on you, knocking the both of you senseless!")) user.forceMove(get_turf(target)) - target.adjustStaminaLoss(60) + target.apply_damage(100, STAMINA) // CRASHING THIS PLANE WITH NO SURVIVORS target.Paralyze(1 SECONDS) target.Knockdown(5 SECONDS) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 3, 10 SECONDS) else user.visible_message(span_warning("[user] lands a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), span_userdanger("You land a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), ignored_mobs = target) to_chat(target, span_userdanger("[user] lands a monster [tackle_word] on you, knocking you senseless and aggressively pinning you!")) @@ -233,15 +253,90 @@ user.SetKnockdown(0, ignore_canstun = TRUE) user.get_up(TRUE) user.forceMove(get_turf(target)) - target.adjustStaminaLoss(40) + target.apply_damage(60, STAMINA) target.Paralyze(0.5 SECONDS) target.Knockdown(3 SECONDS) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 3, 10 SECONDS) if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(S.dna.species, TYPE_PROC_REF(/datum/species, grab), S, T) - S.setGrabState(GRAB_AGGRESSIVE) + INVOKE_ASYNC(human_sacker.dna.species, TYPE_PROC_REF(/datum/species, grab), human_sacker, human_target) + human_sacker.setGrabState(GRAB_AGGRESSIVE) + +/** + * Our neutral tackling outcome. + * + * Our tackler and our target are staggered. The target longer than the tackler. However, the tackler stands up after this outcome. This is maybe less neutral than it appears, but the tackler initiated, so... + * This outcome also occurs when our target has blocked the tackle in some way, preventing situations where someone tackling into a blocker is too severely punished as a result. Hence, this has its own proc. +*/ + +/datum/component/tackler/proc/neutral_outcome(mob/living/carbon/user, mob/living/carbon/target, roll = 1, tackle_word = "tackle") - return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH + user.visible_message(span_warning("[user] lands a [tackle_word] on [target], briefly staggering them both!"), span_userdanger("You land a [tackle_word] on [target], briefly staggering [target.p_them()] and yourself!"), ignored_mobs = target) + to_chat(target, span_userdanger("[user] lands a [tackle_word] on you, briefly staggering you both!")) + + user.get_up(TRUE) + user.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 10 SECONDS) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) //okay maybe slightly good for the sacker, it's a mild benefit okay? + +/** + * Our negative tackling outcomes. + * + * We pass our tackle result here to determine the potential outcome of the tackle. Typically, this results in a very poor state for the tackler, and a mostly okay outcome for the tackled. + * + * First, we determine severity by taking our roll result, multiplying it by -10, and then rolling within that value. + * + * If our tackler is human, their armor will reduce the severity of the roll. We pass along any MELEE armor as a percentage reduction. + * If they're not human (such as a carbon), we give them a small grace of a 10% reduction. + * + * Finally, we figure out what effect our target receives and what our tackler receives: + * * 1 to 20: Our tackler is knocked down and become staggered, and our target suffers stamina damage and is knocked staggered. So not all bad, but the target most likely can punish you for this. + * * 21 to 49: Our tackler is knocked down, suffers stamina damage, and is staggered. Ouch. + * * 50 to inf: Our tackler suffers a catastrophic failure, receiving significant stamina damage, a concussion, and is paralyzed for 3 seconds. Oh, and they're staggered for a LONG time. +*/ + +/datum/component/tackler/proc/negative_outcome(mob/living/carbon/user, mob/living/carbon/target, roll = -1, tackle_word = "tackle") + var/potential_roll_outcome = (roll * -10) + + if(ishuman(user)) + var/mob/living/carbon/human/human_sacker = target + var/attacker_armor = human_sacker.run_armor_check(BODY_ZONE_CHEST, MELEE) + potential_roll_outcome *= ((100 - attacker_armor) /100) + else + potential_roll_outcome *= 0.9 + + var/actual_roll = rand(1, potential_roll_outcome) + + switch(actual_roll) + + if(-INFINITY to 0) //I don't want to know how this has happened, okay? + neutral_outcome(user, target, roll, tackle_word) //Default to neutral + + if(1 to 20) // It's not completely terrible! But you are somewhat vulernable for doing it. + user.visible_message(span_warning("[user] lands a weak [tackle_word] on [target], briefly staggering [target.p_them()]!"), span_userdanger("You land a weak [tackle_word] on [target], briefly staggering [target.p_them()]!"), ignored_mobs = target) + to_chat(target, span_userdanger("[user] lands a weak [tackle_word] on you, staggering you!")) + + user.Knockdown(1 SECONDS) + user.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) + target.apply_damage(20, STAMINA) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) + + if(21 to 49) // oughe + user.visible_message(span_warning("[user] lands a dreadful [tackle_word] on [target], briefly knocking [user.p_them()] to the ground!"), span_userdanger("You land a dreadful [tackle_word] on [target], briefly knocking you to the ground!"), ignored_mobs = target) + to_chat(target, span_userdanger("[user] lands a dreadful [tackle_word] on you, briefly knocking [user.p_them()] to the ground!")) + + user.Knockdown(3 SECONDS) + user.apply_damage(40, STAMINA) + user.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 2, 10 SECONDS) + + if(50 to INFINITY) // It has been decided that you will suffer + user.visible_message(span_danger("[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into [target], knocking [user.p_them()]self silly!"), span_userdanger("You botch your [tackle_word] and slam your head into [target], knocking yourself silly!"), ignored_mobs = target) + to_chat(target, span_userdanger("[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into you, knocking [user.p_them()]self silly!")) + + user.Paralyze(3 SECONDS) + user.apply_damage(80, STAMINA) + user.apply_damage(20, BRUTE, BODY_ZONE_HEAD) + user.gain_trauma(/datum/brain_trauma/mild/concussion) + user.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH * 3, 10 SECONDS) /** * This handles all of the modifiers for the actual carbon-on-carbon tackling, and gets its own proc because of how many there are (with plenty more in mind!) @@ -266,14 +361,21 @@ else if(target_drunkenness > 30) defense_mod -= 1 + //Arms contribute a great deal to potential tackling prowess and defense. Better arms = better bonus + var/obj/item/bodypart/arm/defender_arm = target.get_active_hand() + + if(defender_arm) //the target may not actually have arms + defense_mod += (defender_arm.unarmed_effectiveness/10) + else //sucks to be you if you don't though haha + defense_mod -= 2 + if(HAS_TRAIT(target, TRAIT_CLUMSY)) defense_mod -= 2 if(HAS_TRAIT(target, TRAIT_FAT)) // chonkers are harder to knock over defense_mod += 1 if(HAS_TRAIT(target, TRAIT_GRABWEAKNESS)) defense_mod -= 2 - if(HAS_TRAIT(target, TRAIT_DWARF)) - defense_mod -= 2 + if(HAS_TRAIT(target, TRAIT_GIANT)) defense_mod += 2 if(target.get_organic_health() < 50) @@ -289,15 +391,15 @@ if(ishuman(target)) var/mob/living/carbon/human/tackle_target = target + if(tackle_target.get_mob_height() <= HUMAN_HEIGHT_SHORTEST) //WHO ARE YOU CALLING SHORT? + defense_mod -= 2 + if(isnull(tackle_target.wear_suit) && isnull(tackle_target.w_uniform)) // who honestly puts all of their effort into tackling a naked guy? defense_mod += 2 if(tackle_target.mob_negates_gravity()) defense_mod += 1 if(tackle_target.is_shove_knockdown_blocked()) // riot armor and such defense_mod += 5 - if(tackle_target.is_holding_item_of_type(/obj/item/shield)) - defense_mod += 2 - var/obj/item/organ/external/tail/lizard/el_tail = tackle_target.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) if(HAS_TRAIT(tackle_target, TRAIT_TACKLING_TAILED_DEFENDER) && !el_tail) @@ -308,6 +410,15 @@ // OF-FENSE var/mob/living/carbon/sacker = parent var/sacker_drunkenness = sacker.get_drunk_amount() + + //Arms contribute a great deal to potential tackling prowess and defense. Better arms = better bonus + var/obj/item/bodypart/arm/sacker_arm = sacker.get_active_hand() + + if(sacker_arm) //I have no idea how you would be tackling without hands, but just in case + attack_mod += (sacker_arm.unarmed_effectiveness/10) + else //I don't want to know how you got to this point but if you have, fuck you, good luck tackling without ARMS + attack_mod -= 4 + if(sacker_drunkenness > 60) // you're far too drunk to hold back! attack_mod += 1 else if(sacker_drunkenness > 30) // if you're only a bit drunk though, you're just sloppy @@ -315,10 +426,10 @@ if(HAS_TRAIT(sacker, TRAIT_CLUMSY)) attack_mod -= 2 - if(HAS_TRAIT(sacker, TRAIT_DWARF)) - attack_mod -= 2 if(HAS_TRAIT(sacker, TRAIT_GIANT)) attack_mod += 2 + if(HAS_TRAIT(sacker, TRAIT_NOGUNS)) //Those dedicated to martial combat are particularly skilled tacklers + attack_mod += 2 if(HAS_TRAIT(sacker, TRAIT_TACKLING_WINGED_ATTACKER)) var/obj/item/organ/external/wings/moth/sacker_moth_wing = sacker.get_organ_slot(ORGAN_SLOT_EXTERNAL_WINGS) @@ -328,16 +439,22 @@ if(sacker_wing) attack_mod += 2 - if(ishuman(target)) - var/mob/living/carbon/human/S = sacker + if(ishuman(sacker)) + var/mob/living/carbon/human/human_sacker = sacker - var/suit_slot = S.get_item_by_slot(ITEM_SLOT_OCLOTHING) - if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/armor/riot))) // tackling in riot armor is more effective, but tiring + if(human_sacker.get_mob_height() <= HUMAN_HEIGHT_SHORTEST) //JUST YOU WAIT TILL I FIND A CHAIR, BUDDY, THEN YOU'LL BE SORRY + attack_mod -= 2 + + if(human_sacker.mob_mood.sanity_level == SANITY_INSANE) //I've gone COMPLETELY INSANE + attack_mod += 15 + human_sacker.adjustStaminaLoss(100) //AHAHAHAHAHAHAHAHA + + if(human_sacker.is_shove_knockdown_blocked()) // tackling with riot specialized armor, like riot armor, is effective but tiring attack_mod += 2 - sacker.adjustStaminaLoss(20) + human_sacker.adjustStaminaLoss(20) - var/r = rand(-3, 3) - defense_mod + attack_mod + skill_mod - return r + var/randomized_tackle_roll = rand(-3, 3) - defense_mod + attack_mod + skill_mod + return randomized_tackle_roll /** diff --git a/code/datums/status_effects/debuffs/staggered.dm b/code/datums/status_effects/debuffs/staggered.dm new file mode 100644 index 00000000000..5723bc16013 --- /dev/null +++ b/code/datums/status_effects/debuffs/staggered.dm @@ -0,0 +1,41 @@ +/datum/status_effect/staggered + id = "staggered" + tick_interval = 0.5 SECONDS + alert_type = null + remove_on_fullheal = TRUE + +/datum/status_effect/staggered/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/staggered/on_apply() + + //a very mild animation, but you can't stagger the dead. + if(owner.stat == DEAD) + owner.do_stagger_animation(duration / 10) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(clear_staggered)) + owner.add_movespeed_modifier(/datum/movespeed_modifier/staggered) + return TRUE + +/datum/status_effect/staggered/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + owner.remove_movespeed_modifier(/datum/movespeed_modifier/staggered) + // Resetting both X on remove so we're back to normal + owner.pixel_x = owner.base_pixel_x + +/// Signal proc that self deletes our staggered effect +/datum/status_effect/staggered/proc/clear_staggered(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/datum/status_effect/staggered/tick(seconds_between_ticks) + owner.do_stagger_animation() + +/// Helper proc that causes the mob to do a stagger animation. +/// Doesn't change significantly, just meant to represent swaying back and forth +/mob/living/proc/do_stagger_animation() + animate(src, pixel_x = 4, time = 0.2 SECONDS, loop = 6, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL) + animate(pixel_x = -4, time = 0.2 SECONDS, flags = ANIMATION_RELATIVE) diff --git a/code/game/objects/items/storage/garment.dm b/code/game/objects/items/storage/garment.dm index ca3c6500b4e..a12e484c693 100644 --- a/code/game/objects/items/storage/garment.dm +++ b/code/game/objects/items/storage/garment.dm @@ -82,6 +82,7 @@ new /obj/item/clothing/under/rank/security/head_of_security/grey(src) new /obj/item/clothing/under/rank/security/head_of_security/parade(src) new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src) + new /obj/item/clothing/gloves/tackler/combat(src) new /obj/item/clothing/suit/armor/hos(src) new /obj/item/clothing/suit/armor/hos/hos_formal(src) new /obj/item/clothing/suit/armor/hos/trenchcoat/winter(src) diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 32daa4e7a49..f16647c0c05 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -375,11 +375,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/chair/stool/bar, 0) new /obj/item/stack/rods(get_turf(loc), 2) qdel(src) - - - /obj/item/chair/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) - if(attack_type == UNARMED_ATTACK && prob(hit_reaction_chance)) + if(attack_type == UNARMED_ATTACK && prob(hit_reaction_chance) || attack_type == LEAP_ATTACK && prob(hit_reaction_chance)) owner.visible_message(span_danger("[owner] fends off [attack_text] with [src]!")) return TRUE return FALSE diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index 19e5083a3b9..86226c56266 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -104,6 +104,7 @@ new /obj/item/radio/headset/headset_sec/alt(src) new /obj/item/clothing/glasses/hud/security/sunglasses(src) new /obj/item/flashlight/seclite(src) + new /obj/item/clothing/gloves/tackler(src) /obj/structure/closet/secure_closet/security/sec diff --git a/code/modules/clothing/gloves/tacklers.dm b/code/modules/clothing/gloves/tacklers.dm index 888ecac39ee..986d8356b1a 100644 --- a/code/modules/clothing/gloves/tacklers.dm +++ b/code/modules/clothing/gloves/tacklers.dm @@ -21,7 +21,7 @@ /// See: [/datum/component/tackler/var/speed] var/tackle_speed = 1 /// See: [/datum/component/tackler/var/skill_mod] - var/skill_mod = 0 + var/skill_mod = 1 /obj/item/clothing/gloves/tackler/Destroy() tackler = null diff --git a/code/modules/clothing/shoes/_shoes.dm b/code/modules/clothing/shoes/_shoes.dm index 5ffc8c5d67d..d74bbbe5655 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -244,9 +244,7 @@ if(14 to 25) // 1.3ish% chance to stumble and be a bit off balance (like being disarmed) to_chat(our_guy, span_danger("You stumble a bit on your untied shoelaces!")) - if(!our_guy.has_movespeed_modifier(/datum/movespeed_modifier/shove)) - our_guy.add_movespeed_modifier(/datum/movespeed_modifier/shove) - addtimer(CALLBACK(our_guy, TYPE_PROC_REF(/mob/living/carbon, clear_shove_slowdown)), SHOVE_SLOWDOWN_LENGTH) + our_guy.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 10 SECONDS) if(26 to 1000) wiser = FALSE diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 5ea493d4198..63f78b45c35 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -345,13 +345,14 @@ /obj/item/clothing/suit/armor/swat name = "MK.I SWAT Suit" - desc = "A tactical suit first developed in a joint effort by the defunct IS-ERI and Nanotrasen in 2321 for military operations. It has a minor slowdown, but offers decent protection." + desc = "A tactical suit first developed in a joint effort by the defunct IS-ERI and Nanotrasen in 2321 for military operations. \ + It has a minor slowdown, but offers decent protection and helps the wearer resist shoving in close quarters." icon_state = "heavy" inhand_icon_state = "swat_suit" armor_type = /datum/armor/armor_swat strip_delay = 120 resistance_flags = FIRE_PROOF | ACID_PROOF - clothing_flags = THICKMATERIAL + clothing_flags = BLOCKS_SHOVE_KNOCKDOWN | THICKMATERIAL cold_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT_OFF heat_protection = CHEST | GROIN | LEGS | FEET | ARMS | HANDS diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index b2cc3d7b0c0..cb565324c67 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -331,21 +331,14 @@ if(!is_type_in_typecache(target_held_item, GLOB.shove_disarming_types)) //It's too expensive we'll get caught target_held_item = null - if(!target.has_movespeed_modifier(/datum/movespeed_modifier/shove)) - target.add_movespeed_modifier(/datum/movespeed_modifier/shove) - target.emote("sway") - if(target_held_item) - append_message = "loosening [target.p_their()] grip on [target_held_item]" - target.visible_message(span_danger("[target.name]'s grip on \the [target_held_item] loosens!"), //He's already out what are you doing - span_warning("Your grip on \the [target_held_item] loosens!"), null, COMBAT_MESSAGE_RANGE) - addtimer(CALLBACK(target, TYPE_PROC_REF(/mob/living/carbon, clear_shove_slowdown)), SHOVE_SLOWDOWN_LENGTH) - - else if(target_held_item) + if(target_held_item && target.get_timed_status_effect_duration(/datum/status_effect/staggered)) target.dropItemToGround(target_held_item) append_message = "causing [target.p_them()] to drop [target_held_item]" target.visible_message(span_danger("[target.name] drops \the [target_held_item]!"), span_warning("You drop \the [target_held_item]!"), null, COMBAT_MESSAGE_RANGE) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 10 SECONDS) + log_combat(src, target, "shoved", append_message) /mob/living/carbon/proc/is_shove_knockdown_blocked() //If you want to add more things that block shove knockdown, extend this @@ -354,12 +347,6 @@ return TRUE return FALSE -/mob/living/carbon/proc/clear_shove_slowdown() - remove_movespeed_modifier(/datum/movespeed_modifier/shove) - var/active_item = get_active_held_item() - if(is_type_in_typecache(active_item, GLOB.shove_disarming_types)) - visible_message(span_warning("[name] regains their grip on \the [active_item]!"), span_warning("You regain your grip on \the [active_item]"), null, COMBAT_MESSAGE_RANGE) - /mob/living/carbon/blob_act(obj/structure/blob/B) if (stat == DEAD) return diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index c386b19d954..6af88e20028 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1150,14 +1150,14 @@ GLOBAL_LIST_EMPTY(features_by_species) return FALSE user.do_attack_animation(target, atk_effect) - //has our target been shoved recently? If so, they're off-balance and we get an easy hit. - var/off_balance = FALSE + //has our target been shoved recently? If so, they're staggered and we get an easy hit. + var/staggered = FALSE //Someone in a grapple is much more vulnerable to being harmed by punches. var/grappled = FALSE - if(target.has_movespeed_modifier(/datum/movespeed_modifier/shove)) - off_balance = TRUE + if(target.get_timed_status_effect_duration(/datum/status_effect/staggered)) + staggered = TRUE if(target.pulledby && target.pulledby.grab_state >= GRAB_AGGRESSIVE) grappled = TRUE @@ -1169,7 +1169,7 @@ GLOBAL_LIST_EMPTY(features_by_species) var/miss_chance = 100//calculate the odds that a punch misses entirely. considers stamina and brute damage of the puncher. punches miss by default to prevent weird cases if(attacking_bodypart.unarmed_damage_low) - if((target.body_position == LYING_DOWN) || HAS_TRAIT(user, TRAIT_PERFECT_ATTACKER) || off_balance) //kicks and attacks against off-balance targets never miss (provided your species deals more than 0 damage) + if((target.body_position == LYING_DOWN) || HAS_TRAIT(user, TRAIT_PERFECT_ATTACKER) || staggered) //kicks and attacks against staggered targets never miss (provided your species deals more than 0 damage) miss_chance = 0 else miss_chance = clamp(UNARMED_MISS_CHANCE_BASE - limb_accuracy + user.getStaminaLoss() + (user.getBruteLoss()*0.5), 0, UNARMED_MISS_CHANCE_MAX) //Limb miss chance + various damage. capped at 80 so there is at least a chance to land a hit. @@ -1211,8 +1211,8 @@ GLOBAL_LIST_EMPTY(features_by_species) target.force_say() log_combat(user, target, "punched") - //If we rolled a punch high enough to hit our stun threshold, or our target is off-balance and they have at least 40 damage+stamina loss, we knock them down - if((target.stat != DEAD) && prob(limb_accuracy) || (target.stat != DEAD) && off_balance && (target.getStaminaLoss() + user.getBruteLoss()) >= 40) + //If we rolled a punch high enough to hit our stun threshold, or our target is staggered and they have at least 40 damage+stamina loss, we knock them down + if((target.stat != DEAD) && prob(limb_accuracy) || (target.stat != DEAD) && staggered && (target.getStaminaLoss() + user.getBruteLoss()) >= 40) target.visible_message(span_danger("[user] knocks [target] down!"), \ span_userdanger("You're knocked down by [user]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, user) to_chat(user, span_danger("You knock [target] down!")) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index c1ee706ae9a..f407c1703e5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1116,8 +1116,8 @@ /mob/living/resist_grab(moving_resist) . = TRUE - //If we're in an aggressive grab or higher, we're lying down, we're vulnerable to grabs, or we've been shoved and we have some amount of stamina loss, we must resist - if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || has_movespeed_modifier(/datum/movespeed_modifier/shove) && getStaminaLoss() >= 30) + //If we're in an aggressive grab or higher, we're lying down, we're vulnerable to grabs, or we're staggered and we have some amount of stamina loss, we must resist + if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || get_timed_status_effect_duration(/datum/status_effect/staggered) && getStaminaLoss() >= 30) var/altered_grab_state = pulledby.grab_state if((body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) && pulledby.grab_state < GRAB_KILL) //If prone, resisting out of a grab is equivalent to 1 grab state higher. won't make the grab state exceed the normal max, however altered_grab_state++ diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 49358223e35..b782f2fc959 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -81,8 +81,8 @@ blacklisted_movetypes = FLOATING variable = TRUE -/datum/movespeed_modifier/shove - multiplicative_slowdown = SHOVE_SLOWDOWN_STRENGTH +/datum/movespeed_modifier/staggered + multiplicative_slowdown = STAGGERED_SLOWDOWN_STRENGTH /datum/movespeed_modifier/human_carry multiplicative_slowdown = HUMAN_CARRY_SLOWDOWN diff --git a/code/modules/vending/security.dm b/code/modules/vending/security.dm index b54edffe4c0..feae0499655 100644 --- a/code/modules/vending/security.dm +++ b/code/modules/vending/security.dm @@ -16,6 +16,7 @@ /obj/item/storage/box/evidence = 6, /obj/item/flashlight/seclite = 4, /obj/item/restraints/legcuffs/bola/energy = 7, + /obj/item/clothing/gloves/tackler = 5, ) contraband = list( /obj/item/clothing/glasses/sunglasses = 2, @@ -26,7 +27,6 @@ /obj/item/coin/antagtoken = 1, /obj/item/clothing/head/helmet/blueshirt = 1, /obj/item/clothing/suit/armor/vest/blueshirt = 1, - /obj/item/clothing/gloves/tackler = 5, /obj/item/grenade/stingbang = 1, /obj/item/watertank/pepperspray = 2, /obj/item/storage/belt/holster/energy = 4, diff --git a/tgstation.dme b/tgstation.dme index 939d7cd4c22..3a7162cb159 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1751,6 +1751,7 @@ #include "code\datums\status_effects\debuffs\slimed.dm" #include "code\datums\status_effects\debuffs\spacer.dm" #include "code\datums\status_effects\debuffs\speech_debuffs.dm" +#include "code\datums\status_effects\debuffs\staggered.dm" #include "code\datums\status_effects\debuffs\static_vision.dm" #include "code\datums\status_effects\debuffs\strandling.dm" #include "code\datums\status_effects\debuffs\terrified.dm"