From 289d581e6652facc16f7bdc50466993ef3974b42 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Thu, 26 Mar 2020 12:19:09 -0700 Subject: [PATCH] ok bet --- code/__DEFINES/combat/block.dm | 8 + code/__DEFINES/movespeed_modification.dm | 4 +- code/__DEFINES/traits.dm | 4 + code/_onclick/click.dm | 3 + code/game/objects/items.dm | 2 + code/modules/mob/inventory.dm | 1 + .../modules/mob/living/living_active_block.dm | 143 ++++++++++++++++++ .../modules/mob/living/living_active_parry.dm | 0 code/modules/mob/living/living_block.dm | 23 --- .../mob/living/living_blocking_parrying.dm | 66 ++------ code/modules/mob/mob.dm | 7 + tgstation.dme | 2 + 12 files changed, 188 insertions(+), 75 deletions(-) create mode 100644 code/modules/mob/living/living_active_block.dm create mode 100644 code/modules/mob/living/living_active_parry.dm diff --git a/code/__DEFINES/combat/block.dm b/code/__DEFINES/combat/block.dm index d3c6d1e90e..0391656506 100644 --- a/code/__DEFINES/combat/block.dm +++ b/code/__DEFINES/combat/block.dm @@ -20,6 +20,8 @@ #define BLOCK_TARGET_DODGED (1<<7) /// Meta-flag for run_block/do_run_block : By default, BLOCK_SUCCESS tells do_run_block() to assume the attack is completely blocked and not continue the block chain. If this is present, it will continue to check other items in the chain rather than stopping. #define BLOCK_CONTINUE_CHAIN (1<<8) +/// Attack should change the amount of damage incurred. This means something calling run_block() has to handle it! +#define BLOCK_CHANGE_DAMAGE (1<<9) /// For keys in associative list/block_return as we don't want to saturate our (somewhat) limited flags. #define BLOCK_RETURN_REDIRECT_METHOD "REDIRECT_METHOD" @@ -39,6 +41,12 @@ #define BLOCK_RETURN_NORMAL_BLOCK_CHANCE "normal_block_chance" /// Tells the caller about how many hits we can soak on average before our blocking fails. #define BLOCK_RETURN_BLOCK_CAPACITY "block_capacity" +/// Tells the caller we got blocked by active directional block. +#define BLOCK_RETURN_ACTIVE_BLOCK "active_block" +/// Tells the caller our damage mitigation for their attack. +#define BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED "damage_mitigated" +/// For [BLOCK_CHANGE_DAMAGE]. Set damage to this. +#define BLOCK_RETURN_SET_DAMAGE_TO "set_damage_to" /// Default if the above isn't set in the list. #define DEFAULT_REDIRECT_METHOD_PROJECTILE REDIRECT_METHOD_DEFLECT diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm index 76c326cec0..0c073b85d7 100644 --- a/code/__DEFINES/movespeed_modification.dm +++ b/code/__DEFINES/movespeed_modification.dm @@ -78,4 +78,6 @@ #define MOVESPEED_ID_COLD "COLD" #define MOVESPEED_ID_HUNGRY "HUNGRY" #define MOVESPEED_ID_DAMAGE_SLOWDOWN "DAMAGE" -#define MOVESPEED_ID_DAMAGE_SLOWDOWN_FLYING "FLYING" \ No newline at end of file +#define MOVESPEED_ID_DAMAGE_SLOWDOWN_FLYING "FLYING" + +#define MOVESPEED_ID_ACTIVE_BLOCK "ACTIVE_BLOCK" diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 4f4b7c6baa..a053db7575 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -278,3 +278,7 @@ #define CLOWNOP_TRAIT "clown-op" #define MEGAFAUNA_TRAIT "megafauna" #define DEATHSQUAD_TRAIT "deathsquad" +/// This trait is added by the active directional block system. +#define ACTIVE_BLOCK_TRAIT "active_block" +/// This trait is added by the parry system. +#define ACTIVE_PARRY_TRAIT "active_parry" diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index e8a57bb257..586758a279 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -17,6 +17,9 @@ // eg: 10*0.5 = 5 deciseconds of delay // DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK +/mob/proc/timeToNextMove() + return max(0, next_move - world.time) + /mob/proc/changeNext_move(num) next_move = world.time + ((num+next_move_adjust)*next_move_modifier) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 50a8273dbb..25ddefb817 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -367,6 +367,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) return ITALICS | REDUCE_RANGE /obj/item/proc/dropped(mob/user) + SHOULD_CALL_PARENT(TRUE) for(var/X in actions) var/datum/action/A = X A.Remove(user) @@ -378,6 +379,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) // called just as an item is picked up (loc is not yet changed) /obj/item/proc/pickup(mob/user) + SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) item_flags |= IN_INVENTORY diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 9f7d2067de..1c518c251c 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -334,6 +334,7 @@ else I.forceMove(newloc) I.dropped(src) + on_item_dropped(I) return TRUE //Outdated but still in use apparently. This should at least be a human proc. diff --git a/code/modules/mob/living/living_active_block.dm b/code/modules/mob/living/living_active_block.dm new file mode 100644 index 0000000000..9a67772ec8 --- /dev/null +++ b/code/modules/mob/living/living_active_block.dm @@ -0,0 +1,143 @@ +// 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 + /// The item the user is actively blocking with. + var/obj/item/active_block_item + +/mob/living/on_item_dropped(obj/item/I) + if(I == active_block_item) + stop_active_blocking() + return ..() + +/mob/living/proc/stop_active_blocking() + active_blocking = FALSE + active_block_item = null + REMOVE_TRAIT(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) + active_block_effect_end() + return TRUE + +/mob/living/proc/start_active_blocking(obj/item/I) + if(active_blocking) + 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]!") + active_blocking = TRUE + active_block_item = I + if(I.block_parry_data.block_lock_attacking) + ADD_TRAIT(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) + active_block_effect_start() + return TRUE + +/// 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_damage_absorption_override[attack_type] + var/efficiency = block_damage_damage_multiplier_override[attack_type] + var/limit = block_damage_limit_override[attack_type] + // must use isnulls to handle 0's. + if(isnull(absorption)) + absorption = block_damage_absorption + if(isnull(efficiency)) + efficiency = block_damage_damage_multiplier + if(isnull(limit)) + limit = block_damage_limit + // now we calculate damage to reduce. + var/final_damage = 0 + // apply limit + if(damage > limit) //clamp and apply overrun + final_damage += (damage - limit) + damage = limit + // apply absorption + damage -= min(absorption, damage) //this way if damage is less than absorption it 0's properly. + // apply multiplier to remaining + final_damage += (damage * efficiency) + return final_damage + +/// 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_stamina_efficiency_override[attack_type] + if(isnull(efficiency)) + efficiency = 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/held_index = owner.get_held_index_of_item(src) + var/obj/item/bodypart/BP = owner.hand_bodyparts[held_index] + if(!BP?.body_zone) + return owner.adjustStaminaLossBuffered(stamina_amount) //nah + var/zone = BP.body_zone + var/stamina_to_zone = block_stamina_limb_ratio * stamina_amount + var/stamina_to_chest = stamina_amount - stamina_to_zone + var/stamina_buffered = stamina_to_chest * block_stamina_buffer_ratio + stamina_to_chest -= stamina_buffered + owner.apply_damage(stamina_to_zone, STAMINA, zone) + owner.apply_damage(stamina_to_chest, STAMINA, BODY_ZONE_CHEST) + owner.adjustStaminaLossBuffered(stamina_buffered) + +/obj/item/proc/active_block(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_BLOCK)) + return + if(!CHECK_BITFIELD(item_flags, ITEM_CAN_BLOCK)) + return + var/incoming_direction = get_dir(get_turf(attacker) || get_turf(object)) + if(!can_block_direction(owner.dir, incoming_direction)) + return + block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE + var/damage_mitigated = active_block_damage_mitigation(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + var/final_damage = max(0, damage - damage_mitigated) + var/stamina_cost = active_block_stamina_cost(owner, object, final_damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + active_block_do_stamina_damage(owner, object, stamina_cost, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATED] = damage_mitigated + block_return[BLOCK_RETURN_SET_DAMAGE_TO] = final_damage + . = BLOCK_CHANGE_DAMAGE + if(final_damage <= 0) + . |= BLOCK_SUCCESS //full block + +/obj/item/proc/check_active_block(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_BLOCK)) + return + var/incoming_direction = get_dir(get_turf(attacker) || get_turf(object)) + if(!can_block_direction(owner.dir, incoming_direction)) + return + block_return[BLOCK_RETURN_ACTIVE_BLOCK] = TRUE + block_return[BLOCK_RETURN_ACTIVE_BLOCK_DAMAGE_MITIGATION] = active_block_damage_mitigation(owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + +/** + * 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 + +/** + * Checks if we can block from a specific direction from our direction. + * + * @params + * * our_dir - our direction. + * * their_dir - their direction. Must be a single direction, or NONE for an attack from the same tile. + */ +/obj/item/proc/can_block_direction(our_dir, their_dir) + if(our_dir != NORTH) + var/turn_angle = dir2angle(our_dir) + // dir2angle(), ss13 proc is clockwise so dir2angle(EAST) == 90 + // turn(), byond proc is counterclockwise so turn(NORTH, 90) == WEST + their_dir = turn(their_dir, turn_angle) + return (DIR2BLOCKDIR(their_dir) in blockable_directions()) + +/** + * can_block_direction but for "compound" directions to check all of them and return the number of directions that were blocked. + * + * @params + * * our_dir - our direction. + * * their_dirs - list of their directions as we cannot use bitfields here. + */ +/obj/item/proc/can_block_directions_multiple(our_dir, list/their_dirs) + . = FALSE + for(var/i in their_dirs) + . |= can_block_direction(our_dir, i) diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm new file mode 100644 index 0000000000..e69de29bb2 diff --git a/code/modules/mob/living/living_block.dm b/code/modules/mob/living/living_block.dm index 29cd7be888..dea23435b2 100644 --- a/code/modules/mob/living/living_block.dm +++ b/code/modules/mob/living/living_block.dm @@ -1,28 +1,5 @@ // This file has a weird name, but it's for anything related to the checks for shields, blocking, dodging, and similar "stop this attack before it actually impacts the target" as opposed to "defend once it has hit". -/* -/// Bitflags for check_block() and handle_block(). Meant to be combined. You can be hit and still reflect, for example, if you do not use BLOCK_SUCCESS. -/// Attack was not blocked -#define BLOCK_NONE NONE -/// Attack was blocked, do not do damage. THIS FLAG MUST BE THERE FOR DAMAGE/EFFECT PREVENTION! -#define BLOCK_SUCCESS (1<<1) - -/// The below are for "metadata" on "how" the attack was blocked. - -/// Attack was and should be reflected (NOTE: the SHOULD here is important, as it says "the thing blocking isn't handling the reflecting for you so do it yourself"!) -#define BLOCK_SHOULD_REFLECT (1<<2) -/// Attack was manually redirected (including reflected) by any means by the defender. For when YOU are handling the reflection, rather than the thing hitting you. (see sleeping carp) -#define BLOCK_REDIRECTED (1<<3) -/// Attack was blocked by something like a shield. -#define BLOCK_PHYSICAL_EXTERNAL (1<<4) -/// Attack was blocked by something worn on you. -#define BLOCK_PHYSICAL_INTERNAL (1<<5) -/// Attack should pass through. Like SHOULD_REFLECT but for.. well, passing through harmlessly. -#define BLOCK_SHOULD_PASSTHROUGH (1<<6) -/// Attack outright missed because the target dodged. Should usually be combined with SHOULD_PASSTHROUGH or something (see martial arts) -#define BLOCK_TARGET_DODGED (1<<7) -*/ - ///Check whether or not we can block, without "triggering" a block. Basically run checks without effects like depleting shields. Wrapper for do_run_block(). The arguments on that means the same as for this. /mob/living/proc/check_block(atom/object, damage, attack_text = "the attack", attack_type, armour_penetration, mob/attacker, def_zone, list/return_list) return do_run_block(FALSE, object, damage, attack_text, attack_type, armour_penetration, attacker, check_zone(def_zone), return_list) diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm index 412fe8ea82..4e42d2383c 100644 --- a/code/modules/mob/living/living_blocking_parrying.dm +++ b/code/modules/mob/living/living_blocking_parrying.dm @@ -1,11 +1,12 @@ // yell at me later for file naming // This file contains stuff relating to the new directional blocking and parry system. /mob/living - var/obj/item/active_block_item - var/obj/item/active_parry_item + /// Whether ot not the user is in the middle of an active parry. var/parrying = FALSE - var/parry_frame = 0 - + /// The itme the user is currently parrying with. + var/obj/item/active_parry_item + /// world.time of parry action start + var/parry_start_time = 0 GLOBAL_LIST_EMPTY(block_parry_data) @@ -51,17 +52,23 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Override upper bound of damage block, list(ATTACK_TYPE_DEFINE = absorption), see [block_damage_limit] var/list/block_damage_limit_override + /* + * NOTE: Overrides for attack types for most the block_stamina variables were removed, + * because at the time of writing nothing needed to use it. Add them if you need it, + * it should be pretty easy, just copy [active_block_damage_mitigation] + * for how to override with list. + */ + /// Default damage-to-stamina coefficient, higher is better. This is based on amount of damage BLOCKED, not initial damage, to prevent damage from "double dipping". var/block_stamina_efficiency = 2 /// Override damage-to-stamina coefficient, see [block_efficiency], this should be list(ATTACK_TYPE_DEFINE = coefficient_number) var/list/block_stamina_efficiency_override - /// Ratio of stamina incurred by blocking that goes to the arm holding the object instead of the chest. Has no effect if this is not held in hand. var/block_stamina_limb_ratio = 0.5 - /// Override stamina limb ratio, list(ATTACK_TYPE_DEFINE = absorption), see [block_stamina_limb_ratio] - var/list/block_stamina_limb_ratio_override + /// Ratio of stamina incurred by chest (so after [block_stamina_limb_ratio] runs) that is buffered. + var/block_stamina_buffer_ratio = 1 - /// Stamina dealt directly via adjustStaminaLoss() per SECOND of block. + /// Stamina dealt directly via adjustStaminaLossBuffered() per SECOND of block. var/block_stamina_cost_per_second = 1.5 /////////// PARRYING //////////// @@ -86,52 +93,9 @@ GLOBAL_LIST_EMPTY(block_parry_data) /// Efficiency in percent on perfect parry. var/parry_efficiency_perfect = 120 - - -/obj/item/proc/active_block(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_BLOCK)) - return - /// Yadda yadda WIP access block/parry data... - /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... -/obj/item/proc/check_active_block(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_BLOCK)) - return - /// Yadda yadda WIP access block/parry data... -/** - * 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 - -/** - * Checks if we can block from a specific direction from our direction. - * - * @params - * * our_dir - our direction. - * * their_dir - their direction. Must be a single direction, or NONE for an attack from the same tile. - */ -/obj/item/proc/can_block_direction(our_dir, their_dir) - if(our_dir != NORTH) - var/turn_angle = dir2angle(our_dir) - // dir2angle(), ss13 proc is clockwise so dir2angle(EAST) == 90 - // turn(), byond proc is counterclockwise so turn(NORTH, 90) == WEST - their_dir = turn(their_dir, turn_angle) - return (DIR2BLOCKDIR(their_dir) in blockable_directions()) - -/** - * can_block_direction but for "compound" directions to check all of them and return the number of directions that were blocked. - * - * @params - * * our_dir - our direction. - * * their_dirs - list of their directions as we cannot use bitfields here. - */ -/obj/item/proc/can_block_directions_multiple(our_dir, list/their_dirs) - . = FALSE - for(var/i in their_dirs) - . |= can_block_direction(our_dir, i) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 5225534cee..83845801cf 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1000,3 +1000,10 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0) for(var/obj/item/I in held_items) if(I.item_flags & SLOWS_WHILE_IN_HAND) . += I.slowdown + +/** + * Mostly called by doUnEquip() + * Like item dropped() on mob side. + */ +/mob/proc/on_item_dropped(obj/item/I) + return diff --git a/tgstation.dme b/tgstation.dme index 05bc4b848a..89200716a5 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2207,6 +2207,8 @@ #include "code\modules\mob\living\emote.dm" #include "code\modules\mob\living\life.dm" #include "code\modules\mob\living\living.dm" +#include "code\modules\mob\living\living_active_block.dm" +#include "code\modules\mob\living\living_active_parry.dm" #include "code\modules\mob\living\living_block.dm" #include "code\modules\mob\living\living_blocking_parrying.dm" #include "code\modules\mob\living\living_combat.dm"