let's finish parrying
This commit is contained in:
@@ -95,4 +95,4 @@
|
||||
|
||||
///Gets called when a projectile hits the owner. Returning anything other than BULLET_ACT_HIT will stop the projectile from hitting the mob.
|
||||
/datum/martial_art/proc/on_projectile_hit(mob/living/carbon/human/A, obj/item/projectile/P, def_zone)
|
||||
return BULLET_ACT_HIT
|
||||
return BULLET_ACT_HIT
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/datum/keybinding/living/active_block
|
||||
hotkey_keys = list("Northwest", "F") // HOME
|
||||
name = "active_block"
|
||||
full_name = "Block"
|
||||
full_name = "Block (Hold)"
|
||||
category = CATEGORY_COMBAT
|
||||
description = "Hold down to actively block with your currently in-hand object."
|
||||
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
// Active directional block system. Shared code is in [living_blocking_parrying.dm]
|
||||
/mob/living
|
||||
/// 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
|
||||
|
||||
/mob/living/on_item_dropped(obj/item/I)
|
||||
if(I == active_block_item)
|
||||
stop_active_blocking()
|
||||
|
||||
@@ -1 +1,293 @@
|
||||
// Active parry system goes in here.
|
||||
|
||||
/**
|
||||
* Called from keybindings.
|
||||
*/
|
||||
/mob/living/proc/keybind_parry()
|
||||
initiate_parry_sequence()
|
||||
|
||||
/**
|
||||
* Initiates a parrying sequence.
|
||||
*/
|
||||
/mob/living/proc/initiate_parry_sequence()
|
||||
if(!CHECK_MOBILITY(src, MOBILITY_USE))
|
||||
to_chat(src, "<span class='warning'>You are incapacitated, or otherwise unable to swing a weapon to parry with!")
|
||||
return FALSE
|
||||
var/datum/block_parry_data/data
|
||||
// Prioritize item, then martial art, then unarmed.
|
||||
// yanderedev else if time
|
||||
var/obj/item/using_item = get_active_held_item()
|
||||
var/method
|
||||
if(using_item.item_flags & ITEM_CAN_PARRY)
|
||||
data = using_item.block_parry_data
|
||||
method = ITEM_PARRY
|
||||
else if(mind?.martial_art?.can_martial_parry)
|
||||
data = mind.martial_art.block_parry_data
|
||||
method = MARTIAL_PARRY
|
||||
else if(parry_while_unarmed)
|
||||
data = block_parry_data
|
||||
method = UNARMED_PARRY
|
||||
else
|
||||
to_chat(src, "<span class='warning'>You have nothing to parry with!</span>")
|
||||
return FALSE
|
||||
data = return_block_parry_datum(data)
|
||||
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))
|
||||
to_chat(src, "<span class='warning'>You are not ready to parry (again)!</span>")
|
||||
return
|
||||
// Point of no return, make sure everything is set.
|
||||
parrying = method
|
||||
if(method == ITEM_PARRY)
|
||||
active_parry_item = using_item
|
||||
adjustStaminaLossBuffered(data.parry_stamina_cost)
|
||||
parry_start_time = world.time
|
||||
successful_parries = list()
|
||||
addtimer(CALLBACK(src, .proc/end_parry_sequence), full_parry_duration)
|
||||
handle_parry_starting_effects()
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Called via timer when the parry sequence ends.
|
||||
*/
|
||||
/mob/living/proc/end_parry_sequence()
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
if(!length(successful_parries)) // didn't parry anything successfully
|
||||
if(data.parry_failed_stagger_duration)
|
||||
Stagger(data.parry_failed_stagger_duration)
|
||||
if(data.parry_failed_clickcd_duration)
|
||||
changeNext_move(data.parry_failed_clickcd_duration)
|
||||
handle_parry_ending_effects()
|
||||
parrying = NOT_PARRYING
|
||||
parry_start_time = 0
|
||||
successful_parries = null
|
||||
|
||||
/**
|
||||
* Handles starting effects for parrying.
|
||||
*/
|
||||
/mob/living/proc/handle_parry_starting_effects(datum/block_parry_data/data)
|
||||
new /obj/effect/abstract/parry/main(null, data, src)
|
||||
|
||||
/**
|
||||
* Handles ending effects for parrying.
|
||||
*/
|
||||
/mob/living/proc/handle_parry_ending_effects()
|
||||
return
|
||||
|
||||
/**
|
||||
* Gets this item's datum/block_parry_data
|
||||
*/
|
||||
/obj/item/proc/get_block_parry_data()
|
||||
return return_block_parry_datum(block_parry_data)
|
||||
|
||||
/**
|
||||
* Called every life tick to handle blocking/parrying effects.
|
||||
*/
|
||||
/mob/living/proc/handle_block_parry(seconds = 1)
|
||||
if(active_blocking)
|
||||
var/datum/block_parry_data/data = return_block_parry_datum(active_block_item.block_parry_data)
|
||||
adjustStaminaLossBuffered(data.block_stamina_cost_per_second * seconds)
|
||||
|
||||
//Stubs.
|
||||
|
||||
/**
|
||||
* Called when an attack is parried using this, whether or not the parry was successful.
|
||||
*/
|
||||
/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)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried innately, whether or not the parry was successful.
|
||||
*/
|
||||
/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)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried using this, whether or not the parry was successful.
|
||||
*/
|
||||
/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)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
*/
|
||||
/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)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
*/
|
||||
/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)
|
||||
|
||||
/**
|
||||
* Called when an attack is parried and block_parra_data indicates to use a proc to handle counterattack.
|
||||
*/
|
||||
/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.
|
||||
*/
|
||||
/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_end + data.parry_time_active
|
||||
var/spindown_end = active_end + 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
|
||||
|
||||
/**
|
||||
* 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()
|
||||
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.attack_type_list_scan(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.attack_type_list_scan(data.parry_imperfect_falloff_percent_override, attack_type)
|
||||
if(isnull(falloff))
|
||||
falloff = data.parry_imperfect_falloff_percent
|
||||
. -= falloff * difference
|
||||
|
||||
/**
|
||||
* Gets the current decisecond "frame" of an active parry.
|
||||
*/
|
||||
/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(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)
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/efficiency = get_parry_efficiency(attack_type)
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
. = 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(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(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
|
||||
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)
|
||||
successful_parries += efficiency
|
||||
|
||||
/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()
|
||||
if(data.parry_sounds)
|
||||
playsound(src, pick(data.parry_sounds), 75)
|
||||
visible_message("<span class='danger'>[src] parries \the [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""]!</span>")
|
||||
|
||||
/// Run counterattack if any
|
||||
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency)
|
||||
if(!isliving(attacker))
|
||||
return
|
||||
var/mob/living/L = attacker
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/list/effect_text = list()
|
||||
if(data.parry_data[PARRY_REFLEX_COUNTERATTACK])
|
||||
switch(data.parry_data[PARRY_REFLEX_COUNTERATTACK])
|
||||
if(PARRY_COUNTERATTACK_PROC)
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
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(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(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)
|
||||
active_parry_item.melee_attack_chain(src, attacker, null)
|
||||
if(UNARMED_PARRY)
|
||||
UnarmedAttack(attacker)
|
||||
if(MARTIAL_PARRY)
|
||||
UnarmedAttack(attacker)
|
||||
if(data.parry_data[PARRY_DISARM_ATTACKER])
|
||||
L.drop_all_held_items()
|
||||
effect_text += "disarming"
|
||||
if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
|
||||
L.DefaultCombatKnockdown(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
|
||||
effect_text += "knocking them to the ground"
|
||||
if(data.parry_data[PARRY_STAGGER_ATTACKER])
|
||||
L.Stagger(data.parry_data[PARRY_STAGGER_ATTACKER])
|
||||
effect_text += "staggering"
|
||||
if(data.parry_data[PARRY_DAZE_ATTACKER])
|
||||
L.Daze(data.parry_data[PARRY_DAZE_ATTACKER])
|
||||
effect_text += "dazing"
|
||||
return effect_text
|
||||
|
||||
/// 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.get_block_parry_data()
|
||||
else if(parrying == UNARMED_PARRY)
|
||||
return return_block_parry_datum(block_parry_data)
|
||||
else if(parrying == MARTIAL_PARRY)
|
||||
return return_block_parry_datum(mind.martial_art.block_parry_data)
|
||||
|
||||
/// Effects
|
||||
/obj/effect/abstract/parry
|
||||
icon = 'icons/effects/block_parry.dmi'
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
layer = FLOAT_LAYER
|
||||
plane = FLOAT_PLANE
|
||||
vis_flags = VIS_INHERIT_LAYER|VIS_INHERIT_PLANE
|
||||
/// The person we're on
|
||||
var/mob/living/owner
|
||||
|
||||
/obj/effect/abstract/parry/main
|
||||
icon_state = "parry_bm_hold"
|
||||
|
||||
/obj/effect/abstract/parry/main/Initialize(mapload, datum/block_parry_data/data, mob/living/owner)
|
||||
. = ..()
|
||||
if(owner)
|
||||
attach_to(owner)
|
||||
if(data)
|
||||
run_animation(data.parry_time_windup, data.parry_time_active, data.parry_time_spindown, TRUE)
|
||||
|
||||
/obj/effect/abstract/parry/main/Destroy()
|
||||
detach_from(owner)
|
||||
return ..()
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/attach_to(mob/living/attaching)
|
||||
if(owner)
|
||||
detach_from(owner)
|
||||
owner = attaching
|
||||
owner.vis_contents += src
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/detach_from(mob/living/detaching)
|
||||
if(detaching == owner)
|
||||
owner = null
|
||||
detaching.vis_contents -= src
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/run_animation(windup_time = 2, active_time = 5, spindown_time = 3, qdel_end = TRUE)
|
||||
if(qdel_end)
|
||||
QDEL_IN(src, windup_time + active_time + spindown_time)
|
||||
var/matrix/current = transform
|
||||
transform = matrix(0.1, 0, 0, 0, 0.1, 0)
|
||||
animate(src, transform = current, time = windup_time)
|
||||
sleep(active_time)
|
||||
flick(icon, "parry_bm_end")
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
// yell at me later for file naming
|
||||
// This file contains stuff relating to the new directional blocking and parry system.
|
||||
/mob/living
|
||||
/// Whether ot not the user is in the middle of an active parry. Set to [UNARMED_PARRY], [ITEM_PARRY], [MARTIAL_PARRY] if parrying.
|
||||
var/parrying = FALSE
|
||||
/// The itme the user is currently parrying with, if any.
|
||||
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
|
||||
/// 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. YOU MUST RUN [get_block_parry_data(this)] INSTEAD OF DIRECTLY ACCESSING!
|
||||
var/datum/block_parry_data/block_parry_data
|
||||
/// Successful parries within the current parry cycle. It's a list of efficiency percentages.
|
||||
var/list/successful_parries
|
||||
|
||||
GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
|
||||
/proc/return_block_parry_datum(datum/block_parry_data/type_id_datum)
|
||||
@@ -102,11 +86,9 @@ 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 doesn't work if you aren't able to otherwise attack due to clickdelay
|
||||
var/parry_respect_clickdelay
|
||||
#warn implement
|
||||
var/parry_respect_clickdelay = TRUE
|
||||
/// Parry stamina cost
|
||||
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
|
||||
@@ -136,10 +118,8 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
var/list/parry_sounds = list('sound/block_parry/block_metal1.ogg' = 1, 'sound/block_parry/block_metal1.ogg' = 1)
|
||||
/// Stagger duration post-parry if you fail to parry an attack
|
||||
var/parry_failed_stagger_duration = 3.5 SECONDS
|
||||
#warn implement
|
||||
/// Clickdelay duration post-parry if you fail to parry an attack
|
||||
var/parry_failed_clickcd_duration = 2 SECONDS
|
||||
#warn implement
|
||||
|
||||
/**
|
||||
* Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag.
|
||||
@@ -155,174 +135,3 @@ GLOBAL_LIST_EMPTY(block_parry_data)
|
||||
if(!div)
|
||||
return
|
||||
return total/div //groan
|
||||
|
||||
/**
|
||||
* Gets this item's datum/block_parry_data
|
||||
*/
|
||||
/obj/item/proc/get_block_parry_data()
|
||||
return return_block_parry_datum(block_parry_data)
|
||||
|
||||
/mob/living/proc/handle_block_parry(seconds = 1)
|
||||
if(active_blocking)
|
||||
var/datum/block_parry_data/data = return_block_parry_datum(active_block_item.block_parry_data)
|
||||
adjustStaminaLossBuffered(data.block_stamina_cost_per_second * seconds)
|
||||
|
||||
//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, 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, 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, parry_time)
|
||||
|
||||
/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(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(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.
|
||||
*/
|
||||
/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_end + data.parry_time_active
|
||||
var/spindown_end = active_end + 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
|
||||
|
||||
/**
|
||||
* 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()
|
||||
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.attack_type_list_scan(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.attack_type_list_scan(data.parry_imperfect_falloff_percent_override, attack_type)
|
||||
if(isnull(falloff))
|
||||
falloff = data.parry_imperfect_falloff_percent
|
||||
. -= falloff * difference
|
||||
|
||||
/**
|
||||
* Gets the current decisecond "frame" of an active parry.
|
||||
*/
|
||||
/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(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)
|
||||
return BLOCK_NONE
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/efficiency = get_parry_efficiency(attack_type)
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
. = 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(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(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
|
||||
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)
|
||||
successful_parries += efficiency
|
||||
|
||||
/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()
|
||||
if(data.parry_sounds)
|
||||
playsound(src, pick(data.parry_sounds), 75)
|
||||
visible_message("<span class='danger'>[src] parries \the [attack_text][length(effect_text)? ", [english_list(effect_text)] [attacker]" : ""]!</span>")
|
||||
|
||||
/// Run counterattack if any
|
||||
/mob/living/proc/run_parry_countereffects(atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, list/return_list = list(), parry_efficiency)
|
||||
if(!isliving(attacker))
|
||||
return
|
||||
var/mob/living/L = attacker
|
||||
var/datum/block_parry_data/data = get_parry_data()
|
||||
var/list/effect_text = list()
|
||||
if(data.parry_data[PARRY_REFLEX_COUNTERATTACK])
|
||||
switch(data.parry_data[PARRY_REFLEX_COUNTERATTACK])
|
||||
if(PARRY_COUNTERATTACK_PROC)
|
||||
switch(parrying)
|
||||
if(ITEM_PARRY)
|
||||
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(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(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)
|
||||
active_parry_item.melee_attack_chain(src, attacker, null)
|
||||
if(UNARMED_PARRY)
|
||||
UnarmedAttack(attacker)
|
||||
if(MARTIAL_PARRY)
|
||||
UnarmedAttack(attacker)
|
||||
if(data.parry_data[PARRY_DISARM_ATTACKER])
|
||||
L.drop_all_held_items()
|
||||
effect_text += "disarming"
|
||||
if(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
|
||||
L.DefaultCombatKnockdown(data.parry_data[PARRY_KNOCKDOWN_ATTACKER])
|
||||
effect_text += "knocking them to the ground"
|
||||
if(data.parry_data[PARRY_STAGGER_ATTACKER])
|
||||
L.Stagger(data.parry_data[PARRY_STAGGER_ATTACKER])
|
||||
effect_text += "staggering"
|
||||
if(data.parry_data[PARRY_DAZE_ATTACKER])
|
||||
L.Daze(data.parry_data[PARRY_DAZE_ATTACKER])
|
||||
effect_text += "dazing"
|
||||
return effect_text
|
||||
|
||||
/// 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.get_block_parry_data()
|
||||
else if(parrying == UNARMED_PARRY)
|
||||
return return_block_parry_datum(block_parry_data)
|
||||
else if(parrying == MARTIAL_PARRY)
|
||||
return return_block_parry_datum(mind.martial_art.block_parry_data)
|
||||
|
||||
/// Effects
|
||||
/obj/effect/abstract/parry
|
||||
icon = 'icons/effects/block_parry.dmi'
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
layer = FLOAT_LAYER
|
||||
plane = FLOAT_PLANE
|
||||
vis_flags = VIS_INHERIT_LAYER|VIS_INHERIT_PLANE
|
||||
|
||||
/obj/effect/abstract/parry/main
|
||||
icon_state = "parry_bm_hold"
|
||||
|
||||
/obj/effect/abstract/parry/main/proc/run_animation(windup_time = 2, active_time = 5, spindown_time = 3, qdel_end = TRUE)
|
||||
QDEL_IN(src, windup_time + active_time + spindown_time)
|
||||
var/matrix/current = transform
|
||||
transform = matrix(0.1, 0, 0, 0, 0.1, 0)
|
||||
animate(src, transform = current, time = windup_time)
|
||||
sleep(active_time)
|
||||
flick(icon, "parry_bm_end")
|
||||
|
||||
@@ -27,6 +27,30 @@
|
||||
|
||||
var/mobility_flags = MOBILITY_FLAGS_DEFAULT
|
||||
|
||||
// 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
|
||||
// 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
|
||||
/// Whether or not the user is in the middle of an active parry. Set to [UNARMED_PARRY], [ITEM_PARRY], [MARTIAL_PARRY] if parrying.
|
||||
var/parrying = FALSE
|
||||
/// The itme the user is currently parrying with, if any.
|
||||
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
|
||||
/// Successful parries within the current parry cycle. It's a list of efficiency percentages.
|
||||
var/list/successful_parries
|
||||
|
||||
var/confused = 0 //Makes the mob move in random directions.
|
||||
|
||||
var/hallucination = 0 //Directly affects how long a mob will hallucinate for
|
||||
|
||||
Reference in New Issue
Block a user