diff --git a/code/__DEFINES/combat/block.dm b/code/__DEFINES/combat/block.dm
index b489662078..84b739a1c7 100644
--- a/code/__DEFINES/combat/block.dm
+++ b/code/__DEFINES/combat/block.dm
@@ -51,6 +51,8 @@
#define BLOCK_RETURN_SET_DAMAGE_TO "set_damage_to"
/// For [BLOCK_SHOULD_PARTIAL_MITIGATE]. Percentage mitigation.
#define BLOCK_RETURN_MITIGATION_PERCENT "partial_mitigation"
+/// Used internally by run_parry proc, use on an on_active_parry() proc to override parrying efficiency.
+#define BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY "override_parry_efficiency"
/// Default if the above isn't set in the list.
#define DEFAULT_REDIRECT_METHOD_PROJECTILE REDIRECT_METHOD_DEFLECT
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 98db89a100..f0e4a0ea08 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -125,8 +125,10 @@
if(!CHECK_MOBILITY(user, MOBILITY_STAND))
totitemdamage *= 0.5
//CIT CHANGES END HERE
- if((user != src) && run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user) & BLOCK_SUCCESS)
+ var/list/block_return = list()
+ if((user != src) && run_block(I, totitemdamage, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, return_list = block_return) & BLOCK_SUCCESS)
return FALSE
+ totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
send_item_attack_message(I, user)
I.do_stagger_action(src, user)
if(I.force)
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index c2db529675..2923e35bf7 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -664,4 +664,4 @@
. = ..()
overlay = mutable_appearance(icon, overlay_state)
overlay.appearance_flags = RESET_COLOR
- add_overlay(overlay)
\ No newline at end of file
+ add_overlay(overlay)
diff --git a/code/game/objects/items/shields.dm b/code/game/objects/items/shields.dm
index 593aa599c0..30e1460f8a 100644
--- a/code/game/objects/items/shields.dm
+++ b/code/game/objects/items/shields.dm
@@ -25,6 +25,8 @@
var/shieldbash_push_distance = 1
/datum/block_parry_data/shield
+ block_stamina_efficiency = 1
+ block_stamina_cost_per_second = 3
/obj/item/shield/examine(mob/user)
. = ..()
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 9c4f0316b7..0b0edc5742 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -87,8 +87,10 @@
totitemdamage *= 1.5
//CIT CHANGES END HERE
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))
+ var/list/block_return = list()
+ if((user != src) && (run_block(I, totitemdamage, "the [I]", ATTACK_TYPE_MELEE, I.armour_penetration, user, impacting_zone, return_list = block_return) & BLOCK_SUCCESS))
return FALSE
+ totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
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]
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index aeab0042ac..7205a4bf59 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -1668,10 +1668,22 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
disarm(M, H, attacker_style)
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/bodypart/affecting, intent, mob/living/carbon/human/H)
+ //CIT CHANGES START HERE - combatmode and resting checks
+ var/totitemdamage = I.force
+ if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ totitemdamage *= 0.5
+ if(!CHECK_MOBILITY(user, MOBILITY_STAND))
+ totitemdamage *= 0.5
+ if(istype(H))
+ if(!(H.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
+ totitemdamage *= 1.5
+ //CIT CHANGES END HERE
// Allows you to put in item-specific reactions based on species
if(user != H)
- if(H.run_block(I, I.force, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone) & BLOCK_SUCCESS)
+ var/list/block_return = list()
+ if(H.run_block(I, I.force, "the [I.name]", ATTACK_TYPE_MELEE, I.armour_penetration, user, affecting.body_zone, return_list = block_return) & BLOCK_SUCCESS)
return 0
+ totitemdamage = block_calculate_resultant_damage(totitemdamage, block_return)
if(H.check_martial_melee_block())
H.visible_message("[H] blocks [I]!")
return 0
@@ -1686,16 +1698,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/armor_block = H.run_armor_check(affecting, "melee", "Your armor has protected your [hit_area].", "Your armor has softened a hit to your [hit_area].",I.armour_penetration)
armor_block = min(90,armor_block) //cap damage reduction at 90%
var/Iforce = I.force //to avoid runtimes on the forcesay checks at the bottom. Some items might delete themselves if you drop them. (stunning yourself, ninja swords)
- //CIT CHANGES START HERE - combatmode and resting checks
- var/totitemdamage = I.force
- if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
- totitemdamage *= 0.5
- if(!CHECK_MOBILITY(user, MOBILITY_STAND))
- totitemdamage *= 0.5
- if(istype(H))
- if(!(H.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
- totitemdamage *= 1.5
- //CIT CHANGES END HERE
+
var/weakness = H.check_weakness(I, user)
apply_damage(totitemdamage * weakness, I.damtype, def_zone, armor_block, H) //CIT CHANGE - replaces I.force with totitemdamage
diff --git a/code/modules/mob/living/living_block.dm b/code/modules/mob/living/living_block.dm
index c0f2bc809d..1efc487f60 100644
--- a/code/modules/mob/living/living_block.dm
+++ b/code/modules/mob/living/living_block.dm
@@ -93,3 +93,15 @@
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)
+
+// HELPER PROCS
+
+/**
+ * Considers a block return_list and calculates damage to use from that.
+ */
+/proc/block_calculate_resultant_damage(damage, list/block_return)
+ if(!isnull(block_return[BLOCK_RETURN_SET_DAMAGE_TO)) // higher priority
+ return block_return[BLOCK_RETURN_SET_DAMAGE_TO]
+ else if(!isnull(block_return[BLOCK_RETURN_MITIGATION_PERCENT]))
+ return damage * ((100 - block_return[BLOCK_RETURN_MITIGATION_PERCENT]) * 0.01)
+ return damage
diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm
index b721cb4a1a..ac867b7a75 100644
--- a/code/modules/mob/living/living_blocking_parrying.dm
+++ b/code/modules/mob/living/living_blocking_parrying.dm
@@ -108,7 +108,6 @@ GLOBAL_LIST_EMPTY(block_parry_data)
var/parry_stamina_cost = 5
#warn implement
-
/// Parry windup duration in deciseconds. 0 to this is windup, afterwards is main stage.
var/parry_time_windup = 2
/// Parry spindown duration in deciseconds. main stage end to this is the spindown stage, afterwards the parry fully ends.
@@ -170,17 +169,17 @@ GLOBAL_LIST_EMPTY(block_parry_data)
//Stubs.
-/obj/item/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency)
+/obj/item/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
-/mob/living/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency)
+/mob/living/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
-/datum/martial_art/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency)
+/datum/martial_art/proc/on_active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return, parry_efficiency, parry_time)
-/obj/item/proc/active_parry_reflex_counter(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list)
+/obj/item/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency)
-/mob/living/proc/active_parry_reflex_counter(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list)
+/mob/living/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency)
-/datum/martial_art/proc/active_parry_reflex_counter(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list)
+/datum/martial_art/proc/active_parry_reflex_counter(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list, parry_efficiency)
/**
* Gets the stage of our parry sequence we're currently in.
@@ -203,6 +202,8 @@ GLOBAL_LIST_EMPTY(block_parry_data)
/**
* Gets the percentage efficiency of our parry.
+ *
+ * Returns a percentage in normal 0 to 100 scale, but not clamped to just 0 to 100.
*/
/mob/living/proc/get_parry_efficiency(attack_type)
var/datum/block_parry_data/data = get_parry_data()
@@ -236,16 +237,18 @@ GLOBAL_LIST_EMPTY(block_parry_data)
var/efficiency = get_parry_efficiency(attack_type)
switch(parrying)
if(ITEM_PARRY)
- . = active_parry_item.on_active_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
+ . = active_parry_item.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
if(UNARMED_PARRY)
- . = on_active_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
+ . = on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
if(MARTIAL_PARRY)
- . = mind.martial_art.on_active_parry(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
- if(efficiency <= 0)
+ . = mind.martial_art.on_active_parry(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, get_parry_time())
+ if(!isnull(return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY])) // one of our procs overrode
+ efficiency = return_list[BLOCK_RETURN_OVERRIDE_PARRY_EFFICIENCY]
+ if(efficiency <= 0) // Do not allow automatically handled/standardized parries that increase damage for now.
return
. |= BLOCK_SHOULD_PARTIAL_MITIGATE
- var/current = return_list[BLOCK_RETURN_MITIGATION_PERCENT] || 0
- return_list[BLOCK_RETURN_MITIGATION_PERCENT] = 100 - (clamp(100 - current, 0, 100) * clamp(1 - (efficiency / 100), 0, 1))
+ if(isnull(return_list[BLOCK_RETURN_MITIGATION_PERCENT])) // if one of the on_active_parry procs overrode. We don't have to worry about interference since parries are the first thing checked in the [do_run_block()] sequence.
+ return_list[BLOCK_RETURN_MITIGATION_PERCENT] = clamp(efficiency, 0, 100) // do not allow > 100% or < 0% for now.
var/list/effect_text = run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
if(data.parry_default_handle_feedback)
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text)
@@ -269,11 +272,11 @@ GLOBAL_LIST_EMPTY(block_parry_data)
if(PARRY_COUNTERATTACK_PROC)
switch(parrying)
if(ITEM_PARRY)
- active_parry_item.active_parry_reflex_counter(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
+ active_parry_item.active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency)
if(UNARMED_PARRY)
- active_parry_reflex_counter(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
+ active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency)
if(MARTIAL_PARRY)
- mind.martial_art.active_parry_reflex_counter(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
+ mind.martial_art.active_parry_reflex_counter(src, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, parry_efficiency)
if(PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN)
switch(parrying)
if(ITEM_PARRY)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 3e4fbd9625..1335c8c2dc 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -66,6 +66,7 @@
CRASH("Invalid rediretion mode [redirection_mode]")
/mob/living/bullet_act(obj/item/projectile/P, def_zone)
+ var/totaldamage = P.damage
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
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)
@@ -76,9 +77,10 @@
if(returned & BLOCK_SUCCESS)
P.on_hit(src, 100, def_zone)
return BULLET_ACT_BLOCK
+ totaldamage = block_calculate_resultant_damage(totaldamage, returnlist)
var/armor = run_armor_check(def_zone, P.flag, null, null, P.armour_penetration, null)
if(!P.nodamage)
- apply_damage(P.damage, P.damage_type, def_zone, armor)
+ apply_damage(totaldamage, P.damage_type, def_zone, armor)
if(P.dismemberment)
check_projectile_dismemberment(P, def_zone)
return P.on_hit(src, armor) ? BULLET_ACT_HIT : BULLET_ACT_BLOCK
@@ -111,10 +113,13 @@
I = AM
throwpower = I.throwforce
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)
+ var/list/block_return = list()
+ var/total_damage = I.throwforce
+ if(run_block(AM, throwpower, "\the [AM.name]", ATTACK_TYPE_THROWN, 0, throwingdatum?.thrower, impacting_zone, return_list = block_return) & BLOCK_SUCCESS)
hitpush = FALSE
skipcatch = TRUE
blocked = TRUE
+ total_damage = block_calculate_resultant_damage(total_damage, block_return)
else if(I && I.throw_speed >= EMBED_THROWSPEED_THRESHOLD && can_embed(I, src) && prob(I.embedding.embed_chance) && !HAS_TRAIT(src, TRAIT_PIERCEIMMUNE) && (!HAS_TRAIT(src, TRAIT_AUTO_CATCH_ITEM) || incapacitated() || get_active_held_item()))
embed_item(I)
hitpush = FALSE
@@ -143,7 +148,7 @@
visible_message("[src] has been hit by [I].", \
"[src] has been hit by [I].")
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)
+ apply_damage(total_damage, dtype, impacting_zone, armor)
if(I.thrownby)
log_combat(I.thrownby, src, "threw and hit", I)
else
@@ -303,8 +308,10 @@
var/damage = rand(5, 35)
if(M.is_adult)
damage = rand(20, 40)
- if(run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected)) & BLOCK_SUCCESS)
+ var/list/block_return = list()
+ if(run_block(M, damage, "the [M.name]", ATTACK_TYPE_MELEE, null, M, check_zone(M.zone_selected), return_list = block_return) & BLOCK_SUCCESS)
return FALSE
+ damage = block_calculate_resultant_damage(damage, block_return)
if (stat != DEAD)
log_combat(M, src, "attacked")
diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm
index dced5ae869..d03785f71c 100644
--- a/code/modules/mob/living/silicon/silicon_defense.dm
+++ b/code/modules/mob/living/silicon/silicon_defense.dm
@@ -113,6 +113,7 @@
flash_act(affect_silicon = 1)
/mob/living/silicon/bullet_act(obj/item/projectile/P, def_zone)
+ var/totaldamage = P.damage
if(P.original != src || P.firer != src) //try to block or reflect the bullet, can't do so when shooting oneself
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)
@@ -123,17 +124,14 @@
if(returned & BLOCK_SUCCESS)
P.on_hit(src, 100, def_zone)
return BULLET_ACT_BLOCK
+ totaldamage = block_calculate_resultant_damage(totaldamage, returnlist)
if((P.damage_type == BRUTE || P.damage_type == BURN))
- adjustBruteLoss(P.damage)
- if(prob(P.damage*1.5))
- for(var/mob/living/M in buckled_mobs)
- M.visible_message("[M] is knocked off of [src]!")
- unbuckle_mob(M)
- M.DefaultCombatKnockdown(40)
- if(P.stun || P.knockdown)
+ adjustBruteLoss(totaldamage)
+ if((P.damage >= 10) || P.stun || P.knockdown || (P.stamina >= 20))
for(var/mob/living/M in buckled_mobs)
+ M.visible_message("[M] is knocked off of [src]!")
unbuckle_mob(M)
- M.visible_message("[M] is knocked off of [src] by the [P]!")
+ M.DefaultCombatKnockdown(40)
P.on_hit(src)
return BULLET_ACT_HIT