Skip to content

Commit

Permalink
Ports timed_action_flags & centralizes do_afters (#2865)
Browse files Browse the repository at this point in the history
Ports:
- tgstation/tgstation#54409
- tgstation/tgstation#55172
- BeeStation/BeeStation-Hornet#8635
- BeeStation/BeeStation-Hornet#8678

Fix to #2899 
- tgstation/tgstation#82413

<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

This PR moves unique do_after behavior under a flag system. 

It also deletes do_mob & do_atom, and centralizes them under do_after

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game

Makes do_after behavior much more granular and easy to edit. Flag
systems are straight forward, and the docs I added tell you exactly what
their behavior is.

do_mob and do_atom are essentially just snowflake versions of do_after,
its much cleaner just to put em under the same umbrella.

<!-- Please add a short description of why you think these changes would
benefit the game. If you can't justify it in words, it might not be
worth adding. -->

## Changelog

:cl: rkz, Benjamin(benbot16), Rohesie, Qustinnus, jlsnow301
refactor: refactored do_afters to use timed_action_flags for unique args
code: centralized do_mob and do_atom into do_after
/:cl:

<!-- Both :cl:'s are required for the changelog to work! You can put
your name to the right of the first :cl: if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->

---------

Signed-off-by: Sun-Soaked <[email protected]>
Co-authored-by: Sun-Soaked <[email protected]>
  • Loading branch information
Tsar-Salat and Sun-Soaked authored Jun 2, 2024
1 parent 861735d commit 41aedd0
Show file tree
Hide file tree
Showing 83 changed files with 230 additions and 348 deletions.
4 changes: 4 additions & 0 deletions code/__DEFINES/do_afters.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#define DOAFTER_SOURCE_SURGERY "doafter_surgery"
#define DOAFTER_SOURCE_MECHADRILL "doafter_mechadrill"
#define DOAFTER_SOURCE_SURVIVALPEN "doafter_survivalpen"
#define DOAFTER_SOURCE_GETTING_UP "doafter_gettingup"
6 changes: 5 additions & 1 deletion code/__DEFINES/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,11 @@
#define WABBAJACK (1<<6)

#define SLEEP_CHECK_DEATH(X) sleep(X); if(QDELETED(src) || stat == DEAD) return;
#define INTERACTING_WITH(X, Y) (Y in X.do_afters)

#define DOING_INTERACTION(user, interaction_key) (LAZYACCESS(user.do_afters, interaction_key))
#define DOING_INTERACTION_LIMIT(user, interaction_key, max_interaction_count) ((LAZYACCESS(user.do_afters, interaction_key) || 0) >= max_interaction_count)
#define DOING_INTERACTION_WITH_TARGET(user, target) (LAZYACCESS(user.do_afters, target))
#define DOING_INTERACTION_WITH_TARGET_LIMIT(user, target, max_interaction_count) ((LAZYACCESS(user.do_afters, target) || 0) >= max_interaction_count)

/// If you examine the same atom twice in this timeframe, we call examine_more() instead of examine()
#define EXAMINE_MORE_TIME 1 SECONDS
Expand Down
10 changes: 10 additions & 0 deletions code/__DEFINES/timed_action.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// timed_action_flags parameter for 'proc/do_after'

// The user can move freely without canceling the do_after
#define IGNORE_USER_LOC_CHANGE (1<<0)
// The target can move freely without canceling the do_after
#define IGNORE_TARGET_LOC_CHANGE (1<<1)
/// Can do the action even if the item is no longer being held
#define IGNORE_HELD_ITEM (1<<2)
/// Can do the action even if the mob is incapacitated
#define IGNORE_INCAPACITATED (1<<3)
213 changes: 46 additions & 167 deletions code/__HELPERS/mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -241,66 +241,6 @@ GLOBAL_LIST_EMPTY(species_list)
else
return "unknown"

///Timed action involving two mobs, the user and the target.
/proc/do_mob(mob/user , mob/target, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks = null, ignore_loc_change = FALSE, hidden = FALSE)
if(!user || !target)
return FALSE

if(target && INTERACTING_WITH(user, target))
to_chat(user, "<span class='warning'>You're already interacting with [target]!</span>")
return

var/user_loc = user.loc

var/drifting = FALSE
if(!user.Process_Spacemove(0) && user.inertia_dir)
drifting = TRUE

var/target_loc = target.loc

LAZYADD(user.do_afters, target)
LAZYADD(target.targeted_by, user)
var/holding = user.get_active_held_item()
var/datum/progressbar/progbar
var/datum/cogbar/cog
if (progress)
progbar = new(user, time, target)
if(!hidden && time >= 1 SECONDS)
cog = new(user)

var/endtime = world.time+time
var/starttime = world.time
. = TRUE
while (world.time < endtime)
stoplag(1)
if(!QDELETED(progbar))
progbar.update(world.time - starttime)
if(QDELETED(user) || QDELETED(target))
. = FALSE
break
if(uninterruptible)
continue

if(drifting && !user.inertia_dir)
drifting = FALSE
user_loc = user.loc


if(!ignore_loc_change && ((!drifting && user.loc != user_loc) || target.loc != target_loc))
. = FALSE
break

if(user.get_active_held_item() != holding || user.incapacitated() || (extra_checks && !extra_checks.Invoke()))
. = FALSE
break
if(!QDELETED(progbar))
progbar.end_progress()

cog?.remove()
if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)

//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)
Expand All @@ -315,161 +255,100 @@ GLOBAL_LIST_EMPTY(species_list)
checked_health["health"] = health
return ..()

///Timed action involving one mob user. Target is optional.
/proc/do_after(mob/user, delay, needhand = TRUE, atom/target = null, progress = TRUE, datum/callback/extra_checks = null, hidden = FALSE)
/**
* Timed action involving one mob user. A target can also be specified, but it is optional.
*
* Checks that `user` does not move, change hands, get stunned, etc. for the
* given `delay`. Returns `TRUE` on success or `FALSE` on failure.
*
* Arguments:
* * user - the primary "user" of the do_after.
* * delay - how long the do_after takes. Defaults to 3 SECONDS.
* * target - the (optional) target mob of the do_after. If they move/cease to exist, the do_after is cancelled.
* * timed_action_flags - optional flags to override certain do_after checks (see DEFINES/timed_action.dm).
* * progress - if TRUE, a progress bar is displayed.
* * extra_checks - a callback that can be used to add extra checks to the do_after. Returning false in this callback will cancel the do_after.
*/
/proc/do_after(mob/user, delay = 3 SECONDS, atom/target, timed_action_flags = NONE, progress = TRUE, datum/callback/extra_checks, interaction_key, max_interact_count = 1, hidden = FALSE)
if(!user)
return FALSE
if(!isnum(delay))
CRASH("do_after was passed a non-number delay: [delay || "null"].")

if(target && INTERACTING_WITH(user, target))
if(target && DOING_INTERACTION_WITH_TARGET(user, target))
to_chat(user, "<span class='warning'>You're already interacting with [target]!</span>")
return

var/atom/Tloc = null
if(target && !isturf(target))
Tloc = target.loc

if(target)
LAZYADD(user.do_afters, target)
LAZYADD(target.targeted_by, user)
if(!interaction_key && target)
interaction_key = target //Use the direct ref to the target
if(interaction_key) //Do we have a interaction_key now?
var/current_interaction_count = LAZYACCESS(user.do_afters, interaction_key) || 0
if(current_interaction_count >= max_interact_count) //We are at our peak
return
LAZYSET(user.do_afters, interaction_key, current_interaction_count + 1)

var/atom/Uloc = user.loc
var/atom/user_loc = user.loc
var/atom/target_loc = target?.loc

var/drifting = FALSE
if(!user.Process_Spacemove(0) && user.inertia_dir)
drifting = TRUE

var/holding = user.get_active_held_item()

var/holdingnull = TRUE //User's hand started out empty, check for an empty hand
if(holding)
holdingnull = FALSE //Users hand started holding something, check to see if it's still holding that

delay *= user.do_after_coefficent()

var/datum/progressbar/progbar
var/datum/cogbar/cog

if(progress)
progbar = new(user, delay, target || user)
if(user.client)
progbar = new(user, delay, target || user)

if(!hidden && delay >= 1 SECONDS)
cog = new(user)

var/endtime = world.time + delay
var/starttime = world.time
. = TRUE
while (world.time < endtime)
stoplag(1)

if(!QDELETED(progbar))
progbar.update(world.time - starttime)

if(drifting && !user.inertia_dir)
drifting = FALSE
Uloc = user.loc
user_loc = user.loc

if(QDELETED(user) || user.stat || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke()))
// Check flags
if(QDELETED(user) \
|| (!(timed_action_flags & IGNORE_USER_LOC_CHANGE) && !drifting && user.loc != user_loc) \
|| (!(timed_action_flags & IGNORE_HELD_ITEM) && user.get_active_held_item() != holding) \
|| (!(timed_action_flags & IGNORE_INCAPACITATED) && HAS_TRAIT(user, TRAIT_INCAPACITATED)) \
|| (extra_checks && !extra_checks.Invoke()))
. = FALSE
break

if(isliving(user))
var/mob/living/L = user
if(L.IsStun() || L.IsParalyzed())
. = FALSE
break

if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc))
if((Uloc != Tloc || Tloc != user) && !drifting)
. = FALSE
break

if(target && !(target in user.do_afters))
// If we have a target, we check for them moving here. We don't care about it if we're drifting or we ignore target loc change
if(target && (user != target) && \
(QDELETED(target) \
|| (!(timed_action_flags & IGNORE_TARGET_LOC_CHANGE) && target.loc != target_loc)))
. = FALSE
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)
. = FALSE
break
if(user.get_active_held_item() != holding)
. = FALSE
break
if(!QDELETED(progbar))
progbar.end_progress()

cog?.remove()

if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)
if(interaction_key)
LAZYREMOVE(user.do_afters, interaction_key)

/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
. = 1
return

///Timed action involving at least one mob user and a list of targets.
/proc/do_after_mob(mob/user, list/targets, time = 3 SECONDS, uninterruptible = FALSE, progress = TRUE, datum/callback/extra_checks)
if(!user)
return FALSE
if(!islist(targets))
targets = list(targets)
if(!length(targets))
return FALSE

for(var/i in targets)
var/mob/living/target = i
if(INTERACTING_WITH(user, target))
to_chat(user, "<span class='warning'>You're already interacting with [target]!</span>")
return


var/user_loc = user.loc

var/drifting = FALSE
if(!user.Process_Spacemove(0) && user.inertia_dir)
drifting = TRUE

var/list/originalloc = list()
for(var/atom/target in targets)
originalloc[target] = target.loc
LAZYADD(user.do_afters, target)
LAZYADD(target.targeted_by, user)

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
. = TRUE
mainloop:
while(world.time < endtime)
stoplag(1)
if(!QDELETED(progbar))
progbar.update(world.time - starttime)
if(QDELETED(user) || !targets)
. = FALSE
break
if(uninterruptible)
continue

if(drifting && !user.inertia_dir)
drifting = FALSE
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() || (extra_checks && !extra_checks.Invoke()))
. = FALSE
break mainloop
if(!QDELETED(progbar))
progbar.end_progress()

for(var/thing in targets)
var/atom/target = thing
if(!QDELETED(target))
LAZYREMOVE(user.do_afters, target)
LAZYREMOVE(target.targeted_by, user)

/proc/is_species(A, species_datum)
. = FALSE
if(ishuman(A))
Expand Down
38 changes: 0 additions & 38 deletions code/__HELPERS/unsorted.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1329,44 +1329,6 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new)
temp = ((temp + (temp>>3))&29127) % 63 //070707
return temp

//same as do_mob except for movables and it allows both to drift and doesn't draw progressbar
/proc/do_atom(atom/movable/user , atom/movable/target, time = 30, uninterruptible = 0,datum/callback/extra_checks = null)
if(!user || !target)
return TRUE
var/user_loc = user.loc

var/drifting = FALSE
if(!user.Process_Spacemove(0) && user.inertia_dir)
drifting = TRUE

var/target_drifting = FALSE
if(!target.Process_Spacemove(0) && target.inertia_dir)
target_drifting = TRUE

var/target_loc = target.loc

var/endtime = world.time+time
. = TRUE
while (world.time < endtime)
stoplag(1)
if(QDELETED(user) || QDELETED(target))
. = 0
break
if(uninterruptible)
continue

if(drifting && !user.inertia_dir)
drifting = FALSE
user_loc = user.loc

if(target_drifting && !target.inertia_dir)
target_drifting = FALSE
target_loc = target.loc

if((!drifting && user.loc != user_loc) || (!target_drifting && target.loc != target_loc) || (extra_checks && !extra_checks.Invoke()))
. = FALSE
break

//returns a GUID like identifier (using a mostly made up record format)
//guids are not on their own suitable for access or security tokens, as most of their bits are predictable.
// (But may make a nice salt to one)
Expand Down
8 changes: 6 additions & 2 deletions code/datums/components/butchering.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@
/datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user)
to_chat(user, "<span class='notice'>You begin to butcher [M]...</span>")
playsound(M.loc, butcher_sound, 50, TRUE, -1)
if(do_mob(user, M, speed) && M.Adjacent(source))
if(do_after(user, speed, M) && M.Adjacent(source))
Butcher(user, M)

/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user)
if(DOING_INTERACTION_WITH_TARGET(user, H))
to_chat(user, "<span class = 'warning'>You're already interacting with [H]!</span>")
return

user.visible_message("<span class='danger'>[user] is slitting [H]'s throat!</span>", \
"<span class='danger'>You start slicing [H]'s throat!</span>", \
"<span class='hear'>You hear a cutting noise!</span>", ignored_mobs = H)
Expand All @@ -63,7 +67,7 @@
log_combat(user, H, "starts slicing the throat of")

playsound(H.loc, butcher_sound, 50, TRUE, -1)
if(do_mob(user, H, clamp(500 / source.force, 30, 100)) && H.Adjacent(source))
if(do_after(user, clamp(500 / source.force, 30, 100), H) && H.Adjacent(source))
if(H.has_status_effect(/datum/status_effect/neck_slice))
user.show_message("<span class='warning'>[H]'s neck has already been already cut, you can't make the bleeding any worse!</span>", MSG_VISUAL, \
"<span class='warning'>Their neck has already been already cut, you can't make the bleeding any worse!</span>")
Expand Down
4 changes: 2 additions & 2 deletions code/datums/components/edible.dm
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Behavior that's still missing from this component that original food items had t
. = COMPONENT_ITEM_NO_ATTACK //Point of no return I suppose

if(eater == feeder)//If you're eating it yourself.
if(!do_mob(feeder, eater, eat_time)) //Gotta pass the minimal eat time
if(!do_after(feeder, eat_time, eater)) //Gotta pass the minimal eat time
return
var/eatverb = pick(eatverbs)
if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
Expand Down Expand Up @@ -143,7 +143,7 @@ Behavior that's still missing from this component that original food items had t
eater.visible_message("<span class='warning'>[feeder] cannot force any more of [parent] down [eater]'s throat!</span>", \
"<span class='warning'>[feeder] cannot force any more of [parent] down your throat!</span>")
return
if(!do_mob(feeder, eater)) //Wait 3 seconds before you can feed
if(!do_after(feeder, target = eater)) //Wait 3 seconds before you can feed
return

log_combat(feeder, eater, "fed", owner.reagents.log_list())
Expand Down
Loading

0 comments on commit 41aedd0

Please sign in to comment.