Skip to content

Commit

Permalink
Merge pull request #4302 from MistakeNot4892/feature/astarautomove
Browse files Browse the repository at this point in the history
Adding SSpathfinding.
  • Loading branch information
out-of-phaze authored Dec 13, 2024
2 parents 6d605ea + e34ae1b commit 7c188b4
Show file tree
Hide file tree
Showing 36 changed files with 631 additions and 380 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# ignore misc BYOND files
Thumbs.db
Thumbs.db:encryptable
*.log
*.int
*.rsc
Expand Down
2 changes: 2 additions & 0 deletions code/__defines/ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#define STANCE_ATTACKING /decl/mob_controller_stance/attacking
#define STANCE_TIRED /decl/mob_controller_stance/tired
#define STANCE_CONTAINED /decl/mob_controller_stance/contained
#define STANCE_BUSY /decl/mob_controller_stance/busy

//basically 'do nothing'
#define STANCE_COMMANDED_STOP /decl/mob_controller_stance/commanded/stop
//follows a target
Expand Down
1 change: 1 addition & 0 deletions code/__defines/subsystem-priority.dm
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#define SS_PRIORITY_GHOST_IMAGES 10 // Updates ghost client images.
#define SS_PRIORITY_ZCOPY 10 // Builds appearances for Z-Mimic.
#define SS_PRIORITY_PROJECTILES 10 // Projectile processing!
#define SS_PRIORITY_PATHFINDING 10 // Processing pathfinding requests

// SS_BACKGROUND
#define SS_PRIORITY_OBJECTS 100 // processing_objects processing.
Expand Down
5 changes: 4 additions & 1 deletion code/controllers/subsystems/mob_ai/auto_movement.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,8 @@ SUBSYSTEM_DEF(automove)
if(controller.handle_mover(mover, moving_metadata[mover]) == PROCESS_KILL && !QDELETED(mover))
mover.stop_automove()
if(MC_TICK_CHECK)
processing_atoms.Cut(1, i+1)
if(i >= length(processing_atoms))
processing_atoms.Cut()
else
processing_atoms.Cut(1, i+1)
return
123 changes: 123 additions & 0 deletions code/controllers/subsystems/pathfinding.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
SUBSYSTEM_DEF(pathfinding)
name = "Pathfinding"
priority = SS_PRIORITY_PATHFINDING
init_order = SS_INIT_MISC_LATE
wait = 1

var/list/pending = list()
var/list/processing = list()
var/list/mover_metadata = list()

VAR_PRIVATE/static/_default_adjacency_call = TYPE_PROC_REF(/turf, CardinalTurfsWithAccess)
VAR_PRIVATE/static/_default_distance_call = TYPE_PROC_REF(/turf, Distance)

/atom/movable
var/waiting_for_path

/atom/movable/proc/path_found(list/path)
SHOULD_CALL_PARENT(TRUE)
waiting_for_path = null

/atom/movable/proc/path_not_found()
SHOULD_CALL_PARENT(TRUE)
waiting_for_path = null

/datum/controller/subsystem/pathfinding/proc/dequeue_mover(atom/movable/mover, include_processing = TRUE)
if(!istype(mover))
return
mover.waiting_for_path = null
pending -= mover
mover_metadata -= mover
if(include_processing)
processing -= mover

// Hook to allow legacy use of AStar* to reuse the callback refs
/datum/controller/subsystem/pathfinding/proc/find_path_immediate(start, end, max_nodes, max_node_depth = 30, min_target_dist = 0, min_node_dist, id, datum/exclude, check_tick = FALSE)
return find_path_astar(start, end, _default_adjacency_call, _default_distance_call, max_nodes, max_node_depth, min_target_dist, min_node_dist, id, exclude, check_tick)

/datum/controller/subsystem/pathfinding/proc/enqueue_mover(atom/movable/mover, atom/target, datum/pathfinding_metadata/metadata)
if(!istype(mover) || mover.waiting_for_path)
return FALSE
if(!istype(target))
return FALSE
pending |= mover
pending[mover] = target
if(istype(metadata))
mover_metadata[mover] = metadata
mover.waiting_for_path = world.time
return TRUE

/datum/controller/subsystem/pathfinding/stat_entry(msg)
. = ..("Q:[length(pending)] P:[length(processing)]")

/datum/controller/subsystem/pathfinding/fire(resumed)

if(!resumed)
processing = pending?.Copy()

var/atom/movable/mover
var/atom/target
var/datum/pathfinding_metadata/metadata
var/i = 0

while(i < processing.len)

i++
mover = processing[i]
target = processing[mover]
metadata = mover_metadata[mover]
dequeue_mover(mover, include_processing = FALSE)

if(!QDELETED(mover) && !QDELETED(target))
try_find_path(mover, target, metadata)

if (MC_TICK_CHECK)
processing.Cut(1, i+1)
return

processing.Cut()

/datum/controller/subsystem/pathfinding/proc/try_find_path(atom/movable/mover, atom/target, datum/pathfinding_metadata/metadata, adjacency_call = _default_adjacency_call, distance_call = _default_distance_call)

var/started_pathing = world.time
mover.waiting_for_path = started_pathing

var/list/path = find_path_astar(
get_turf(mover),
target,
adjacency_call,
distance_call,
(metadata?.max_nodes || null),
(metadata?.max_node_depth || 250),
metadata?.min_target_dist,
metadata?.min_node_depth,
(metadata?.id || mover.GetIdCard()),
metadata?.obstacle,
check_tick = TRUE
)
if(mover.waiting_for_path == started_pathing)
if(length(path))
mover.path_found(path)
else
mover.path_not_found()

/datum/pathfinding_metadata
var/max_nodes = null
var/max_node_depth = 250
var/atom/id = null
var/min_target_dist = null
var/min_node_depth = null
var/obstacle = null

/datum/pathfinding_metadata/New(_max_nodes, _max_node_depth, _id, _min_target_dist, _min_node_depth, _obstacle)

id = _id
obstacle = _obstacle
max_nodes = _max_nodes

if(!isnull(_max_node_depth))
max_node_depth = _max_node_depth
if(!isnull(_min_target_dist))
min_target_dist = _min_target_dist
if(!isnull(_min_node_depth))
min_node_depth = _min_node_depth
154 changes: 20 additions & 134 deletions code/datums/ai/_ai.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@
/// Aggressive AI var; defined here for reference without casting.
var/try_destroy_surroundings = FALSE

/// Reference to the atom we are targetting.
var/weakref/target_ref

/// Current path for A* pathfinding.
var/list/executing_path
/// A counter for times we have failed to progress along our path.
var/path_frustration = 0
/// A list of any obstacles we should path around in future.
var/list/path_obstacles = null

/// Radius of target scan area when looking for valid targets. Set to 0 to disable target scanning.
var/target_scan_distance = 0
/// Time tracker for next target scan.
var/next_target_scan_time
/// How long minimum between scans.
var/target_scan_delay = 1 SECOND

/datum/mob_controller/New(var/mob/living/target_body)
body = target_body
if(expected_type && !istype(body, expected_type))
Expand All @@ -71,6 +88,7 @@
/datum/mob_controller/Destroy()
LAZYCLEARLIST(_friends)
LAZYCLEARLIST(_enemies)
set_target(null)
if(is_processing)
STOP_PROCESSING(SSmob_ai, src)
if(body)
Expand All @@ -79,12 +97,6 @@
body = null
. = ..()

/datum/mob_controller/proc/get_automove_target(datum/automove_metadata/metadata)
return null

/datum/mob_controller/proc/can_do_automated_move(variant_move_delay)
return body && !body.client

/datum/mob_controller/proc/can_process()
if(!body || !body.loc || ((body.client || body.mind) && !(body.status_flags & ENABLE_AI)))
return FALSE
Expand All @@ -111,13 +123,13 @@
// This is the place to actually do work in the AI.
/datum/mob_controller/proc/do_process()
SHOULD_CALL_PARENT(TRUE)
if(!QDELETED(body) && !QDELETED(src))
if(get_stance() != STANCE_BUSY && !QDELETED(body) && !QDELETED(src))
if(!body.stat)
try_unbuckle()
try_wander()
try_bark()
// Recheck in case we walked into lava or something during wandering.
return !QDELETED(body) && !QDELETED(src)
return get_stance() != STANCE_BUSY && !QDELETED(body) && !QDELETED(src)
return TRUE
return FALSE

Expand All @@ -132,16 +144,6 @@
else if(prob(25))
body.visible_message(SPAN_WARNING("\The [body] struggles against \the [body.buckled]!"))


/datum/mob_controller/proc/get_activity()
return current_activity

/datum/mob_controller/proc/set_activity(new_activity)
if(current_activity != new_activity)
current_activity = new_activity
return TRUE
return FALSE

// The mob will periodically sit up or step 1 tile in a random direction.
/datum/mob_controller/proc/try_wander()
//Movement
Expand Down Expand Up @@ -185,133 +187,17 @@
else if(ispath(do_emote, /decl/emote))
body.emote(do_emote)

/datum/mob_controller/proc/get_target()
return null

/datum/mob_controller/proc/set_target(atom/new_target)
return

/datum/mob_controller/proc/find_target()
return

/datum/mob_controller/proc/valid_target(var/atom/A)
return

/datum/mob_controller/proc/move_to_target(var/move_only = FALSE)
return

/datum/mob_controller/proc/stop_wandering()
stop_wander = TRUE

/datum/mob_controller/proc/resume_wandering()
stop_wander = FALSE

/datum/mob_controller/proc/set_stance(new_stance)
if(stance != new_stance)
stance = new_stance
return TRUE
return FALSE

/datum/mob_controller/proc/get_stance()
return stance

/datum/mob_controller/proc/list_targets(var/dist = 7)
return

/datum/mob_controller/proc/open_fire()
return

/datum/mob_controller/proc/startle()
if(QDELETED(body) || body.stat != UNCONSCIOUS)
return
body.set_stat(CONSCIOUS)
if(body.current_posture?.prone)
body.set_posture(/decl/posture/standing)

/datum/mob_controller/proc/retaliate(atom/source)
SHOULD_CALL_PARENT(TRUE)
if(!istype(body) || body.stat == DEAD)
return FALSE
startle()
if(isliving(source))
remove_friend(source)
return TRUE

/datum/mob_controller/proc/destroy_surroundings()
return

/datum/mob_controller/proc/lose_target()
return

/datum/mob_controller/proc/lost_target()
return

/datum/mob_controller/proc/handle_death(gibbed)
return

/datum/mob_controller/proc/pacify(mob/user)
lose_target()
add_friend(user)

// General-purpose memorise proc, used by /commanded
/datum/mob_controller/proc/memorise(mob/speaker, message)
return

// General-purpose memory checking proc, used by /faithful_hound
/datum/mob_controller/proc/check_memory(mob/speaker, message)
return FALSE

/// General-purpose scooping reaction proc, used by /passive.
/// Returns TRUE if the scoop should proceed, FALSE if it should be canceled.
/datum/mob_controller/proc/scooped_by(mob/initiator)
return TRUE

// Enemy tracking - used on /aggressive
/datum/mob_controller/proc/get_enemies()
return _enemies

/datum/mob_controller/proc/add_enemy(mob/enemy)
if(istype(enemy))
LAZYDISTINCTADD(_enemies, weakref(enemy))

/datum/mob_controller/proc/add_enemies(list/enemies)
for(var/thing in enemies)
if(ismob(thing))
add_friend(thing)
else if(istype(thing, /weakref))
LAZYDISTINCTADD(_enemies, thing)

/datum/mob_controller/proc/remove_enemy(mob/enemy)
LAZYREMOVE(_enemies, weakref(enemy))

/datum/mob_controller/proc/set_enemies(list/new_enemies)
_enemies = new_enemies

/datum/mob_controller/proc/is_enemy(mob/enemy)
. = istype(enemy) && LAZYLEN(_enemies) && (weakref(enemy) in _enemies)

/datum/mob_controller/proc/clear_enemies()
LAZYCLEARLIST(_enemies)

// Friend tracking - used on /aggressive.
/datum/mob_controller/proc/get_friends()
return _friends

/datum/mob_controller/proc/add_friend(mob/friend)
if(istype(friend))
LAZYDISTINCTADD(_friends, weakref(friend))
return TRUE
return FALSE

/datum/mob_controller/proc/remove_friend(mob/friend)
LAZYREMOVE(_friends, weakref(friend))

/datum/mob_controller/proc/set_friends(list/new_friends)
_friends = new_friends

/datum/mob_controller/proc/is_friend(mob/friend)
. = istype(friend) && LAZYLEN(_friends) && (weakref(friend) in _friends)

// By default, randomize the target area a bit to make armor/combat
// a bit more dynamic (and avoid constant organ damage to the chest)
/datum/mob_controller/proc/update_target_zone()
Expand Down
Loading

0 comments on commit 7c188b4

Please sign in to comment.