From a0b51bd7d9d691ff44043f2ed8ea67f49f667b12 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Thu, 29 Aug 2024 06:19:46 -0400 Subject: [PATCH 01/11] This --- citadel.dme | 1 + code/modules/ai/ai_pathing.dm | 2 +- code/modules/ai/ai_tracking.dm | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 code/modules/ai/ai_tracking.dm diff --git a/citadel.dme b/citadel.dme index 626a47b9f5ac..db63d5d5129d 100644 --- a/citadel.dme +++ b/citadel.dme @@ -2168,6 +2168,7 @@ #include "code\modules\admin\view_variables\topic_list.dm" #include "code\modules\admin\view_variables\view_variables.dm" #include "code\modules\ai\ai_pathing.dm" +#include "code\modules\ai\ai_tracking.dm" #include "code\modules\ai\holders\ai_holder-movement.dm" #include "code\modules\ai\holders\ai_holder-pathfinding.dm" #include "code\modules\ai\holders\ai_holder-scheduling.dm" diff --git a/code/modules/ai/ai_pathing.dm b/code/modules/ai/ai_pathing.dm index 9fe14ef0f440..aa949e6b6f3f 100644 --- a/code/modules/ai/ai_pathing.dm +++ b/code/modules/ai/ai_pathing.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 silicons *// +//* Copyright (c) 2024 Citadel Station Developers *// /** * Pathfinding results. diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm new file mode 100644 index 000000000000..eac34be3c0f7 --- /dev/null +++ b/code/modules/ai/ai_tracking.dm @@ -0,0 +1,16 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/datum/ai_tracking + +/atom/movable/var/datum/ai_tracking/ai_tracking + +/** + * Requests our AI tracking datum. + * + * * Will make one if it's not there. + * * Will keep an existing one alive. + */ +/atom/movable/proc/request_ai_tracking() + +#warn impl From 516088481bff1832036ae7cd522a6fa4d6949d29 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:52:16 -0400 Subject: [PATCH 02/11] slight refactoring --- citadel.dme | 2 +- .../{movement.dm => movable-movement.dm} | 136 ++++++++++-------- code/game/atoms/movable/movable.dm | 13 ++ code/game/atoms/movable/throwing.dm | 2 +- code/modules/ai/ai_tracking.dm | 87 ++++++++++- code/modules/mob/mob_defines.dm | 4 +- code/modules/mob/movement.dm | 12 +- code/modules/movespeed/movespeed_modifier.dm | 4 +- 8 files changed, 185 insertions(+), 75 deletions(-) rename code/game/atoms/movable/{movement.dm => movable-movement.dm} (89%) diff --git a/citadel.dme b/citadel.dme index db63d5d5129d..76466d3434f2 100644 --- a/citadel.dme +++ b/citadel.dme @@ -1019,8 +1019,8 @@ #include "code\game\atoms\movement.dm" #include "code\game\atoms\say.dm" #include "code\game\atoms\vv.dm" +#include "code\game\atoms\movable\movable-movement.dm" #include "code\game\atoms\movable\movable.dm" -#include "code\game\atoms\movable\movement.dm" #include "code\game\atoms\movable\pulling.dm" #include "code\game\atoms\movable\throwing.dm" #include "code\game\atoms\movable\vv.dm" diff --git a/code/game/atoms/movable/movement.dm b/code/game/atoms/movable/movable-movement.dm similarity index 89% rename from code/game/atoms/movable/movement.dm rename to code/game/atoms/movable/movable-movement.dm index 64c2e193c2be..c863b739177a 100644 --- a/code/game/atoms/movable/movement.dm +++ b/code/game/atoms/movable/movable-movement.dm @@ -208,81 +208,95 @@ if(glide_size_override) set_glide_size(glide_size_override, FALSE) - if(loc != newloc) - if (!(direct & (direct - 1))) //Cardinal move - . = ..() - else //Diagonal move, split it into cardinal moves - moving_diagonally = FIRST_DIAG_STEP - var/first_step_dir - // The `&& moving_diagonally` checks are so that a force_move taking - // place due to a Crossed, Bumped, etc. call will interrupt - // the second half of the diagonal movement, or the second attempt - // at a first half if step() fails because we hit something. - if (direct & NORTH) - if (direct & EAST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & WEST) - if (step(src, NORTH) && moving_diagonally) - first_step_dir = NORTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, NORTH) - else if (direct & SOUTH) - if (direct & EAST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, EAST) - else if (moving_diagonally && step(src, EAST)) - first_step_dir = EAST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - else if (direct & WEST) - if (step(src, SOUTH) && moving_diagonally) - first_step_dir = SOUTH - moving_diagonally = SECOND_DIAG_STEP - . = step(src, WEST) - else if (moving_diagonally && step(src, WEST)) - first_step_dir = WEST - moving_diagonally = SECOND_DIAG_STEP - . = step(src, SOUTH) - if(moving_diagonally == SECOND_DIAG_STEP) - if(!.) - setDir(first_step_dir) - else if (!inertia_moving) - inertia_next_move = world.time + inertia_move_delay - newtonian_move(direct) - moving_diagonally = NOT_IN_DIAG_STEP - --in_move - return - else // trying to move to the same place + var/time_since_last_move = world.time - last_move + last_move = world.time + + // trying to move to the same place + if(loc == newloc) if(direct) last_move_dir = direct setDir(direct) --in_move - return TRUE // not moving is technically success + ai_tracking?.track_movement(time_since_last_move, NONE) + // not moving is technically success + return TRUE + + //Cardinal move + if (!(direct & (direct - 1))) + . = ..() + //Diagonal move, split it into cardinal moves + else + moving_diagonally = FIRST_DIAG_STEP + var/first_step_dir + // The `&& moving_diagonally` checks are so that a force_move taking + // place due to a Crossed, Bumped, etc. call will interrupt + // the second half of the diagonal movement, or the second attempt + // at a first half if step() fails because we hit something. + if (direct & NORTH) + if (direct & EAST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & WEST) + if (step(src, NORTH) && moving_diagonally) + first_step_dir = NORTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, NORTH) + else if (direct & SOUTH) + if (direct & EAST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, EAST) + else if (moving_diagonally && step(src, EAST)) + first_step_dir = EAST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + else if (direct & WEST) + if (step(src, SOUTH) && moving_diagonally) + first_step_dir = SOUTH + moving_diagonally = SECOND_DIAG_STEP + . = step(src, WEST) + else if (moving_diagonally && step(src, WEST)) + first_step_dir = WEST + moving_diagonally = SECOND_DIAG_STEP + . = step(src, SOUTH) + if(moving_diagonally == SECOND_DIAG_STEP) + if(!.) + setDir(first_step_dir) + else if (!inertia_moving) + inertia_next_move = world.time + inertia_move_delay + newtonian_move(direct) + moving_diagonally = NOT_IN_DIAG_STEP + --in_move + return if(!loc || (loc == oldloc && oldloc != newloc)) last_move_dir = NONE --in_move - return + ai_tracking?.track_movement(time_since_last_move, NONE) + return FALSE - if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s) + //movement failed due to buckled mob(s) + if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) --in_move + ai_tracking?.track_movement(time_since_last_move, NONE) return FALSE if(.) Moved(oldloc, direct) + ai_tracking?.track_movement(time_since_last_move, direct) + else + ai_tracking?.track_movement(time_since_last_move, NONE) if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. if(pulling.anchored) diff --git a/code/game/atoms/movable/movable.dm b/code/game/atoms/movable/movable.dm index d2e4a5f95cf7..9be084a15cc5 100644 --- a/code/game/atoms/movable/movable.dm +++ b/code/game/atoms/movable/movable.dm @@ -25,6 +25,8 @@ //* AI Holders /// AI holder bound to us var/datum/ai_holder/ai_holder + /// AI tracking datum. Handled by procs in [code/modules/ai/ai_tracking.dm]. + var/datum/ai_tracking/ai_tracking //? Intrinsics /// movable flags - see [code/__DEFINES/_flags/atoms.dm] @@ -36,28 +38,39 @@ /// Set this to TRUE if we are not a [TILE_MOVER]! var/pixel_movement = FALSE /// Whatever we're pulling. + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/atom/movable/pulling /// Who's currently pulling us + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/atom/movable/pulledby /// If false makes [CanPass][/atom/proc/CanPass] call [CanPassThrough][/atom/movable/proc/CanPassThrough] on this type instead of using default behaviour + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/generic_canpass = TRUE /// Pass flags. var/pass_flags = NONE /// movement calls we're in + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/in_move = 0 /// a direction, or null + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/moving_diagonally = NOT_IN_DIAG_STEP /// attempt to resume grab after moving instead of before. This is what atom/movable is pulling us during move-from-pulling. + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/atom/movable/moving_from_pull /// Direction of our last move. + /// /// * this variable is not visible and should not be edited in the map editor. var/tmp/last_move_dir = NONE + /// world.time of our last move + /// + /// * this variable is not visible and should not be edited in the map editor. + var/tmp/last_move /// Our default glide_size. Null to use global default. var/default_glide_size /// Movement types, see [code/__DEFINES/flags/movement.dm] diff --git a/code/game/atoms/movable/throwing.dm b/code/game/atoms/movable/throwing.dm index b6b21f4ce20c..a5a41b6dd187 100644 --- a/code/game/atoms/movable/throwing.dm +++ b/code/game/atoms/movable/throwing.dm @@ -186,7 +186,7 @@ // user momentum var/user_speed = L.movement_delay() // 1 decisecond of margin - if(L.last_move_dir && (L.last_move_time >= (world.time - user_speed + 1))) + if(L.last_move_dir && (L.last_self_move >= (world.time - user_speed + 1))) user_speed = max(user_speed, world.tick_lag) // convert to tiles per **decisecond** user_speed = 1/user_speed diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index eac34be3c0f7..fa07250afbc7 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -1,9 +1,80 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2024 Citadel Station Developers *// +/** + * Datum to track a movable entity for things like motion and other predictive qualities. + * + * todo: either this or spatial grid signals need to allow for signal on projectile fire / attack / etc. + */ /datum/ai_tracking + //* System *// + /// last time we were requested + var/last_requested + /// time since last requested we should delete at + var/gc_timeout = 30 SECONDS + /// timerid + var/gc_timerid + + //* Movement *// + + // last movement record + var/movement_record_last + + // basic vel x / y immediate ; tracks within a second or two. only useful for current velocity. // + + var/imm_vel_x = 0 + var/imm_vel_y = 0 + + // average vel x / y ; uses a fast rolling average. use to suppress attempts at baiting shots. // + + var/fast_vel_x = 0 + var/fast_vel_y = 0 + + // movement volatility ; this is the approximate tile radius you need to aim to hit this person. this is high if they are rapidly dashing back and forth. // + + var/vel_volatile_radius = 0 -/atom/movable/var/datum/ai_tracking/ai_tracking +/datum/ai_tracking/Destroy() + if(gc_timerid) + deltimer(gc_timerid) + return ..() + +/** + * Notifies us that we've been requested. + */ +/datum/ai_tracking/proc/keep_alive() + src.last_requested = world.time + +/** + * Tracks movement state + * + * * time - time since last move + * * dir - direction of move. if it's just a Move() or otherwie standing still, this is NONE. + */ +/datum/ai_tracking/proc/track_movement(time, dir) + movement_record_last = world.time + var/sx + var/sy + switch(dir) + if(NORTH) + sy = 1 + if(SOUTH) + sy = -1 + if(EAST) + sx = 1 + if(WEST) + sx = -1 + #warn impl + +/** + * Tells us to flush movement state. + * + * * Always call this before checking movement vars. + */ +/datum/ai_tracking/proc/flush_movement() + if(movement_record_last == world.time) + return + track_movement(world.time - movement_record_last, NONE) /** * Requests our AI tracking datum. @@ -12,5 +83,17 @@ * * Will keep an existing one alive. */ /atom/movable/proc/request_ai_tracking() + RETURN_TYPE(/datum/ai_tracking) + if(src.ai_tracking) + return src.ai_tracking + src.ai_tracking = new + src.ai_tracking.keep_alive() + src.ai_tracking.gc_timerid = addtimer(CALLBACK(src, PROC_REF(expire_ai_tracking)), src.ai_tracking.gc_timeout, TIMER_LOOP, TIMER_STOPPABLE) + return src.ai_tracking -#warn impl +/atom/movable/proc/expire_ai_tracking() + if(!ai_tracking) + return + if(ai_tracking.last_requested + ai_tracking.gc_timeout > world.time) + return + QDEL_NULL(ai_tracking) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index f8d9fc7ab7f9..3c939862f217 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -79,9 +79,9 @@ /// Next world.time we will be able to move. var/move_delay = 0 /// Last world.time we finished a normal, non relay/intercepted move - var/last_move_time = 0 + var/last_self_move = 0 /// Last world.time we turned in our spot without moving (see: facing directions) - var/last_turn = 0 + var/last_self_turn = 0 /// Tracks if we have gravity from environment right now. var/in_gravity diff --git a/code/modules/mob/movement.dm b/code/modules/mob/movement.dm index a42342538f4a..2ace673f6e37 100644 --- a/code/modules/mob/movement.dm +++ b/code/modules/mob/movement.dm @@ -352,14 +352,14 @@ // preserve momentum: for non-evenly-0.5-multiple movespeeds (HELLO, DIAGONAL MOVES), // we need to store how much we're cheated out of our tick and carry it through // make an intelligent guess at if they're trying to keep moving, tho! - if(mob.last_move_time > (world.time - add_delay * 1.25)) + if(mob.last_self_move > (world.time - add_delay * 1.25)) mob.move_delay = old_delay + add_delay else mob.move_delay = world.time + add_delay SMOOTH_GLIDE_SIZE(mob, DELAY_TO_GLIDE_SIZE(add_delay)) - mob.last_move_time = world.time + mob.last_self_move = world.time /mob/proc/SelfMove(turf/T, dir) in_selfmove = TRUE @@ -554,7 +554,7 @@ if(!canface()) return FALSE setDir(EAST) - last_turn = world.time + last_self_turn = world.time return TRUE ///Hidden verb to turn west @@ -564,7 +564,7 @@ if(!canface()) return FALSE setDir(WEST) - last_turn = world.time + last_self_turn = world.time return TRUE ///Hidden verb to turn north @@ -574,7 +574,7 @@ if(!canface()) return FALSE setDir(NORTH) - last_turn = world.time + last_self_turn = world.time return TRUE ///Hidden verb to turn south @@ -584,7 +584,7 @@ if(!canface()) return FALSE setDir(SOUTH) - last_turn = world.time + last_self_turn = world.time return TRUE //! Pixel Shifting diff --git a/code/modules/movespeed/movespeed_modifier.dm b/code/modules/movespeed/movespeed_modifier.dm index 0cc2c6291271..97be21d22456 100644 --- a/code/modules/movespeed/movespeed_modifier.dm +++ b/code/modules/movespeed/movespeed_modifier.dm @@ -290,14 +290,14 @@ GLOBAL_LIST_EMPTY(movespeed_modification_cache) cached_multiplicative_slowdown = min(., 10 / MOVESPEED_ABSOLUTE_MINIMUM_TILES_PER_SECOND) if(!client) return - var/diff = (last_move_time - move_delay) - cached_multiplicative_slowdown + var/diff = (last_self_move - move_delay) - cached_multiplicative_slowdown if(diff > 0) // your delay decreases, "give" the delay back to the client if(move_delay > world.time + 1.5) move_delay -= diff #ifdef SMOOTH_MOVEMENT var/timeleft = world.time - move_delay - var/elapsed = world.time - last_move_time + var/elapsed = world.time - last_self_move var/glide_size_current = glide_size if((timeleft <= 0) || (elapsed > 20)) SMOOTH_GLIDE_SIZE(src, 16, TRUE) From 675878cdaff7978ec9847a1d5f7a83b02865b14b Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:21:19 -0400 Subject: [PATCH 03/11] that --- code/game/atoms/movable/movable-movement.dm | 19 ++++++++++---- code/modules/ai/ai_tracking.dm | 29 ++++++++++++++++++--- code/modules/mob/movement.dm | 2 +- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/code/game/atoms/movable/movable-movement.dm b/code/game/atoms/movable/movable-movement.dm index c863b739177a..91c6ceb264f1 100644 --- a/code/game/atoms/movable/movable-movement.dm +++ b/code/game/atoms/movable/movable-movement.dm @@ -278,25 +278,30 @@ newtonian_move(direct) moving_diagonally = NOT_IN_DIAG_STEP --in_move + // track movement if we're no longer in a move; this way this fires only once for diag steps + if(ai_tracking && !in_move) + ai_tracking.track_movement(time_since_last_move, . ? direct : NONE) return if(!loc || (loc == oldloc && oldloc != newloc)) last_move_dir = NONE --in_move - ai_tracking?.track_movement(time_since_last_move, NONE) + // track movement if we're no longer in a move; this way this fires only once for diag steps + if(ai_tracking && !in_move) + ai_tracking.track_movement(time_since_last_move, NONE) return FALSE //movement failed due to buckled mob(s) if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) + last_move_dir = NONE --in_move - ai_tracking?.track_movement(time_since_last_move, NONE) + // track movement if we're no longer in a move; this way this fires only once for diag steps + if(ai_tracking && !in_move) + ai_tracking.track_movement(time_since_last_move, NONE) return FALSE if(.) Moved(oldloc, direct) - ai_tracking?.track_movement(time_since_last_move, direct) - else - ai_tracking?.track_movement(time_since_last_move, NONE) if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. if(pulling.anchored) @@ -325,6 +330,10 @@ --in_move + // track movement if we're no longer in a move; this way this fires only once for diag steps + if(ai_tracking && !in_move) + ai_tracking.track_movement(time_since_last_move, direct) + // legacy move_speed = world.time - l_move_time l_move_time = world.time diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index fa07250afbc7..51fede8da2b5 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -22,18 +22,18 @@ // basic vel x / y immediate ; tracks within a second or two. only useful for current velocity. // + /// in tiles / second var/imm_vel_x = 0 + /// in tiles / second var/imm_vel_y = 0 // average vel x / y ; uses a fast rolling average. use to suppress attempts at baiting shots. // + /// in tiles / second var/fast_vel_x = 0 + /// in tiles / second var/fast_vel_y = 0 - // movement volatility ; this is the approximate tile radius you need to aim to hit this person. this is high if they are rapidly dashing back and forth. // - - var/vel_volatile_radius = 0 - /datum/ai_tracking/Destroy() if(gc_timerid) deltimer(gc_timerid) @@ -64,8 +64,29 @@ sx = 1 if(WEST) sx = -1 + + var/imm_old_multiplier = max(0, ((1 SECONDS) - time) / (1 SECONDS)) + var/imm_new_multiplier = 1 - imm_old_multiplier + imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx + imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy + + var/fast_old_multiplier = max(0, ((5 SECONDS) - time) / (5 SECONDS)) + var/fast_new_multiplier = 1 - fast_old_multiplier + fast_vel_x = (fast_vel_x) * fast_old_multiplier + sx / 5 + fast_vel_y = (fast_vel_y) * fast_old_multiplier + sx / 5 + + #warn impl + if(usr) + to_chat(world, SPAN_NOTICE("imm: [imm_vel_x], [imm_vel_y]; fast: [fast_vel_x], [fast_vel_y]; calc uncert rad: [get_uncertainty_radius()]")) + +/** + * radius in tiles you have to aim to hit this person from the projected center of their immediate velocity + */ +/datum/ai_tracking/proc/get_uncertainty_radius() + return sqrt((imm_vel_x - fast_vel_x) ** 2 + (imm_vel_y - fast_vel_y) ** 2) + /** * Tells us to flush movement state. * diff --git a/code/modules/mob/movement.dm b/code/modules/mob/movement.dm index 2ace673f6e37..11ee6bf01ee6 100644 --- a/code/modules/mob/movement.dm +++ b/code/modules/mob/movement.dm @@ -537,7 +537,7 @@ * * we are not restrained */ /mob/proc/canface() - if(world.time <= last_turn) + if(world.time <= last_self_turn) return FALSE if(stat == DEAD || stat == UNCONSCIOUS) return FALSE From fca7baaa35a7560a668c0aa2aab48740761b6062 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Thu, 29 Aug 2024 21:17:12 -0400 Subject: [PATCH 04/11] That --- code/__HELPERS/lists/string.dm | 20 ++++++++-- code/controllers/subsystem/air.dm | 4 +- code/game/atoms/movable/movable-movement.dm | 5 ++- code/modules/ai/ai_tracking.dm | 41 ++++++++++++++++----- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/code/__HELPERS/lists/string.dm b/code/__HELPERS/lists/string.dm index c6317712253e..5ba3d8fa9273 100644 --- a/code/__HELPERS/lists/string.dm +++ b/code/__HELPERS/lists/string.dm @@ -1,8 +1,22 @@ /** * Returns a list in plain english as a string. + * + * @params + * * input - the list to interpolate into strings + * * nothing_text - the text to emit if the list is empty + * * and_text - the text to emit on the last element instead of a comma + * * comma_text - the glue between elements + * * final_comma_text - the glue between the last two elements; usually empty for use with 'and_text' + * * limit - limit the entries processed. if the limit was reached, the last two elements will not use the usual glue text. + * * limit_text - text to append at the end if we were limited. defaults to "..." */ -/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) +/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "", limit, limit_text = "...") var/total = input.len + var/limited = FALSE + if(!isnull(limit)) + if(total > limit) + total = limit + limited = TRUE if (!total) return "[nothing_text]" else if (total == 1) @@ -13,13 +27,13 @@ var/output = "" var/index = 1 while (index < total) - if (index == total - 1) + if ((index == (total - 1)) && !limited) comma_text = final_comma_text output += "[input[index]][comma_text]" index++ - return "[output][and_text][input[index]]" + return "[output][limited ? "" : and_text][input[index]][limited? limit_text : ""]" /** * Removes a string from a list. diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 8609e1f99b1a..a7b59ec711b0 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -137,7 +137,7 @@ SUBSYSTEM_DEF(air) var/timer if(!resumed) if(LAZYLEN(currentrun) != 0) - stack_trace("Currentrun not empty before processing cycle when it should be. [english_list(currentrun)]") + stack_trace("Currentrun not empty before processing cycle when it should be. [english_list(currentrun, limit = 5)]") currentrun = list() if(current_step != null) stack_trace("current_step before processing cycle was [current_step] instead of null") @@ -152,7 +152,7 @@ SUBSYSTEM_DEF(air) // Okay, we're done! Woo! Got thru a whole SSair cycle! if(LAZYLEN(currentrun) != 0) - stack_trace("Currentrun not empty after processing cycle when it should be. [english_list(currentrun.Copy(1, min(currentrun.len, 5)))]") + stack_trace("Currentrun not empty after processing cycle when it should be. [english_list(currentrun, limit = 5)]") currentrun = null if(current_step != SSAIR_DONE) stack_trace("current_step after processing cycle was [current_step] instead of [SSAIR_DONE]") diff --git a/code/game/atoms/movable/movable-movement.dm b/code/game/atoms/movable/movable-movement.dm index 91c6ceb264f1..c278b13dd710 100644 --- a/code/game/atoms/movable/movable-movement.dm +++ b/code/game/atoms/movable/movable-movement.dm @@ -280,7 +280,7 @@ --in_move // track movement if we're no longer in a move; this way this fires only once for diag steps if(ai_tracking && !in_move) - ai_tracking.track_movement(time_since_last_move, . ? direct : NONE) + ai_tracking.track_movement(time_since_last_move, . ? direct : (moving_diagonally == SECOND_DIAG_STEP ? first_step_dir : NONE)) return if(!loc || (loc == oldloc && oldloc != newloc)) @@ -594,6 +594,9 @@ /atom/movable/proc/doMove(atom/destination) . = FALSE + // completely reset ai tracking + ai_tracking?.reset_movement() + ++in_move var/atom/oldloc = loc diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index 51fede8da2b5..e4318e60dd29 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -45,6 +45,8 @@ /datum/ai_tracking/proc/keep_alive() src.last_requested = world.time +//* Movement *// + /** * Tracks movement state * @@ -64,22 +66,32 @@ sx = 1 if(WEST) sx = -1 + if(NORTHWEST) + sy = 1 + sx = -1 + if(NORTHEAST) + sy = 1 + sx = 1 + if(SOUTHEAST) + sy = -1 + sx = 1 + if(SOUTHWEST) + sy = -1 + sx = -1 - var/imm_old_multiplier = max(0, ((1 SECONDS) - time) / (1 SECONDS)) + var/imm_old_multiplier = max(0, ((0.33 SECONDS) - time) / (0.33 SECONDS)) var/imm_new_multiplier = 1 - imm_old_multiplier - imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx - imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy + imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx * 3 + imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy * 3 - var/fast_old_multiplier = max(0, ((5 SECONDS) - time) / (5 SECONDS)) + var/fast_old_multiplier = max(0, ((2 SECONDS) - time) / (2 SECONDS)) var/fast_new_multiplier = 1 - fast_old_multiplier - fast_vel_x = (fast_vel_x) * fast_old_multiplier + sx / 5 - fast_vel_y = (fast_vel_y) * fast_old_multiplier + sx / 5 - + fast_vel_x = (fast_vel_x) * fast_old_multiplier + sx / 2 + fast_vel_y = (fast_vel_y) * fast_old_multiplier + sy / 2 #warn impl - if(usr) - to_chat(world, SPAN_NOTICE("imm: [imm_vel_x], [imm_vel_y]; fast: [fast_vel_x], [fast_vel_y]; calc uncert rad: [get_uncertainty_radius()]")) + to_chat(world, SPAN_NOTICE("imm: [round(imm_vel_x, 0.1)], [round(imm_vel_y, 0.1)]; fast: [round(fast_vel_x, 0.1)], [round(fast_vel_y, 0.1)]; calc uncert rad: [round(get_uncertainty_radius(), 0.1)]")) /** * radius in tiles you have to aim to hit this person from the projected center of their immediate velocity @@ -87,6 +99,13 @@ /datum/ai_tracking/proc/get_uncertainty_radius() return sqrt((imm_vel_x - fast_vel_x) ** 2 + (imm_vel_y - fast_vel_y) ** 2) +/** + * Tells us to completely drop movement state. + */ +/datum/ai_tracking/proc/reset_movement() + imm_vel_x = imm_vel_y = 0 + fast_vel_x = fast_vel_y = 0 + /** * Tells us to flush movement state. * @@ -97,6 +116,8 @@ return track_movement(world.time - movement_record_last, NONE) +//* /atom/movable API *// + /** * Requests our AI tracking datum. * @@ -109,7 +130,7 @@ return src.ai_tracking src.ai_tracking = new src.ai_tracking.keep_alive() - src.ai_tracking.gc_timerid = addtimer(CALLBACK(src, PROC_REF(expire_ai_tracking)), src.ai_tracking.gc_timeout, TIMER_LOOP, TIMER_STOPPABLE) + src.ai_tracking.gc_timerid = addtimer(CALLBACK(src, PROC_REF(expire_ai_tracking)), src.ai_tracking.gc_timeout, TIMER_LOOP | TIMER_STOPPABLE) return src.ai_tracking /atom/movable/proc/expire_ai_tracking() From 61c04403563c784048b7a531e8cb868f5502cfe3 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Fri, 30 Aug 2024 01:12:46 -0400 Subject: [PATCH 05/11] e --- code/modules/ai/ai_tracking.dm | 43 +++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index e4318e60dd29..80b526d8fb00 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -17,8 +17,14 @@ //* Movement *// - // last movement record - var/movement_record_last + // last movement flush + var/movement_flush_last + + // basic stores // + var/moved_x_fast + var/moved_y_fast + var/moved_x_slow + var/moved_y_slow // basic vel x / y immediate ; tracks within a second or two. only useful for current velocity. // @@ -54,7 +60,6 @@ * * dir - direction of move. if it's just a Move() or otherwie standing still, this is NONE. */ /datum/ai_tracking/proc/track_movement(time, dir) - movement_record_last = world.time var/sx var/sy switch(dir) @@ -79,15 +84,25 @@ sy = -1 sx = -1 - var/imm_old_multiplier = max(0, ((0.33 SECONDS) - time) / (0.33 SECONDS)) - var/imm_new_multiplier = 1 - imm_old_multiplier - imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx * 3 - imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy * 3 + moved_x_fast += sx + moved_y_fast += sy + moved_x_slow += sx + moved_y_slow += sy + + /// tiles / ds + var/immediate_speed = 10 / time + imm_vel_x = min(immediate_speed, imm_vel_x * 0.5 + immediate_speed * 0.5) + imm_vel_y = min(immediate_speed, imm_vel_y * 0.5 + immediate_speed * 0.5) + + // var/imm_old_multiplier = max(0, ((0.33 SECONDS) - time) / (0.33 SECONDS)) + // var/imm_new_multiplier = 1 - imm_old_multiplier + // imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx * 3 + // imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy * 3 var/fast_old_multiplier = max(0, ((2 SECONDS) - time) / (2 SECONDS)) var/fast_new_multiplier = 1 - fast_old_multiplier - fast_vel_x = (fast_vel_x) * fast_old_multiplier + sx / 2 - fast_vel_y = (fast_vel_y) * fast_old_multiplier + sy / 2 + fast_vel_x = (fast_vel_x) * fast_old_multiplier + sx + fast_vel_y = (fast_vel_y) * fast_old_multiplier + sy #warn impl @@ -105,6 +120,7 @@ /datum/ai_tracking/proc/reset_movement() imm_vel_x = imm_vel_y = 0 fast_vel_x = fast_vel_y = 0 + moved_x_fast = moved_y_fast = moved_y_fast = moved_y_slow = 0 /** * Tells us to flush movement state. @@ -112,9 +128,14 @@ * * Always call this before checking movement vars. */ /datum/ai_tracking/proc/flush_movement() - if(movement_record_last == world.time) + if(movement_flush_last == world.time) return - track_movement(world.time - movement_record_last, NONE) + + var/elapsed = world.time - movement_flush_last + + // penalize immediate speed + imm_vel_x = min(imm_vel_x, 10 / elapsed) + imm_vel_y = min(imm_vel_y, 10 / elapsed) //* /atom/movable API *// From b48dc9272a271656dd1ec43da9e1832e55b1090d Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Fri, 30 Aug 2024 21:28:06 -0400 Subject: [PATCH 06/11] that --- code/modules/ai/ai_tracking.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index 80b526d8fb00..5840f14e1d10 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -101,8 +101,8 @@ var/fast_old_multiplier = max(0, ((2 SECONDS) - time) / (2 SECONDS)) var/fast_new_multiplier = 1 - fast_old_multiplier - fast_vel_x = (fast_vel_x) * fast_old_multiplier + sx - fast_vel_y = (fast_vel_y) * fast_old_multiplier + sy + fast_vel_x = (fast_vel_x) * fast_old_multiplier + imm_vel_x * fast_new_multiplier + fast_vel_y = (fast_vel_y) * fast_old_multiplier + imm_vel_y * fast_new_multiplier #warn impl From faa66784f1ce448f41a6f2e7bfc44fd456574e1b Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Fri, 30 Aug 2024 23:29:27 -0400 Subject: [PATCH 07/11] that --- code/modules/ai/ai_tracking.dm | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index 5840f14e1d10..ab6c78a94f18 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -17,8 +17,8 @@ //* Movement *// - // last movement flush - var/movement_flush_last + // last movement record + var/movement_record_last // basic stores // var/moved_x_fast @@ -33,6 +33,8 @@ /// in tiles / second var/imm_vel_y = 0 + var/static/imm_vel_multiplier = 0.1 + // average vel x / y ; uses a fast rolling average. use to suppress attempts at baiting shots. // /// in tiles / second @@ -40,6 +42,8 @@ /// in tiles / second var/fast_vel_y = 0 + var/static/fast_vel_multiplier = 2.5 + /datum/ai_tracking/Destroy() if(gc_timerid) deltimer(gc_timerid) @@ -60,6 +64,10 @@ * * dir - direction of move. if it's just a Move() or otherwie standing still, this is NONE. */ /datum/ai_tracking/proc/track_movement(time, dir) + var/elapsed = world.time - movement_record_last + // flush_movement() + movement_record_last = world.time + var/sx var/sy switch(dir) @@ -91,16 +99,17 @@ /// tiles / ds var/immediate_speed = 10 / time - imm_vel_x = min(immediate_speed, imm_vel_x * 0.5 + immediate_speed * 0.5) - imm_vel_y = min(immediate_speed, imm_vel_y * 0.5 + immediate_speed * 0.5) + var/imm_vel_inverse = 1 - imm_vel_multiplier + imm_vel_x = min(immediate_speed, imm_vel_x * imm_vel_multiplier + immediate_speed * imm_vel_inverse * sx) + imm_vel_y = min(immediate_speed, imm_vel_y * imm_vel_multiplier + immediate_speed * imm_vel_inverse * sy) // var/imm_old_multiplier = max(0, ((0.33 SECONDS) - time) / (0.33 SECONDS)) // var/imm_new_multiplier = 1 - imm_old_multiplier // imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx * 3 // imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy * 3 - var/fast_old_multiplier = max(0, ((2 SECONDS) - time) / (2 SECONDS)) - var/fast_new_multiplier = 1 - fast_old_multiplier + var/fast_new_multiplier = clamp(fast_vel_multiplier * (time / 10), 0, 1) + var/fast_old_multiplier = clamp(1 - fast_new_multiplier, 0, 1) fast_vel_x = (fast_vel_x) * fast_old_multiplier + imm_vel_x * fast_new_multiplier fast_vel_y = (fast_vel_y) * fast_old_multiplier + imm_vel_y * fast_new_multiplier @@ -128,14 +137,18 @@ * * Always call this before checking movement vars. */ /datum/ai_tracking/proc/flush_movement() - if(movement_flush_last == world.time) + if(movement_record_last == world.time) return - var/elapsed = world.time - movement_flush_last + var/elapsed = world.time - movement_record_last + movement_record_last = world.time // penalize immediate speed imm_vel_x = min(imm_vel_x, 10 / elapsed) imm_vel_y = min(imm_vel_y, 10 / elapsed) + // penalize fast speed + fast_vel_x = min(fast_vel_x, 20 / elapsed) + fast_vel_y = min(fast_vel_y, 20 / elapsed) //* /atom/movable API *// @@ -159,4 +172,5 @@ return if(ai_tracking.last_requested + ai_tracking.gc_timeout > world.time) return + deltimer(ai_tracking.gc_timerid) QDEL_NULL(ai_tracking) From 582c2407738f964c2dddceefac8b2502a5e07c9b Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Sun, 1 Sep 2024 11:18:58 -0400 Subject: [PATCH 08/11] that --- code/modules/ai/ai_tracking.dm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index ab6c78a94f18..4ce0d597a647 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -60,13 +60,15 @@ /** * Tracks movement state * + * todo: this doesn't support forced movement or anything like that that is faster than a tile second + * * * time - time since last move * * dir - direction of move. if it's just a Move() or otherwie standing still, this is NONE. */ /datum/ai_tracking/proc/track_movement(time, dir) var/elapsed = world.time - movement_record_last - // flush_movement() - movement_record_last = world.time + // flushing changes last record + flush_movement() var/sx var/sy From b78bc53fa5e40762455fa4cb45d74c3dddc9fad0 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Sun, 1 Sep 2024 11:19:28 -0400 Subject: [PATCH 09/11] that --- code/modules/ai/ai_tracking.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index 4ce0d597a647..3498cae4da9e 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -66,7 +66,7 @@ * * dir - direction of move. if it's just a Move() or otherwie standing still, this is NONE. */ /datum/ai_tracking/proc/track_movement(time, dir) - var/elapsed = world.time - movement_record_last + var/elapsed = max(world.time - movement_record_last, world.tick_lag) // flushing changes last record flush_movement() From 3bad3b3fc3b7b3b3ebd7e35db94ed9d71e0fba8a Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Sun, 1 Sep 2024 11:24:52 -0400 Subject: [PATCH 10/11] let's do this later --- code/modules/ai/ai_tracking.dm | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index 3498cae4da9e..f3ea2b7ad2cd 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -20,12 +20,6 @@ // last movement record var/movement_record_last - // basic stores // - var/moved_x_fast - var/moved_y_fast - var/moved_x_slow - var/moved_y_slow - // basic vel x / y immediate ; tracks within a second or two. only useful for current velocity. // /// in tiles / second @@ -94,37 +88,17 @@ sy = -1 sx = -1 - moved_x_fast += sx - moved_y_fast += sy - moved_x_slow += sx - moved_y_slow += sy - /// tiles / ds var/immediate_speed = 10 / time var/imm_vel_inverse = 1 - imm_vel_multiplier imm_vel_x = min(immediate_speed, imm_vel_x * imm_vel_multiplier + immediate_speed * imm_vel_inverse * sx) imm_vel_y = min(immediate_speed, imm_vel_y * imm_vel_multiplier + immediate_speed * imm_vel_inverse * sy) - // var/imm_old_multiplier = max(0, ((0.33 SECONDS) - time) / (0.33 SECONDS)) - // var/imm_new_multiplier = 1 - imm_old_multiplier - // imm_vel_x = (imm_vel_x) * imm_old_multiplier + sx * 3 - // imm_vel_y = (imm_vel_y) * imm_old_multiplier + sy * 3 - var/fast_new_multiplier = clamp(fast_vel_multiplier * (time / 10), 0, 1) var/fast_old_multiplier = clamp(1 - fast_new_multiplier, 0, 1) fast_vel_x = (fast_vel_x) * fast_old_multiplier + imm_vel_x * fast_new_multiplier fast_vel_y = (fast_vel_y) * fast_old_multiplier + imm_vel_y * fast_new_multiplier - #warn impl - - to_chat(world, SPAN_NOTICE("imm: [round(imm_vel_x, 0.1)], [round(imm_vel_y, 0.1)]; fast: [round(fast_vel_x, 0.1)], [round(fast_vel_y, 0.1)]; calc uncert rad: [round(get_uncertainty_radius(), 0.1)]")) - -/** - * radius in tiles you have to aim to hit this person from the projected center of their immediate velocity - */ -/datum/ai_tracking/proc/get_uncertainty_radius() - return sqrt((imm_vel_x - fast_vel_x) ** 2 + (imm_vel_y - fast_vel_y) ** 2) - /** * Tells us to completely drop movement state. */ @@ -152,6 +126,10 @@ fast_vel_x = min(fast_vel_x, 20 / elapsed) fast_vel_y = min(fast_vel_y, 20 / elapsed) +// todo: get projected tile location (): list(center, radius) +// todo: calculate immediate intercept (atom/source, speed (pixels per second)): list(x, y) +// todo: calculate fast intercept (atom/source, speed (pixels per second)): list(x, y) + //* /atom/movable API *// /** From 61370c57e3925ed7eef5a09bb6192ce0809241ea Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Sun, 1 Sep 2024 11:27:08 -0400 Subject: [PATCH 11/11] fix --- code/modules/ai/ai_tracking.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/ai/ai_tracking.dm b/code/modules/ai/ai_tracking.dm index f3ea2b7ad2cd..fc962abb4066 100644 --- a/code/modules/ai/ai_tracking.dm +++ b/code/modules/ai/ai_tracking.dm @@ -103,9 +103,9 @@ * Tells us to completely drop movement state. */ /datum/ai_tracking/proc/reset_movement() + movement_record_last = world.time imm_vel_x = imm_vel_y = 0 fast_vel_x = fast_vel_y = 0 - moved_x_fast = moved_y_fast = moved_y_fast = moved_y_slow = 0 /** * Tells us to flush movement state.