From 02b52e61429250e01c2f1b699c0ddc3ba74d8b3a Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Sat, 20 Mar 2021 16:02:26 -0700 Subject: [PATCH] framework --- code/_onclick/drag_drop.dm | 5 +- code/datums/martial/sleeping_carp.dm | 2 + code/game/objects/items.dm | 15 +++ code/game/objects/items/electrostaff.dm | 1 - code/game/objects/items/melee/misc.dm | 23 ++-- code/modules/client/client_defines.dm | 4 + .../modules/mob/living/living_active_block.dm | 2 +- .../modules/mob/living/living_active_parry.dm | 117 +++++++++++++----- .../mob/living/living_blocking_parrying.dm | 17 +-- code/modules/mob/living/living_defines.dm | 2 + code/modules/surgery/bodyparts/_bodyparts.dm | 2 +- 11 files changed, 132 insertions(+), 58 deletions(-) diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index 8e2e0c3fb5..43f2581de8 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -94,6 +94,10 @@ mouseControlObject = control if(mob) SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOUSEMOVE, object, location, control, params) + // god forgive me for i have sinned - used for autoparry. currently at 5 objects. + mousedOverObjects[object] = world.time + if(mousedOverObjects.len > 7) + mousedOverObjects.Cut(1, 2) ..() /client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params) @@ -115,7 +119,6 @@ if(active_mousedown_item) active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) - /obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) return diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm index f41529564d..d6e2b1a2c0 100644 --- a/code/datums/martial/sleeping_carp.dm +++ b/code/datums/martial/sleeping_carp.dm @@ -151,7 +151,9 @@ parry_efficiency_considered_successful = 65 // VERY generous parry_efficiency_perfect = 100 parry_failed_stagger_duration = 4 SECONDS + parry_failed_cooldown_duration = 2 SECONDS parry_cooldown = 0.5 SECONDS + parry_flags = NONE /mob/living/carbon/human/UseStaminaBuffer(amount, warn = FALSE, considered_action = TRUE) amount *= physiology? physiology.stamina_buffer_mod : 1 diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 8a09bca0bf..c3b0ab0d26 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -453,6 +453,21 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) item_flags |= IN_INVENTORY + if(combat_flags & (ITEM_CAN_BLOCK | ITEM_CAN_PARRY) && user.client && !(type in user.client.block_parry_hinted)) + var/list/dat = list("You have picked up an item that can be used to block and/or parry:") + // cit change - parry/block feedback + var/datum/block_parry_data/data = return_block_parry_datum(block_parry_data) + if(combat_flags & ITEM_CAN_BLOCK) + dat += "[src] can be used to block damage using directional block. Press your active block keybind to use it." + if(data.block_automatic_enabled) + dat += "[src] is also capable of automatically blocking damage, if you are facing the right direction (usually towards your attacker)!" + if(combat_flags & ITEM_CAN_PARRY) + dat += "[src] can be used to parry damage using active parry. Pressed your active parry keybind to initiate a timed parry sequence." + if(data.parry_automatic_enabled) + dat += "[src] is also capable of automatically parrying an incoming attack, if your mouse is over your attacker at the time if you being hit in a direct, melee attack." + dat += "Examine [src] to get a full readout of its block/parry stats." + to_chat(user, dat.Join("
")) + user.client.block_parry_hinted |= type // called when "found" in pockets and storage items. Returns 1 if the search should end. /obj/item/proc/on_found(mob/finder) diff --git a/code/game/objects/items/electrostaff.dm b/code/game/objects/items/electrostaff.dm index 71cebc38c6..52b5c556c9 100644 --- a/code/game/objects/items/electrostaff.dm +++ b/code/game/objects/items/electrostaff.dm @@ -61,7 +61,6 @@ TEXT_ATTACK_TYPE_PROJECTILE = 1 // extremely harsh window for projectiles ) // not extremely punishing to fail, but no spamming the parry. - parry_cooldown = 1.5 SECONDS parry_failed_stagger_duration = 1.5 SECONDS parry_failed_clickcd_duration = 1 SECONDS diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index 1043876061..c361c1e20d 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -72,15 +72,16 @@ block_parry_data = /datum/block_parry_data/captain_saber /datum/block_parry_data/captain_saber - parry_time_windup = 0.5 - parry_time_active = 4 - parry_time_spindown = 1 + parry_time_windup = 0 + parry_time_active = 6 + parry_time_spindown = 0 parry_time_perfect = 0.75 parry_time_perfect_leeway = 0.75 parry_imperfect_falloff_percent = 30 parry_efficiency_perfect = 100 parry_failed_stagger_duration = 3 SECONDS parry_failed_clickcd_duration = 1 SECONDS + parry_flags = NONE /obj/item/melee/sabre/Initialize() . = ..() @@ -176,28 +177,22 @@ // Fast, efficient parry. /datum/block_parry_data/traitor_rapier - parry_time_windup = 0.5 - parry_time_active = 5 + parry_time_windup = 0 + parry_time_active = 6 parry_time_spindown = 0 parry_time_active_visual_override = 3 parry_time_spindown_visual_override = 2 parry_flags = PARRY_DEFAULT_HANDLE_FEEDBACK | PARRY_LOCK_ATTACKING - parry_time_perfect = 0 - parry_time_perfect_leeway = 3 + parry_time_perfect = 1 + parry_time_perfect_leeway = 1 parry_time_perfect_leeway_override = list( TEXT_ATTACK_TYPE_PROJECTILE = 1 ) - parry_imperfect_falloff_percent_override = list( - TEXT_ATTACK_TYPE_PROJECTILE = 50 // useless after 3rd decisecond - ) parry_imperfect_falloff_percent = 30 parry_efficiency_to_counterattack = INFINITY parry_efficiency_considered_successful = 1 parry_efficiency_perfect = 100 - parry_data = list( - PARRY_DISARM_ATTACKER = TRUE, - PARRY_KNOCKDOWN_ATTACKER = 10 - ) + parry_stamina_cost = 5 parry_failed_stagger_duration = 2 SECONDS parry_failed_clickcd_duration = CLICK_CD_RANGE parry_cooldown = 0 diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 241cd6be93..a4d94e994c 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -183,3 +183,7 @@ //world.time of when the crew manifest can be accessed var/crew_manifest_delay + /// Should go in persistent round player data sometime. This tracks what items have already warned the user on pickup that they can block/parry. + var/list/block_parry_hinted = list() + /// moused over objects, currently capped at 7. this is awful, and should be replaced with a component to track it using signals for parrying at some point. + var/list/moused_over_objects = list() diff --git a/code/modules/mob/living/living_active_block.dm b/code/modules/mob/living/living_active_block.dm index e6f9a569a3..9ccaeb6c59 100644 --- a/code/modules/mob/living/living_active_block.dm +++ b/code/modules/mob/living/living_active_block.dm @@ -268,7 +268,7 @@ */ /obj/item/proc/blockable_directions(passive = FALSE) var/datum/block_parry_data/data = get_block_parry_data() - return passive? data.block_automatic_directions : data.can_block_directions + return (!isnull(data.block_automatic_directions) && passive)? data.block_automatic_directions : data.can_block_directions /** * Checks if we can block from a specific direction from our direction. diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm index c15b56afe8..bdd6174314 100644 --- a/code/modules/mob/living/living_active_parry.dm +++ b/code/modules/mob/living/living_active_parry.dm @@ -14,18 +14,57 @@ /** * Initiates a parrying sequence. */ -/mob/living/proc/initiate_parry_sequence() +/mob/living/proc/initiate_parry_sequence(silent = FALSE, list/override_method_data) if(parrying) - return // already parrying + return FALSE // already parrying if(!(mobility_flags & MOBILITY_USE)) - to_chat(src, "You can't move your arms!") - return + if(!silent) + to_chat(src, "You can't move your arms!") + return FALSE if(!(combat_flags & COMBAT_FLAG_PARRY_CAPABLE)) - to_chat(src, "You are not something that can parry attacks.") - return + if(!silent) + to_chat(src, "You are not something that can parry attacks.") + return FALSE if(!(mobility_flags & MOBILITY_STAND)) - to_chat(src, "You aren't able to parry without solid footing!") - return + if(!silent) + to_chat(src, "You aren't able to parry without solid footing!") + return FALSE + var/datum/block_parry_data/data + var/list/determined = override_method_data || determine_parry_method(FALSE, FALSE) + if(!islist(determined)) + return FALSE + var/method = determined[1] + data = return_block_parry_datum(determined[2]) + 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 && !CheckActionCooldown()) || ((parry_end_time_last + data.parry_cooldown) > world.time)) + if(!silent) + to_chat(src, "You are not ready to parry (again)!") + return FALSE + // Point of no return, make sure everything is set. + parrying = method + if(method == ITEM_PARRY) + active_parry_item = using_item + if(!UseStaminaBuffer(data.parry_stamina_cost, TRUE)) + return FALSE + parry_start_time = world.time + successful_parries = list() + successful_parry_counterattacks = 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 + +/** + * Massive snowflake proc for getting something to parry with. + * + * @return list of [method, data, tool], where method is the parry method define, data is the block_parry_data var that must be run through return_block_parry_data, and tool is the concept/object/martial art/etc used. + */ +/mob/living/proc/determine_parry_method(silent = TRUE, autoparry = FALSE) // Prioritize item, then martial art, then unarmed. // yanderedev else if time var/obj/item/using_item = get_active_held_item() @@ -55,7 +94,8 @@ var/list/other_items = list() var/list/override = list() if(SEND_SIGNAL(src, COMSIG_LIVING_ACTIVE_PARRY_START, method, tool, other_items, override) & COMPONENT_PREVENT_PARRY_START) - to_chat(src, "Something is preventing you from parrying!") + if(!silent) + to_chat(src, "Something is preventing you from parrying!") return if(length(override)) var/datum/thing = override[1] @@ -72,30 +112,10 @@ method = ITEM_PARRY data = using_item.block_parry_data if(!method) - to_chat(src, "You have nothing to parry with!") + if(!silent) + to_chat(src, "You have nothing to parry with!") 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 && !CheckActionCooldown()) || ((parry_end_time_last + data.parry_cooldown) > world.time)) - to_chat(src, "You are not ready to parry (again)!") - return FALSE - // Point of no return, make sure everything is set. - parrying = method - if(method == ITEM_PARRY) - active_parry_item = using_item - if(!UseStaminaBuffer(data.parry_stamina_cost, TRUE)) - return FALSE - 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 + return list(method, data, tool) /** * Tries to find a backup parry item. @@ -141,6 +161,7 @@ parry_start_time = 0 parry_end_time_last = world.time + (successful? 0 : data.parry_failed_cooldown_duration) successful_parries = null + successful_parry_counterattacks = null /** * Handles starting effects for parrying. @@ -200,6 +221,35 @@ */ /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, list/effect_text) +/** + * Attempts to automatically parry an attacker. + */ +/mob/living/proc/attempt_auto_parry() + // determine how we'll parry + var/list/determined = determine_parry_method(TRUE, TRUE) + if(!islist(determined)) + return FALSE + var/method = determined[1] + var/datum/block_parry_data/data = return_block_parry_datum(determined[2]) + if(!data.parry_automatic_enabled) + return FALSE + // before doing anything, check if the user moused over them properly + + // if that works, try to start parry + // first, check cooldowns + + // now, depending on if we're doing a single simulation or a full sequence + if(data.autoparry_sequence_simulation) + // for full sequence simulation + initiate_parry_sequence(TRUE, determined) + if(data.autoparry_sequence_start_time == -1) + parry_start_time = world.time - data.parry_time_windup + else + parry_start_time = world.time - data.autoparry_sequence_start_time + else + // for single attack block + + /** * Gets the stage of our parry sequence we're currently in. */ @@ -279,7 +329,7 @@ pacifist_counter_check = (!active_parry_item.force || active_parry_item.damtype == STAMINA) else pacifist_counter_check = FALSE //Both martial and unarmed counter attacks generally are harmful, so no need to have the same line twice. - if(efficiency >= data.parry_efficiency_to_counterattack && pacifist_counter_check && !return_list[BLOCK_RETURN_FORCE_NO_PARRY_COUNTERATTACK]) + if(efficiency >= data.parry_efficiency_to_counterattack && pacifist_counter_check && !return_list[BLOCK_RETURN_FORCE_NO_PARRY_COUNTERATTACK] && (!(attacker in successful_parry_counterattacks) && !data.parry_allow_repeated_counterattacks)) effect_text = 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) @@ -303,6 +353,7 @@ var/mob/living/L = attacker var/datum/block_parry_data/data = get_parry_data() var/list/effect_text = list() + successful_parry_counterattacks |= attacker // Always proc so items can override behavior easily switch(parrying) if(ITEM_PARRY) diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm index b273324028..36fb160e0f 100644 --- a/code/modules/mob/living/living_blocking_parrying.dm +++ b/code/modules/mob/living/living_blocking_parrying.dm @@ -98,15 +98,15 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Can this item automatically block? var/block_automatic_enabled = TRUE #warn have autoblock items warn user on pickup - /// Directions that you can autoblock in - var/block_automatic_directions = BLOCK_DIR_NORTH | BLOCK_DIR_NORTHEAST | BLOCK_DIR_NORTHWEST + /// Directions that you can autoblock in. Null to default to normal directions. + var/block_automatic_directions = null /// Effectiveness multiplier for automated block. Only applies to efficiency, absorption and limits stay the same! var/block_automatic_mitigation_multiplier = 0.33 /// Stamina cost multiplier for automated block var/block_automatic_stamina_multiplier = 1 /////////// PARRYING //////////// - /// Prioriry for [mob/do_run_block()] while we're being used to parry. + /// Priority 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 = TRUE @@ -178,12 +178,13 @@ GLOBAL_LIST_EMPTY(block_parry_data) var/perfect_parry_block_return_list var/imperfect_parry_block_return_list var/failed_parry_block_return_list + /// Allow multiple counterattacks per parry sequence. Bad idea. + var/parry_allow_repeated_counterattacks = FALSE // Auto parry // Anything not specified like cooldowns/clickdelay respecting is pulled from above. /// Can this data automatically parry? This is off by default because this is something that requires thought to balance. var/parry_automatic_enabled = FALSE -#warn implement autoparry in items #warn have autoparry items warn user on pickup /// Hard autoparry cooldown var/autoparry_cooldown_absolute = 3 SECONDS @@ -195,16 +196,17 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Clickdelay to inflict on single-attack autoparry var/autoparry_single_click_cooldown = 0 /// Single attack autoparry - efficiency - var/autoparry_single_efficiency = 60 + var/autoparry_single_efficiency = 75 /// Single attack autoparry - efficiency overrides by attack type, see above var/list/autoparry_single_efficiency_override // Parry sequence simulation: /// Decisecond of sequence to start on. -1 to start to 0th tick of active parry window. var/autoparry_sequence_start_time = -1 - /// Efficiency multiplier - var/autoparry_sequence_efficiency_multiplier = 0.8 // Clickdelay/cooldown settings not included, as well as whether or not to lock attack/sprinting/etc. They will be pulled from the above. + /// ADVANCED - Autoparry requirement for time since last moused over for a specific object + var/autoparry_mouse_delay_maximum = 0.5 + /** * Quirky proc to get average of flags in list that are in attack_type because why is attack_type a flag. */ @@ -346,6 +348,7 @@ GLOBAL_LIST_EMPTY(block_parry_data) RENDER_VARIABLE_SIMPLE(parry_cooldown, "Deciseconds it has to be since the last time a parry sequence ended for you before you can parry again.") RENDER_VARIABLE_SIMPLE(parry_failed_stagger_duration, "Deciseconds you are staggered for at the of the parry sequence if you do not successfully parry anything.") RENDER_VARIABLE_SIMPLE(parry_failed_clickcd_duration, "Deciseconds you are put on attack cooldown at the end of the parry sequence if you do not successfully parry anything.") + dat += "" dat += "" return dat.Join("") #undef RENDER_VARIABLE_SIMPLE diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 386f1174e1..14acf72f57 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -52,6 +52,8 @@ var/parry_end_time_last = 0 /// Successful parries within the current parry cycle. It's a list of efficiency percentages. var/list/successful_parries + /// Current parry counterattacks. Makes sure we can only counterattack someone once per parry. + var/list/successful_parry_counterattacks var/confused = 0 //Makes the mob move in random directions. diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index 9144eefdbc..83c587b81d 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -211,7 +211,7 @@ switch(animal_origin) if(ALIEN_BODYPART,LARVA_BODYPART) //aliens take some additional burn //nothing can burn with so much snowflake code around - burn *= 1.5 + burn *= 1.75 /* // START WOUND HANDLING