diff --git a/code/__DEFINES/combat/block_parry.dm b/code/__DEFINES/combat/block_parry.dm index 730d3da8e9..87e9fed8ff 100644 --- a/code/__DEFINES/combat/block_parry.dm +++ b/code/__DEFINES/combat/block_parry.dm @@ -29,6 +29,23 @@ GLOBAL_LIST_INIT(dir2blockdir, list( #define ITEM_PARRY "item" /// Parry phase we're in -#define PARRY_WINDUP "windup" -#define PARRY_ACTIVE "main" -#define PARRY_SPINDOWN "spindown" +#define NOT_PARRYING 0 +#define PARRY_WINDUP 1 +#define PARRY_ACTIVE 2 +#define PARRY_SPINDOWN 3 + +/// Parry effects. +/// List association must be one of the things underneath +#define PARRY_REFLEX_COUNTERATTACK "reflex_counter" + // Uses active_parry_reflex_counter() on the mob (if unarmed)/item/martial art used to parry. + #define PARRY_COUNTERATTACK_PROC "proc" + // Automatically melee attacks back normally, LMB equivalent action of an harm intent attack. + #define PARRY_COUNTERATTACK_MELEE_ATTACK_CHAIN "default" +/// No list association +#define PARRY_DISARM_ATTACKER "disarm_attacker" +/// List association should be duration or null for just plain knockdown. +#define PARRY_KNOCKDOWN_ATTACKER "knockdown_attacker" +/// List association should be duration. +#define PARRY_STAGGER_ATTACKER "stagger_attacker" +/// List association should be amount to increase clickcd of attacker to. +#define PARRY_CLICKCD_ATTACKER "clickcd_attacker" diff --git a/code/datums/martial/_martial.dm b/code/datums/martial/_martial.dm index 0b5caaa414..9dbe106ee3 100644 --- a/code/datums/martial/_martial.dm +++ b/code/datums/martial/_martial.dm @@ -12,7 +12,7 @@ var/allow_temp_override = TRUE //if this martial art can be overridden by temporary martial arts /// Can we be used to unarmed parry? var/can_martial_parry = FALSE - /// Set this variable to something not null, this'll be the preferred unarmed parry in most cases if [can_martial_parry] is TRUE. + /// Set this variable to something not null, this'll be the preferred unarmed parry in most cases if [can_martial_parry] is TRUE. YOU MUST RUN [get_block_parry_data(this)] INSTEAD OF DIRECTLY ACCESSING! var/datum/block_parry_data/block_parry_data /datum/martial_art/proc/disarm_act(mob/living/carbon/human/A, mob/living/carbon/human/D) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 25ddefb817..5814fe71d5 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -110,6 +110,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) /* Our block parry data. Should be set in init, or something if you are using it. * This won't be accessed without ITEM_CAN_BLOCK or ITEM_CAN_PARRY so do not set it unless you have to to save memory. * If you decide it's a good idea to leave this unset while turning the flags on, you will runtime. Enjoy. + * If this is set to a path, it'll run get_block_parry_data(path). YOU MUST RUN [get_block_parry_data(this)] INSTEAD OF DIRECTLY ACCESSING! */ var/datum/block_parry_data/block_parry_data diff --git a/code/modules/mob/living/living_active_block.dm b/code/modules/mob/living/living_active_block.dm index 5a3aeb2387..d96e6fb10a 100644 --- a/code/modules/mob/living/living_active_block.dm +++ b/code/modules/mob/living/living_active_block.dm @@ -16,8 +16,9 @@ active_block_item = null REMOVE_TRAIT(src, TRAIT_MOBILITY_NOUSE, ACTIVE_BLOCK_TRAIT) remove_movespeed_modifier(MOVESPEED_ID_ACTIVE_BLOCK) - if(timeToNextMove() < I.block_parry_data.block_end_click_cd_add) - changeNext_move(I.block_parry_data.block_end_click_cd_add) + var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data) + if(timeToNextMove() < I.data.block_end_click_cd_add) + changeNext_move(I.data.block_end_click_cd_add) active_block_effect_end() return TRUE @@ -26,13 +27,14 @@ return FALSE if(!(I in held_items)) return FALSE - if(!istype(I.block_parry_data)) //Typecheck because if an admin/coder screws up varediting or something we do not want someone being broken forever, the CRASH logs feedback so we know what happened. - CRASH("start_active_blocking called with an item with no valid block_parry_data: [I.block_parry_data]!") + var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data) + if(!istype(I.data)) //Typecheck because if an admin/coder screws up varediting or something we do not want someone being broken forever, the CRASH logs feedback so we know what happened. + CRASH("start_active_blocking called with an item with no valid data: [I.data]!") active_blocking = TRUE active_block_item = I - if(I.block_parry_data.block_lock_attacking) + if(I.data.block_lock_attacking) ADD_TRAIT(src, TRAIT_MOBILITY_NOMOVE, ACTIVE_BLOCK_TRAIT) - add_movespeed_modifier(MOVESPEED_ID_ACTIVE_BLOCK, TRUE, 100, override = TRUE, multiplicative_slowdown = I.block_parry_data.block_slowdown, blacklisted_movetypes = FLOATING) + add_movespeed_modifier(MOVESPEED_ID_ACTIVE_BLOCK, TRUE, 100, override = TRUE, multiplicative_slowdown = I.data.block_slowdown, blacklisted_movetypes = FLOATING) active_block_effect_start() return TRUE @@ -60,16 +62,17 @@ /// The amount of damage that is blocked. /obj/item/proc/active_block_damage_mitigation(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) - var/absorption = block_parry_data.block_damage_absorption_override[attack_type] - var/efficiency = block_parry_data.block_damage_multiplier_override[attack_type] - var/limit = block_parry_data.block_damage_limit_override[attack_type] + var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data) + var/absorption = data.block_damage_absorption_override[attack_type] + var/efficiency = data.block_damage_multiplier_override[attack_type] + var/limit = data.block_damage_limit_override[attack_type] // must use isnulls to handle 0's. if(isnull(absorption)) - absorption = block_parry_data.block_damage_absorption + absorption = data.block_damage_absorption if(isnull(efficiency)) - efficiency = block_parry_data.block_damage_multiplier + efficiency = data.block_damage_multiplier if(isnull(limit)) - limit = block_parry_data.block_damage_limit + limit = data.block_damage_limit // now we calculate damage to reduce. var/final_damage = 0 // apply limit @@ -84,13 +87,15 @@ /// Amount of stamina from damage blocked. Note that the damage argument is damage_blocked. /obj/item/proc/active_block_stamina_cost(mob/living/owner, atom/object, damage_blocked, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) - var/efficiency = block_parry_data.block_stamina_efficiency_override[attack_type] + var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data) + var/efficiency = data.block_stamina_efficiency_override[attack_type] if(isnull(efficiency)) - efficiency = block_parry_data.block_stamina_efficiency + efficiency = data.block_stamina_efficiency return damage_blocked / efficiency /// Apply the stamina damage to our user, notice how damage argument is stamina_amount. /obj/item/proc/active_block_do_stamina_damage(mob/living/owner, atom/object, stamina_amount, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) + var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data) if(iscarbon(owner)) var/mob/living/carbon/C = owner var/held_index = C.get_held_index_of_item(src) @@ -98,9 +103,9 @@ if(!BP?.body_zone) return C.adjustStaminaLossBuffered(stamina_amount) //nah var/zone = BP.body_zone - var/stamina_to_zone = block_parry_data.block_stamina_limb_ratio * stamina_amount + var/stamina_to_zone = data.block_stamina_limb_ratio * stamina_amount var/stamina_to_chest = stamina_amount - stamina_to_zone - var/stamina_buffered = stamina_to_chest * block_parry_data.block_stamina_buffer_ratio + var/stamina_buffered = stamina_to_chest * data.block_stamina_buffer_ratio stamina_to_chest -= stamina_buffered C.apply_damage(stamina_to_zone, STAMINA, zone) C.apply_damage(stamina_to_chest, STAMINA, BODY_ZONE_CHEST) @@ -143,7 +148,8 @@ * Gets the list of directions we can block. Include DOWN to block attacks from our same tile. */ /obj/item/proc/blockable_directions() - return block_parry_data.can_block_directions + var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data) + return data.can_block_directions /** * Checks if we can block from a specific direction from our direction. diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm index 5d100f9280..99903485f0 100644 --- a/code/modules/mob/living/living_blocking_parrying.dm +++ b/code/modules/mob/living/living_blocking_parrying.dm @@ -11,18 +11,20 @@ var/parry_while_unarmed = FALSE /// Should we prioritize martial art parrying when unarmed? var/parry_prioritize_martial = TRUE - /// Our block_parry_data for unarmed blocks/parries. Currently only used for parrying, as unarmed block isn't implemented yet. + /// 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 GLOBAL_LIST_EMPTY(block_parry_data) -/proc/get_block_parry_data(type_or_id) - if(ispath(type_or_id)) - . = GLOB.block_parry_data["[type_or_id]"] +/proc/get_block_parry_data(datum/block_parry_data/type_id_datum) + if(istype(type_id_datum)) + return type_id_datum + if(ispath(type_id_datum)) + . = GLOB.block_parry_data["[type_id_datum]"] if(!.) - . = GLOB.block_parry_data["[type_or_id]"] = new type_or_id + . = GLOB.block_parry_data["[type_id_datum]"] = new type_id_datum else //text id - return GLOB.block_parry_data["[type_or_id]"] + return GLOB.block_parry_data["[type_id_datum]"] /proc/set_block_parry_data(id, datum/block_parry_data/data) if(ispath(id)) @@ -81,43 +83,78 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Prioriry for [mob/do_run_block()] while we're being used to parry. // None - Parry is always highest priority! - /// Parry windup duration in deciseconds + /// Parry windup duration in deciseconds. 0 to this is windup, afterwards is main stage. var/parry_time_windup = 2 - /// Parry spindown duration in deciseconds + /// Parry spindown duration in deciseconds. main stage end to this is the spindown stage, afterwards the parry fully ends. var/parry_time_spindown = 3 - /// Main parry window in deciseconds + /// Main parry window in deciseconds. This is between [parry_time_windup] and [parry_time_spindown] var/parry_time_active = 5 - /// Perfect parry window in deciseconds from the main window. 3 with main 5 = perfect on third decisecond of main window. + /// Perfect parry window in deciseconds from the start of the main window. 3 with main 5 = perfect on third decisecond of main window. var/parry_time_perfect = 2.5 - /// Time on both sides of perfect parry that still counts as well, perfect + /// Time on both sides of perfect parry that still counts as part of the perfect window. var/parry_time_perfect_leeway = 1 /// [parry_time_perfect_leeway] override for attack types, list(ATTACK_TYPE_DEFINE = deciseconds) var/list/parry_time_perfect_leeway_override /// Parry "efficiency" falloff in percent per decisecond once perfect window is over. - var/parry_time_imperfect_falloff_percent = 20 + var/parry_imperfect_falloff_percent = 20 /// [parry_imperfect_falloff_percent] override for attack types, list(ATTACK_TYPE_DEFINE = deciseconds) - var/list/parry_time_imperfect_falloff_percent_override + var/list/parry_imperfect_falloff_percent_override /// Efficiency in percent on perfect parry. var/parry_efficiency_perfect = 120 + /// Flags for things like what kind of counter we do if successful and such + var/parry_flags = PARRY_FLAGS_DEFAULT /obj/item/proc/active_parry(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) if(!CHECK_BITFIELD(item_flags, ITEM_CAN_PARRY)) return /// Yadda yadda WIP access block/parry data... +/mob/living/proc/get_parry_stage() + if(!parrying) + return NOT_PARRYING + var/datum/block_parry_data/data = get_parry_data() + var/windup_end = data.parry_time_windup + var/active_end = windup + data.parry_time_active + var/spindown_end = active + data.parry_time_spindown + switch(get_parry_time()) + if(0 to windup_end) + return PARRY_WINDUP + if(windup_end to active_end) + return PARRY_ACTIVE + if(active_end to spindown_end) + return PARRY_SPINDOWN + return NOT_PARRYING + +/mob/living/proc/get_parry_efficiency(attack_type) + var/datum/block_parry_data/data = get_parry_data() + if(get_parry_stage() != PARRY_ACTIVE) + return 0 + var/difference = abs(get_parry_time() - (data.parry_time_perfect + data.parry_time_windup) + var/leeway = data.parry_time_perfect_leeway_override[attack_type] + if(isnull(leeway)) + leeway = data.parry_time_perfect_leeway + difference -= leeway + . = data.parry_efficiency_perfect + if(difference <= 0) + return + var/falloff = data.parry_imperfect_falloff_percent_override[attack_type] + if(isnull(falloff)) + falloff = data.parry_imperfect_falloff_percent + . -= falloff * difference + +/mob/living/proc/get_parry_time() + return world.time - parry_start_time + /// same return values as normal blocking, called with absolute highest priority in the block "chain". /mob/living/proc/run_parry(real_attack = TRUE, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list()) /// Gets the datum/block_parry_data we're going to use to parry. /mob/living/proc/get_parry_data() if(parrying == ITEM_PARRY) - return active_parry_item.block_parry_data + return get_block_parry_data(active_parry_item.block_parry_data) else if(parrying == UNARMED_PARRY) - return block_parry_data + return get_block_parry_data(block_parry_data) else if(parrying == MARTIAL_PARRY) - return mind.martial_art.block_parry_data + return get_block_parry_data(mind.martial_art.block_parry_data) -#define UNARMED_PARRY -#define MARTIAL_PARRY -#define ITEM_PARRY