diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm
index fc7d9e13e0..cdb5c48dbd 100644
--- a/code/__DEFINES/combat.dm
+++ b/code/__DEFINES/combat.dm
@@ -29,10 +29,11 @@
#define EFFECT_DROWSY "drowsy"
#define EFFECT_JITTER "jitter"
+// mob/living/var/combat_flags variable.
/// Default combat flags for those affected by sprinting (combat mode has been made into its own component)
-#define COMBAT_FLAGS_DEFAULT NONE
+#define COMBAT_FLAGS_DEFAULT (COMBAT_FLAG_PARRY_CAPABLE | COMBAT_FLAG_BLOCK_CAPABLE)
/// Default combat flags for everyone else (so literally everyone but humans).
-#define COMBAT_FLAGS_SPRINT_EXEMPT (COMBAT_FLAG_SPRINT_ACTIVE | COMBAT_FLAG_SPRINT_TOGGLED | COMBAT_FLAG_SPRINT_FORCED)
+#define COMBAT_FLAGS_SPRINT_EXEMPT (COMBAT_FLAG_SPRINT_ACTIVE | COMBAT_FLAG_SPRINT_TOGGLED | COMBAT_FLAG_SPRINT_FORCED | COMBAT_FLAG_PARRY_CAPABLE | COMBAT_FLAG_BLOCK_CAPABLE)
/// The user wants sprint mode on
#define COMBAT_FLAG_SPRINT_TOGGLED (1<<0)
@@ -50,6 +51,16 @@
#define COMBAT_FLAG_SOFT_STAMCRIT (1<<6)
/// Force sprint mode on at all times, overrides everything including sprint disable traits.
#define COMBAT_FLAG_SPRINT_FORCED (1<<7)
+/// This mob is capable of using the active parrying system.
+#define COMBAT_FLAG_PARRY_CAPABLE (1<<8)
+/// This mob is capable of using the active blocking system.
+#define COMBAT_FLAG_BLOCK_CAPABLE (1<<9)
+/// This mob is capable of unarmed parrying
+#define COMBAT_FLAG_UNARMED_PARRY (1<<10)
+/// This mob is currently actively blocking
+#define COMBAT_FLAG_ACTIVE_BLOCKING (1<<11)
+/// This mob is currently starting an active block
+#define COMBAT_FLAG_ACTIVE_BLOCK_STARTING (1<<12)
// Helpers for getting someone's stamcrit state. Cast to living.
#define NOT_STAMCRIT 0
diff --git a/code/__DEFINES/combat/block_parry.dm b/code/__DEFINES/combat/block_parry.dm
index ed7a889321..66c35a405d 100644
--- a/code/__DEFINES/combat/block_parry.dm
+++ b/code/__DEFINES/combat/block_parry.dm
@@ -37,6 +37,14 @@ GLOBAL_LIST_INIT(dir2blockdir, list(
#define PARRY_ACTIVE 2
#define PARRY_SPINDOWN 3
+// /datum/block_parry_data/var/parry_flags
+/// Default handling for audio/visual feedback
+#define PARRY_DEFAULT_HANDLE_FEEDBACK (1<<0)
+/// Lock sprinting while parrying
+#define PARRY_LOCK_SPRINTING (1<<1)
+/// Lock attacking while parrying
+#define PARRY_LOCK_ATTACKING (1<<2)
+
/// Parry effects.
/// List association must be one of the things underneath
#define PARRY_REFLEX_COUNTERATTACK "reflex_counter"
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index c1038fbb41..5b21abfb84 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -19,6 +19,8 @@
med_hud_set_status()
/mob/living/Destroy()
+ end_parry_sequence()
+ stop_active_blocking()
if(LAZYLEN(status_effects))
for(var/s in status_effects)
var/datum/status_effect/S = s
diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm
index a1ddb6e2b5..ef23c28d70 100644
--- a/code/modules/mob/living/living_active_parry.dm
+++ b/code/modules/mob/living/living_active_parry.dm
@@ -39,7 +39,7 @@
var/full_parry_duration = data.parry_time_windup + data.parry_time_active + data.parry_time_spindown
// no system in place to "fallback" if out of the 3 the top priority one can't parry due to constraints but something else can.
// can always implement it later, whatever.
- if(data.parry_respect_clickdelay && (next_move > world.time))
+ if((data.parry_respect_clickdelay && (next_move > world.time)) || ((parry_end_time_last + data.parry_cooldown) > world.time))
to_chat(src, "You are not ready to parry (again)!")
return
// Point of no return, make sure everything is set.
@@ -50,6 +50,10 @@
parry_start_time = world.time
successful_parries = list()
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
+ if(data.parry_flags & PARRY_LOCK_ATTACKING)
+ ADD_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_PARRY_TRAIT)
+ if(data.parry_flags & PARRY_LOCK_SPRINTING)
+ ADD_TRAIT(src, TRAIT_SPRINT_LOCKED, ACTIVE_PARRY_TRAIT)
handle_parry_starting_effects(data)
return TRUE
@@ -59,9 +63,16 @@
/mob/living/proc/end_parry_sequence()
if(!parrying)
return
+ if(parry_visual_effect)
+ QDEL_NULL(parry_visual_effect)
var/datum/block_parry_data/data = get_parry_data()
var/list/effect_text = list()
- if(!length(successful_parries)) // didn't parry anything successfully
+ var/successful = FALSE
+ for(var/efficiency in successful_parries)
+ if(efficiency >= data.parry_efficiency_considered_successful)
+ successful = TRUE
+ break
+ if(!successful) // didn't parry anything successfully
if(data.parry_failed_stagger_duration)
Stagger(data.parry_failed_stagger_duration)
effect_text += "staggering themselves"
@@ -78,7 +89,7 @@
*/
/mob/living/proc/handle_parry_starting_effects(datum/block_parry_data/data)
playsound(src, data.parry_start_sound, 75, 1)
- new /obj/effect/abstract/parry/main(null, data, src)
+ parry_visual_effect = new /obj/effect/abstract/parry/main(null, data, src, data.parry_effect_icon_state)
switch(parrying)
if(ITEM_PARRY)
visible_message("[src] swings [active_parry_item]!")
@@ -185,7 +196,7 @@
/// same return values as normal blocking, called with absolute highest priority in the block "chain".
/mob/living/proc/run_parry(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list())
var/stage = get_parry_stage()
- if(stage == NOT_PARRYING)
+ if(stage != PARRY_ACTIVE)
return BLOCK_NONE
if(!CHECK_MOBILITY(src, MOBILITY_USE))
to_chat(src, "Your parry is interrupted!")
@@ -208,10 +219,14 @@
. |= BLOCK_SHOULD_PARTIAL_MITIGATE
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)
+ var/list/effect_text
+ if(efficiency >= data.parry_efficiency_to_counterattack)
+ run_parry_countereffects(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency)
+ if(data.parry_flags & PARRY_DEFAULT_HANDLE_FEEDBACK)
handle_parry_feedback(object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list, efficiency, effect_text)
successful_parries += efficiency
+ if(length(successful_parries) >= data.parry_max_attacks)
+ end_parry_sequence()
/mob/living/proc/handle_parry_feedback(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency, list/effect_text)
var/datum/block_parry_data/data = get_parry_data()
@@ -283,10 +298,10 @@
/obj/effect/abstract/parry/main
name = null
- icon_state = "parry_bm_hold"
-/obj/effect/abstract/parry/main/Initialize(mapload, datum/block_parry_data/data, mob/living/owner)
+/obj/effect/abstract/parry/main/Initialize(mapload, datum/block_parry_data/data, mob/living/owner, icon_state)
. = ..()
+ src.icon_state = icon_state
if(owner)
attach_to(owner)
if(data)
diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm
index a5971afed2..0a4bf18768 100644
--- a/code/modules/mob/living/living_blocking_parrying.dm
+++ b/code/modules/mob/living/living_blocking_parrying.dm
@@ -93,6 +93,8 @@ GLOBAL_LIST_EMPTY(block_parry_data)
var/parry_stamina_cost = 5
/// Attack types we can block
var/parry_attack_types = ALL
+ /// Parry flags
+ var/parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK | PARRY_LOCK_ATTACKING
/// Parry windup duration in deciseconds. 0 to this is windup, afterwards is main stage.
var/parry_time_windup = 2
@@ -116,8 +118,16 @@ GLOBAL_LIST_EMPTY(block_parry_data)
var/list/parry_data = list(
PARRY_REFLEX_COUNTERATTACK = PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN
)
- /// Enable default handling of audio/visual feedback
- var/parry_default_handle_feedback = TRUE
+ /// Efficiency must be at least this to be considered successful
+ var/parry_efficiency_considered_successful = 0.1
+ /// Efficiency must be at least this to run automatic counterattack
+ var/parry_efficiency_to_counterattack = 0.1
+ /// Maximum attacks to parry successfully or unsuccessfully during active period, hitting this immediately ends the sequence.
+ var/parry_max_attacks = INFINITY
+ /// Visual icon state override for parrying
+ var/parry_effect_icon_state = "parry_bm_hold"
+ /// Parrying cooldown, separate of clickdelay. It must be this much deciseconds since their last parry for them to parry with this object.
+ var/parry_cooldown = 0
/// Parry start sound
var/parry_start_sound = 'sound/block_parry/sfx-parry.ogg'
/// Sounds for parrying
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 6e19d2147c..b9e8feee7f 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -29,14 +29,8 @@
// Combat - Blocking/Parrying system
/// Our block_parry_data for unarmed blocks/parries. Currently only used for parrying, as unarmed block isn't implemented yet. YOU MUST RUN [get_block_parry_data(this)] INSTEAD OF DIRECTLY ACCESSING!
- var/datum/block_parry_data/block_parry_data
+ var/datum/block_parry_data/block_parry_data = /datum/block_parry_data // defaults to *something* because [combat_flags] dictates whether or not we can unarmed block/parry.
// Blocking
- /// Whether or not the user is actively blocking.
- var/active_blocking = FALSE
- /// Whether or not we can actively block. Disabled by default since a lot of mobs do not support stamina damage. Imagine a dextrous guardian with a shield..
- var/active_block_enabled = FALSE
- /// Whether or not we are in the process of raising our shield/whatever.
- var/active_block_starting = FALSE
/// The item the user is actively blocking with if any.
var/obj/item/active_block_item
// Parrying
@@ -46,8 +40,11 @@
var/obj/item/active_parry_item
/// world.time of parry action start
var/parry_start_time = 0
- /// Whether or not we can unarmed parry
- var/parry_while_unarmed = FALSE
+ /// Current parry effect.
+ var/obj/effect/abstract/parry/parry_visual_effect
+ /// world.time of last parry end
+ var/parry_end_time_last = 0
+ #warn implement above
/// Successful parries within the current parry cycle. It's a list of efficiency percentages.
var/list/successful_parries