From 234687c5c848b14f522f1df8cb9c8d9eb7566495 Mon Sep 17 00:00:00 2001
From: kevinz000 <2003111+kevinz000@users.noreply.github.com>
Date: Sat, 11 Apr 2020 05:10:07 -0700
Subject: [PATCH] do_after_advanced
---
code/__DEFINES/flags/do_after.dm | 32 ++
code/__HELPERS/do_after.dm | 329 ++++++++++++++++++
code/__HELPERS/mobs.dm | 166 ---------
code/modules/keybindings/bindings_mob.dm | 17 +-
.../mob/living/carbon/human/human_defines.dm | 4 +-
.../modules/mob/living/living_active_block.dm | 51 ++-
.../modules/mob/living/living_active_parry.dm | 1 +
.../mob/living/living_blocking_parrying.dm | 2 +
tgstation.dme | 2 +
9 files changed, 431 insertions(+), 173 deletions(-)
create mode 100644 code/__DEFINES/flags/do_after.dm
create mode 100644 code/__HELPERS/do_after.dm
diff --git a/code/__DEFINES/flags/do_after.dm b/code/__DEFINES/flags/do_after.dm
new file mode 100644
index 0000000000..ffef70afbb
--- /dev/null
+++ b/code/__DEFINES/flags/do_after.dm
@@ -0,0 +1,32 @@
+/// Requires absolute stillness from the user
+#define DO_AFTER_DISALLOW_MOVING_ABSOLUTE_USER (1<<0)
+/// Requires absolute stillness from the target
+#define DO_AFTER_DISALLOW_MOVING_ABSOLUTE_TARGET (1<<1)
+/// Requires that the user is on a turf.
+#define DO_AFTER_REQUIRES_USER_ON_TURF (1<<2)
+/// Requires relative stillness to our target via dx and dy coordinate difference but only if both are spacedrifting. Specify DO_AFTER_ALLOW_NONSPACEDRIFT_RELATIVITY to say otherwise.
+#define DO_AFTER_DISALLOW_MOVING_RELATIVE (1<<3)
+/// Breaks if active hand item changes. Requires a tool be specified, otherwise defaults to active item
+#define DO_AFTER_DISALLOW_ACTIVE_ITEM_CHANGE (1<<4)
+/// Breaks if the user has no free hands. If a tool is specified, allows that as well.
+#define DO_AFTER_REQUIRE_FREE_HAND_OR_TOOL (1<<5)
+/// Do not display progressbar.
+#define DO_AFTER_NO_PROGRESSBAR (1<<6)
+/// Do not check do_after_coefficient()
+#define DO_AFTER_NO_COEFFICIENT (1<<7)
+/// For relative stillness, allow non spacedrift relative movement
+#define DO_AFTER_ALLOW_NONSPACEDRIFT_RELATIVITY (1<<8)
+
+/// Ignores checks.
+#define DO_AFTER_PROCEED 2
+/// Uses all other checks
+#define DO_AFTER_CONTINUE NONE
+/// Breaks
+#define DO_AFTER_STOP 1
+
+/// Stage - initiating a do_after
+#define DO_AFTER_STARTING 1
+/// Stage - main loop of a do_after
+#define DO_AFTER_PROGRESSING 2
+/// Stage - Last check of a do_after
+#define DO_AFTER_FINISHING 3
diff --git a/code/__HELPERS/do_after.dm b/code/__HELPERS/do_after.dm
new file mode 100644
index 0000000000..f9d54dd676
--- /dev/null
+++ b/code/__HELPERS/do_after.dm
@@ -0,0 +1,329 @@
+/**
+ * Higher overhead "advanced" version of do_after.
+ * @params
+ * - atom/user is the atom doing the action or the "physical" user
+ * - delay is time in deciseconds
+ * - atom/target is the atom the action is being done to, defaults to user
+ * - do_after_flags see __DEFINES/flags/do_after.dm for details.
+ * - datum/callback/extra_checks - Every time this ticks, extra_checks() is invoked with (user, delay, target, time_left, do_after_flags, required_mobility_flags, required_combat_flags, mob_redirect, stage, initially_held_item, tool).
+ * Stage can be DO_AFTER_STARTING, DO_AFTER_PROGRESSING, DO_AFTER_FINISHING
+ * If it returns DO_AFTER_STOP, this breaks.
+ * If it returns nothing, all other checks are done.
+ * If it returns DO_AFTER_PROCEED, all other checks are ignored.
+ * - required_mobility_flags is checked with CHECK_ALL_MOBILITY. Will immediately fail if the user isn't a mob.
+ * - requried_combat_flags is checked with CHECK_MULTIPLE_BITFIELDS. Will immediately fail if the user isn't a mob.
+ * - mob/living/mob_redirect - advanced option: If this is specified, movement and mobility/combat flag checks will use this instead of user. Progressbars will also go to this.
+ * - obj/item/tool - The tool we're using. See do_after flags for details.
+ */
+#define INVOKE_CALLBACK cb_return = extra_checks?.Invoke(user, delay, target, world.time - starttime, do_after_flags, required_mobility_flags, required_combat_flags, mob_redirect, stage, initiailly_held_item, tool)
+#define CHECK_FLAG_FAILURE ((required_mobility_flags || required_combat_flags) && (!living_user || !CHECK_ALL_MOBILITY(living_user, required_mobility_flags) || !CHECK_ALL_BITFIELDS(living_user.combat_flags, required_combat_flags)))
+#define TIMELEFT (world.time - starttime)
+/proc/do_after_advanced(atom/user, delay, atom/target, do_after_flags, datum/callback/extra_checks, required_mobility_flags, required_combat_flags, mob/living/mob_redirect, obj/item/tool)
+ // CHECK AND SET VARIABLES
+ if(!user)
+ return FALSE
+ if(!target)
+ target = user
+ if((user.loc == null) || (target.loc == null))
+ return FALSE
+ var/mob/living/living_user = mob_redirect
+ if(!living_user && isliving(user))
+ living_user = user
+ var/stage = DO_AFTER_STARTING
+ var/startlocuser = user.loc
+ var/startloctarget = target.loc
+ var/turf/userturf = get_turf(user)
+ var/turf/targetturf = get_turf(target)
+ if(!userturf || !targetturf)
+ return FALSE
+ if((do_after_flags & DO_AFTER_REQUIRES_USER_ON_TURF) && !isturf(user.loc))
+ return FALSE
+ var/starttime = world.time
+ var/endtime = world.time + delay
+ var/obj/item/initially_held_item = mob_redirect?.get_active_held_item()
+ if(!(do_after_flags & DO_AFTER_NO_COEFFICIENT) && living_user)
+ delay *= living_user.do_after_coefficient()
+ var/drifting = user.Process_Spacemove(NONE) && user.inertia_dir
+ var/initial_dx = targetturf.x - userturf.x
+ var/initial_dy = targetturf.y - userturf.y
+ var/dx = initial_dx
+ var/dy = initial_dy
+ // DO OUR STARTING CHECKS
+ var/cb_return
+ INVOKE_CALLBACK
+ if(cb_return == DO_AFTER_STOP)
+ return FALSE
+ else if(cb_return != DO_AFTER_PROCEED)
+ if(CHECK_FLAG_FAILURE)
+ return FALSE
+ // SETUP LOOP
+ var/datum/progressbar/progbar
+ if(living_user)
+ if(!(do_after_flags & DO_AFTER_NO_PROGRESSBAR))
+ progbar = new(living_user, delay, target)
+ // MAIN LOOP
+ . = TRUE
+ var/obj/item/held
+ var/locchanged
+ var/ctu
+ var/ctt
+ while(world.time < endtime)
+ stoplag(1)
+ progbar?.update(TIMELEFT)
+ if(QDELETED(user) || QDELETED(target) || (user.loc == null) || (target.loc == null))
+ . = FALSE
+ break
+ INVOKE_CALLBACK
+ if(cb_return == DO_AFTER_STOP)
+ . = FALSE
+ break
+ else if(cb_return == DO_AFTER_PROCEED)
+ continue
+ // otherwise, go through our normal checks.
+ if(((do_after_flags & DO_AFTER_DISALLOW_MOVING_ABSOLUTE_USER) && (user.loc != startlocuser)) || ((do_after_flags & DO_AFTER_DISALLOW_MOVING_ABSOLUTE_TARGET) && (target.loc != startloctarget)))
+ . = FALSE
+ break
+ else if(do_after_flags & DO_AFTER_DISALLOW_MOVING_RELATIVE)
+ ctu = get_turf(user)
+ ctt = get_turf(target)
+ locchanged = (user_turf != ctu) || (target_turf != ctt)
+ userturf = ctu
+ targetturf = ctt
+ dx = targetturf.x - userturf.x
+ dy = targetturf.y - userturf.y
+ if((dx != initial_dx) || (dy != initial_dy))
+ . = FALSE
+ break
+ if(loc_changed && !drifting && !(do_after_flags & DO_AFTER_ALLOW_NONSPACEDRIFT_RELATIVITY))
+ . = FALSE
+ break
+ if(!user.inertia_dir)
+ drifting = FALSE
+ if((do_after_flags & DO_AFTER_REQUIRES_USER_ON_TURF) && !isturf(user.loc))
+ return FALSE
+ if(CHECK_FLAG_FAILURE)
+ . = FALSE
+ break
+ held = living_user?.get_active_held_item()
+ if((do_after_flags & DO_AFTER_DISALLOW_ACTIVE_ITEM_CHANGE) && (held != (tool || initially_held_item)))
+ . = FALSE
+ break
+ if((do_after_flags & DO_AFTER_REQUIRE_FREE_HAND_OR_TOOL) && (!living_user?.is_holding(tool) && !length(living_user?.get_empty_held_indexes())))
+ . = FALSE
+ break
+
+ // CLEANUP
+ qdel(progbar)
+ // If we failed, just return.
+ if(!.)
+ return FALSE
+ // DO FINISHING CHECKS
+ if(QDELETED(user) || QDELETED(target))
+ return FALSE
+ INVOKE_CALLBACK
+ if(cb_return == DO_AFTER_STOP)
+ return FALSE
+ else if(cb_return != DO_AFTER_PROCEED)
+ if(CHECK_FLAG_FAILURE)
+ return FALSE
+ if(((do_after_flags & DO_AFTER_DISALLOW_MOVING_ABSOLUTE_USER) && (user.loc != startlocuser)) || ((do_after_flags & DO_AFTER_DISALLOW_MOVING_ABSOLUTE_TARGET) && (target.loc != startloctarget)))
+ . = FALSE
+ break
+ else if(do_after_flags & DO_AFTER_DISALLOW_MOVING_RELATIVE)
+ ctu = get_turf(user)
+ ctt = get_turf(target)
+ locchanged = (user_turf != ctu) || (target_turf != ctt)
+ userturf = ctu
+ targetturf = ctt
+ dx = targetturf.x - userturf.x
+ dy = targetturf.y - userturf.y
+ if((dx != initial_dx) || (dy != initial_dy))
+ . = FALSE
+ break
+ if(loc_changed && !drifting && !(do_after_flags & DO_AFTER_ALLOW_NONSPACEDRIFT_RELATIVITY))
+ . = FALSE
+ break
+ if((do_after_flags & DO_AFTER_REQUIRES_USER_ON_TURF) && !isturf(user.loc))
+ return FALSE
+ held = living_user?.get_active_held_item()
+ if((do_after_flags & DO_AFTER_DISALLOW_ACTIVE_ITEM_CHANGE) && (held != (tool || initially_held_item)))
+ . = FALSE
+ break
+ if((do_after_flags & DO_AFTER_REQUIRE_FREE_HAND_OR_TOOL) && (!living_user?.is_holding(tool) && !length(living_user?.get_empty_held_indexes())))
+ . = FALSE
+ break
+
+/// Requires absolute stillness in the user
+#define DO_AFTER_DISALLOW_MOVING_ABSOLUTE_USER (1<<0)
+/// Requires relative stillness to our target
+#define DO_AFTER_DISALLOW_MOVING_RELATIVE_TARGET (1<<1)
+
+#undef INVOKE_CALLBACK
+#undef CHECK_FLAG_FAILURE
+
+/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0)
+ if(!user || !target)
+ return 0
+ var/user_loc = user.loc
+
+ var/drifting = 0
+ if(!user.Process_Spacemove(0) && user.inertia_dir)
+ drifting = 1
+
+ var/target_loc = target.loc
+
+ var/holding = user.get_active_held_item()
+ var/datum/progressbar/progbar
+ if (progress)
+ progbar = new(user, time, target)
+
+ var/endtime = world.time+time
+ var/starttime = world.time
+ . = 1
+ while (world.time < endtime)
+ stoplag(1)
+ if (progress)
+ progbar.update(world.time - starttime)
+ if(QDELETED(user) || QDELETED(target))
+ . = 0
+ break
+ if(uninterruptible)
+ continue
+
+ if(drifting && !user.inertia_dir)
+ drifting = 0
+ user_loc = user.loc
+
+ if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
+ . = 0
+ break
+ if (progress)
+ qdel(progbar)
+
+
+//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action
+/mob/proc/break_do_after_checks(list/checked_health, check_clicks)
+ if(check_clicks && next_move > world.time)
+ return FALSE
+ return TRUE
+
+//pass a list in the format list("health" = mob's health var) to check health during this
+/mob/living/break_do_after_checks(list/checked_health, check_clicks)
+ if(islist(checked_health))
+ if(health < checked_health["health"])
+ return FALSE
+ checked_health["health"] = health
+ return ..()
+
+/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null, required_mobility_flags = (MOBILITY_USE|MOBILITY_MOVE))
+ if(!user)
+ return 0
+ var/atom/Tloc = null
+ if(target && !isturf(target))
+ Tloc = target.loc
+
+ var/atom/Uloc = user.loc
+
+ var/drifting = 0
+ if(!user.Process_Spacemove(0) && user.inertia_dir)
+ drifting = 1
+
+ var/holding = user.get_active_held_item()
+
+ var/holdingnull = 1 //User's hand started out empty, check for an empty hand
+ if(holding)
+ holdingnull = 0 //Users hand started holding something, check to see if it's still holding that
+
+ delay *= user.do_after_coefficent()
+
+ var/datum/progressbar/progbar
+ if (progress)
+ progbar = new(user, delay, target)
+
+ var/endtime = world.time + delay
+ var/starttime = world.time
+ . = 1
+ var/mob/living/L = isliving(user) && user //evals to last thing eval'd
+ while (world.time < endtime)
+ stoplag(1)
+ if (progress)
+ progbar.update(world.time - starttime)
+
+ if(drifting && !user.inertia_dir)
+ drifting = 0
+ Uloc = user.loc
+
+ if(L && !CHECK_ALL_MOBILITY(L, required_mobility_flags))
+ . = 0
+ break
+
+ if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke()))
+ . = 0
+ break
+
+ if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc))
+ if((Uloc != Tloc || Tloc != user) && !drifting)
+ . = 0
+ break
+
+ if(needhand)
+ //This might seem like an odd check, but you can still need a hand even when it's empty
+ //i.e the hand is used to pull some item/tool out of the construction
+ if(!holdingnull)
+ if(!holding)
+ . = 0
+ break
+ if(user.get_active_held_item() != holding)
+ . = 0
+ break
+ if (progress)
+ qdel(progbar)
+
+/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
+ . = 1
+ return
+
+/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks)
+ if(!user || !targets)
+ return 0
+ if(!islist(targets))
+ targets = list(targets)
+ var/user_loc = user.loc
+
+ var/drifting = 0
+ if(!user.Process_Spacemove(0) && user.inertia_dir)
+ drifting = 1
+
+ var/list/originalloc = list()
+ for(var/atom/target in targets)
+ originalloc[target] = target.loc
+
+ var/holding = user.get_active_held_item()
+ var/datum/progressbar/progbar
+ if(progress)
+ progbar = new(user, time, targets[1])
+
+ var/endtime = world.time + time
+ var/starttime = world.time
+ . = 1
+ mainloop:
+ while(world.time < endtime)
+ stoplag(1)
+ if(progress)
+ progbar.update(world.time - starttime)
+ if(QDELETED(user) || !targets)
+ . = 0
+ break
+ if(uninterruptible)
+ continue
+
+ if(drifting && !user.inertia_dir)
+ drifting = 0
+ user_loc = user.loc
+
+ for(var/atom/target in targets)
+ if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
+ . = 0
+ break mainloop
+ if(progbar)
+ qdel(progbar)
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index febd333578..1d5f360581 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -304,172 +304,6 @@ GLOBAL_LIST_EMPTY(species_list)
else
return "unknown"
-/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0)
- if(!user || !target)
- return 0
- var/user_loc = user.loc
-
- var/drifting = 0
- if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
-
- var/target_loc = target.loc
-
- var/holding = user.get_active_held_item()
- var/datum/progressbar/progbar
- if (progress)
- progbar = new(user, time, target)
-
- var/endtime = world.time+time
- var/starttime = world.time
- . = 1
- while (world.time < endtime)
- stoplag(1)
- if (progress)
- progbar.update(world.time - starttime)
- if(QDELETED(user) || QDELETED(target))
- . = 0
- break
- if(uninterruptible)
- continue
-
- if(drifting && !user.inertia_dir)
- drifting = 0
- user_loc = user.loc
-
- if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
- . = 0
- break
- if (progress)
- qdel(progbar)
-
-
-//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action
-/mob/proc/break_do_after_checks(list/checked_health, check_clicks)
- if(check_clicks && next_move > world.time)
- return FALSE
- return TRUE
-
-//pass a list in the format list("health" = mob's health var) to check health during this
-/mob/living/break_do_after_checks(list/checked_health, check_clicks)
- if(islist(checked_health))
- if(health < checked_health["health"])
- return FALSE
- checked_health["health"] = health
- return ..()
-
-/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null, required_mobility_flags = (MOBILITY_USE|MOBILITY_MOVE))
- if(!user)
- return 0
- var/atom/Tloc = null
- if(target && !isturf(target))
- Tloc = target.loc
-
- var/atom/Uloc = user.loc
-
- var/drifting = 0
- if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
-
- var/holding = user.get_active_held_item()
-
- var/holdingnull = 1 //User's hand started out empty, check for an empty hand
- if(holding)
- holdingnull = 0 //Users hand started holding something, check to see if it's still holding that
-
- delay *= user.do_after_coefficent()
-
- var/datum/progressbar/progbar
- if (progress)
- progbar = new(user, delay, target)
-
- var/endtime = world.time + delay
- var/starttime = world.time
- . = 1
- var/mob/living/L = isliving(user) && user //evals to last thing eval'd
- while (world.time < endtime)
- stoplag(1)
- if (progress)
- progbar.update(world.time - starttime)
-
- if(drifting && !user.inertia_dir)
- drifting = 0
- Uloc = user.loc
-
- if(L && !CHECK_ALL_MOBILITY(L, required_mobility_flags))
- . = 0
- break
-
- if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke()))
- . = 0
- break
-
- if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc))
- if((Uloc != Tloc || Tloc != user) && !drifting)
- . = 0
- break
-
- if(needhand)
- //This might seem like an odd check, but you can still need a hand even when it's empty
- //i.e the hand is used to pull some item/tool out of the construction
- if(!holdingnull)
- if(!holding)
- . = 0
- break
- if(user.get_active_held_item() != holding)
- . = 0
- break
- if (progress)
- qdel(progbar)
-
-/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
- . = 1
- return
-
-/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks)
- if(!user || !targets)
- return 0
- if(!islist(targets))
- targets = list(targets)
- var/user_loc = user.loc
-
- var/drifting = 0
- if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
-
- var/list/originalloc = list()
- for(var/atom/target in targets)
- originalloc[target] = target.loc
-
- var/holding = user.get_active_held_item()
- var/datum/progressbar/progbar
- if(progress)
- progbar = new(user, time, targets[1])
-
- var/endtime = world.time + time
- var/starttime = world.time
- . = 1
- mainloop:
- while(world.time < endtime)
- stoplag(1)
- if(progress)
- progbar.update(world.time - starttime)
- if(QDELETED(user) || !targets)
- . = 0
- break
- if(uninterruptible)
- continue
-
- if(drifting && !user.inertia_dir)
- drifting = 0
- user_loc = user.loc
-
- for(var/atom/target in targets)
- if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
- . = 0
- break mainloop
- if(progbar)
- qdel(progbar)
/proc/is_species(A, species_datum)
. = FALSE
diff --git a/code/modules/keybindings/bindings_mob.dm b/code/modules/keybindings/bindings_mob.dm
index 36b4775c34..95a883dda7 100644
--- a/code/modules/keybindings/bindings_mob.dm
+++ b/code/modules/keybindings/bindings_mob.dm
@@ -10,12 +10,18 @@
else
stop_pulling()
return
- if("Insert", "G")
- a_intent_change(INTENT_HOTKEY_RIGHT)
+ if("Insert")
+ if(client.keys_held["Ctrl"])
+ keybind_toggle_active_blocking()
+ return
+ else
+ keybind_parry()
+ return
+ if("G")
+ keybind_parry()
return
if("F")
- a_intent_change(INTENT_HOTKEY_LEFT)
- return
+ keybind_start_active_blocking()
if("X", "Northeast") // Northeast is Page-up
swap_hand()
return
@@ -91,4 +97,7 @@
if("Alt")
toggle_move_intent()
return
+ if("F")
+ keybind_stop_active_blocking()
+ return
return ..()
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 68c312b19c..04e9e56244 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -11,7 +11,9 @@
status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH|CANSTAGGER
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
-
+
+ active_block_enabled = TRUE
+
//Hair colour and style
var/hair_color = "000"
var/hair_style = "Bald"
diff --git a/code/modules/mob/living/living_active_block.dm b/code/modules/mob/living/living_active_block.dm
index 3fff9a9c03..b5cdd8c556 100644
--- a/code/modules/mob/living/living_active_block.dm
+++ b/code/modules/mob/living/living_active_block.dm
@@ -2,6 +2,10 @@
/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
@@ -52,7 +56,7 @@
/mob/living/get_standard_pixel_x_offset()
. = ..()
- if(active_blocking)
+ if(active_blocking || active_block_starting)
if(dir & EAST)
. += 12
if(dir & WEST)
@@ -60,12 +64,55 @@
/mob/living/get_standard_pixel_y_offset()
. = ..()
- if(active_blocking)
+ if(active_blocking || active_block_starting)
if(dir & NORTH)
. += 12
if(dir & SOUTH)
. -= 12
+/**
+ * Proc called by keybindings to toggle active blocking.
+ */
+/mob/living/proc/keybind_toggle_active_blocking()
+ if(active_blocking)
+ return keybind_stop_active_blocking()
+ else
+ return keybind_start_active_blocking()
+
+/**
+ * Proc called by keybindings to start active blocking.
+ */
+/mob/living/proc/keybind_start_active_blocking()
+ if(active_blocking || active_block_starting)
+ return FALSE
+ if(!CHECK_BITFIELD(combat_flags, COMBAT_FLAG_COMBAT_ACTIVE))
+ to_chat(src, "You must be in combat mode to actively block!")
+ return FALSE
+ var/obj/item/I = get_active_held_item()
+ if(!I)
+ to_chat(src, "You can't block with your bare hands!")
+ return
+ if(!(I.item_flags & ITEM_CAN_BLOCK))
+ to_chat(src, "[I] is not capable of actively being used to block!")
+ return
+ var/datum/block_parry_data/data = get_block_parry_data(I.block_parry_data)
+ var/delay = data.block_start_delay
+ active_block_starting = TRUE
+ animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = delay, FALSE, SINE_EASING | EASE_IN)
+ if(!do_after_advanced(src, delay, src, DO_AFTER_REQUIRES_USER_ON_TURF|DO_AFTER_NO_COEFFICIENT|DO_AFTER_DISALLOW_ACTIVE_ITEM_CHANGE, null, MOBILITY_USE, COMBAT_FLAG_COMBAT_ACTIVE, null, I))
+ to_chat(src, "You fail to raise [src].")
+ animate(src, pixel_x = get_standard_pixel_x_offset(), pixel_y = get_standard_pixel_y_offset(), time = 2.5, FALSE, SINE_EASING | EASE_IN, ANIMATION_END_NOW)
+ return
+ active_block_starting = FALSe
+ start_active_blocking(I)
+
+/**
+ * Proc called by keybindings to stop active blocking.
+ */
+/mob/living/proc/keybind_stop_active_blocking()
+ stop_active_blocking(FALSE)
+ 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/datum/block_parry_data/data = get_block_parry_data(block_parry_data)
diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm
index e69de29bb2..8ba203d59c 100644
--- a/code/modules/mob/living/living_active_parry.dm
+++ b/code/modules/mob/living/living_active_parry.dm
@@ -0,0 +1 @@
+/mob/living/proc/keybind_parry()
diff --git a/code/modules/mob/living/living_blocking_parrying.dm b/code/modules/mob/living/living_blocking_parrying.dm
index 7359d07cb0..83de7d65c7 100644
--- a/code/modules/mob/living/living_blocking_parrying.dm
+++ b/code/modules/mob/living/living_blocking_parrying.dm
@@ -47,6 +47,8 @@ GLOBAL_LIST_EMPTY(block_parry_data)
var/block_lock_attacking = TRUE
/// The priority we get in [mob/do_run_block()] while we're being used to parry.
var/block_active_priority = BLOCK_PRIORITY_ACTIVE_BLOCK
+ /// Windup before we have our blocking active.
+ var/block_start_delay = 5
/// Amount of "free" damage blocking absorbs
var/block_damage_absorption = 10
diff --git a/tgstation.dme b/tgstation.dme
index 502103ab79..33a25f6345 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -124,6 +124,7 @@
#include "code\__DEFINES\dcs\flags.dm"
#include "code\__DEFINES\dcs\helpers.dm"
#include "code\__DEFINES\dcs\signals.dm"
+#include "code\__DEFINES\flags\do_after.dm"
#include "code\__HELPERS\_cit_helpers.dm"
#include "code\__HELPERS\_lists.dm"
#include "code\__HELPERS\_logging.dm"
@@ -134,6 +135,7 @@
#include "code\__HELPERS\custom_holoforms.dm"
#include "code\__HELPERS\dates.dm"
#include "code\__HELPERS\dna.dm"
+#include "code\__HELPERS\do_after.dm"
#include "code\__HELPERS\donator_groupings.dm"
#include "code\__HELPERS\files.dm"
#include "code\__HELPERS\game.dm"