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 a5b5606bf72..1148e2b5993 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 = isfeline(user) ? "pounce" : "tackle" //If cat, "pounce" instead of "tackle". // SKYRAT EDIT - FELINE TRAITS. Was: isfelinid(user) + var/tackle_word = isfeline(user) ? "pounce" : "tackle" //If cat, "pounce" instead of "tackle". // SKYRAT EDIT - FELINE TRAITS - ORIGINAL : var/tackle_word = isfelinid(user) ? "pounce" : "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. +*/ - return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH +/datum/component/tackler/proc/neutral_outcome(mob/living/carbon/user, mob/living/carbon/target, roll = 1, tackle_word = "tackle") + + + 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 9398b675b1b..e9ff2f28ce1 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 b9c80fd7f6c..ddb588db782 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -380,11 +380,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 f25c9cfec5d..3a2b54b74f5 100755 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -109,6 +109,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 118c28c0e51..e7d87f34bca 100644 --- a/code/modules/clothing/shoes/_shoes.dm +++ b/code/modules/clothing/shoes/_shoes.dm @@ -254,9 +254,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 e2af151e49d..ae8b786fbef 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -369,13 +369,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 2032eb511ff..0eb3945af8c 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -383,21 +383,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 @@ -406,12 +399,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 48956982eb1..a5a41ef0b1f 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -1175,14 +1175,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 @@ -1194,7 +1194,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. @@ -1237,8 +1237,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 5e4a564125c..8611bf85eb4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1163,7 +1163,8 @@ /mob/living/resist_grab(moving_resist) . = TRUE - if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS) || has_movespeed_modifier(/datum/movespeed_modifier/shove) && getStaminaLoss() > STAMINA_THRESHOLD_HARD_RESIST) //SKYRAT EDIT CHANGE - ORIGINAL : 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() > STAMINA_THRESHOLD_HARD_RESIST) //SKYRAT EDIT CHANGE - ORIGINAL : 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 - SKYRAT EDIT CHANGE: if((resting || HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) && pulledby.grab_state < GRAB_KILL) //If resting, 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 97709cd1a6c..feefc44cb28 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1850,6 +1850,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"