diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index be0a4d0f59..14c78f90d0 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -114,12 +114,17 @@ //slowdown when in softcrit #define SOFTCRIT_ADD_SLOWDOWN 6 -//Attack types for checking shields/hit reactions -#define MELEE_ATTACK 1 -#define UNARMED_ATTACK 2 -#define PROJECTILE_ATTACK 3 -#define THROWN_PROJECTILE_ATTACK 4 -#define LEAP_ATTACK 5 +/// Attack types for check_block()/run_block(). Flags, combinable. +/// Attack was melee, whether or not armed. +#define ATTACK_TYPE_MELEE (1<<0) +/// Attack was with a gun or something that should count as a gun (but not if a gun shouldn't count for a gun, crazy right?) +#define ATTACK_TYPE_PROJECTILE (1<<1) +/// Attack was unarmed.. this usually means hand to hand combat. +#define ATTACK_TYPE_UNARMED (1<<2) +/// Attack was a thrown atom hitting the victim. +#define ATTACK_TYPE_THROWN (1<<3) +/// Attack was a bodyslam/leap/tackle. See: Xenomorph leap tackles. +#define ATTACK_TYPE_TACKLE (1<<4) //attack visual effects #define ATTACK_EFFECT_PUNCH "punch" @@ -252,4 +257,53 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( #define BULLET_ACT_FORCE_PIERCE "PIERCE" //It pierces through the object regardless of the bullet being piercing by default. #define BULLET_ACT_TURF "TURF" //It hit us but it should hit something on the same turf too. Usually used for turfs. +/// Bitflags for check_block() and handle_block(). Meant to be combined. You can be hit and still reflect, for example, if you do not use BLOCK_SUCCESS. +/// Attack was not blocked +#define BLOCK_NONE NONE +/// Attack was blocked, do not do damage. THIS FLAG MUST BE THERE FOR DAMAGE/EFFECT PREVENTION! +#define BLOCK_SUCCESS (1<<1) +/// The below are for "metadata" on "how" the attack was blocked. + +/// Attack was and should be redirected according to list argument REDIRECT_METHOD (NOTE: the SHOULD here is important, as it says "the thing blocking isn't handling the reflecting for you so do it yourself"!) +#define BLOCK_SHOULD_REDIRECT (1<<2) +/// Attack was redirected (whether by us or by SHOULD_REDIRECT flagging for automatic handling) +#define BLOCK_REDIRECTED (1<<3) +/// Attack was blocked by something like a shield. +#define BLOCK_PHYSICAL_EXTERNAL (1<<4) +/// Attack was blocked by something worn on you. +#define BLOCK_PHYSICAL_INTERNAL (1<<5) +/// Attack outright missed because the target dodged. Should usually be combined with redirection passthrough or something (see martial arts) +#define BLOCK_TARGET_DODGED (1<<7) +/// Meta-flag for run_block/do_run_block : By default, BLOCK_SUCCESS tells do_run_block() to assume the attack is completely blocked and not continue the block chain. If this is present, it will continue to check other items in the chain rather than stopping. +#define BLOCK_CONTINUE_CHAIN (1<<8) + +/// For keys in associative list/block_return as we don't want to saturate our (somewhat) limited flags. +#define BLOCK_RETURN_REDIRECT_METHOD "REDIRECT_METHOD" + /// Pass through victim + #define REDIRECT_METHOD_PASSTHROUGH "passthrough" + /// Deflect at randomish angle + #define REDIRECT_METHOD_DEFLECT "deflect" + /// reverse 180 angle, basically (as opposed to "realistic" wall reflections) + #define REDIRECT_METHOD_REFLECT "reflect" + /// "do not taser the bad man with the desword" - actually aims at the firer/attacker rather than just reversing + #define REDIRECT_METHOD_RETURN_TO_SENDER "no_you" + +/// These keys are generally only applied to the list if real_attack is FALSE. Used incase we want to make "smarter" mob AI in the future or something. +/// Tells the caller how likely from 0 (none) to 100 (always) we are to reflect energy projectiles +#define BLOCK_RETURN_REFLECT_PROJECTILE_CHANCE "reflect_projectile_chance" +/// Tells the caller how likely we are to block attacks from 0 to 100 in general +#define BLOCK_RETURN_NORMAL_BLOCK_CHANCE "normal_block_chance" +/// Tells the caller about how many hits we can soak on average before our blocking fails. +#define BLOCK_RETURN_BLOCK_CAPACITY "block_capacity" + +/// Default if the above isn't set in the list. +#define DEFAULT_REDIRECT_METHOD_PROJECTILE REDIRECT_METHOD_DEFLECT + +/// Block priorities +#define BLOCK_PRIORITY_HELD_ITEM 100 +#define BLOCK_PRIORITY_CLOTHING 50 +#define BLOCK_PRIORITY_WEAR_SUIT 75 +#define BLOCK_PRIORITY_UNIFORM 25 + +#define BLOCK_PRIORITY_DEFAULT BLOCK_PRIORITY_HELD_ITEM diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 1ef8b9bb66..e75f832df3 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -192,6 +192,8 @@ #define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login" //sent when a mob/login() finishes: (client) #define COMSIG_MOB_CLIENT_MOVE "comsig_mob_client_move" //sent when client/Move() finishes with no early returns: (client, direction, n, oldloc) #define COMSIG_LIVING_GUN_PROCESS_FIRE "living_gun_process_fire" //from base of /obj/item/gun/proc/process_fire(): (atom/target, params, zone_override) +// This returns flags as defined for block in __DEFINES/combat.dm! +#define COMSIG_LIVING_RUN_BLOCK "living_do_run_block" //from base of mob/living/do_run_block(): (real_attack, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone) #define COMSIG_LIVING_COMBAT_ENABLED "combatmode_enabled" //from base of mob/living/enable_combat_mode() (was_forced) #define COMSIG_LIVING_COMBAT_DISABLED "combatmode_disabled" //from base of mob/living/disable_combat_mode() (was_forced) @@ -240,6 +242,9 @@ #define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user) #define COMSIG_ITEM_HIT_REACT "item_hit_react" //from base of obj/item/hit_reaction(): (list/args) #define COMSIG_ITEM_WEARERCROSSED "wearer_crossed" //called on item when crossed by something (): (/atom/movable) +// THE FOLLOWING TWO BLOCKS SHOULD RETURN BLOCK FLAGS AS DEFINED IN __DEFINES/combat.dm! +#define COMSIG_ITEM_CHECK_BLOCK "check_block" //from base of obj/item/check_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) +#define COMSIG_ITEM_RUN_BLOCK "run_block" //from base of obj/item/run_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) // /obj/item/clothing signals #define COMSIG_SHOES_STEP_ACTION "shoes_step_action" //from base of obj/item/clothing/shoes/proc/step_action(): () diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index c95896d853..9b877e8fb0 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -116,4 +116,7 @@ GLOBAL_VAR_INIT(cmp_field, "name") if(a_sign != b_sign) return a_sign - b_sign else - return sorttext(b_name, a_name) \ No newline at end of file + return sorttext(b_name, a_name) + +/proc/cmp_item_block_priority_asc(obj/item/A, obj/item/B) + return A.block_priority - B.block_priority diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index f422e073b2..f846c9c79e 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -125,7 +125,7 @@ if(!CHECK_MOBILITY(user, MOBILITY_STAND)) totitemdamage *= 0.5 //CIT CHANGES END HERE - if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + if((user != src) && run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user) & BLOCK_SUCCESS) return FALSE send_item_attack_message(I, user) I.do_stagger_action(src, user) diff --git a/code/datums/components/wearertargeting.dm b/code/datums/components/wearertargeting.dm index feaa88f934..4760757701 100644 --- a/code/datums/components/wearertargeting.dm +++ b/code/datums/components/wearertargeting.dm @@ -19,4 +19,4 @@ UnregisterSignal(equipper, signals) /datum/component/wearertargeting/proc/on_drop(datum/source, mob/user) - UnregisterSignal(user, signals) \ No newline at end of file + UnregisterSignal(user, signals) diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm index a9e1b8a729..050743fd36 100644 --- a/code/datums/martial/sleeping_carp.dm +++ b/code/datums/martial/sleeping_carp.dm @@ -237,7 +237,7 @@ else return ..() -/obj/item/twohanded/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(wielded) - return ..() - return FALSE +/obj/item/twohanded/bostaff/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!wielded) + return BLOCK_NONE + return ..() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 222b82d134..8b4567a754 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -93,8 +93,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) var/tool_behaviour = NONE var/toolspeed = 1 - var/block_chance = 0 - var/hit_reaction_chance = 0 //If you want to have something unrelated to blocking/armour piercing etc. Maybe not needed, but trying to think ahead/allow more freedom var/reach = 1 //In tiles, how far this weapon can reach; 1 for adjacent, which is default //The list of slots by priority. equip_to_appropriate_slot() uses this list. Doesn't matter if a mob type doesn't have a slot. @@ -365,13 +363,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) // afterattack() and attack() prototypes moved to _onclick/item_attack.dm for consistency -/obj/item/proc/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - SEND_SIGNAL(src, COMSIG_ITEM_HIT_REACT, args) - if(prob(final_block_chance)) - owner.visible_message("[owner] blocks [attack_text] with [src]!") - return 1 - return 0 - /obj/item/proc/talk_into(mob/M, input, channel, spans, datum/language/language) return ITALICS | REDUCE_RANGE @@ -464,9 +455,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) /obj/item/proc/ui_action_click(mob/user, actiontype) attack_self(user) -/obj/item/proc/IsReflect(var/def_zone) //This proc determines if and at what% an object will reflect energy projectiles if it's in l_hand,r_hand or wear_suit - return 0 - /obj/item/proc/eyestab(mob/living/carbon/M, mob/living/carbon/user) if(HAS_TRAIT(user, TRAIT_PACIFISM)) to_chat(user, "You don't want to harm [M]!") diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index b0882b6e5d..e2140bd0fd 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -234,15 +234,16 @@ /obj/item/flamethrower/full/tank create_with_tank = TRUE -/obj/item/flamethrower/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - var/obj/item/projectile/P = hitby - if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) - owner.visible_message("\The [attack_text] hits the fueltank on [owner]'s [name], rupturing it! What a shot!") - var/target_turf = get_turf(owner) - igniter.ignite_turf(src,target_turf, release_amount = 100) - qdel(ptank) - return 1 //It hit the flamethrower, not them - +/obj/item/flamethrower/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type & ATTACK_TYPE_PROJECTILE) + var/obj/item/projectile/P = object + if(istype(P) && (P.damage_type != STAMINA) && damage && !P.nodamage && prob(15)) + owner.visible_message("\The [attack_text] hits the fueltank on [owner]'s [name], rupturing it! What a shot!") + var/target_turf = get_turf(owner) + igniter.ignite_turf(src,target_turf, release_amount = 100) + qdel(ptank) + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return ..() /obj/item/assembly/igniter/proc/flamethrower_process(turf/open/location) location.hotspot_expose(700,2) diff --git a/code/game/objects/items/grenades/grenade.dm b/code/game/objects/items/grenades/grenade.dm index 77b4b33a8c..8a41ee4601 100644 --- a/code/game/objects/items/grenades/grenade.dm +++ b/code/game/objects/items/grenades/grenade.dm @@ -115,12 +115,14 @@ /obj/item/grenade/attack_paw(mob/user) return attack_hand(user) -/obj/item/grenade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - var/obj/item/projectile/P = hitby - if(damage && attack_type == PROJECTILE_ATTACK && P.damage_type != STAMINA && prob(15)) - owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") - prime() - return TRUE //It hit the grenade, not them +/obj/item/grenade/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type & ATTACK_TYPE_PROJECTILE) + var/obj/item/projectile/P = object + if(damage && !P.nodamage && (P.damage_type != STAMINA) && prob(15)) + owner.visible_message("[attack_text] hits [owner]'s [src], setting it off! What a shot!") + prime() + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return ..() /obj/item/proc/grenade_prime_react(obj/item/grenade/nade) return diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index b8f15ec295..84a9193eaf 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -328,9 +328,9 @@ hitsound = 'sound/weapons/bladeslice.ogg' attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") -/obj/item/nullrod/claymore/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 //Don't bring a sword to a gunfight +/obj/item/nullrod/claymore/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type & ATTACK_TYPE_PROJECTILE) // Don't bring a sword to a gunfight + return NONE return ..() /obj/item/nullrod/claymore/darkblade diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index aba07c9120..766da28d51 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -113,10 +113,10 @@ else RemoveElement(/datum/element/sword_point) -/obj/item/melee/transforming/energy/sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) - return ..() - return 0 +/obj/item/melee/transforming/energy/sword/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!active) + return NONE + return ..() /obj/item/melee/transforming/energy/sword/cyborg item_color = "red" @@ -148,8 +148,8 @@ tool_behaviour = TOOL_SAW toolspeed = 0.7 -/obj/item/melee/transforming/energy/sword/cyborg/saw/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return 0 +/obj/item/melee/transforming/energy/sword/cyborg/saw/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + return NONE /obj/item/melee/transforming/energy/sword/saber var/list/possible_colors = list("red" = LIGHT_COLOR_RED, "blue" = LIGHT_COLOR_LIGHT_CYAN, "green" = LIGHT_COLOR_GREEN, "purple" = LIGHT_COLOR_LAVENDER) diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index cbbd50b573..011837d48b 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -2,13 +2,12 @@ item_flags = NEEDS_PERMIT /obj/item/melee/proc/check_martial_counter(mob/living/carbon/human/target, mob/living/carbon/human/user) - if(target.check_block()) + if(target.check_martial_melee_block()) target.visible_message("[target.name] blocks [src] and twists [user]'s arm behind [user.p_their()] back!", "You block the attack!") user.Stun(40) return TRUE - /obj/item/melee/chainofcommand name = "chain of command" desc = "A tool used by great men to placate the frothing masses." @@ -75,9 +74,9 @@ AddComponent(/datum/component/butchering, 30, 95, 5) //fast and effective, but as a sword, it might damage the results. AddElement(/datum/element/sword_point) -/obj/item/melee/sabre/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 //Don't bring a sword to a gunfight +/obj/item/melee/sabre/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type & ATTACK_TYPE_PROJECTILE) // Don't bring a sword to a gunfight. + return BLOCK_NONE return ..() /obj/item/melee/sabre/on_exit_storage(datum/component/storage/S) @@ -165,8 +164,8 @@ . = ..() AddComponent(/datum/component/butchering, 20, 65, 0) -/obj/item/melee/rapier/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) +/obj/item/melee/rapier/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type == ATTACK_TYPE_PROJECTILE) final_block_chance = 0 return ..() @@ -305,7 +304,7 @@ return else if(cooldown_check < world.time) - if(target.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) + if(target.run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user) & BLOCK_SUCCESS) playsound(target, 'sound/weapons/genhit.ogg', 50, 1) return if(ishuman(target)) diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 0ca2fbd386..318753e4ad 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -11,7 +11,7 @@ var/charge_cost = 30 /obj/item/borg/stun/attack(mob/living/M, mob/living/user) - if(M.check_shields(src, 0, "[M]'s [name]", MELEE_ATTACK)) + if(M.run_block(src, 0, "[M]'s [name]", ATTACK_TYPE_MELEE, 0, user, ran_zone(user.zone_selected)) & BLOCK_SUCCESS) playsound(M, 'sound/weapons/genhit.ogg', 50, 1) return FALSE if(iscyborg(user)) diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm index 07f4cb4e40..37dfc417d5 100644 --- a/code/game/objects/items/shields.dm +++ b/code/game/objects/items/shields.dm @@ -5,7 +5,7 @@ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 0, "bomb" = 30, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 70) var/transparent = FALSE // makes beam projectiles pass through the shield -/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) +/obj/item/shield/proc/on_shield_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance) return TRUE /obj/item/shield/riot @@ -26,16 +26,18 @@ transparent = TRUE max_integrity = 75 -/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) - if(transparent && (hitby.pass_flags & PASSGLASS)) - return FALSE - if(attack_type == THROWN_PROJECTILE_ATTACK) +/obj/item/shield/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(ismovableatom(object)) + var/atom/movable/AM = object + if(transparent && (AM.pass_flags & PASSGLASS)) + return BLOCK_NONE + if(attack_type & ATTACK_TYPE_THROWN) final_block_chance += 30 - if(attack_type == LEAP_ATTACK) + if(attack_type & ATTACK_TYPE_TACKLE) final_block_chance = 100 . = ..() - if(.) - on_shield_block(owner, hitby, attack_text, damage, attack_type) + if(. & BLOCK_SUCCESS) + on_shield_block(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) /obj/item/shield/riot/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/melee/baton)) @@ -69,10 +71,10 @@ playsound(owner, 'sound/effects/glassbr3.ogg', 100) new /obj/item/shard((get_turf(src))) -/obj/item/shield/riot/on_shield_block(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) +/obj/item/shield/riot/on_shield_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(obj_integrity <= damage) var/turf/T = get_turf(owner) - T.visible_message("[hitby] destroys [src]!") + T.visible_message("[attack_text] destroys [src]!") shatter(owner) qdel(src) return FALSE @@ -139,11 +141,11 @@ . = ..() icon_state = "[base_icon_state]0" -/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) - return 0 - -/obj/item/shield/energy/IsReflect() - return (active) +/obj/item/shield/energy/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if((attack_type & ATTACK_TYPE_PROJECTILE) && is_energy_reflectable_projectile(object)) + block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_DEFLECT + return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT + return ..() /obj/item/shield/energy/attack_self(mob/living/carbon/human/user) if(clumsy_check && HAS_TRAIT(user, TRAIT_CLUMSY) && prob(50)) @@ -182,10 +184,10 @@ w_class = WEIGHT_CLASS_NORMAL var/active = 0 -/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) - if(active) - return ..() - return 0 +/obj/item/shield/riot/tele/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!active) + return BLOCK_NONE + return ..() /obj/item/shield/riot/tele/attack_self(mob/living/user) active = !active @@ -249,7 +251,7 @@ transparent = FALSE item_flags = SLOWS_WHILE_IN_HAND -/obj/item/shield/riot/implant/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) +/obj/item/shield/riot/implant/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type & ATTACK_TYPE_PROJECTILE) final_block_chance = 60 //Massive shield return ..() diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 0a29562d6b..4893e92d77 100755 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -804,3 +804,4 @@ attack_verb = list("bashed", "slashes", "prods", "pokes") fitting_swords = list(/obj/item/melee/rapier) starting_sword = /obj/item/melee/rapier + diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 64a0026424..0fc47f649a 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -170,7 +170,7 @@ return disarming || (user.a_intent != INTENT_HARM) /obj/item/melee/baton/proc/baton_stun(mob/living/L, mob/user, disarming = FALSE) - if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + if(L.run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user) & BLOCK_SUCCESS) //No message; check_shields() handles that playsound(L, 'sound/weapons/genhit.ogg', 50, 1) return FALSE var/stunpwr = stamforce diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm index ba51fa3d65..ef213c233a 100644 --- a/code/game/objects/items/toys.dm +++ b/code/game/objects/items/toys.dm @@ -454,11 +454,8 @@ total_mass_on = TOTAL_MASS_TOY_SWORD sharpness = IS_BLUNT -/obj/item/twohanded/dualsaber/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return FALSE - -/obj/item/twohanded/dualsaber/toy/IsReflect()//Stops Toy Dualsabers from reflecting energy projectiles - return FALSE +/obj/item/twohanded/dualsaber/toy/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + return BLOCK_NONE /obj/item/twohanded/dualsaber/hypereutactic/toy name = "\improper DX Hyper-Euplastic LightSword" @@ -474,11 +471,8 @@ slowdown_wielded = 0 sharpness = IS_BLUNT -/obj/item/twohanded/dualsaber/hypereutactic/toy/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - return FALSE - -/obj/item/twohanded/dualsaber/hypereutactic/toy/IsReflect()//Stops it from reflecting energy projectiles - return FALSE +/obj/item/twohanded/dualsaber/hypereutactic/toy/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + return BLOCK_NONE /obj/item/twohanded/dualsaber/hypereutactic/toy/rainbow name = "\improper Hyper-Euclidean Reciprocating Trigonometric Zweihander" diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm index b188250fff..4bc8bdb36d 100644 --- a/code/game/objects/items/twohanded.dm +++ b/code/game/objects/items/twohanded.dm @@ -289,6 +289,8 @@ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 70) resistance_flags = FIRE_PROOF var/hacked = FALSE + /// Can this reflect all energy projectiles? + var/can_reflect = TRUE var/brightness_on = 6 //TWICE AS BRIGHT AS A REGULAR ESWORD var/list/possible_colors = list("red", "blue", "green", "purple") var/list/rainbow_colors = list(LIGHT_COLOR_RED, LIGHT_COLOR_GREEN, LIGHT_COLOR_LIGHT_CYAN, LIGHT_COLOR_LAVENDER) @@ -373,10 +375,13 @@ else user.adjustStaminaLoss(25) -/obj/item/twohanded/dualsaber/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(wielded) - return ..() - return 0 +/obj/item/twohanded/dualsaber/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!wielded) + return NONE + if(can_reflect && is_energy_reflectable_projectile(object) && (attack_type & ATTACK_TYPE_PROJECTILE)) + block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_RETURN_TO_SENDER //no you + return BLOCK_SHOULD_REDIRECT | BLOCK_SUCCESS | BLOCK_REDIRECTED + return ..() /obj/item/twohanded/dualsaber/attack_hulk(mob/living/carbon/human/user, does_attack_animation = 0) //In case thats just so happens that it is still activated on the groud, prevents hulk from picking it up if(wielded) @@ -419,10 +424,6 @@ /obj/item/twohanded/dualsaber/proc/rainbow_process() light_color = pick(rainbow_colors) -/obj/item/twohanded/dualsaber/IsReflect() - if(wielded) - return 1 - /obj/item/twohanded/dualsaber/ignition_effect(atom/A, mob/user) // same as /obj/item/melee/transforming/energy, mostly if(!wielded) @@ -560,15 +561,13 @@ block_chance = 50 armour_penetration = 0 var/chaplain_spawnable = TRUE + can_reflect = FALSE obj_flags = UNIQUE_RENAME /obj/item/twohanded/dualsaber/hypereutactic/chaplain/ComponentInitialize() . = ..() AddComponent(/datum/component/anti_magic, TRUE, TRUE, FALSE, null, null, FALSE) -/obj/item/twohanded/dualsaber/hypereutactic/chaplain/IsReflect() - return FALSE - //spears /obj/item/twohanded/spear icon_state = "spearglass0" @@ -752,12 +751,16 @@ armour_penetration = 100 force_on = 30 -/obj/item/twohanded/required/chainsaw/doomslayer/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) +/obj/item/twohanded/required/chainsaw/doomslayer/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + block_return[BLOCK_RETURN_REFLECT_PROJECTILE_CHANCE] = 100 + return ..() + +/obj/item/twohanded/required/chainsaw/doomslayer/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(attack_type & ATTACK_TYPE_PROJECTILE) owner.visible_message("Ranged attacks just make [owner] angrier!") playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1) - return 1 - return 0 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return ..() //GREY TIDE /obj/item/twohanded/spear/grey_tide @@ -897,19 +900,20 @@ AddComponent(/datum/component/butchering, 20, 105) AddElement(/datum/element/sword_point) -/obj/item/twohanded/vibro_weapon/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) +/obj/item/twohanded/vibro_weapon/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(wielded) final_block_chance *= 2 - if(wielded || attack_type != PROJECTILE_ATTACK) + if(wielded || !(attack_type & ATTACK_TYPE_PROJECTILE)) if(prob(final_block_chance)) - if(attack_type == PROJECTILE_ATTACK) + if(attack_type & ATTACK_TYPE_PROJECTILE) owner.visible_message("[owner] deflects [attack_text] with [src]!") playsound(src, pick('sound/weapons/bulletflyby.ogg', 'sound/weapons/bulletflyby2.ogg', 'sound/weapons/bulletflyby3.ogg'), 75, 1) - return 1 + block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_DEFLECT + return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL else owner.visible_message("[owner] parries [attack_text] with [src]!") - return 1 - return 0 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return NONE /obj/item/twohanded/vibro_weapon/update_icon_state() icon_state = "hfrequency[wielded]" @@ -1055,11 +1059,9 @@ var/mob/living/silicon/robot/R = loc . = R.get_cell() -/obj/item/twohanded/electrostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!on) - return FALSE - if((attack_type == PROJECTILE_ATTACK) && !can_block_projectiles) - return FALSE +/obj/item/twohanded/electrostaff/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!on || (!can_block_projectiles && (attack_type & ATTACK_TYPE_PROJECTILE))) + return BLOCK_NONE return ..() /obj/item/twohanded/electrostaff/proc/min_hitcost() @@ -1180,7 +1182,7 @@ if(iscyborg(target)) ..() return - if(target.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) //No message; check_shields() handles that + if(target.run_block(src, 0, "[user]'s [name]", ATTACK_TYPE_MELEE, 0, user) & BLOCK_SUCCESS) //No message; run_block() handles that playsound(target, 'sound/weapons/genhit.ogg', 50, 1) return FALSE if(user.a_intent != INTENT_HARM) diff --git a/code/game/objects/items/weaponry.dm b/code/game/objects/items/weaponry.dm index 7f40e3d698..2f4f569e69 100644 --- a/code/game/objects/items/weaponry.dm +++ b/code/game/objects/items/weaponry.dm @@ -155,8 +155,10 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 return to_chat(user, "[src] thrums and points to the [dir2text(get_dir(user, closest_victim))].") -/obj/item/claymore/highlander/IsReflect() - return 1 //YOU THINK YOUR PUNY LASERS CAN STOP ME? +/obj/item/claymore/highlander/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if((attack_type & ATTACK_TYPE_PROJECTILE) && is_energy_reflectable_projectile(object)) + return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED + return ..() /obj/item/claymore/highlander/proc/add_notch(mob/living/user) //DYNAMIC CLAYMORE PROGRESSION SYSTEM - THIS IS THE FUTURE notches++ @@ -607,14 +609,13 @@ for further reading, please see: https://github.com/tgstation/tgstation/pull/301 force = 12 throwforce = 15 -/obj/item/melee/baseball_bat/ablative/IsReflect()//some day this will reflect thrown items instead of lasers - var/picksound = rand(1,2) - var/turf = get_turf(src) - if(picksound == 1) - playsound(turf, 'sound/weapons/effects/batreflect1.ogg', 50, 1) - if(picksound == 2) - playsound(turf, 'sound/weapons/effects/batreflect2.ogg', 50, 1) - return 1 +/obj/item/melee/baseball_bat/ablative/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + //some day this will reflect thrown items instead of lasers + if(is_energy_reflectable_projectile(object) && (attack_type == ATTACK_TYPE_PROJECTILE)) + var/turf = get_turf(src) + playsound(turf, pick('sound/weapons/effects/batreflect1.ogg', 'sound/weapons/effects/batreflect2.ogg'), 50, 1) + return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED + return ..() /obj/item/melee/baseball_bat/ablative/syndi name = "syndicate major league bat" diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 06c1d96711..c593451c24 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -317,7 +317,7 @@ throwforce = 10 throw_range = 3 hitsound = 'sound/items/trayhit1.ogg' - hit_reaction_chance = 50 + block_chance = 50 custom_materials = list(/datum/material/iron = 2000) var/break_chance = 5 //Likely hood of smashing the chair. var/obj/structure/chair/origin_type = /obj/structure/chair @@ -362,18 +362,17 @@ new stack_type(get_turf(loc)) 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) - if(attack_type == UNARMED_ATTACK && prob(hit_reaction_chance)) - owner.visible_message("[owner] fends off [attack_text] with [src]!") - return 1 - return 0 +/obj/item/chair/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!(attack_type & ATTACK_TYPE_UNARMED)) + return NONE + return ..() /obj/item/chair/afterattack(atom/target, mob/living/carbon/user, proximity) . = ..() if(!proximity) return if(prob(break_chance)) - user.visible_message("[user] smashes \the [src] to pieces against \the [target]") + user.visible_message("[user] smashes [src] to pieces against [target]") if(iscarbon(target)) var/mob/living/carbon/C = target if(C.health < C.maxHealth*0.5) diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm index 39cdf8a5fa..a9f97942a1 100644 --- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm +++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm @@ -92,10 +92,8 @@ M.cut_overlays() M.regenerate_icons() -/obj/item/clothing/suit/armor/abductor/vest/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - DeactivateStealth() - -/obj/item/clothing/suit/armor/abductor/vest/IsReflect() +/obj/item/clothing/suit/armor/abductor/vest/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + . = ..() DeactivateStealth() /obj/item/clothing/suit/armor/abductor/vest/ui_action_click() @@ -493,7 +491,7 @@ user.do_attack_animation(L) - if(L.check_shields(src, 0, "[user]'s [name]", MELEE_ATTACK)) + if(L.run_block(src, 0, "[user]'s [src]", ATTACK_TYPE_MELEE, 0, user, check_zone(user.zone_selected)) & BLOCK_SUCCESS) playsound(L, 'sound/weapons/genhit.ogg', 50, TRUE) return FALSE diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index 49c34f4d1f..8514df4bd6 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -441,23 +441,23 @@ var/remaining_uses //Set by the changeling ability. -/obj/item/shield/changeling/Initialize() +/obj/item/shield/changeling/Initialize(mapload) . = ..() ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT) if(ismob(loc)) loc.visible_message("The end of [loc.name]\'s hand inflates rapidly, forming a huge shield-like mass!", "We inflate our hand into a strong shield.", "You hear organic matter ripping and tearing!") -/obj/item/shield/changeling/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(remaining_uses < 1) +/obj/item/shield/changeling/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses + return ..() + +/obj/item/shield/changeling/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + . = ..() + if(--remaining_uses < 1) if(ishuman(loc)) var/mob/living/carbon/human/H = loc H.visible_message("With a sickening crunch, [H] reforms [H.p_their()] shield into an arm!", "We assimilate our shield into our body", "[owner] deflects [attack_text] with [src]!") playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, 1) - return TRUE + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT else playsound(src, 'sound/weapons/parry.ogg', 75, 1) owner.visible_message("[owner] parries [attack_text] with [src]!") - return TRUE - return FALSE + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return BLOCK_NONE /obj/item/twohanded/required/cult_bastard/afterattack(atom/target, mob/user, proximity, click_parameters) . = ..() @@ -423,7 +419,13 @@ user.adjustBruteLoss(25) user.dropItemToGround(src, TRUE) -/obj/item/clothing/suit/hooded/cultrobes/cult_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) +/obj/item/clothing/suit/hooded/cultrobes/cult_shield/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(current_charges) + block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100 + block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + current_charges + return ..() + +/obj/item/clothing/suit/hooded/cultrobes/cult_shield/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(current_charges) owner.visible_message("\The [attack_text] is deflected in a burst of blood-red sparks!") current_charges-- @@ -431,8 +433,8 @@ if(!current_charges) owner.visible_message("The runed shield around [owner] suddenly disappears!") owner.update_inv_wear_suit() - return 1 - return 0 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return BLOCK_NONE /obj/item/clothing/suit/hooded/cultrobes/cult_shield/worn_overlays(isinhands, icon_file, style_flags = NONE) . = list() @@ -734,19 +736,19 @@ playsound(T, 'sound/effects/glassbr3.ogg', 100) qdel(src) -/obj/item/twohanded/cult_spear/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) +/obj/item/twohanded/cult_spear/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(wielded) final_block_chance *= 2 if(prob(final_block_chance)) - if(attack_type == PROJECTILE_ATTACK) + if(attack_type & ATTACK_TYPE_PROJECTILE) owner.visible_message("[owner] deflects [attack_text] with [src]!") playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, 1) - return TRUE + return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_REDIRECTED | BLOCK_PHYSICAL_EXTERNAL else playsound(src, 'sound/weapons/parry.ogg', 100, 1) owner.visible_message("[owner] parries [attack_text] with [src]!") - return TRUE - return FALSE + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return BLOCK_NONE /datum/action/innate/cult/spear name = "Bloody Bond" @@ -945,10 +947,18 @@ hitsound = 'sound/weapons/smash.ogg' var/illusions = 2 -/obj/item/shield/mirror/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) +/obj/item/shield/mirror/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + block_return[BLOCK_RETURN_REFLECT_PROJECTILE_CHANCE] = max(block_return[BLOCK_RETURN_REFLECT_PROJECTILE_CHANCE] || null, final_block_chance) + return ..() + +/obj/item/shield/mirror/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(iscultist(owner)) - if(istype(hitby, /obj/item/projectile)) - var/obj/item/projectile/P = hitby + if(istype(object, /obj/item/projectile) && (attack_type == ATTACK_TYPE_PROJECTILE)) + if(is_energy_reflectable_projectile(object)) + if(prob(final_block_chance)) + return BLOCK_SUCCESS | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED + return BLOCK_NONE //To avoid reflection chance double-dipping with block chance + var/obj/item/projectile/P = object if(P.damage >= 30) var/turf/T = get_turf(owner) T.visible_message("The sheer force from [P] shatters the mirror shield!") @@ -956,11 +966,9 @@ playsound(T, 'sound/effects/glassbr3.ogg', 100) owner.DefaultCombatKnockdown(25) qdel(src) - return FALSE - if(P.is_reflectable) - return FALSE //To avoid reflection chance double-dipping with block chance + return BLOCK_NONE . = ..() - if(.) + if(. & BLOCK_SUCCESS) playsound(src, 'sound/weapons/parry.ogg', 100, 1) if(illusions > 0) illusions-- @@ -975,7 +983,7 @@ E.Copy_Parent(owner, 70, 10) E.GiveTarget(owner) E.Goto(owner, owner.movement_delay(), E.minimum_distance) - return TRUE + return else if(prob(50)) var/mob/living/simple_animal/hostile/illusion/H = new(owner.loc) @@ -984,7 +992,7 @@ H.GiveTarget(owner) H.move_to_delay = owner.movement_delay() to_chat(owner, "[src] betrays you!") - return FALSE + return BLOCK_NONE /obj/item/shield/mirror/proc/readd() illusions++ @@ -992,11 +1000,6 @@ var/mob/living/holder = loc to_chat(holder, "The shield's illusions are back at full strength!") -/obj/item/shield/mirror/IsReflect() - if(prob(block_chance)) - return TRUE - return FALSE - /obj/item/shield/mirror/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) var/turf/T = get_turf(hit_atom) var/datum/thrownthing/D = throwingdatum diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 13ea317b9b..0fbc5cf71c 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -312,7 +312,7 @@ if(holder) holder.update_icon() -/obj/item/assembly/flash/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) +/obj/item/assembly/flash/shield/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) activate() return ..() diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 7e2d73190a..780eda532b 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -3,6 +3,7 @@ resistance_flags = FLAMMABLE max_integrity = 200 integrity_failure = 0.4 + block_priority = BLOCK_PRIORITY_CLOTHING var/damaged_clothes = 0 //similar to machine's BROKEN stat and structure's broken var var/flash_protect = 0 //What level of bright light protection item has. 1 = Flashers, Flashes, & Flashbangs | 2 = Welding | -1 = OH GOD WELDING BURNT OUT MY RETINAS var/tint = 0 //Sets the item's level of visual impairment tint, normally set to the same as flash_protect diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 1f23569a9c..26767395ed 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -762,7 +762,13 @@ if(!allowed) allowed = GLOB.advanced_hardsuit_allowed -/obj/item/clothing/suit/space/hardsuit/shielded/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) +/obj/item/clothing/suit/space/hardsuit/shielded/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(current_charges > 0) + block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100 + block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + current_charges + return ..() + +/obj/item/clothing/suit/space/hardsuit/shielded/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) recharge_cooldown = world.time + recharge_delay if(current_charges > 0) var/datum/effect_system/spark_spread/s = new @@ -776,8 +782,8 @@ owner.visible_message("[owner]'s shield overloads!") shield_state = "broken" owner.update_inv_wear_suit() - return 1 - return 0 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return BLOCK_NONE /obj/item/clothing/suit/space/hardsuit/shielded/Destroy() STOP_PROCESSING(SSobj, src) diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index 8c39427c49..b01fe5eb8e 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -456,7 +456,8 @@ Contains: armor = list("melee" = 5, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 10, "fire" = 0, "acid" = 0) strip_delay = 65 -/obj/item/clothing/suit/space/fragile/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) +/obj/item/clothing/suit/space/fragile/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + . = ..() if(!torn && prob(50) && damage >= 5) to_chat(owner, "[src] tears from the damage, breaking the air-tight seal!") clothing_flags &= ~STOPSPRESSUREDAMAGE diff --git a/code/modules/clothing/suits/_suits.dm b/code/modules/clothing/suits/_suits.dm index 1b2080feb1..e01ec2dde8 100644 --- a/code/modules/clothing/suits/_suits.dm +++ b/code/modules/clothing/suits/_suits.dm @@ -1,6 +1,7 @@ /obj/item/clothing/suit icon = 'icons/obj/clothing/suits.dmi' name = "suit" + block_priority = BLOCK_PRIORITY_WEAR_SUIT var/fire_resist = T0C+100 allowed = list(/obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 7384fbde21..a19dfaa889 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -173,12 +173,13 @@ armor = list("melee" = 10, "bullet" = 10, "laser" = 60, "energy" = 50, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF var/hit_reflect_chance = 40 + var/list/protected_zones = list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN) -/obj/item/clothing/suit/armor/laserproof/IsReflect(def_zone) - if(!(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_PRECISE_GROIN))) //If not shot where ablative is covering you, you don't get the reflection bonus! - return 0 - if (prob(hit_reflect_chance)) - return 1 +/obj/item/clothing/suit/armor/laserproof/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(def_zone in protected_zones) + if(prob(hit_reflect_chance)) + return BLOCK_SHOULD_REDIRECT | BLOCK_REDIRECTED | BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL + return ..() /obj/item/clothing/suit/armor/vest/det_suit name = "detective's armor vest" diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index 87e7098ebd..c8f54f8bda 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -36,8 +36,8 @@ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) actions_types = list(/datum/action/item_action/toggle) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF - hit_reaction_chance = 50 // Only on the chest yet blocks all attacks? rad_flags = RAD_PROTECT_CONTENTS | RAD_NO_CONTAMINATE + var/hit_reaction_chance = 50 /obj/item/clothing/suit/armor/reactive/attack_self(mob/user) active = !(active) @@ -62,6 +62,14 @@ item_state = "reactiveoff" reactivearmor_cooldown = world.time + 200 +/obj/item/clothing/suit/armor/reactive/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!active) + return BLOCK_NONE + return block_action(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + +/obj/item/clothing/suit/armor/reactive/proc/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + return BLOCK_NONE + //When the wearer gets hit, this armor will teleport the user a short distance away (to safety or to more danger, no one knows. That's the fun of it!) /obj/item/clothing/suit/armor/reactive/teleport name = "reactive teleport armor" @@ -71,18 +79,15 @@ var/rad_amount_before = 120 reactivearmor_cooldown_duration = 100 -/obj/item/clothing/suit/armor/reactive/teleport/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - . = FALSE - if(!active) - return +/obj/item/clothing/suit/armor/reactive/teleport/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(prob(hit_reaction_chance)) var/mob/living/carbon/human/H = owner if(world.time < reactivearmor_cooldown) owner.visible_message("The reactive teleport system is still recharging! It fails to teleport [H]!") return owner.visible_message("The reactive teleport system flings [H] clear of [attack_text], shutting itself off in the process!") - playsound(get_turf(owner),'sound/magic/blink.ogg', 100, 1) - var/list/turfs = new/list() + playsound(get_turf(owner), 'sound/magic/blink.ogg', 100, 1) + var/list/turfs = new var/turf/old = get_turf(src) for(var/turf/T in orange(tele_range, H)) if(T.density) @@ -101,7 +106,9 @@ radiation_pulse(old, rad_amount_before) radiation_pulse(src, rad_amount) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration - return TRUE + block_return[BLOCK_RETURN_REDIRECT_METHOD] = REDIRECT_METHOD_PASSTHROUGH + return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT | BLOCK_TARGET_DODGED + return BLOCK_NONE //Fire @@ -109,9 +116,7 @@ name = "reactive incendiary armor" desc = "An experimental suit of armor with a reactive sensor array rigged to a flame emitter. For the stylish pyromaniac." -/obj/item/clothing/suit/armor/reactive/fire/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 +/obj/item/clothing/suit/armor/reactive/fire/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(prob(hit_reaction_chance)) if(world.time < reactivearmor_cooldown) owner.visible_message("The reactive incendiary armor on [owner] activates, but fails to send out flames as it is still recharging its flame jets!") @@ -124,8 +129,8 @@ C.IgniteMob() owner.fire_stacks = -20 reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration - return 1 - return 0 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL + return BLOCK_NONE //Stealth @@ -134,9 +139,7 @@ reactivearmor_cooldown_duration = 65 desc = "An experimental suit of armor that renders the wearer invisible on detection of imminent harm, and creates a decoy that runs away from the owner. You can't fight what you can't see." -/obj/item/clothing/suit/armor/reactive/stealth/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 +/obj/item/clothing/suit/armor/reactive/stealth/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(prob(hit_reaction_chance)) if(world.time < reactivearmor_cooldown) owner.visible_message("The reactive stealth system on [owner] activates, but is still recharging its holographic emitters!") @@ -150,7 +153,8 @@ spawn(40) owner.alpha = initial(owner.alpha) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration - return 1 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL + return BLOCK_NONE //Tesla @@ -175,9 +179,7 @@ if(slot_flags & slotdefine2slotbit(slot)) //Was equipped to a valid slot for this item? user.flags_1 |= TESLA_IGNORE_1 -/obj/item/clothing/suit/armor/reactive/tesla/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return FALSE +/obj/item/clothing/suit/armor/reactive/tesla/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(prob(hit_reaction_chance)) if(world.time < reactivearmor_cooldown) var/datum/effect_system/spark_spread/sparks = new /datum/effect_system/spark_spread @@ -196,7 +198,8 @@ M.adjustFireLoss(legacy_dmg) playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration - return TRUE + return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL + return NONE //Repulse @@ -205,9 +208,7 @@ reactivearmor_cooldown_duration = 20 desc = "An experimental suit of armor that violently throws back attackers." -/obj/item/clothing/suit/armor/reactive/repulse/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 +/obj/item/clothing/suit/armor/reactive/repulse/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(prob(hit_reaction_chance)) if(world.time < reactivearmor_cooldown) owner.visible_message("The repulse generator is still recharging!") @@ -234,7 +235,8 @@ AM.throw_at(throwtarget,10,1) safety-- reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration - return 1 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL + return BLOCK_NONE /obj/item/clothing/suit/armor/reactive/table name = "reactive table armor" @@ -242,9 +244,7 @@ desc = "If you can't beat the memes, embrace them." var/tele_range = 10 -/obj/item/clothing/suit/armor/reactive/table/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(!active) - return 0 +/obj/item/clothing/suit/armor/reactive/table/block_action(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(prob(hit_reaction_chance)) var/mob/living/carbon/human/H = owner if(world.time < reactivearmor_cooldown) @@ -270,8 +270,8 @@ H.forceMove(picked) new /obj/structure/table(get_turf(owner)) reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration - return 1 - return 0 + return BLOCK_SUCCESS | BLOCK_PHYSICAL_INTERNAL + return BLOCK_NONE /obj/item/clothing/suit/armor/reactive/table/emp_act() return diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index cc91391393..45aee9085c 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -3,6 +3,7 @@ name = "under" body_parts_covered = CHEST|GROIN|LEGS|ARMS permeability_coefficient = 0.9 + block_priority = BLOCK_PRIORITY_UNIFORM slot_flags = ITEM_SLOT_ICLOTHING armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) var/fitted = FEMALE_UNIFORM_FULL // For use in alternate clothing styles for women diff --git a/code/modules/clothing/under/color.dm b/code/modules/clothing/under/color.dm index 6b40e08af9..f612138984 100644 --- a/code/modules/clothing/under/color.dm +++ b/code/modules/clothing/under/color.dm @@ -79,9 +79,11 @@ name = "ancient jumpsuit" desc = "A terribly ragged and frayed grey jumpsuit. It looks like it hasn't been washed in over a decade." -/obj/item/clothing/under/color/grey/glorf/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - owner.forcesay(GLOB.hit_appends) - return 0 +/obj/item/clothing/under/color/grey/glorf/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + . = ..() + if(ishuman(owner)) + var/mob/living/carbon/human/H = owner + H.forcesay(GLOB.hit_appends) /obj/item/clothing/under/color/blue name = "blue jumpsuit" diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index 884769ebd4..b2e016635f 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -34,10 +34,10 @@ . = ..() item_color = "red" -/obj/item/holo/esword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(active) +/obj/item/holo/esword/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(!active) return ..() - return 0 + return ..() /obj/item/holo/esword/attack(target as mob, mob/user as mob) ..() diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index a95c0e24ed..56e1a04b95 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -835,9 +835,9 @@ force = CLAMP((ghost_counter * 4), 0, 75) user.visible_message("[user] strikes with the force of [ghost_counter] vengeful spirits!") - ..() + return ..() -/obj/item/melee/ghost_sword/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) +/obj/item/melee/ghost_sword/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) var/ghost_counter = ghost_check() final_block_chance += CLAMP((ghost_counter * 5), 0, 75) owner.visible_message("[owner] is protected by a ring of [ghost_counter] ghosts!") diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm index e0647159a5..faea8e9bbc 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/hunter.dm @@ -63,13 +63,13 @@ if(hit_atom) if(isliving(hit_atom)) var/mob/living/L = hit_atom - if(!L.check_shields(src, 0, "the [name]", attack_type = LEAP_ATTACK)) + if(L.run_block(src, 0, "the [name]", ATTACK_TYPE_TACKLE, 0, src) & BLOCK_SUCCESS) + DefaultCombatKnockdown(40, 1, 1) + else L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") L.DefaultCombatKnockdown(100) sleep(2)//Runtime prevention (infinite bump() calls on hulks) step_towards(src,L) - else - DefaultCombatKnockdown(40, 1, 1) toggle_leap(0) else if(hit_atom.density && !hit_atom.CanPass(src)) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 836560ca99..9e35f59090 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -86,13 +86,10 @@ if(!(combat_flags & COMBAT_FLAG_COMBAT_ACTIVE)) totitemdamage *= 1.5 //CIT CHANGES END HERE - if(user != src && check_shields(I, totitemdamage, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + var/impacting_zone = (user == src)? check_zone(user.zone_selected) : ran_zone(user.zone_selected) + if((user != src) && (run_block(I, totitemdamage, "the [I]", ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone) & BLOCK_SUCCESS)) return FALSE - var/obj/item/bodypart/affecting - if(user == src) - affecting = get_bodypart(check_zone(user.zone_selected)) //we're self-mutilating! yay! - else - affecting = get_bodypart(ran_zone(user.zone_selected)) + var/obj/item/bodypart/affecting = get_bodypart(impacting_zone) if(!affecting) //missing limb? we select the first bodypart (you can never have zero, because of chest) affecting = bodyparts[1] SEND_SIGNAL(I, COMSIG_ITEM_ATTACK_ZONE, src, user, affecting) diff --git a/code/modules/mob/living/carbon/human/human_block.dm b/code/modules/mob/living/carbon/human/human_block.dm new file mode 100644 index 0000000000..b1a5153ce5 --- /dev/null +++ b/code/modules/mob/living/carbon/human/human_block.dm @@ -0,0 +1,8 @@ +/mob/living/carbon/human/get_blocking_items() + . = ..() + if(wear_suit) + . += wear_suit + if(w_uniform) + . += w_uniform + if(wear_neck) + . += wear_neck diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index f4227e390e..324420a7dd 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -52,36 +52,12 @@ return martial_art_result return ..() -/mob/living/carbon/human/check_reflect(def_zone) - if(wear_suit?.IsReflect(def_zone)) - return TRUE - return ..() - -/mob/living/carbon/human/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) - . = ..() - if(.) - return - var/block_chance_modifier = round(damage / -3) - if(wear_suit) - var/final_block_chance = wear_suit.block_chance - (CLAMP((armour_penetration-wear_suit.armour_penetration)/2,0,100)) + block_chance_modifier - if(wear_suit.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - if(w_uniform) - var/final_block_chance = w_uniform.block_chance - (CLAMP((armour_penetration-w_uniform.armour_penetration)/2,0,100)) + block_chance_modifier - if(w_uniform.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - if(wear_neck) - var/final_block_chance = wear_neck.block_chance - (CLAMP((armour_penetration-wear_neck.armour_penetration)/2,0,100)) + block_chance_modifier - if(wear_neck.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - return FALSE - /mob/living/carbon/human/can_embed(obj/item/I) if(I.get_sharpness() || is_pointed(I) || is_type_in_typecache(I, GLOB.can_embed_types)) return TRUE return FALSE -/mob/living/carbon/human/proc/check_block() +/mob/living/carbon/human/proc/check_martial_melee_block() if(mind) if(mind.martial_art && prob(mind.martial_art.block_chance) && mind.martial_art.can_use(src) && in_throw_mode && !incapacitated(FALSE, TRUE)) return TRUE diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 34c65982dc..c2f6ac28c6 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1437,7 +1437,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) to_chat(user, "You do not breathe, so you cannot perform CPR.") /datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) - if(target.check_block()) + if(target.check_martial_melee_block()) target.visible_message("[target] blocks [user]'s grab attempt!") return 0 if(attacker_style && attacker_style.grab_act(user,target)) @@ -1453,7 +1453,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(IS_STAMCRIT(user)) //CITADEL CHANGE - makes it impossible to punch while in stamina softcrit to_chat(user, "You're too exhausted.") //CITADEL CHANGE - ditto return FALSE //CITADEL CHANGE - ditto - if(target.check_block()) + if(target.check_martial_melee_block()) target.visible_message("[target] blocks [user]'s attack!") return FALSE if(attacker_style && attacker_style.harm_act(user,target)) @@ -1557,6 +1557,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/aim_for_groin = user.zone_selected == "groin" var/target_aiming_for_groin = target.zone_selected == "groin" + if(target.check_martial_melee_block()) //END EDIT + target.visible_message("[target] blocks [user]'s disarm attempt!") + return FALSE if(IS_STAMCRIT(user)) to_chat(user, "You're too exhausted!") return FALSE @@ -1685,9 +1688,9 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) /datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H) // Allows you to put in item-specific reactions based on species if(user != H) - if(H.check_shields(I, I.force, "the [I.name]", MELEE_ATTACK, I.armour_penetration)) + if(H.run_block(I, I.force, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone) & BLOCK_SUCCESS) return 0 - if(H.check_block()) + if(H.check_martial_melee_block()) H.visible_message("[H] blocks [I]!") return 0 @@ -1803,7 +1806,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) return TRUE if(M.mind) attacker_style = M.mind.martial_art - if((M != H) && M.a_intent != INTENT_HELP && H.check_shields(M, 0, M.name, attack_type = UNARMED_ATTACK)) + if((M != H) && M.a_intent != INTENT_HELP && (H.run_block(M, 0, "[M]", ATTACK_TYPE_UNARMED, 0, M, M.zone_selected) & BLOCK_SUCCESS)) log_combat(M, H, "attempted to touch") H.visible_message("[M] attempted to touch [H]!") return TRUE @@ -1841,7 +1844,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(IS_STAMCRIT(user)) to_chat(user, "You're too exhausted.") return FALSE - if(target.check_block()) + if(target.check_martial_melee_block()) target.visible_message("[target] blocks [user]'s shoving attempt!") return FALSE if(attacker_style && attacker_style.disarm_act(user,target)) diff --git a/code/modules/mob/living/living_block.dm b/code/modules/mob/living/living_block.dm new file mode 100644 index 0000000000..d9e3309e47 --- /dev/null +++ b/code/modules/mob/living/living_block.dm @@ -0,0 +1,98 @@ +// This file has a weird name, but it's for anything related to the checks for shields, blocking, dodging, and similar "stop this attack before it actually impacts the target" as opposed to "defend once it has hit". + +/* +/// Bitflags for check_block() and handle_block(). Meant to be combined. You can be hit and still reflect, for example, if you do not use BLOCK_SUCCESS. +/// Attack was not blocked +#define BLOCK_NONE NONE +/// Attack was blocked, do not do damage. THIS FLAG MUST BE THERE FOR DAMAGE/EFFECT PREVENTION! +#define BLOCK_SUCCESS (1<<1) + +/// The below are for "metadata" on "how" the attack was blocked. + +/// Attack was and should be reflected (NOTE: the SHOULD here is important, as it says "the thing blocking isn't handling the reflecting for you so do it yourself"!) +#define BLOCK_SHOULD_REFLECT (1<<2) +/// Attack was manually redirected (including reflected) by any means by the defender. For when YOU are handling the reflection, rather than the thing hitting you. (see sleeping carp) +#define BLOCK_REDIRECTED (1<<3) +/// Attack was blocked by something like a shield. +#define BLOCK_PHYSICAL_EXTERNAL (1<<4) +/// Attack was blocked by something worn on you. +#define BLOCK_PHYSICAL_INTERNAL (1<<5) +/// Attack should pass through. Like SHOULD_REFLECT but for.. well, passing through harmlessly. +#define BLOCK_SHOULD_PASSTHROUGH (1<<6) +/// Attack outright missed because the target dodged. Should usually be combined with SHOULD_PASSTHROUGH or something (see martial arts) +#define BLOCK_TARGET_DODGED (1<<7) +*/ + +///Check whether or not we can block, without "triggering" a block. Basically run checks without effects like depleting shields. Wrapper for do_run_block(). The arguments on that means the same as for this. +/mob/living/proc/check_block(atom/object, damage, attack_text = "the attack", attack_type, armour_penetration, mob/attacker, def_zone, list/return_list) + return do_run_block(FALSE, object, damage, attack_text, attack_type, armour_penetration, attacker, check_zone(def_zone), return_list) + +/// Runs a block "sequence", effectively checking and then doing effects if necessary. Wrapper for do_run_block(). The arguments on that means the same as for this. +/mob/living/proc/run_block(atom/object, damage, attack_text = "the attack", attack_type, armour_penetration, mob/attacker, def_zone, list/return_list) + return do_run_block(TRUE, object, damage, attack_text, attack_type, armour_penetration, attacker, check_zone(def_zone), return_list) + +/** The actual proc for block checks. DO NOT USE THIS DIRECTLY UNLESS YOU HAVE VERY GOOD REASON TO. To reduce copypaste for differences between handling for real attacks and virtual checks. + * Automatically checks all held items for /obj/item/proc/run_block() with the same parameters. + * @params + * real_attack - If this attack is real. This one is quirky; If it's real, run_block is called. If it's not, check_block is called and none of the regular checks happen, and this is effectively only useful + * for populating return_list with blocking metadata. + * object - Whatever /atom is actually hitting us, in essence. For example, projectile if gun, item if melee, structure/whatever if it's a thrown, etc. + * damage - The nominal damage this would do if it was to hit. Obviously doesn't take into effect explosions/magic/similar things.. unless you implement it to raise the value. + * attack_text - The text that this attack should show, in the context of something like "[src] blocks [attack_text]!" + * attack_type - See __DEFINES/combat.dm - Attack types, to distinguish between, for example, someone throwing an item at us vs bashing us with it. + * armour_penetration - 0-100 value of how effectively armor penetrating the attack should be. + * attacker - Set to the mob attacking IF KNOWN. Do not expect this to always be set! + * def_zone - The zone this'll impact. + * return_list - If something wants to grab things from what items/whatever put into list/block_return on obj/item/run_block and the comsig, pass in a list so you can grab anything put in it after block runs. + */ +/mob/living/proc/do_run_block(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list()) + // Component signal block runs have highest priority.. for now. + . = SEND_SIGNAL(src, COMSIG_LIVING_RUN_BLOCK, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list) + if((. & BLOCK_SUCCESS) && !(. & BLOCK_CONTINUE_CHAIN)) + return + var/list/obj/item/tocheck = get_blocking_items() + sortTim(tocheck, /proc/cmp_item_block_priority_asc) + // i don't like this + var/block_chance_modifier = round(damage / -3) + if(real_attack) + for(var/obj/item/I in tocheck) + // i don't like this too + var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example + var/results = I.run_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list) + . |= results + if((results & BLOCK_SUCCESS) && !(results & BLOCK_CONTINUE_CHAIN)) + break + else + for(var/obj/item/I in tocheck) + // i don't like this too + var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example + I.check_block(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, return_list) + +/// Gets an unsortedlist of objects to run block checks on. +/mob/living/proc/get_blocking_items() + . = list() + for(var/obj/item/I in held_items) + // this is a bad check but i am not removing it until a better catchall is made + if(istype(I, /obj/item/clothing)) + continue + . |= I + +/obj/item + /// The 0% to 100% chance for the default implementation of random block rolls. + var/block_chance = 0 + /// Block priority, higher means we check this higher in the "chain". + var/block_priority = BLOCK_PRIORITY_DEFAULT + +/// Runs block and returns flag for do_run_block to process. +/obj/item/proc/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + SEND_SIGNAL(src, COMSIG_ITEM_RUN_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + if(prob(final_block_chance)) + owner.visible_message("[owner] blocks [attack_text] with [src]!") + return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return BLOCK_NONE + +/// Returns block information using list/block_return. Used for check_block() on mobs. +/obj/item/proc/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + SEND_SIGNAL(src, COMSIG_ITEM_CHECK_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + var/existing = block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] + block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = max(existing || 0, final_block_chance) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 9980d2b830..7d220739d5 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -36,48 +36,44 @@ /mob/living/proc/on_hit(obj/item/projectile/P) return BULLET_ACT_HIT -/mob/living/proc/check_shields(atom/AM, damage, attack_text = "the attack", attack_type = MELEE_ATTACK, armour_penetration = 0) - var/block_chance_modifier = round(damage / -3) - for(var/obj/item/I in held_items) - if(!istype(I, /obj/item/clothing)) - var/final_block_chance = I.block_chance - (CLAMP((armour_penetration-I.armour_penetration)/2,0,100)) + block_chance_modifier //So armour piercing blades can still be parried by other blades, for example - if(I.hit_reaction(src, AM, attack_text, final_block_chance, damage, attack_type)) - return TRUE - return FALSE - -/mob/living/proc/check_reflect(def_zone) //Reflection checks for anything in your hands, based on the reflection chance of the object(s) - for(var/obj/item/I in held_items) - if(I.IsReflect(def_zone)) - return TRUE - return FALSE - -/mob/living/proc/reflect_bullet_check(obj/item/projectile/P, def_zone) - if(P.is_reflectable && check_reflect(def_zone)) // Checks if you've passed a reflection% check - visible_message("The [P.name] gets reflected by [src]!", \ - "The [P.name] gets reflected by [src]!") - // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(src) - // redirect the projectile - P.original = locate(new_x, new_y, P.z) - P.starting = curloc - P.firer = src - P.yo = new_y - curloc.y - P.xo = new_x - curloc.x - var/new_angle_s = P.Angle + rand(120,240) - while(new_angle_s > 180) // Translate to regular projectile degrees - new_angle_s -= 360 - P.setAngle(new_angle_s) - return TRUE - return FALSE +/mob/living/proc/handle_projectile_attack_redirection(obj/item/projectile/P, redirection_mode, silent = FALSE) + P.ignore_source_check = TRUE + switch(redirection_mode) + if(REDIRECT_METHOD_DEFLECT) + P.setAngle(SIMPLIFY_DEGREES(P.Angle + rand(120, 240))) + if(!silent) + visible_message("[P] gets deflected by [src]!", \ + "[P] gets deflected by [src]!") + if(REDIRECT_METHOD_REFLECT) + P.setAngle(SIMPLIFY_DEGREES(P.Angle + 180)) + if(!silent) + visible_message("[P] gets reflected by [src]!", \ + "[P] gets reflected by [src]!") + if(REDIRECT_METHOD_PASSTHROUGH) + if(!silent) + visible_message("[P] passes through [src]!", \ + "[P] passes through [src]!") + return + if(REDIRECT_METHOD_RETURN_TO_SENDER) + if(!silent) + visible_message("[src] deflects [P] back at their attacker!", \ + "[src] deflects [P] back at their attacker!") + if(P.firer) + P.setAngle(Get_Angle(src, P.firer)) + else + P.setAngle(SIMPLIFY_DEGREES(P.Angle + 180)) + else + CRASH("Invalid rediretion mode [redirection_mode]") /mob/living/bullet_act(obj/item/projectile/P, def_zone) if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself - if(reflect_bullet_check(P, def_zone)) - return BULLET_ACT_FORCE_PIERCE // complete projectile permutation - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) + var/list/returnlist = list() + var/returned = run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist) + if(returned & BLOCK_SHOULD_REDIRECT) + handle_projectile_attack_redirection(P, returnlist[BLOCK_RETURN_REDIRECT_METHOD]) + if(returned & BLOCK_REDIRECTED) + return BULLET_ACT_FORCE_PIERCE + if(returned & BLOCK_SUCCESS) P.on_hit(src, 100, def_zone) return BULLET_ACT_BLOCK var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null) @@ -108,12 +104,14 @@ return FALSE /mob/living/hitby(atom/movable/AM, skipcatch, hitpush = TRUE, blocked = FALSE, datum/thrownthing/throwingdatum) + // Throwingdatum can be null if someone had an accident() while slipping with an item in hand. var/obj/item/I var/throwpower = 30 if(isitem(AM)) I = AM throwpower = I.throwforce - if(check_shields(AM, throwpower, "\the [AM.name]", THROWN_PROJECTILE_ATTACK)) + var/impacting_zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest + if(run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone) & BLOCK_SUCCESS) hitpush = FALSE skipcatch = TRUE blocked = TRUE @@ -124,10 +122,9 @@ if(I) if(!skipcatch && isturf(I.loc) && catch_item(I)) return TRUE - var/zone = ran_zone(BODY_ZONE_CHEST, 65)//Hits a random part of the body, geared towards the chest var/dtype = BRUTE var/volume = I.get_volume_by_throwforce_and_or_w_class() - SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, zone) + SEND_SIGNAL(I, COMSIG_MOVABLE_IMPACT_ZONE, src, impacting_zone) dtype = I.damtype if (I.throwforce > 0) //If the weapon's throwforce is greater than zero... @@ -145,8 +142,8 @@ if(!blocked) visible_message("[src] has been hit by [I].", \ "[src] has been hit by [I].") - var/armor = run_armor_check(zone, "melee", "Your armor has protected your [parse_zone(zone)].", "Your armor has softened hit to your [parse_zone(zone)].",I.armour_penetration) - apply_damage(I.throwforce, dtype, zone, armor) + var/armor = run_armor_check(impacting_zone, "melee", "Your armor has protected your [parse_zone(impacting_zone)].", "Your armor has softened hit to your [parse_zone(impacting_zone)].",I.armour_penetration) + apply_damage(I.throwforce, dtype, impacting_zone, armor) if(I.thrownby) log_combat(I.thrownby, src, "threw and hit", I) else @@ -272,7 +269,7 @@ /mob/living/attack_hand(mob/user) ..() //Ignoring parent return value here. SEND_SIGNAL(src, COMSIG_MOB_ATTACK_HAND, user) - if((user != src) && user.a_intent != INTENT_HELP && check_shields(user, 0, user.name, attack_type = UNARMED_ATTACK)) + if((user != src) && user.a_intent != INTENT_HELP && (run_block(user, 0, user.name, ATTACK_TYPE_UNARMED | ATTACK_TYPE_MELEE, null, user, check_zone(user.zone_selected)) & BLOCK_SUCCESS)) log_combat(user, src, "attempted to touch") visible_message("[user] attempted to touch [src]!") return TRUE @@ -283,7 +280,7 @@ to_chat(user, "You don't want to hurt [src]!") return TRUE var/hulk_verb = pick("smash","pummel") - if(user != src && check_shields(user, 15, "the [hulk_verb]ing")) + if(user != src && (run_block(user, 15, "the [hulk_verb]ing", ATTACK_TYPE_MELEE, null, user, check_zone(user.zone_selected)) & BLOCK_SUCCESS)) return TRUE ..() return FALSE @@ -298,14 +295,14 @@ M.Feedstop() return // can't attack while eating! - if(HAS_TRAIT(src, TRAIT_PACIFISM)) + if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") return FALSE var/damage = rand(5, 35) if(M.is_adult) damage = rand(20, 40) - if(check_shields(M, damage, "the [M.name]")) + if(run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS) return FALSE if (stat != DEAD) @@ -324,7 +321,7 @@ if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") return FALSE - if(check_shields(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", MELEE_ATTACK, M.armour_penetration)) + if(run_block(M, rand(M.melee_damage_lower, M.melee_damage_upper), "the [M.name]", ATTACK_TYPE_MELEE, M.armour_penetration, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS) return FALSE if(M.attack_sound) playsound(loc, M.attack_sound, 50, 1, 1) @@ -334,17 +331,15 @@ log_combat(M, src, "attacked") return TRUE - /mob/living/attack_paw(mob/living/carbon/monkey/M) if (M.a_intent == INTENT_HARM) if(HAS_TRAIT(M, TRAIT_PACIFISM)) to_chat(M, "You don't want to hurt anyone!") return FALSE - if(M.is_muzzled() || (M.wear_mask && M.wear_mask.flags_cover & MASKCOVERSMOUTH)) to_chat(M, "You can't bite with your mouth covered!") return FALSE - if(check_shields(M, 0, "the [M.name]")) + if(run_block(M, 0, "the [M.name]", ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS) return FALSE M.do_attack_animation(src, ATTACK_EFFECT_BITE) if (prob(75)) @@ -368,7 +363,7 @@ if(HAS_TRAIT(L, TRAIT_PACIFISM)) to_chat(L, "You don't want to hurt anyone!") return FALSE - if(L != src && check_shields(L, rand(1, 3), "the [L.name]")) + if(L != src && (run_block(L, rand(1, 3), "the [L.name]", ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED, L, check_zone(L.zone_selected)) & BLOCK_SUCCESS)) return FALSE L.do_attack_animation(src) if(prob(90)) @@ -382,7 +377,7 @@ "[L.name] has attempted to bite [src]!", null, COMBAT_MESSAGE_RANGE) /mob/living/attack_alien(mob/living/carbon/alien/humanoid/M) - if((M != src) && M.a_intent != INTENT_HELP && check_shields(M, 0, "the [M.name]")) + if((M != src) && M.a_intent != INTENT_HELP && (run_block(M, 0, "the [M.name]", ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS)) visible_message("[M] attempted to touch [src]!") return FALSE switch(M.a_intent) diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index a93bc8662e..9b3db8af62 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -112,11 +112,15 @@ /mob/living/silicon/bullet_act(obj/item/projectile/P, def_zone) if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself - if(reflect_bullet_check(P, def_zone)) - return -1 // complete projectile permutation - if(check_shields(P, P.damage, "the [P.name]", PROJECTILE_ATTACK, P.armour_penetration)) + var/list/returnlist = list() + var/returned = run_block(P, P.damage, "the [P.name]", ATTACK_TYPE_PROJECTILE, P.armour_penetration, P.firer, def_zone, returnlist) + if(returned & BLOCK_SHOULD_REDIRECT) + handle_projectile_attack_redirection(P, returnlist[BLOCK_RETURN_REDIRECT_METHOD]) + if(returned & BLOCK_REDIRECTED) + return BULLET_ACT_FORCE_PIERCE + if(returned & BLOCK_SUCCESS) P.on_hit(src, 100, def_zone) - return 2 + return BULLET_ACT_BLOCK if((P.damage_type == BRUTE || P.damage_type == BURN)) adjustBruteLoss(P.damage) if(prob(P.damage*1.5)) diff --git a/code/modules/mob/living/simple_animal/guardian/types/charger.dm b/code/modules/mob/living/simple_animal/guardian/types/charger.dm index 237ab9d919..1ece6dde37 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/charger.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/charger.dm @@ -54,7 +54,7 @@ var/blocked = FALSE if(hasmatchingsummoner(hit_atom)) //if the summoner matches don't hurt them blocked = TRUE - if(L.check_shields(src, 90, "[name]", attack_type = THROWN_PROJECTILE_ATTACK)) + if(L.run_block(src, 90, "[name]", ATTACK_TYPE_TACKLE, 0, src) & BLOCK_SUCCESS) blocked = TRUE if(!blocked) L.drop_all_held_items() diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm index 95fafbabfd..884e3bb989 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/herald.dm @@ -40,27 +40,27 @@ deathsound = 'sound/magic/demon_dies.ogg' deathmessage = "begins to shudder as it becomes transparent..." loot_drop = /obj/item/clothing/neck/cloak/herald_cloak - + can_talk = 1 attack_action_types = list(/datum/action/innate/elite_attack/herald_trishot, /datum/action/innate/elite_attack/herald_directionalshot, /datum/action/innate/elite_attack/herald_teleshot, /datum/action/innate/elite_attack/herald_mirror) - + var/mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/my_mirror = null var/is_mirror = FALSE - + /mob/living/simple_animal/hostile/asteroid/elite/herald/death() . = ..() if(!is_mirror) addtimer(CALLBACK(src, .proc/become_ghost), 8) if(my_mirror != null) qdel(my_mirror) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/become_ghost() icon_state = "herald_ghost" - + /mob/living/simple_animal/hostile/asteroid/elite/herald/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) . = ..() if(.) @@ -71,25 +71,25 @@ button_icon_state = "herald_trishot" chosen_message = "You are now firing three shots in your chosen direction." chosen_attack_num = HERALD_TRISHOT - + /datum/action/innate/elite_attack/herald_directionalshot name = "Circular Shot" button_icon_state = "herald_directionalshot" chosen_message = "You are firing projectiles in all directions." chosen_attack_num = HERALD_DIRECTIONALSHOT - + /datum/action/innate/elite_attack/herald_teleshot name = "Teleport Shot" button_icon_state = "herald_teleshot" chosen_message = "You will now fire a shot which teleports you where it lands." chosen_attack_num = HERALD_TELESHOT - + /datum/action/innate/elite_attack/herald_mirror name = "Summon Mirror" button_icon_state = "herald_mirror" chosen_message = "You will spawn a mirror which duplicates your attacks." chosen_attack_num = HERALD_MIRROR - + /mob/living/simple_animal/hostile/asteroid/elite/herald/OpenFire() if(client) switch(chosen_attack) @@ -124,7 +124,7 @@ my_mirror.herald_teleshot(target) if(HERALD_MIRROR) herald_mirror() - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/shoot_projectile(turf/marker, set_angle, var/is_teleshot) var/turf/startloc = get_turf(src) var/obj/item/projectile/herald/H = null @@ -137,7 +137,7 @@ if(target) H.original = target H.fire(set_angle) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_trishot(target) ranged_cooldown = world.time + 30 playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) @@ -151,17 +151,17 @@ addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 10) addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 12) addtimer(CALLBACK(src, .proc/shoot_projectile, target_turf, angle_to_target, FALSE), 14) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_circleshot() var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315) for(var/i in directional_shot_angles) shoot_projectile(get_turf(src), i, FALSE) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/unenrage() if(stat == DEAD || is_mirror) return icon_state = "herald" - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_directionalshot() ranged_cooldown = world.time + 50 if(!is_mirror) @@ -172,14 +172,14 @@ playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) addtimer(CALLBACK(src, .proc/herald_circleshot), 15) addtimer(CALLBACK(src, .proc/unenrage), 20) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_teleshot(target) ranged_cooldown = world.time + 30 playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) var/target_turf = get_turf(target) var/angle_to_target = Get_Angle(src, target_turf) shoot_projectile(target_turf, angle_to_target, TRUE) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/proc/herald_mirror() ranged_cooldown = world.time + 40 playsound(get_turf(src), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) @@ -190,7 +190,7 @@ my_mirror = new_mirror my_mirror.my_master = src my_mirror.faction = faction.Copy() - + /mob/living/simple_animal/hostile/asteroid/elite/herald/mirror name = "herald's mirror" desc = "This fiendish work of magic copies the herald's attacks. Seems logical to smash it." @@ -203,16 +203,16 @@ del_on_death = TRUE is_mirror = TRUE var/mob/living/simple_animal/hostile/asteroid/elite/herald/my_master = null - + /mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Initialize() ..() toggle_ai(AI_OFF) - + /mob/living/simple_animal/hostile/asteroid/elite/herald/mirror/Destroy() if(my_master != null) my_master.my_mirror = null . = ..() - + /obj/item/projectile/herald name ="death bolt" icon_state= "chronobolt" @@ -222,7 +222,7 @@ eyeblur = 0 damage_type = BRUTE pass_flags = PASSTABLE - + /obj/item/projectile/herald/teleshot name ="golden bolt" damage = 0 @@ -239,11 +239,11 @@ var/mob/living/F = firer if(F != null && istype(F, /mob/living/simple_animal/hostile/asteroid/elite) && F.faction_check_mob(L)) L.heal_overall_damage(damage) - + /obj/item/projectile/herald/teleshot/on_hit(atom/target, blocked = FALSE) . = ..() firer.forceMove(get_turf(src)) - + //Herald's loot: Cloak of the Prophet /obj/item/clothing/neck/cloak/herald_cloak @@ -252,13 +252,13 @@ icon = 'icons/obj/lavaland/elite_trophies.dmi' icon_state = "herald_cloak" body_parts_covered = CHEST|GROIN|ARMS - hit_reaction_chance = 10 - + var/hit_reaction_chance = 10 + /obj/item/clothing/neck/cloak/herald_cloak/proc/reactionshot(mob/living/carbon/owner) var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315) for(var/i in directional_shot_angles) shoot_projectile(get_turf(owner), i, owner) - + /obj/item/clothing/neck/cloak/herald_cloak/proc/shoot_projectile(turf/marker, set_angle, mob/living/carbon/owner) var/turf/startloc = get_turf(owner) var/obj/item/projectile/herald/H = null @@ -266,12 +266,11 @@ H.preparePixelProjectile(marker, startloc) H.firer = owner H.fire(set_angle) - -/obj/item/clothing/neck/cloak/herald_cloak/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) + +/obj/item/clothing/neck/cloak/herald_cloak/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) . = ..() if(rand(1,100) > hit_reaction_chance) return owner.visible_message("[owner]'s [src] emits a loud noise as [owner] is struck!") - var/static/list/directional_shot_angles = list(0, 45, 90, 135, 180, 225, 270, 315) playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 20, TRUE) addtimer(CALLBACK(src, .proc/reactionshot, owner), 10) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm index d83a38ae24..c9011c5f5f 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm @@ -12,7 +12,7 @@ * - Charges at the target after a telegraph, throwing them across the arena should it connect. * - Legionnaire's head detaches, attacking as it's own entity. Has abilities of it's own later into the fight. Once dead, regenerates after a brief period. If the skill is used while the head is off, it will be killed. * - Leaves a pile of bones at your location. Upon using this skill again, you'll swap locations with the bone pile. - * - Spews a cloud of smoke from it's maw, wherever said maw is. + * - Spews a cloud of smoke from it's maw, wherever said maw is. * A unique fight incorporating the head mechanic of legion into a whole new beast. Combatants will need to make sure the tag-team of head and body don't lure them into a deadly trap. */ @@ -45,35 +45,35 @@ /datum/action/innate/elite_attack/head_detach, /datum/action/innate/elite_attack/bonfire_teleport, /datum/action/innate/elite_attack/spew_smoke) - + var/mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/myhead = null var/obj/structure/legionnaire_bonfire/mypile = null var/has_head = TRUE - + /datum/action/innate/elite_attack/legionnaire_charge name = "Legionnaire Charge" button_icon_state = "legionnaire_charge" chosen_message = "You will attempt to grab your opponent and throw them." chosen_attack_num = LEGIONNAIRE_CHARGE - + /datum/action/innate/elite_attack/head_detach name = "Release Head" button_icon_state = "head_detach" chosen_message = "You will now detach your head or kill it if it is already released." chosen_attack_num = HEAD_DETACH - + /datum/action/innate/elite_attack/bonfire_teleport name = "Bonfire Teleport" button_icon_state = "bonfire_teleport" chosen_message = "You will leave a bonfire. Second use will let you swap positions with it indefintiely. Using this move on the same tile as your active bonfire removes it." chosen_attack_num = BONFIRE_TELEPORT - + /datum/action/innate/elite_attack/spew_smoke name = "Spew Smoke" button_icon_state = "spew_smoke" chosen_message = "Your head will spew smoke in an area, wherever it may be." chosen_attack_num = SPEW_SMOKE - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/OpenFire() if(client) switch(chosen_attack) @@ -96,7 +96,7 @@ bonfire_teleport() if(SPEW_SMOKE) spew_smoke() - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge(target) ranged_cooldown = world.time + 50 var/dir_to_target = get_dir(get_turf(src), get_turf(target)) @@ -107,7 +107,7 @@ playsound(src,'sound/magic/demon_attack1.ogg', 200, 1) visible_message("[src] prepares to charge!") addtimer(CALLBACK(src, .proc/legionnaire_charge_2, dir_to_target, 0), 5) - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/legionnaire_charge_2(var/move_dir, var/times_ran) if(times_ran >= 4) return @@ -136,7 +136,7 @@ L.Stun(20) //substituting this for the Paralyze from the line above, because we don't have tg paralysis stuff L.adjustBruteLoss(50) addtimer(CALLBACK(src, .proc/legionnaire_charge_2, move_dir, (times_ran + 1)), 2) - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/head_detach(target) ranged_cooldown = world.time + 10 if(myhead != null) @@ -160,11 +160,11 @@ else if(health < maxHealth * 0.5) myhead.melee_damage_lower = 20 myhead.melee_damage_upper = 20 - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/onHeadDeath() myhead = null addtimer(CALLBACK(src, .proc/regain_head), 50) - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/regain_head() has_head = TRUE if(stat == DEAD) @@ -196,7 +196,7 @@ forceMove(pileturf) visible_message("[src] forms from the bonfire!") mypile.forceMove(legionturf) - + /mob/living/simple_animal/hostile/asteroid/elite/legionnaire/proc/spew_smoke() ranged_cooldown = world.time + 60 var/turf/T = null @@ -213,7 +213,7 @@ var/datum/effect_system/smoke_spread/smoke = new smoke.set_up(2, T) smoke.start() - + //The legionnaire's head. Basically the same as any legion head, but we have to tell our creator when we die so they can generate another head. /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead name = "legionnaire head" @@ -239,12 +239,12 @@ faction = list() ranged = FALSE var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/body = null - + /mob/living/simple_animal/hostile/asteroid/elite/legionnairehead/death() . = ..() if(body) body.onHeadDeath() - + //The legionnaire's bonfire, which can be swapped positions with. Also sets flammable living beings on fire when they walk over it. /obj/structure/legionnaire_bonfire name = "bone pile" @@ -258,20 +258,20 @@ light_range = 4 light_color = LIGHT_COLOR_RED var/mob/living/simple_animal/hostile/asteroid/elite/legionnaire/myowner = null - - + + /obj/structure/legionnaire_bonfire/Entered(atom/movable/mover, turf/target) if(isliving(mover)) var/mob/living/L = mover L.adjust_fire_stacks(3) L.IgniteMob() . = ..() - + /obj/structure/legionnaire_bonfire/Destroy() if(myowner != null) myowner.mypile = null . = ..() - + //The visual effect which appears in front of legionnaire when he goes to charge. /obj/effect/temp_visual/dragon_swoop/legionnaire duration = 10 @@ -280,7 +280,7 @@ /obj/effect/temp_visual/dragon_swoop/legionnaire/Initialize() . = ..() transform *= 0.33 - + // Legionnaire's loot: Legionnaire Spine /obj/item/crusher_trophy/legionnaire_spine diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index 14b2ff0c1a..c993c0013a 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -90,9 +90,10 @@ . = ..() AddComponent(/datum/component/butchering, 15, 125, 0, hitsound) -/obj/item/gun/magic/staff/spellblade/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) - if(attack_type == PROJECTILE_ATTACK) - final_block_chance = 0 +/obj/item/gun/magic/staff/spellblade/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + // Do not block projectiles. + if(attack_type & ATTACK_TYPE_PROJECTILE) + return BLOCK_NONE return ..() /obj/item/gun/magic/staff/locker diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 9b60f96d16..6094a7aa46 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -20,7 +20,7 @@ var/atom/movable/firer = null//Who shot it var/atom/fired_from = null // the atom that the projectile was fired from (gun, turret) var/suppressed = FALSE //Attack message var/suppressed = FALSE //Attack message - var/candink = FALSE //Can this projectile play the dink sound when hitting the head? var/yo = null + var/candink = FALSE //Can this projectile play the dink sound when hitting the head? var/yo = null var/xo = null var/atom/original = null // the original target clicked @@ -695,3 +695,9 @@ /obj/item/projectile/experience_pressure_difference() return + +/////// MISC HELPERS //////// +/// Is this atom reflectable with ""standardized"" reflection methods like you know eshields and deswords and similar +/proc/is_energy_reflectable_projectile(atom/A) + var/obj/item/projectile/P = A + return istype(P) && P.is_reflectable diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm index b8bdffbabf..795a57b82c 100644 --- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm +++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm @@ -137,8 +137,7 @@ Slimecrossing Armor var/hit_reflect_chance = 10 // Citadel Change: because 40% chance of bouncing lasers back into peoples faces isn't good. armor = list("melee" = 70, "bullet" = 70, "laser" = 40, "energy" = 40, "bomb" = 80, "bio" = 80, "rad" = 80, "fire" = 70, "acid" = 90) //Citadel Change to avoid immortal Xenobiologists. -/obj/item/clothing/suit/armor/heavy/adamantine/IsReflect(def_zone) - if(def_zone in list(BODY_ZONE_CHEST, BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) && prob(hit_reflect_chance)) - return TRUE - else - return FALSE +/obj/item/clothing/suit/armor/heavy/adamantine/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + if(is_energy_reflectable_projectile(object) && prob(hit_reflect_chance)) + return BLOCK_SUCCESS | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT | BLOCK_PHYSICAL_INTERNAL + return ..() diff --git a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm index 6d080ee898..0a3bdb727d 100644 --- a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm +++ b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm @@ -424,15 +424,15 @@ SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! if(hit_atom) if(isliving(hit_atom)) var/mob/living/L = hit_atom - if(!L.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) + if(L.run_block(0, "the [name]", src, ATTACK_TYPE_TACKLE, 0, src) & BLOCK_SUCCESS) + DefaultCombatKnockdown(15, 1, 1) + else L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") L.DefaultCombatKnockdown(iscarbon(L) ? 60 : 45, override_stamdmg = CLAMP(pounce_stamloss, 0, pounce_stamloss_cap-L.getStaminaLoss())) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) sleep(2)//Runtime prevention (infinite bump() calls on hulks) step_towards(src,L) log_combat(src, L, "borg pounced") - else - DefaultCombatKnockdown(15, 1, 1) pounce_cooldown = !pounce_cooldown spawn(pounce_cooldown_time) //3s by default diff --git a/tgstation.dme b/tgstation.dme index 5870935f66..d56d8489cc 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2234,6 +2234,7 @@ #include "code\modules\mob\living\emote.dm" #include "code\modules\mob\living\life.dm" #include "code\modules\mob\living\living.dm" +#include "code\modules\mob\living\living_block.dm" #include "code\modules\mob\living\living_combat.dm" #include "code\modules\mob\living\living_defense.dm" #include "code\modules\mob\living\living_defines.dm" @@ -2311,6 +2312,7 @@ #include "code\modules\mob\living\carbon\human\emote.dm" #include "code\modules\mob\living\carbon\human\examine.dm" #include "code\modules\mob\living\carbon\human\human.dm" +#include "code\modules\mob\living\carbon\human\human_block.dm" #include "code\modules\mob\living\carbon\human\human_defense.dm" #include "code\modules\mob\living\carbon\human\human_defines.dm" #include "code\modules\mob\living\carbon\human\human_helpers.dm"