From 68677dc721442eef5402ca896617fc51b3a03e0a Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Wed, 17 Jan 2024 02:35:56 +0100 Subject: [PATCH] Disarm refactor, plus shoving people with shields (#80123) ## About The Pull Request I wanted to add the ability to shove people with shields by right-clicking your target, just like how it works barehanded. This also required a solid refactor of disarm code, effectively bringing down the core of it to `mob/living` from `mob/living/carbon` or `mob/living/carbon/human`. This also means you can shove simple mobs inside closets, bins and on tables. Xenos and borgs are pretty much immune to regular disarms, but using a shield will work (borgs and royal xenos are immune to the knockdown). The riot shield armor has been balanced. It now tanks melee attacks pretty well, but will break against bullets in just about 2 to 4 hits depending on the bullet damage. I've always found the lack of sturdiness of the riot shields for what they're supposed to be good for a bit detrimental. Because I've refactored an item flag into a trait, I've had to add a new MOD module that grants protection from shove knockdown and staggering; found pre-installed in the administrative MODsuit, but I've also added it to the black market to make it cooler. You can bash people with the strobe shield on combat mode. ## Why It's Good For The Game Currently, shields are simply items that take a held slot in return of some block chance without being anything special, save for the strobe shield's integrated flash I guess, but are also a botherance as most crumple under the duress of less than half a dozen attacks. Meanwhile swords and other weapons with blok chance just don't care. TL;DR, I want them a bit more remarkable, and flexible as a tool. Of course, this ended up in a larger refactor because the right-click / disarm code was inconsistent. ## Changelog :cl: add: Shields (and pillows) can be used to shove people around the same way barehanded right-clicking does. Xenos and borgs can actually be moved this way. add: Added a new MODsuit module, the bulwark module, which prevents knockdown and staggering from shoving, and getting pushed away by thrown objects. Inbuilt for the safeguard MODsuit, but one might also it in the black market. refactor: Disarming has been refactored. You can now shove simple critters onto tables and into bins and closets balance: Shields now take their own armor values and the armor penetration of the attack they blocked when damaged. This means shields are a bit sturdier now. balance: Riot shields can tank a lot more damage against melee weapons, but less against bullets. qol: strobe shields can now be used to bash people while combat mode is on. /:cl: --- code/__DEFINES/combat.dm | 15 +++ .../signals/signals_mob/signals_mob_carbon.dm | 9 -- .../signals/signals_mob/signals_mob_living.dm | 9 ++ code/__DEFINES/dcs/signals/signals_object.dm | 6 +- code/__DEFINES/obj_flags.dm | 22 ++-- code/__DEFINES/traits/declarations.dm | 6 + code/_globalvars/bitfields.dm | 1 - code/_globalvars/traits/_traits.dm | 3 + code/datums/components/gunpoint.dm | 6 +- code/datums/components/knockoff.dm | 12 +- code/datums/components/tackle.dm | 4 +- code/datums/elements/disarm_attack.dm | 35 ++++++ .../status_effects/debuffs/staggered.dm | 10 +- code/game/atoms_movable.dm | 4 +- code/game/objects/items/pillow.dm | 1 + code/game/objects/items/shields.dm | 67 +++++++--- .../structures/crates_lockers/closets.dm | 24 ++-- code/game/objects/structures/tables_racks.dm | 12 +- code/game/turfs/closed/walls.dm | 4 +- code/modules/basketball/basketball.dm | 9 +- .../cargo/markets/market_items/misc.dm | 8 ++ code/modules/clothing/suits/armor.dm | 6 +- .../modules/mob/living/basic/basic_defense.dm | 39 +++--- .../carbon/alien/adult/adult_defense.dm | 8 +- .../mob/living/carbon/alien/adult/queen.dm | 2 + .../mob/living/carbon/alien/alien_defense.dm | 24 ++-- .../carbon/alien/larva/larva_defense.dm | 36 +++--- code/modules/mob/living/carbon/carbon.dm | 23 ---- .../mob/living/carbon/carbon_defense.dm | 111 ++--------------- code/modules/mob/living/carbon/human/human.dm | 2 + .../mob/living/carbon/human/human_defense.dm | 23 +++- code/modules/mob/living/living_defense.dm | 115 +++++++++++++++++- .../mob/living/silicon/robot/robot_defense.dm | 5 + code/modules/mob/living/silicon/silicon.dm | 1 + .../mob/living/silicon/silicon_defense.dm | 35 +++--- .../living/simple_animal/animal_defense.dm | 29 +++-- code/modules/mod/mod_theme.dm | 3 +- code/modules/mod/mod_types.dm | 1 + code/modules/mod/modules/modules_security.dm | 21 +++- code/modules/recycling/disposal/bin.dm | 12 +- .../organs/internal/heart/heart_ethereal.dm | 6 +- icons/obj/clothing/modsuit/mod_modules.dmi | Bin 55176 -> 55978 bytes tgstation.dme | 1 + 43 files changed, 456 insertions(+), 314 deletions(-) create mode 100644 code/datums/elements/disarm_attack.dm diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index c12c9b57713..19c87f3b4e0 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -370,3 +370,18 @@ GLOBAL_LIST_INIT(arm_zones, list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) #define COMBO_STEPS "steps" /// The proc the combo calls #define COMBO_PROC "proc" + +///Checks If the target can be moved at all by shoving them +#define SHOVE_CAN_MOVE (1<<0) +///If the target can be shoved into something something with perhaps special interactions. +#define SHOVE_CAN_HIT_SOMETHING (1<<1) +///Keeps knockdowns at bay for the target +#define SHOVE_KNOCKDOWN_BLOCKED (1<<2) +///If the target can be briefly paralized by shoving them once again after knocking them down. +#define SHOVE_CAN_KICK_SIDE (1<<3) +///Whether the staggered status effect can be applied on the target +#define SHOVE_CAN_STAGGER (1<<4) +///If the target could move, but didn't because there's an obstacle in the path. +#define SHOVE_BLOCKED (1<<5) +///If the obstacle is an object at the border of the turf (so no signal from being sent to the other turf) +#define SHOVE_DIRECTIONAL_BLOCKED (1<<6) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm index fe7673ee3ca..8b73010b8f9 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm @@ -13,13 +13,6 @@ ///Called from /mob/living/carbon/help_shake_act on the helper, after any hugs have ocurred. (mob/living/helped) #define COMSIG_CARBON_HELPED "carbon_helped_someone" -///Before a carbon mob is shoved, sent to the turf we're trying to shove onto (mob/living/carbon/shover, mob/living/carbon/target) -#define COMSIG_CARBON_DISARM_PRESHOVE "carbon_disarm_preshove" - #define COMSIG_CARBON_ACT_SOLID (1<<0) //Tells disarm code to act as if the mob was shoved into something solid, even we we're not -///When a carbon mob is disarmed, this is sent to the turf we're trying to shove onto (mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) -#define COMSIG_CARBON_DISARM_COLLIDE "carbon_disarm_collision" - #define COMSIG_CARBON_SHOVE_HANDLED (1<<0) - ///When a carbon slips. Called on /turf/open/handle_slip() #define COMSIG_ON_CARBON_SLIP "carbon_slip" // /mob/living/carbon physiology signals @@ -125,8 +118,6 @@ ///Applied preferences to a human #define COMSIG_HUMAN_PREFS_APPLIED "human_prefs_applied" -///Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) -#define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" ///Whenever EquipRanked is called, called after job is set #define COMSIG_JOB_RECEIVED "job_received" ///from /mob/living/carbon/human/proc/set_coretemperature(): (oldvalue, newvalue) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index b1838793e98..7ac77efcaa7 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -228,6 +228,15 @@ #define COMSIG_LIVING_CHECK_BLOCK "living_check_block" #define SUCCESSFUL_BLOCK (1<<0) +///Hit by successful disarm attack (mob/living/attacker, zone_targeted, item/weapon) +#define COMSIG_LIVING_DISARM_HIT "living_disarm_hit" +///Before a living mob is shoved, sent to the turf we're trying to shove onto (mob/living/shover, mob/living/target) +#define COMSIG_LIVING_DISARM_PRESHOVE "living_disarm_preshove" + #define COMSIG_LIVING_ACT_SOLID (1<<0) //Tells disarm code to act as if the mob was shoved into something solid, even we we're not +///When a living mob is disarmed, this is sent to the turf we're trying to shove onto (mob/living/shover, mob/living/target, shove_blocked) +#define COMSIG_LIVING_DISARM_COLLIDE "living_disarm_collision" + #define COMSIG_LIVING_SHOVE_HANDLED (1<<0) + /// Sent on a mob from /datum/component/mob_chain when component is attached with it as the "front" : (mob/living/basic/tail) #define COMSIG_MOB_GAINED_CHAIN_TAIL "living_gained_chain_tail" /// Sent on a mob from /datum/component/mob_chain when component is detached from it as the "front" : (mob/living/basic/tail) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index b3149b82719..25efe87272f 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -444,7 +444,7 @@ #define COMPONENT_SECONDARY_CONTINUE_ATTACK_CHAIN (1<<1) #define COMPONENT_SECONDARY_CALL_NORMAL_ATTACK_CHAIN (1<<2) /// From base of [/obj/item/proc/attack_secondary()]: (atom/target, mob/user, params) -#define COMSIG_ITEM_ATTACK_SECONDARY "item_pre_attack_secondary" +#define COMSIG_ITEM_ATTACK_SECONDARY "item_attack_secondary" ///from base of obj/item/afterattack(): (atom/target, mob/user, proximity_flag, click_parameters) #define COMSIG_ITEM_AFTERATTACK "item_afterattack" /// Flag for when /afterattack potentially acts on an item. @@ -462,6 +462,10 @@ /// from base of obj/item/failedEmbed() #define COMSIG_ITEM_FAILED_EMBED "item_failed_embed" +/// from base of datum/element/disarm_attack/secondary_attack(), used to prevent shoving: (victim, user, send_message) +#define COMSIG_ITEM_CAN_DISARM_ATTACK "item_pre_disarm_attack" + #define COMPONENT_BLOCK_ITEM_DISARM_ATTACK (1<<0) + ///from /obj/item/assembly/proc/pulsed(mob/pulser) #define COMSIG_ASSEMBLY_PULSED "assembly_pulsed" diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 9427328fcc2..8ace303b843 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -68,28 +68,26 @@ #define VOICEBOX_TOGGLABLE (1<<6) /// The voicebox is currently turned off. #define VOICEBOX_DISABLED (1<<7) -/// Prevents shovies against a dense object from knocking the wearer down. -#define BLOCKS_SHOVE_KNOCKDOWN (1<<8) /// Prevents knock-off from things like hat-throwing. -#define SNUG_FIT (1<<9) +#define SNUG_FIT (1<<8) /// Hats with negative effects when worn (i.e the tinfoil hat). -#define ANTI_TINFOIL_MANEUVER (1<<10) +#define ANTI_TINFOIL_MANEUVER (1<<9) /// Clothes that cause a larger notification when placed on a person. -#define DANGEROUS_OBJECT (1<<11) +#define DANGEROUS_OBJECT (1<<10) /// Clothes that use large icons, for applying the proper overlays like blood -#define LARGE_WORN_ICON (1<<12) +#define LARGE_WORN_ICON (1<<11) /// Clothes that block speech (i.e the muzzle). Can be applied to any clothing piece. -#define BLOCKS_SPEECH (1<<13) +#define BLOCKS_SPEECH (1<<12) /// prevents from placing on plasmaman helmet or modsuit hat holder -#define STACKABLE_HELMET_EXEMPT (1<<14) +#define STACKABLE_HELMET_EXEMPT (1<<13) /// Prevents plasmamen from igniting when wearing this -#define PLASMAMAN_PREVENT_IGNITION (1<<15) +#define PLASMAMAN_PREVENT_IGNITION (1<<14) /// Usable as casting clothes by wizards (matters for suits, glasses and headwear) -#define CASTING_CLOTHES (1<<16) +#define CASTING_CLOTHES (1<<15) ///Moths can't eat the clothing that has this flag. -#define INEDIBLE_CLOTHING (1<<17) +#define INEDIBLE_CLOTHING (1<<16) /// Headgear/helmet allows internals -#define HEADINTERNALS (1<<18) +#define HEADINTERNALS (1<<17) /// Integrity defines for clothing (not flags but close enough) #define CLOTHING_PRISTINE 0 // We have no damage on the clothing diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index af171751f33..132f8d91487 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -211,6 +211,12 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_BLOOD_DEFICIENCY "blood_deficiency" #define TRAIT_JOLLY "jolly" #define TRAIT_NOCRITDAMAGE "no_crit" +/// Prevents shovies against a dense object from knocking them down. +#define TRAIT_SHOVE_KNOCKDOWN_BLOCKED "shove_knockdown_blocked" +/// Prevents staggering. +#define TRAIT_NO_STAGGER "no_stagger" +/// Getting hit by thrown movables won't push you away +#define TRAIT_NO_THROW_HITPUSH "no_throw_hitpush" ///Added to mob or mind, changes the icons of the fish shown in the minigame UI depending on the possible reward. #define TRAIT_REVEAL_FISH "reveal_fish" diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 89c0b4e1c4d..ccfffecd7eb 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -83,7 +83,6 @@ DEFINE_BITFIELD(car_traits, list( DEFINE_BITFIELD(clothing_flags, list( "ANTI_TINFOIL_MANEUVER" = ANTI_TINFOIL_MANEUVER, - "BLOCKS_SHOVE_KNOCKDOWN" = BLOCKS_SHOVE_KNOCKDOWN, "BLOCKS_SPEECH" = BLOCKS_SPEECH, "BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT, "CASTING_CLOTHES" = CASTING_CLOTHES, diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 98275fe8231..5eda73a12f4 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -52,6 +52,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NO_FLOATING_ANIM" = TRAIT_NO_FLOATING_ANIM, "TRAIT_NO_MANIFEST_CONTENTS_ERROR" = TRAIT_NO_MANIFEST_CONTENTS_ERROR, "TRAIT_NO_MISSING_ITEM_ERROR" = TRAIT_NO_MISSING_ITEM_ERROR, + "TRAIT_NO_THROW_HITPUSH" = TRAIT_NO_THROW_HITPUSH, "TRAIT_NOT_ENGRAVABLE" = TRAIT_NOT_ENGRAVABLE, "TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT" = TRAIT_ODD_CUSTOMIZABLE_FOOD_INGREDIENT, "TRAIT_RUNECHAT_HIDDEN" = TRAIT_RUNECHAT_HIDDEN, @@ -328,6 +329,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NO_SLIP_SLIDE" = TRAIT_NO_SLIP_SLIDE, "TRAIT_NO_SLIP_WATER" = TRAIT_NO_SLIP_WATER, "TRAIT_NO_SOUL" = TRAIT_NO_SOUL, + "TRAIT_NO_STAGGER" = TRAIT_NO_STAGGER, "TRAIT_NO_STRIP" = TRAIT_NO_STRIP, "TRAIT_NO_TRANSFORM" = TRAIT_NO_TRANSFORM, "TRAIT_NO_TWOHANDING" = TRAIT_NO_TWOHANDING, @@ -393,6 +395,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SHAVED" = TRAIT_SHAVED, "TRAIT_SHIFTY_EYES" = TRAIT_SHIFTY_EYES, "TRAIT_SHOCKIMMUNE" = TRAIT_SHOCKIMMUNE, + "TRAIT_SHOVE_KNOCKDOWN_BLOCKED" = TRAIT_SHOVE_KNOCKDOWN_BLOCKED, "TRAIT_SIGN_LANG" = TRAIT_SIGN_LANG, "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, "TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE, diff --git a/code/datums/components/gunpoint.dm b/code/datums/components/gunpoint.dm index cb8dfb1ec8d..ed4142f41ce 100644 --- a/code/datums/components/gunpoint.dm +++ b/code/datums/components/gunpoint.dm @@ -45,7 +45,7 @@ COMSIG_LIVING_START_PULL), PROC_REF(trigger_reaction)) RegisterSignal(targ, COMSIG_ATOM_EXAMINE, PROC_REF(examine_target)) RegisterSignal(targ, COMSIG_LIVING_PRE_MOB_BUMP, PROC_REF(block_bumps_target)) - RegisterSignals(targ, list(COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_GET_PULLED), PROC_REF(cancel)) + RegisterSignals(targ, list(COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_GET_PULLED), PROC_REF(cancel)) RegisterSignals(weapon, list(COMSIG_ITEM_DROPPED, COMSIG_ITEM_EQUIPPED), PROC_REF(cancel)) var/distance = min(get_dist(shooter, target), 1) // treat 0 distance as adjacent @@ -88,7 +88,7 @@ RegisterSignals(parent, list(COMSIG_LIVING_START_PULL, COMSIG_MOVABLE_BUMP), PROC_REF(check_bump)) RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(examine)) RegisterSignal(parent, COMSIG_LIVING_PRE_MOB_BUMP, PROC_REF(block_bumps_parent)) - RegisterSignal(parent, COMSIG_HUMAN_DISARM_HIT, PROC_REF(cancel)) + RegisterSignal(parent, COMSIG_LIVING_DISARM_HIT, PROC_REF(cancel)) /datum/component/gunpoint/UnregisterFromParent() UnregisterSignal(parent, COMSIG_MOVABLE_MOVED) @@ -98,7 +98,7 @@ UnregisterSignal(parent, list(COMSIG_LIVING_START_PULL, COMSIG_MOVABLE_BUMP)) UnregisterSignal(parent, COMSIG_ATOM_EXAMINE) UnregisterSignal(parent, COMSIG_LIVING_PRE_MOB_BUMP) - UnregisterSignal(parent, COMSIG_HUMAN_DISARM_HIT) + UnregisterSignal(parent, COMSIG_LIVING_DISARM_HIT) ///If the shooter bumps the target, cancel the holdup to avoid cheesing and forcing the charged shot /datum/component/gunpoint/proc/check_bump(atom/B, atom/A) diff --git a/code/datums/components/knockoff.dm b/code/datums/components/knockoff.dm index b5563433c72..db72072789c 100644 --- a/code/datums/components/knockoff.dm +++ b/code/datums/components/knockoff.dm @@ -29,11 +29,11 @@ var/obj/item/item_parent = parent if(ismob(item_parent.loc)) - UnregisterSignal(item_parent.loc, list(COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN)) + UnregisterSignal(item_parent.loc, list(COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN)) -/// Signal proc for [COMSIG_HUMAN_DISARM_HIT] on the mob who's equipped our parent +/// Signal proc for [COMSIG_LIVING_DISARM_HIT] on the mob who's equipped our parent /// Rolls a chance for knockoff whenever we're disarmed -/datum/component/knockoff/proc/on_equipped_mob_disarm(mob/living/carbon/human/source, mob/living/attacker, zone) +/datum/component/knockoff/proc/on_equipped_mob_disarm(mob/living/source, mob/living/attacker, zone, obj/item/weapon) SIGNAL_HANDLER if(!istype(source)) @@ -85,10 +85,10 @@ return if(slots_knockoffable && !(slot & slots_knockoffable)) - UnregisterSignal(equipper, list(COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN)) + UnregisterSignal(equipper, list(COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN)) return - RegisterSignal(equipper, COMSIG_HUMAN_DISARM_HIT, PROC_REF(on_equipped_mob_disarm), TRUE) + RegisterSignal(equipper, COMSIG_LIVING_DISARM_HIT, PROC_REF(on_equipped_mob_disarm), TRUE) RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, PROC_REF(on_equipped_mob_knockdown), TRUE) /// Signal proc for [COMSIG_ITEM_DROPPED] @@ -96,4 +96,4 @@ /datum/component/knockoff/proc/on_dropped(datum/source, mob/living/dropper) SIGNAL_HANDLER - UnregisterSignal(dropper, list(COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN)) + UnregisterSignal(dropper, list(COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN)) diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 9f8bb9227e0..520fa161b69 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -400,7 +400,7 @@ defense_mod += 2 if(tackle_target.mob_negates_gravity()) defense_mod += 1 - if(tackle_target.is_shove_knockdown_blocked()) // riot armor and such + if(HAS_TRAIT(tackle_target, TRAIT_SHOVE_KNOCKDOWN_BLOCKED)) // riot armor and such defense_mod += 5 var/obj/item/organ/external/tail/lizard/el_tail = tackle_target.get_organ_slot(ORGAN_SLOT_EXTERNAL_TAIL) @@ -451,7 +451,7 @@ 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 + if(HAS_TRAIT(human_sacker, TRAIT_SHOVE_KNOCKDOWN_BLOCKED)) // tackling with riot specialized armor, like riot armor, is effective but tiring attack_mod += 2 human_sacker.adjustStaminaLoss(20) diff --git a/code/datums/elements/disarm_attack.dm b/code/datums/elements/disarm_attack.dm new file mode 100644 index 00000000000..a788cd9f35e --- /dev/null +++ b/code/datums/elements/disarm_attack.dm @@ -0,0 +1,35 @@ +///An element that allows items to be used to shove people around just like right-clicking would. +/datum/element/disarm_attack + +/datum/element/disarm_attack/Attach(datum/target) + . = ..() + if(!isitem(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_ITEM_ATTACK_SECONDARY, PROC_REF(secondary_attack)) + RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(examine)) + +/datum/element/disarm_attack/Detach(datum/source) + UnregisterSignal(source, list(COMSIG_ATOM_EXAMINE, COMSIG_ITEM_ATTACK_SECONDARY)) + return ..() + +/datum/element/disarm_attack/proc/secondary_attack(obj/item/source, mob/living/victim, mob/living/user, params) + SIGNAL_HANDLER + if(!user.can_disarm(victim) || !can_disarm_attack(source, victim, user)) + return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN + if(victim.check_block(source, 0, "the [source.name]", MELEE_ATTACK, 0)) + return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN + user.disarm(victim, source) + user.changeNext_move(source.secondary_attack_speed || source.attack_speed) + return COMPONENT_SECONDARY_CANCEL_ATTACK_CHAIN + +///check if the item conditions for the disarm action are met. +/datum/element/disarm_attack/proc/can_disarm_attack(obj/item/source, mob/living/victim, mob/living/user, message = TRUE) + if(SEND_SIGNAL(source, COMSIG_ITEM_CAN_DISARM_ATTACK, victim, user, message) & COMPONENT_BLOCK_ITEM_DISARM_ATTACK) + return FALSE + return TRUE + +/datum/element/disarm_attack/proc/examine(obj/item/source, mob/user, list/examine_list) + SIGNAL_HANDLER + if(can_disarm_attack(source, user, user, FALSE)) + examine_list += span_notice("You can use it to shove people with right-click.") diff --git a/code/datums/status_effects/debuffs/staggered.dm b/code/datums/status_effects/debuffs/staggered.dm index 0ffde66b1f1..89cf4943c19 100644 --- a/code/datums/status_effects/debuffs/staggered.dm +++ b/code/datums/status_effects/debuffs/staggered.dm @@ -10,7 +10,7 @@ /datum/status_effect/staggered/on_apply() //you can't stagger the dead. - if(owner.stat == DEAD) + if(owner.stat == DEAD || HAS_TRAIT(owner, TRAIT_NO_STAGGER)) return FALSE RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(clear_staggered)) @@ -31,10 +31,12 @@ /datum/status_effect/staggered/tick(seconds_between_ticks) //you can't stagger the dead - in case somehow you die mid-stagger - if(owner.stat == DEAD || HAS_TRAIT(owner, TRAIT_FAKEDEATH)) + if(owner.stat == DEAD || HAS_TRAIT(owner, TRAIT_NO_STAGGER)) + qdel(src) return - else - owner.do_stagger_animation() + if(HAS_TRAIT(owner, TRAIT_FAKEDEATH)) + return + 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 diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 0f21f9c5fd0..47c0ecdfa54 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1255,9 +1255,11 @@ return hit_atom.hitby(src, throwingdatum=throwingdatum, hitpush=hitpush) /atom/movable/hitby(atom/movable/hitting_atom, skipcatch, hitpush = TRUE, blocked, datum/thrownthing/throwingdatum) + if(HAS_TRAIT(src, TRAIT_NO_THROW_HITPUSH)) + hitpush = FALSE if(!anchored && hitpush && (!throwingdatum || (throwingdatum.force >= (move_resist * MOVE_FORCE_PUSH_RATIO)))) step(src, hitting_atom.dir) - ..() + return ..() /atom/movable/proc/safe_throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = MOVE_FORCE_STRONG, gentle = FALSE) if((force < (move_resist * MOVE_FORCE_THROW_RATIO)) || (move_resist == INFINITY)) diff --git a/code/game/objects/items/pillow.dm b/code/game/objects/items/pillow.dm index 5364905bdc9..928f1a373bb 100644 --- a/code/game/objects/items/pillow.dm +++ b/code/game/objects/items/pillow.dm @@ -31,6 +31,7 @@ force_unwielded = 5, \ force_wielded = 10, \ ) + AddElement(/datum/element/disarm_attack) var/static/list/slapcraft_recipe_list = list(\ /datum/crafting_recipe/pillow_suit, /datum/crafting_recipe/pillow_hood,\ diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm index ef434c1993d..b711bb63d45 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -35,6 +35,10 @@ fire = 80 acid = 70 +/obj/item/shield/Initialize(mapload) + . = ..() + AddElement(/datum/element/disarm_attack) + /obj/item/shield/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(transparent && (hitby.pass_flags & PASSGLASS)) return FALSE @@ -57,21 +61,32 @@ if(0 to 25) . += span_warning("It's falling apart!") -/obj/item/shield/proc/shatter(mob/living/carbon/human/owner) - playsound(owner, shield_break_sound, 50) - new shield_break_leftover(get_turf(src)) - /obj/item/shield/proc/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE) if(!breakable_by_damage || (damage_type != BRUTE && damage_type != BURN)) return TRUE - if (atom_integrity <= damage) - var/turf/owner_turf = get_turf(owner) - owner_turf.visible_message(span_warning("[hitby] destroys [src]!")) - shatter(owner) - qdel(src) - return FALSE - take_damage(damage) - return TRUE + var/penetration = 0 + var/armor_flag = MELEE + if(isprojectile(hitby)) + var/obj/projectile/bang_bang = hitby + armor_flag = bang_bang.armor_flag + penetration = bang_bang.armour_penetration + else if(isitem(hitby)) + var/obj/item/weapon = hitby + penetration = weapon.armour_penetration + else if(isanimal(hitby)) + var/mob/living/simple_animal/critter = hitby + penetration = critter.armour_penetration + else if(isbasicmob(hitby)) + var/mob/living/basic/critter = hitby + penetration = critter.armour_penetration + take_damage(damage, damage_type, armor_flag, armour_penetration = penetration) + +/obj/item/shield/atom_destruction(damage_flag) + playsound(src, shield_break_sound, 50) + new shield_break_leftover(get_turf(src)) + if(isliving(loc)) + loc.balloon_alert(loc, "shield broken!") + return ..() /obj/item/shield/buckler name = "wooden buckler" @@ -100,9 +115,14 @@ armor_type = /datum/armor/none max_integrity = 30 +/datum/armor/item_shield/riot + melee = 80 + bullet = 20 + laser = 20 + /obj/item/shield/riot name = "riot shield" - desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder." + desc = "A shield adept at blocking blunt objects from connecting with the torso of the shield wielder, less so bullets and laser beams." icon_state = "riot" inhand_icon_state = "riot" custom_materials = list(/datum/material/glass= SHEET_MATERIAL_AMOUNT * 3.75, /datum/material/iron= HALF_SHEET_MATERIAL_AMOUNT) @@ -110,6 +130,7 @@ max_integrity = 75 shield_break_sound = 'sound/effects/glassbr3.ogg' shield_break_leftover = /obj/item/shard + armor_type = /datum/armor/item_shield/riot /obj/item/shield/riot/Initialize(mapload) . = ..() @@ -177,7 +198,9 @@ QDEL_NULL(embedded_flash) return ..() -/obj/item/shield/riot/flash/attack(mob/living/target_mob, mob/user) +/obj/item/shield/riot/flash/attack(mob/living/target_mob, mob/living/user) + if(user.combat_mode) + return ..() flash_away(user, target_mob) /obj/item/shield/riot/flash/attack_self(mob/living/carbon/user) @@ -275,6 +298,7 @@ clumsy_check = !can_clumsy_use, \ ) RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) + RegisterSignal(src, COMSIG_ITEM_CAN_DISARM_ATTACK, PROC_REF(can_disarm_attack)) /obj/item/shield/energy/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(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) @@ -302,6 +326,13 @@ playsound(src, active ? 'sound/weapons/saberon.ogg' : 'sound/weapons/saberoff.ogg', 35, TRUE) return COMPONENT_NO_DEFAULT_MESSAGE +/obj/item/shield/energy/proc/can_disarm_attack(datum/source, mob/living/victim, mob/living/user, send_message = TRUE) + SIGNAL_HANDLER + if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) + if(send_message) + balloon_alert(user, "activate it first!") + return COMPONENT_BLOCK_ITEM_DISARM_ATTACK + /obj/item/shield/energy/advanced name = "advanced combat energy shield" desc = "A hardlight shield capable of reflecting all energy projectiles, as well las providing well-rounded defense from most all other attacks. \ @@ -338,6 +369,7 @@ ) RegisterSignal(src, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) + RegisterSignal(src, COMSIG_ITEM_CAN_DISARM_ATTACK, PROC_REF(can_disarm_attack)) /obj/item/shield/riot/tele/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(HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) @@ -358,4 +390,11 @@ playsound(src, 'sound/weapons/batonextend.ogg', 50, TRUE) return COMPONENT_NO_DEFAULT_MESSAGE +/obj/item/shield/riot/tele/proc/can_disarm_attack(datum/source, mob/living/victim, mob/living/user, send_message = TRUE) + SIGNAL_HANDLER + if(!HAS_TRAIT(src, TRAIT_TRANSFORM_ACTIVE)) + if(send_message) + balloon_alert(user, "extend it first!") + return COMPONENT_BLOCK_ITEM_DISARM_ATTACK + #undef BATON_BASH_COOLDOWN diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index d00c2becbe2..b3d3c7085bd 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -145,7 +145,7 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) populate_contents_immediate() var/static/list/loc_connections = list( - COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(locker_carbon), + COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(locker_living), COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock), ) AddElement(/datum/element/connect_loc, loc_connections) @@ -1160,11 +1160,11 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) /obj/structure/closet/return_temperature() return -/obj/structure/closet/proc/locker_carbon(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) +/obj/structure/closet/proc/locker_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon) SIGNAL_HANDLER if(!opened && (locked || welded)) //Yes this could be less code, no I don't care return - if(!opened && !shove_blocked) + if(!opened && ((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED))) return var/was_opened = opened if(!toggle()) @@ -1174,18 +1174,12 @@ GLOBAL_LIST_EMPTY(roundstart_station_closets) else target.Knockdown(SHOVE_KNOCKDOWN_SOLID) update_icon() - if(target == shover) - target.visible_message(span_danger("[target.name] shoves [target.p_them()]self into [src]!"), - null, - span_hear("You hear shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) - to_chat(shover, span_notice("You shove yourself into [src]!")) - else - target.visible_message(span_danger("[shover.name] shoves [target.name] into [src]!"), - span_userdanger("You're shoved into [src] by [shover.name]!"), - span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) - to_chat(src, span_danger("You shove [target.name] into [src]!")) - log_combat(shover, target, "shoved", "into [src] (locker/crate)") - return COMSIG_CARBON_SHOVE_HANDLED + target.visible_message(span_danger("[shover.name] shoves [target.name] into [src]!"), + span_userdanger("You're shoved into [src] by [shover.name]!"), + span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) + to_chat(src, span_danger("You shove [target.name] into [src]!")) + log_combat(shover, target, "shoved", "into [src] (locker/crate)[weapon ? " with [weapon]" : ""]") + return COMSIG_LIVING_SHOVE_HANDLED /// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Unlock and open up when we get knock casted. /obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/cooldown/spell/aoe/knock/spell, atom/caster) diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index f9178684768..9131607c6c4 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -47,7 +47,7 @@ make_climbable() var/static/list/loc_connections = list( - COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(table_carbon), + COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(table_living), ) AddElement(/datum/element/connect_loc, loc_connections) @@ -323,17 +323,17 @@ return TRUE return FALSE -/obj/structure/table/proc/table_carbon(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) +/obj/structure/table/proc/table_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon) SIGNAL_HANDLER - if(!shove_blocked) + if((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED)) return target.Knockdown(SHOVE_KNOCKDOWN_TABLE) target.visible_message(span_danger("[shover.name] shoves [target.name] onto \the [src]!"), - span_userdanger("You're shoved onto \the [src] by [shover.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) + span_userdanger("You're shoved onto \the [src] by [shover.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) to_chat(shover, span_danger("You shove [target.name] onto \the [src]!")) target.throw_at(src, 1, 1, null, FALSE) //1 speed throws with no spin are basically just forcemoves with a hard collision check - log_combat(shover, target, "shoved", "onto [src] (table)") - return COMSIG_CARBON_SHOVE_HANDLED + log_combat(shover, target, "shoved", "onto [src] (table)[weapon ? " with [weapon]" : ""]") + return COMSIG_LIVING_SHOVE_HANDLED /obj/structure/table/greyscale icon = 'icons/obj/smooth_structures/table_greyscale.dmi' diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index e660e95b72e..02e04557f42 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -72,7 +72,7 @@ ) RegisterSignals(src, list( COMSIG_MOB_CLIENT_PRE_MOVE, - COMSIG_HUMAN_DISARM_HIT, + COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE, @@ -83,7 +83,7 @@ SIGNAL_HANDLER UnregisterSignal(src, list( COMSIG_MOB_CLIENT_PRE_MOVE, - COMSIG_HUMAN_DISARM_HIT, + COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_GET_PULLED, COMSIG_MOVABLE_TELEPORTING, COMSIG_ATOM_DIR_CHANGE, diff --git a/code/modules/basketball/basketball.dm b/code/modules/basketball/basketball.dm index 9fe176715db..77c3214fe56 100644 --- a/code/modules/basketball/basketball.dm +++ b/code/modules/basketball/basketball.dm @@ -48,7 +48,7 @@ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(movement_effect)) RegisterSignal(user, COMSIG_MOB_EMOTED("spin"), PROC_REF(on_spin)) - RegisterSignal(user, COMSIG_HUMAN_DISARM_HIT, PROC_REF(on_equipped_mob_disarm)) + RegisterSignal(user, COMSIG_LIVING_DISARM_HIT, PROC_REF(on_equipped_mob_disarm)) RegisterSignal(user, COMSIG_LIVING_STATUS_KNOCKDOWN, PROC_REF(on_equipped_mob_knockdown)) /obj/item/toy/basketball/proc/remove_ball_effects() @@ -57,7 +57,7 @@ // unlike on_equip, this signal is triggered after the ball is removed from hands // so we can just use is_holding_item_of_type() proc to check for multiple balls if(!wielder.is_holding_item_of_type(/obj/item/toy/basketball)) - UnregisterSignal(wielder, list(COMSIG_MOVABLE_MOVED, COMSIG_MOB_EMOTED("spin"), COMSIG_HUMAN_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOB_THROW)) + UnregisterSignal(wielder, list(COMSIG_MOVABLE_MOVED, COMSIG_MOB_EMOTED("spin"), COMSIG_LIVING_DISARM_HIT, COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOB_THROW)) wielder = null @@ -99,12 +99,9 @@ /// Used to calculate our disarm chance based on stamina, direction, and spinning /// Note - monkeys use attack_paw() and never trigger this signal (so they always have 100% disarm) -/obj/item/toy/basketball/proc/on_equipped_mob_disarm(mob/living/baller, mob/living/stealer, zone) +/obj/item/toy/basketball/proc/on_equipped_mob_disarm(mob/living/baller, mob/living/stealer, zone, obj/item/weapon) SIGNAL_HANDLER - if(!istype(baller)) - return - // spinning gives you a lower disarm chance but it drains stamina var/disarm_chance = HAS_TRAIT(baller, TRAIT_SPINNING) ? 35 : 50 // ballers stamina results in lower disarm, stealer stamina results in higher disarm diff --git a/code/modules/cargo/markets/market_items/misc.dm b/code/modules/cargo/markets/market_items/misc.dm index d2b70704e7a..0953a73e0ac 100644 --- a/code/modules/cargo/markets/market_items/misc.dm +++ b/code/modules/cargo/markets/market_items/misc.dm @@ -50,6 +50,14 @@ stock_max = 2 availability_prob = 30 +/datum/market_item/misc/shove_blocker + name = "MOD Bulwark Module" + desc = "You have no idea how much effort it took us to extract this module from that damn safeguard MODsuit last shift." + price_min = CARGO_CRATE_VALUE * 4 + price_max = CARGO_CRATE_VALUE * 5.75 + stock_max = 1 + availability_prob = 25 + /datum/market_item/misc/holywater name = "Flask of holy water" desc = "Father Lootius' own brand of ready-made holy water." diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index df9db74b88b..ef10536341e 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -280,9 +280,9 @@ cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS armor_type = /datum/armor/armor_riot - clothing_flags = BLOCKS_SHOVE_KNOCKDOWN strip_delay = 80 equip_delay_other = 60 + clothing_traits = list(TRAIT_SHOVE_KNOCKDOWN_BLOCKED) /datum/armor/armor_riot melee = 50 @@ -379,13 +379,15 @@ armor_type = /datum/armor/armor_swat strip_delay = 120 resistance_flags = FIRE_PROOF | ACID_PROOF - clothing_flags = BLOCKS_SHOVE_KNOCKDOWN | THICKMATERIAL + clothing_flags = 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 max_heat_protection_temperature = SPACE_SUIT_MAX_TEMP_PROTECT slowdown = 0.7 body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + clothing_traits = list(TRAIT_SHOVE_KNOCKDOWN_BLOCKED) + //All of the armor below is mostly unused diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm index 69370ed49be..646cabe339d 100644 --- a/code/modules/mob/living/basic/basic_defense.dm +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -4,31 +4,7 @@ return TRUE if(LAZYACCESS(modifiers, RIGHT_CLICK)) - if(user.move_force < move_resist) - return - user.do_attack_animation(src, ATTACK_EFFECT_DISARM) - playsound(src, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - var/shove_dir = get_dir(user, src) - if(!Move(get_step(src, shove_dir), shove_dir)) - log_combat(user, src, "shoved", "failing to move it") - user.visible_message( - span_danger("[user.name] [response_disarm_continuous] [src]!"), - span_danger("You [response_disarm_simple] [src]!"), - span_hear("You hear aggressive shuffling!"), - COMBAT_MESSAGE_RANGE, - list(src), - ) - to_chat(src, span_userdanger("You're shoved by [user.name]!")) - return TRUE - log_combat(user, src, "shoved", "pushing it") - user.visible_message( - span_danger("[user.name] [response_disarm_continuous] [src], pushing [p_them()]!"), - span_danger("You [response_disarm_simple] [src], pushing [p_them()]!"), - span_hear("You hear aggressive shuffling!"), - COMBAT_MESSAGE_RANGE, - list(src), - ) - to_chat(src, span_userdanger("You're pushed by [user.name]!")) + user.disarm(src) return TRUE if(!user.combat_mode) @@ -63,6 +39,19 @@ updatehealth() return TRUE +/mob/living/basic/get_shoving_message(mob/living/shover, obj/item/weapon, shove_flags) + if(weapon) // no "gently pushing aside" if you're pressing a shield at them. + return ..() + var/moved = !(shove_flags & SHOVE_BLOCKED) + shover.visible_message( + span_danger("[shover.name] [response_disarm_continuous] [src][moved ? ", pushing [p_them()]" : ""]!"), + span_danger("You [response_disarm_simple] [src][moved ? ", pushing [p_them()]" : ""]!"), + span_hear("You hear aggressive shuffling!"), + COMBAT_MESSAGE_RANGE, + list(src), + ) + to_chat(src, span_userdanger("You're [moved ? "pushed" : "shoved"] by [shover.name]!")) + /mob/living/basic/attack_hulk(mob/living/carbon/human/user) . = ..() if(!.) diff --git a/code/modules/mob/living/carbon/alien/adult/adult_defense.dm b/code/modules/mob/living/carbon/alien/adult/adult_defense.dm index b4a119d5aba..d89f6de30d6 100644 --- a/code/modules/mob/living/carbon/alien/adult/adult_defense.dm +++ b/code/modules/mob/living/carbon/alien/adult/adult_defense.dm @@ -15,11 +15,8 @@ to_chat(user, span_danger("You [hitverb] [src]!")) /mob/living/carbon/alien/adult/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(!..() || !user.combat_mode) - return - if(LAZYACCESS(modifiers, RIGHT_CLICK)) - if(stat < UNCONSCIOUS) - user.disarm(src) + . = ..() + if(.) return TRUE var/damage = rand(1, 9) if (prob(90)) @@ -41,7 +38,6 @@ span_danger("You avoid [user]'s punch!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, user) to_chat(user, span_warning("Your punch misses [src]!")) - /mob/living/carbon/alien/adult/do_attack_animation(atom/A, visual_effect_icon, obj/item/used_item, no_effect) if(!no_effect && !visual_effect_icon) visual_effect_icon = ATTACK_EFFECT_CLAW diff --git a/code/modules/mob/living/carbon/alien/adult/queen.dm b/code/modules/mob/living/carbon/alien/adult/queen.dm index f2093738eac..1a08d2446d2 100644 --- a/code/modules/mob/living/carbon/alien/adult/queen.dm +++ b/code/modules/mob/living/carbon/alien/adult/queen.dm @@ -18,6 +18,8 @@ . = ..() // as a wise man once wrote: "pull over that ass too fat" REMOVE_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + // that'd be a too cheeky shield bashing strat + ADD_TRAIT(src, TRAIT_SHOVE_KNOCKDOWN_BLOCKED, INNATE_TRAIT) AddComponent(/datum/component/seethrough_mob) /mob/living/carbon/alien/adult/royal/on_lying_down(new_lying_angle) diff --git a/code/modules/mob/living/carbon/alien/alien_defense.dm b/code/modules/mob/living/carbon/alien/alien_defense.dm index 9388dcea560..3717cfcb89a 100644 --- a/code/modules/mob/living/carbon/alien/alien_defense.dm +++ b/code/modules/mob/living/carbon/alien/alien_defense.dm @@ -44,22 +44,22 @@ In all, this is a lot like the monkey code. /N /mob/living/carbon/alien/attack_hand(mob/living/carbon/human/user, list/modifiers) . = ..() - if(.) //to allow surgery to return properly. - return FALSE - - var/martial_result = user.apply_martial_art(src, modifiers) - if (martial_result != MARTIAL_ATTACK_INVALID) - return martial_result - - if(user.combat_mode) - if(LAZYACCESS(modifiers, RIGHT_CLICK)) - user.do_attack_animation(src, ATTACK_EFFECT_DISARM) - return TRUE - user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + if(.) return TRUE + + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + user.disarm(src) + return TRUE + if(user.combat_mode) + user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) else help_shake_act(user) + return TRUE +/mob/living/carbon/alien/get_shove_flags(mob/living/shover, obj/item/weapon) + . = ..() + if(isnull(weapon) || stat != CONSCIOUS) + . &= ~(SHOVE_CAN_MOVE|SHOVE_CAN_HIT_SOMETHING|SHOVE_CAN_STAGGER) /mob/living/carbon/alien/attack_paw(mob/living/carbon/human/user, list/modifiers) if(..()) diff --git a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm index 3fa78271d6f..62dd4f88b21 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva_defense.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva_defense.dm @@ -1,24 +1,26 @@ /mob/living/carbon/alien/larva/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(..()) - var/damage = rand(1, 9) - if (prob(90)) - playsound(loc, SFX_PUNCH, 25, TRUE, -1) - log_combat(user, src, "attacked") - visible_message(span_danger("[user] kicks [src]!"), \ - span_userdanger("[user] kicks you!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), COMBAT_MESSAGE_RANGE, user) - to_chat(user, span_danger("You kick [src]!")) - if ((stat != DEAD) && (damage > 4.9)) - Unconscious(rand(100,200)) + . = ..() + if(.) + return TRUE + var/damage = rand(1, 9) + if (prob(90)) + playsound(loc, SFX_PUNCH, 25, TRUE, -1) + log_combat(user, src, "attacked") + visible_message(span_danger("[user] kicks [src]!"), \ + span_userdanger("[user] kicks you!"), span_hear("You hear a sickening sound of flesh hitting flesh!"), COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You kick [src]!")) + if ((stat != DEAD) && (damage > 4.9)) + Unconscious(rand(100,200)) - var/obj/item/bodypart/affecting = get_bodypart(get_random_valid_zone(user.zone_selected)) - apply_damage(damage, BRUTE, affecting) - else - playsound(loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) - visible_message(span_danger("[user]'s kick misses [src]!"), \ - span_danger("You avoid [user]'s kick!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, user) - to_chat(user, span_warning("Your kick misses [src]!")) + var/obj/item/bodypart/affecting = get_bodypart(get_random_valid_zone(user.zone_selected)) + apply_damage(damage, BRUTE, affecting) + else + playsound(loc, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) + visible_message(span_danger("[user]'s kick misses [src]!"), \ + span_danger("You avoid [user]'s kick!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_warning("Your kick misses [src]!")) /mob/living/carbon/alien/larva/attack_hulk(mob/living/carbon/human/user) . = ..() diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index f4e7a6aa766..0c261c9f156 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -5,11 +5,6 @@ register_context() GLOB.carbon_list += src - var/static/list/loc_connections = list( - COMSIG_CARBON_DISARM_PRESHOVE = PROC_REF(disarm_precollide), - COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(disarm_collision), - ) - AddElement(/datum/element/connect_loc, loc_connections) ADD_TRAIT(src, TRAIT_CAN_HOLD_ITEMS, INNATE_TRAIT) // Carbons are assumed to be innately capable of having arms, we check their arms count instead /mob/living/carbon/Destroy() @@ -1377,24 +1372,6 @@ if(mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD)) AddComponent(/datum/component/rot, 6 MINUTES, 10 MINUTES, 1) -/mob/living/carbon/proc/disarm_precollide(datum/source, mob/living/carbon/shover, mob/living/carbon/target) - SIGNAL_HANDLER - if(can_be_shoved_into) - return COMSIG_CARBON_ACT_SOLID - -/mob/living/carbon/proc/disarm_collision(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) - SIGNAL_HANDLER - if(src == target || LAZYFIND(target.buckled_mobs, src) || !can_be_shoved_into) - return - target.Knockdown(SHOVE_KNOCKDOWN_HUMAN) - if(!is_shove_knockdown_blocked()) - Knockdown(SHOVE_KNOCKDOWN_COLLATERAL) - target.visible_message(span_danger("[shover] shoves [target.name] into [name]!"), - span_userdanger("You're shoved into [name] by [shover]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) - to_chat(src, span_danger("You shove [target.name] into [name]!")) - log_combat(shover, target, "shoved", addition = "into [name]") - return COMSIG_CARBON_SHOVE_HANDLED - /** * This proc is used to determine whether or not the mob can handle touching an acid affected object. */ diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index a2faacb9a94..01d45b0686d 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -148,10 +148,9 @@ /mob/living/carbon/attack_drone_secondary(mob/living/basic/drone/user) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -//ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/carbon/attack_hand(mob/living/carbon/human/user, list/modifiers) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN) - . = TRUE + . = ..() + for(var/thing in diseases) var/datum/disease/D = thing if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) @@ -162,13 +161,8 @@ if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) ContactContractDisease(D) - for(var/datum/surgery/operations as anything in surgeries) - if(user.combat_mode) - break - if(body_position != LYING_DOWN && (operations.surgery_flags & SURGERY_REQUIRE_RESTING)) - continue - if(operations.next_step(user, modifiers)) - return TRUE + if(.) + return TRUE for(var/datum/wound/wounds as anything in all_wounds) if(wounds.try_handling(user)) @@ -234,97 +228,6 @@ return dam_zone -/** - * Attempt to disarm the target mob. - * Will shove the target mob back, and drop them if they're in front of something dense - * or another carbon. -*/ -/mob/living/carbon/proc/disarm(mob/living/carbon/target) - do_attack_animation(target, ATTACK_EFFECT_DISARM) - playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - if (ishuman(target)) - var/mob/living/carbon/human/human_target = target - human_target.w_uniform?.add_fingerprint(src) - - SEND_SIGNAL(target, COMSIG_HUMAN_DISARM_HIT, src, zone_selected) - var/shove_dir = get_dir(loc, target.loc) - var/turf/target_shove_turf = get_step(target.loc, shove_dir) - var/shove_blocked = FALSE //Used to check if a shove is blocked so that if it is knockdown logic can be applied - var/turf/target_old_turf = target.loc - - //Are we hitting anything? or - if(SEND_SIGNAL(target_shove_turf, COMSIG_CARBON_DISARM_PRESHOVE) & COMSIG_CARBON_ACT_SOLID) - shove_blocked = TRUE - else - target.Move(target_shove_turf, shove_dir) - if(get_turf(target) == target_old_turf) - shove_blocked = TRUE - - if(!shove_blocked) - target.setGrabState(GRAB_PASSIVE) - - if(target.IsKnockdown() && !target.IsParalyzed()) //KICK HIM IN THE NUTS - target.Paralyze(SHOVE_CHAIN_PARALYZE) - target.visible_message(span_danger("[name] kicks [target.name] onto [target.p_their()] side!"), - span_userdanger("You're kicked onto your side by [name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) - to_chat(src, span_danger("You kick [target.name] onto [target.p_their()] side!")) - addtimer(CALLBACK(target, TYPE_PROC_REF(/mob/living, SetKnockdown), 0), SHOVE_CHAIN_PARALYZE) - log_combat(src, target, "kicks", "onto their side (paralyzing)") - - var/directional_blocked = FALSE - var/can_hit_something = (!target.is_shove_knockdown_blocked() && !target.buckled) - - //Directional checks to make sure that we're not shoving through a windoor or something like that - if(shove_blocked && can_hit_something && (shove_dir in GLOB.cardinals)) - var/target_turf = get_turf(target) - for(var/obj/obj_content in target_turf) - if(obj_content.flags_1 & ON_BORDER_1 && obj_content.dir == shove_dir && obj_content.density) - directional_blocked = TRUE - break - if(target_turf != target_shove_turf && !directional_blocked) //Make sure that we don't run the exact same check twice on the same tile - for(var/obj/obj_content in target_shove_turf) - if(obj_content.flags_1 & ON_BORDER_1 && obj_content.dir == REVERSE_DIR(shove_dir) && obj_content.density) - directional_blocked = TRUE - break - - if(can_hit_something) - //Don't hit people through windows, ok? - if(!directional_blocked && SEND_SIGNAL(target_shove_turf, COMSIG_CARBON_DISARM_COLLIDE, src, target, shove_blocked) & COMSIG_CARBON_SHOVE_HANDLED) - return - if(directional_blocked || shove_blocked) - target.Knockdown(SHOVE_KNOCKDOWN_SOLID) - target.visible_message(span_danger("[name] shoves [target.name], knocking [target.p_them()] down!"), - span_userdanger("You're knocked down from a shove by [name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) - to_chat(src, span_danger("You shove [target.name], knocking [target.p_them()] down!")) - log_combat(src, target, "shoved", "knocking them down") - return - - target.visible_message(span_danger("[name] shoves [target.name]!"), - span_userdanger("You're shoved by [name]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, src) - to_chat(src, span_danger("You shove [target.name]!")) - - //Take their lunch money - var/target_held_item = target.get_active_held_item() - var/append_message = "" - 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_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 - for (var/obj/item/clothing/clothing in get_equipped_items()) - if(clothing.clothing_flags & BLOCKS_SHOVE_KNOCKDOWN) - return TRUE - return FALSE - /mob/living/carbon/blob_act(obj/structure/blob/B) if (stat == DEAD) return @@ -798,4 +701,10 @@ organs -= organ_type GLOB.bioscrambler_valid_organs = organs +/mob/living/carbon/get_shove_flags(mob/living/shover, obj/item/weapon) + . = ..() + . |= SHOVE_CAN_STAGGER + if(IsKnockdown() && !IsParalyzed()) + . |= SHOVE_CAN_KICK_SIDE + #undef SHAKE_ANIMATION_OFFSET diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 4d71c556736..4791c818e1a 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -29,6 +29,8 @@ AddElement(/datum/element/strippable, GLOB.strippable_human_items, TYPE_PROC_REF(/mob/living/carbon/human/, should_strip)) var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + COMSIG_LIVING_DISARM_PRESHOVE = PROC_REF(disarm_precollide), + COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(disarm_collision), ) AddElement(/datum/element/connect_loc, loc_connections) GLOB.human_list += src diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 094a812a0ab..5dfe6a7a4fa 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -129,12 +129,31 @@ apply_damage(15, BRUTE, wound_bonus=10) /mob/living/carbon/human/attack_hand(mob/user, list/modifiers) - if(..()) //to allow surgery to return properly. - return + . = ..() + if(.) + return TRUE if(ishuman(user)) var/mob/living/carbon/human/H = user dna.species.spec_attack_hand(H, src, null, modifiers) +/mob/living/carbon/human/proc/disarm_precollide(datum/source, mob/living/shover, mob/living/target, obj/item/weapon) + SIGNAL_HANDLER + return COMSIG_LIVING_ACT_SOLID + +/mob/living/carbon/human/proc/disarm_collision(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon) + SIGNAL_HANDLER + if(src == target || LAZYFIND(target.buckled_mobs, src) || !iscarbon(target)) + return + if(!(shove_flags & SHOVE_KNOCKDOWN_BLOCKED)) + target.Knockdown(SHOVE_KNOCKDOWN_HUMAN) + if(!HAS_TRAIT(src, TRAIT_SHOVE_KNOCKDOWN_BLOCKED)) + Knockdown(SHOVE_KNOCKDOWN_COLLATERAL) + target.visible_message(span_danger("[shover] shoves [target.name] into [name]!"), + span_userdanger("You're shoved into [name] by [shover]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_danger("You shove [target.name] into [name]!")) + log_combat(shover, target, "shoved", addition = "into [name][weapon ? " with [weapon]" : ""]") + return COMSIG_LIVING_SHOVE_HANDLED + /mob/living/carbon/human/attack_paw(mob/living/carbon/human/user, list/modifiers) var/dam_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_L_HAND, BODY_ZONE_PRECISE_R_HAND, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) var/obj/item/bodypart/affecting = get_bodypart(get_random_valid_zone(dam_zone)) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 17f8cdad23a..01c2ed409ff 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -371,9 +371,8 @@ return martial_result if(LAZYACCESS(modifiers, RIGHT_CLICK)) - if (user != src && iscarbon(src)) - user.disarm(src) - return TRUE + user.disarm(src) + return TRUE if (!user.combat_mode) return FALSE if(HAS_TRAIT(user, TRAIT_PACIFISM)) @@ -615,6 +614,116 @@ ricocheting_projectile.set_angle(new_angle_s) return TRUE +/** + * Attempt to disarm the target mob. Some items might let you do it, also carbon can do it with right click. + * Will shove the target mob back, and drop them if they're in front of something dense + * or another carbon. +*/ +/mob/living/proc/disarm(mob/living/target, obj/item/weapon) + if(!can_disarm(target)) + return + var/shove_flags = target.get_shove_flags(src, weapon) + if(weapon) + do_attack_animation(target, used_item = weapon) + playsound(target, 'sound/effects/glassbash.ogg', 50, TRUE, -1) + else + do_attack_animation(target, ATTACK_EFFECT_DISARM) + playsound(target, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) + if (ishuman(target) && isnull(weapon)) + var/mob/living/carbon/human/human_target = target + human_target.w_uniform?.add_fingerprint(src) + + SEND_SIGNAL(target, COMSIG_LIVING_DISARM_HIT, src, zone_selected, weapon) + var/shove_dir = get_dir(loc, target.loc) + var/turf/target_shove_turf = get_step(target.loc, shove_dir) + var/turf/target_old_turf = target.loc + + //Are we hitting anything? or + if(shove_flags & SHOVE_CAN_MOVE) + if(SEND_SIGNAL(target_shove_turf, COMSIG_LIVING_DISARM_PRESHOVE, src, target, weapon) & COMSIG_LIVING_ACT_SOLID) + shove_flags |= SHOVE_BLOCKED + else + target.Move(target_shove_turf, shove_dir) + if(get_turf(target) == target_old_turf) + shove_flags |= SHOVE_BLOCKED + + if(!(shove_flags & SHOVE_BLOCKED)) + target.setGrabState(GRAB_PASSIVE) + + //Directional checks to make sure that we're not shoving through a windoor or something like that + if((shove_flags & SHOVE_BLOCKED) && (shove_dir in GLOB.cardinals)) + var/target_turf = get_turf(target) + for(var/obj/obj_content in target_turf) + if(obj_content.flags_1 & ON_BORDER_1 && obj_content.dir == shove_dir && obj_content.density) + shove_flags |= SHOVE_DIRECTIONAL_BLOCKED + break + if(target_turf != target_shove_turf && !(shove_flags && SHOVE_DIRECTIONAL_BLOCKED)) //Make sure that we don't run the exact same check twice on the same tile + for(var/obj/obj_content in target_shove_turf) + if(obj_content.flags_1 & ON_BORDER_1 && obj_content.dir == REVERSE_DIR(shove_dir) && obj_content.density) + shove_flags |= SHOVE_DIRECTIONAL_BLOCKED + break + + if(shove_flags & SHOVE_CAN_HIT_SOMETHING) + //Don't hit people through windows, ok? + if(!(shove_flags & SHOVE_DIRECTIONAL_BLOCKED) && (SEND_SIGNAL(target_shove_turf, COMSIG_LIVING_DISARM_COLLIDE, src, target, shove_flags, weapon) & COMSIG_LIVING_SHOVE_HANDLED)) + return + if((shove_flags & SHOVE_BLOCKED) && !(shove_flags & (SHOVE_KNOCKDOWN_BLOCKED|SHOVE_CAN_KICK_SIDE))) + target.Knockdown(SHOVE_KNOCKDOWN_SOLID) + target.visible_message(span_danger("[name] shoves [target.name], knocking [target.p_them()] down!"), + span_userdanger("You're knocked down from a shove by [name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_danger("You shove [target.name], knocking [target.p_them()] down!")) + log_combat(src, target, "shoved", "knocking them down[weapon ? " with [weapon]" : ""]") + return + + if(shove_flags & SHOVE_CAN_KICK_SIDE) //KICK HIM IN THE NUTS + target.Paralyze(SHOVE_CHAIN_PARALYZE) + target.visible_message(span_danger("[name] kicks [target.name] onto [target.p_their()] side!"), + span_userdanger("You're kicked onto your side by [name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_danger("You kick [target.name] onto [target.p_their()] side!")) + addtimer(CALLBACK(target, TYPE_PROC_REF(/mob/living, SetKnockdown), 0), SHOVE_CHAIN_PARALYZE) + log_combat(src, target, "kicks", "onto their side (paralyzing)") + return + + target.get_shoving_message(src, weapon, shove_flags) + + //Take their lunch money + var/target_held_item = target.get_active_held_item() + var/append_message = weapon ? " with [weapon]" : "" + 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_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) + + if(shove_flags & SHOVE_CAN_STAGGER) + target.adjust_staggered_up_to(STAGGERED_SLOWDOWN_LENGTH, 10 SECONDS) + + log_combat(src, target, "shoved", append_message) + +///Check if the universal conditions for disarming/shoving are met. +/mob/living/proc/can_disarm(mob/living/target) + if(body_position != STANDING_UP || src == target || loc == target.loc) + return FALSE + return TRUE + +///Check if there's anything that could stop the knockdown from being shoved into something or someone. +/mob/living/proc/get_shove_flags(mob/living/shover, obj/item/weapon) + if(shover.move_force >= move_resist) + . |= SHOVE_CAN_MOVE + if(!buckled) + . |= SHOVE_CAN_HIT_SOMETHING + if(HAS_TRAIT(src, TRAIT_SHOVE_KNOCKDOWN_BLOCKED)) + . |= SHOVE_KNOCKDOWN_BLOCKED + +///Send the chat feedback message for shoving +/mob/living/proc/get_shoving_message(mob/living/shover, obj/item/weapon, shove_flags) + visible_message(span_danger("[shover] shoves [name][weapon ? " with [weapon]" : ""]!"), + span_userdanger("You're shoved by [shover][weapon ? " with [weapon]" : ""]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, shover) + to_chat(shover, span_danger("You shove [name][weapon ? " with [weapon]" : ""]!")) + /mob/living/proc/check_block(atom/hit_by, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0, damage_type = BRUTE) if(SEND_SIGNAL(src, COMSIG_LIVING_CHECK_BLOCK, hit_by, damage, attack_text, attack_type, armour_penetration, damage_type) & SUCCESSFUL_BLOCK) return TRUE diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 44e8583b86f..722b92835c8 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -229,6 +229,11 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real step_away(src, user, 15) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_step_away), src, get_turf(user), 15), 3) +/mob/living/silicon/robot/get_shove_flags(mob/living/shover, obj/item/weapon) + . = ..() + if(isnull(weapon) || stat != CONSCIOUS) + . &= ~(SHOVE_CAN_MOVE|SHOVE_CAN_HIT_SOMETHING) + /mob/living/silicon/robot/welder_act(mob/living/user, obj/item/tool) if(user.combat_mode && usr != src) return FALSE diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 8a0c793103f..e17ba69550d 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -75,6 +75,7 @@ TRAIT_MADNESS_IMMUNE, TRAIT_MARTIAL_ARTS_IMMUNE, TRAIT_NOFIRE_SPREAD, + TRAIT_SHOVE_KNOCKDOWN_BLOCKED, ) add_traits(traits_to_apply, ROUNDSTART_TRAIT) diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index ac04f02f953..f1561c2941b 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -56,25 +56,30 @@ span_userdanger("[user] punches you!"), null, COMBAT_MESSAGE_RANGE, user) to_chat(user, span_danger("You punch [src]!")) -//ATTACK HAND IGNORING PARENT RETURN VALUE /mob/living/silicon/attack_hand(mob/living/carbon/human/user, list/modifiers) - . = FALSE - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_HAND, user, modifiers) & COMPONENT_CANCEL_ATTACK_CHAIN) - . = TRUE + . = ..() + if(.) + return TRUE + + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + user.disarm(src) + return TRUE + if(has_buckled_mobs() && !user.combat_mode) user_unbuckle_mob(buckled_mobs[1], user) + return TRUE + if(user.combat_mode) + user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) + playsound(src.loc, 'sound/effects/bang.ogg', 10, TRUE) + visible_message(span_danger("[user] punches [src], but doesn't leave a dent!"), \ + span_warning("[user] punches you, but doesn't leave a dent!"), null, COMBAT_MESSAGE_RANGE, user) + to_chat(user, span_danger("You punch [src], but don't leave a dent!")) + return TRUE else - if(user.combat_mode) - user.do_attack_animation(src, ATTACK_EFFECT_PUNCH) - playsound(src.loc, 'sound/effects/bang.ogg', 10, TRUE) - visible_message(span_danger("[user] punches [src], but doesn't leave a dent!"), \ - span_warning("[user] punches you, but doesn't leave a dent!"), null, COMBAT_MESSAGE_RANGE, user) - to_chat(user, span_danger("You punch [src], but don't leave a dent!")) - else - visible_message(span_notice("[user] pets [src]."), \ - span_notice("[user] pets you."), null, null, user) - to_chat(user, span_notice("You pet [src].")) - SEND_SIGNAL(user, COMSIG_MOB_PAT_BORG) + visible_message(span_notice("[user] pets [src]."), span_notice("[user] pets you."), null, null, user) + to_chat(user, span_notice("You pet [src].")) + SEND_SIGNAL(user, COMSIG_MOB_PAT_BORG) + return TRUE /mob/living/silicon/check_block(atom/hitby, damage, attack_text, attack_type, armour_penetration, damage_type, attack_flag) . = ..() diff --git a/code/modules/mob/living/simple_animal/animal_defense.dm b/code/modules/mob/living/simple_animal/animal_defense.dm index afe4c00db4d..2fea2871c03 100644 --- a/code/modules/mob/living/simple_animal/animal_defense.dm +++ b/code/modules/mob/living/simple_animal/animal_defense.dm @@ -4,21 +4,7 @@ return TRUE if(LAZYACCESS(modifiers, RIGHT_CLICK)) - if(user.move_force < move_resist) - return - user.do_attack_animation(src, ATTACK_EFFECT_DISARM) - playsound(src, 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - var/shove_dir = get_dir(user, src) - if(!Move(get_step(src, shove_dir), shove_dir)) - log_combat(user, src, "shoved", "failing to move it") - user.visible_message(span_danger("[user.name] shoves [src]!"), - span_danger("You shove [src]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) - to_chat(src, span_userdanger("You're shoved by [user.name]!")) - return TRUE - log_combat(user, src, "shoved", "pushing it") - user.visible_message(span_danger("[user.name] shoves [src], pushing [p_them()]!"), - span_danger("You shove [src], pushing [p_them()]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, list(src)) - to_chat(src, span_userdanger("You're pushed by [user.name]!")) + user.disarm(src) return TRUE if(!user.combat_mode) @@ -44,6 +30,19 @@ updatehealth() return TRUE +/mob/living/simple_animal/get_shoving_message(mob/living/shover, obj/item/weapon, shove_flags) + if(weapon) // no "gently pushing aside" if you're pressing a shield at them. + return ..() + var/moved = !(shove_flags & SHOVE_BLOCKED) + shover.visible_message( + span_danger("[shover.name] [response_disarm_continuous] [src][moved ? ", pushing [p_them()]" : ""]!"), + span_danger("You [response_disarm_simple] [src][moved ? ", pushing [p_them()]" : ""]!"), + span_hear("You hear aggressive shuffling!"), + COMBAT_MESSAGE_RANGE, + list(src), + ) + to_chat(src, span_userdanger("You're [moved ? "pushed" : "shoved"] by [shover.name]!")) + /mob/living/simple_animal/attack_hulk(mob/living/carbon/human/user) . = ..() if(!.) diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm index 5e579723f78..4d8f8b4c6b7 100644 --- a/code/modules/mod/mod_theme.dm +++ b/code/modules/mod/mod_theme.dm @@ -760,6 +760,7 @@ armor_type = /datum/armor/mod_theme_safeguard resistance_flags = FIRE_PROOF max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + inbuilt_modules = list(/obj/item/mod/module/shove_blocker/locked) slowdown_inactive = 0.75 slowdown_active = 0.25 allowed_suit_storage = list( @@ -1772,7 +1773,7 @@ UNSEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF, ), CHESTPLATE_FLAGS = list( - UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE|BLOCKS_SHOVE_KNOCKDOWN, + UNSEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE, SEALED_INVISIBILITY = HIDEJUMPSUIT, ), GAUNTLETS_FLAGS = list( diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm index 3e4b89e6a52..3bcd0751ce4 100644 --- a/code/modules/mod/mod_types.dm +++ b/code/modules/mod/mod_types.dm @@ -558,6 +558,7 @@ /obj/item/mod/module/magboot/advanced, /obj/item/mod/module/jetpack/advanced, /obj/item/mod/module/anomaly_locked/kinesis/admin, + /obj/item/mod/module/shove_blocker, ) default_pins = list( /obj/item/mod/module/stealth/ninja, diff --git a/code/modules/mod/modules/modules_security.dm b/code/modules/mod/modules/modules_security.dm index b3527125fd2..6df1d9d0a6f 100644 --- a/code/modules/mod/modules/modules_security.dm +++ b/code/modules/mod/modules/modules_security.dm @@ -424,7 +424,7 @@ if(oldgroup == newgroup) return - + sorted_creatures[oldgroup] -= creature sorted_creatures[newgroup] += creature @@ -578,3 +578,22 @@ #undef SHOOTING_ASSISTANT_OFF #undef STORMTROOPER_MODE #undef SHARPSHOOTER_MODE + +/obj/item/mod/module/anti_stagger + name = "MOD bulwark module" + desc = "Layers upon layers of shock dampening plates, just to stop you from getting shoved into a wall by an angry mob." + icon_state = "bulwark" + complexity = 3 + incompatible_modules = list(/obj/item/mod/module/shove_blocker) + +/obj/item/mod/module/shove_blocker/on_suit_activation() + mod.wearer.add_traits(list(TRAIT_SHOVE_KNOCKDOWN_BLOCKED, TRAIT_NO_STAGGER, TRAIT_NO_THROW_HITPUSH), MOD_TRAIT) + +/obj/item/mod/module/shove_blocker/on_suit_deactivation(deleting = FALSE) + mod.wearer.remove_traits(list(TRAIT_SHOVE_KNOCKDOWN_BLOCKED, TRAIT_NO_STAGGER, TRAIT_NO_THROW_HITPUSH), MOD_TRAIT) + +/obj/item/mod/module/shove_blocker/locked + name = "superglued MOD bulwark module" + desc = "Layers upon layers of shock dampening plates, just to stop you from getting shoved into a wall by an angry mob. Good luck removing this one." + removable = FALSE + complexity = 0 diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index a16e5add1bb..1976a61376f 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -61,7 +61,7 @@ RegisterSignal(src, COMSIG_RAT_INTERACT, PROC_REF(on_rat_rummage)) RegisterSignal(src, COMSIG_STORAGE_DUMP_CONTENT, PROC_REF(on_storage_dump)) var/static/list/loc_connections = list( - COMSIG_CARBON_DISARM_COLLIDE = PROC_REF(trash_carbon), + COMSIG_LIVING_DISARM_COLLIDE = PROC_REF(trash_living), COMSIG_TURF_RECEIVE_SWEEPED_ITEMS = PROC_REF(ready_for_trash), ) AddElement(/datum/element/connect_loc, loc_connections) @@ -551,17 +551,17 @@ return COMPONENT_RAT_INTERACTED /// Handles a carbon mob getting shoved into the disposal bin -/obj/machinery/disposal/proc/trash_carbon(datum/source, mob/living/carbon/shover, mob/living/carbon/target, shove_blocked) +/obj/machinery/disposal/proc/trash_living(datum/source, mob/living/shover, mob/living/target, shove_flags, obj/item/weapon) SIGNAL_HANDLER - if(!shove_blocked) + if((shove_flags & SHOVE_KNOCKDOWN_BLOCKED) || !(shove_flags & SHOVE_BLOCKED)) return target.Knockdown(SHOVE_KNOCKDOWN_SOLID) target.forceMove(src) target.visible_message(span_danger("[shover.name] shoves [target.name] into \the [src]!"), - span_userdanger("You're shoved into \the [src] by [target.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, src) + span_userdanger("You're shoved into \the [src] by [target.name]!"), span_hear("You hear aggressive shuffling followed by a loud thud!"), COMBAT_MESSAGE_RANGE, shover) to_chat(src, span_danger("You shove [target.name] into \the [src]!")) - log_combat(shover, target, "shoved", "into [src] (disposal bin)") - return COMSIG_CARBON_SHOVE_HANDLED + log_combat(shover, target, "shoved", "into [src] (disposal bin)[weapon ? " with [weapon]" : ""]") + return COMSIG_LIVING_SHOVE_HANDLED ///Called when a push broom is trying to sweep items onto the turf this object is standing on. Garbage will be moved inside. /obj/machinery/disposal/proc/ready_for_trash(datum/source, obj/item/pushbroom/broom, mob/user, list/items_to_sweep) diff --git a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm index 50659115872..bd30318a722 100644 --- a/code/modules/surgery/organs/internal/heart/heart_ethereal.dm +++ b/code/modules/surgery/organs/internal/heart/heart_ethereal.dm @@ -88,12 +88,12 @@ crystalize_timer_id = addtimer(CALLBACK(src, PROC_REF(crystalize), victim), CRYSTALIZE_PRE_WAIT_TIME, TIMER_STOPPABLE) - RegisterSignal(victim, COMSIG_HUMAN_DISARM_HIT, PROC_REF(reset_crystalizing)) + RegisterSignal(victim, COMSIG_LIVING_DISARM_HIT, PROC_REF(reset_crystalizing)) RegisterSignal(victim, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine), override = TRUE) RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage)) ///Ran when disarmed, prevents the ethereal from reviving -/obj/item/organ/internal/heart/ethereal/proc/reset_crystalizing(mob/living/defender, mob/living/attacker, zone) +/obj/item/organ/internal/heart/ethereal/proc/reset_crystalizing(mob/living/defender, mob/living/attacker, zone, obj/item/weapon) SIGNAL_HANDLER defender.visible_message( span_notice("The crystals on [defender] are gently broken off."), @@ -120,7 +120,7 @@ ///Stop the crystalization process, unregistering any signals and resetting any variables. /obj/item/organ/internal/heart/ethereal/proc/stop_crystalization_process(mob/living/ethereal, succesful = FALSE) - UnregisterSignal(ethereal, COMSIG_HUMAN_DISARM_HIT) + UnregisterSignal(ethereal, COMSIG_LIVING_DISARM_HIT) UnregisterSignal(ethereal, COMSIG_ATOM_EXAMINE) UnregisterSignal(ethereal, COMSIG_MOB_APPLY_DAMAGE) diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi index f1d19c29da119ef766f376a71990e281037ad787..eb4b0c536ce3cb976cc5cff0d92b3b8f6272e766 100644 GIT binary patch delta 15951 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 delta 15143 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�J?vYlGjH=;f0o%@D)i!hU%6@pq?F4D)0X zuFh)skq{O=hAB%ki$#k2+wRata85NU0bztB^QK`h;k|3!s^N`<_UaQRGXM#M)#q`v zzVK;$GCum&j?1)NzIWHel z@-T@6)|{$4-16M@cH9z9^4n!j5#L$kpW1=b?Si%s95RvZBsUu zk%h21Wu$+`{)j`Cvaw-~?z5>V`~m&q3CO0Ql(G|Fq7F1irucG7W7>Hwce}0d2u{Bz zXxm_l7*FRRf)do^kCYZYAlt0;#iR^qo>Q4dTjarJoC);=pg(|8y8hClPN>LXq2Wti z*=+`lXv)*VffYrQZrC#G<>v~Kr!r#x(uvo3+Z|IU-w!hW9_~Pn7Mw1RpGe+{F9=c; zUgpg=lS8d*7XJ<=)Ex7~S$<(ZZL-%&+3D)Z--f@w1AlAziy~wroAv3#O#FQUrNMAY ziGp+cJ*=^kU-Y2zz}i`vsqK)zh0yaA>Kc1=ODW~f#AlPfTIa;vaz7(u%7>-s53#bM zV7EX0qse{4%NDZc&iA=&^Be_PX}vBTKgAJy z%!=g1P~FPLU%0fpWC0!AqWZLRJ2zXzbDVnh>=(3E-boLryj6yH@uIl61Yj0FYmbnd zOyvGg!4{g08>jZRe{}F@ekT!X_h#rO)O|@0XjUgITtoosnM_@k>R!gPcao=`?NGV3 z;&5+tcLhzRiqFW&`@QJPw*FQcIv#4m{+dWJNh4if?D;h0^c@&-kr0F2x2S}iCKiM9U5B-z*3 z1xd&*-1}BJGO^UZOV%~50;uDe*M65vs=kFMDK1{WTy(C2>?KclX+uhaxw%!%NW2vAyP@nEI_7zJanIKXi3A z*ch%KXOw&06W_8xwR~M(gBJ3t8#DVf{Zpy$gQ65HASptO}$8vyYM3YU0qYz{OF*jrVDR8P^t1-2+x8}YBi>e^W5{aT zSv=6N0_xdC&t>#5g|dN1T%g$ZZx(W5BvhTHn>0O_GnirWus|2#G=u$YVZNVol^iu7 zOFheq8vrVuHejczFv@feRJ$xa5;_lZRrUry!gXO4*4ZLXnFLAPQh0@G%e!6-A0K;a{GwBOo9Fn57`a0^9;^_N9p~uA|k&q$H(+_^{jZSHWBRbl2 zyj{^c#>b*dj`FDpK_(ueOR9O)py{|J$rb{BA+-4^be=>Odf27#Pdy29BFJapn@?!1 z(4UHy(fW63o5=3PrtXDiBDGs8)8KX(=Jyw7j+;=ZmoQ$i(8xRcydj^05gB@YFQQ`9 zx