diff --git a/code/__defines/subsystem-priority.dm b/code/__defines/subsystem-priority.dm index fc69009e88b8..2b8ba561b5dc 100644 --- a/code/__defines/subsystem-priority.dm +++ b/code/__defines/subsystem-priority.dm @@ -39,7 +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 requestes // SS_BACKGROUND #define SS_PRIORITY_OBJECTS 100 // processing_objects processing. #define SS_PRIORITY_OVERMAP 95 // Moving objects on the overmap. diff --git a/code/controllers/subsystems/pathfinding.dm b/code/controllers/subsystems/pathfinding.dm new file mode 100644 index 000000000000..56a19c55c79a --- /dev/null +++ b/code/controllers/subsystems/pathfinding.dm @@ -0,0 +1,120 @@ +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 + + +/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 = AStar( + get_turf(mover), + target, + adjacency_call, + distance_call, + (metadata?.max_nodes || 0), + (metadata?.max_node_depth || 250), + metadata?.min_target_dist, + metadata?.min_node_depth, + (metadata?.id || mover.GetIdCard()), + metadata?.obstacle + ) + 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 = 0 + 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 + + if(!isnull(_max_nodes)) + 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 diff --git a/code/datums/ai/_ai.dm b/code/datums/ai/_ai.dm index be51dd0ee084..3099e1332f6a 100644 --- a/code/datums/ai/_ai.dm +++ b/code/datums/ai/_ai.dm @@ -60,6 +60,9 @@ /// Aggressive AI var; defined here for reference without casting. var/try_destroy_surroundings = FALSE + /// Current path for A* pathfinding. + var/list/executing_path + /datum/mob_controller/New(var/mob/living/target_body) body = target_body if(expected_type && !istype(body, expected_type)) @@ -78,7 +81,26 @@ . = ..() /datum/mob_controller/proc/get_automove_target(datum/automove_metadata/metadata) - return null + var/turf/move_target = (islist(executing_path) && length(executing_path)) ? executing_path : null + if(istype(move_target) && QDELETED(move_target)) + clear_path() + +/datum/mob_controller/proc/handle_post_automoved(atom/old_loc) + if(!islist(executing_path) || length(executing_path) <= 0) + return + var/turf/body_turf = get_turf(body) + if(!istype(body_turf)) + return + if(executing_path[1] != body_turf) + return + if(length(executing_path) > 1) + executing_path.Cut(1, 2) + else + clear_path() + +/datum/mob_controller/proc/clear_path() + executing_path = null + body?.stop_automove() /datum/mob_controller/proc/can_do_automated_move(variant_move_delay) return body && !body.client diff --git a/code/datums/movement/automove_controller.dm b/code/datums/movement/automove_controller.dm index 08dae443f358..b413d9998497 100644 --- a/code/datums/movement/automove_controller.dm +++ b/code/datums/movement/automove_controller.dm @@ -1,7 +1,8 @@ /// Implements automove logic; can be overridden on mob procs if you want to vary the logic from the below. /decl/automove_controller - var/completion_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom reaches its target. - var/failure_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom fails to move. + var/completion_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom reaches its target. + var/failure_signal = FALSE // Set to TRUE if you want movement to stop processing when the atom fails to move. + var/try_avoid_obstacles = TRUE // Will try to move 90 degrees around an obstacle. /decl/automove_controller/proc/handle_mover(atom/movable/mover, datum/automove_metadata/metadata) @@ -49,14 +50,16 @@ return TRUE // no idea how we would get into this position if(mover.SelfMove(target_dir) && (old_loc != mover.loc)) - return TRUE + mover.handle_post_automoved(old_loc) + return (mover.get_automove_target() == mover.loc) // We may have transitioned to the next step in a path. - // Try to move around any obstacle. - var/static/list/_alt_dir_rot = list(45, -45) - for(var/alt_dir in shuffle(_alt_dir_rot)) - mover.reset_movement_delay() - if(mover.SelfMove(turn(target_dir, alt_dir)) && (old_loc != mover.loc)) - return TRUE + if(try_avoid_obstacles) + // Try to move around any obstacle. + var/static/list/_alt_dir_rot = list(45, -45) + for(var/alt_dir in shuffle(_alt_dir_rot)) + mover.reset_movement_delay() + if(mover.SelfMove(turn(target_dir, alt_dir)) && (old_loc != mover.loc)) + return TRUE mover.failed_automove() diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 111e53b49105..8ff5c11be60c 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -581,6 +581,9 @@ if(!.) // If we're under or inside shelter, use the z-level rain (for ambience) . = SSweather.weather_by_z[my_turf.z] +/atom/movable/proc/handle_post_automoved(atom/old_loc) + return + /atom/movable/take_vaporized_reagent(reagent, amount) if(ATOM_IS_OPEN_CONTAINER(src)) return loc?.take_vaporized_reagent(reagent, amount) diff --git a/code/modules/integrated_electronics/subtypes/smart.dm b/code/modules/integrated_electronics/subtypes/smart.dm index 25f9dca52a4e..f92af368dcd8 100644 --- a/code/modules/integrated_electronics/subtypes/smart.dm +++ b/code/modules/integrated_electronics/subtypes/smart.dm @@ -96,6 +96,10 @@ return ..() /obj/item/integrated_circuit/smart/advanced_pathfinder/do_work() + + if(waiting_for_path) + return + if(!assembly) activate_pin(3) return @@ -119,20 +123,30 @@ if(Pl&&islist(Pl)) idc.access = Pl var/turf/a_loc = get_turf(assembly) - var/list/P = AStar(a_loc, locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, 200, id=idc, exclude=get_turf(get_pin_data_as_type(IC_INPUT, 3, /atom))) + SSpathfinding.enqueue_mover( + src, + locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z), + new /datum/pathfinding_metadata( + _max_node_depth = 200, + _id = idc, + _obstacle = get_turf(get_pin_data_as_type(IC_INPUT, 3, /atom)) + ) + ) - if(!P) - activate_pin(3) - return - else - var/list/Xn = new/list(P.len) - var/list/Yn = new/list(P.len) - var/turf/T - for(var/i =1 to P.len) - T=P[i] - Xn[i] = T.x - Yn[i] = T.y - set_pin_data(IC_OUTPUT, 1, Xn) - set_pin_data(IC_OUTPUT, 2, Yn) - push_data() - activate_pin(2) +/obj/item/integrated_circuit/smart/advanced_pathfinder/path_not_found() + ..() + activate_pin(3) + +/obj/item/integrated_circuit/smart/advanced_pathfinder/path_found(list/path) + ..() + var/list/Xn = new/list(path.len) + var/list/Yn = new/list(path.len) + var/turf/T + for(var/i = 1 to path.len) + T=path[i] + Xn[i] = T.x + Yn[i] = T.y + set_pin_data(IC_OUTPUT, 1, Xn) + set_pin_data(IC_OUTPUT, 2, Yn) + push_data() + activate_pin(2) diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm index 89b581adbf3d..e5f1520e449f 100644 --- a/code/modules/mob/living/bot/bot.dm +++ b/code/modules/mob/living/bot/bot.dm @@ -36,6 +36,8 @@ var/frustration = 0 var/max_frustration = 0 + var/seeking_patrol_path = FALSE + layer = HIDING_MOB_LAYER /mob/living/bot/Initialize() @@ -291,13 +293,41 @@ return /mob/living/bot/proc/startPatrol() + if(waiting_for_path) + return + seeking_patrol_path = TRUE var/turf/T = getPatrolTurf() if(T) - patrol_path = AStar(get_turf(loc), T, TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, max_patrol_dist, id = botcard, exclude = obstacle) - if(!patrol_path) - patrol_path = list() - obstacle = null - return + SSpathfinding.enqueue_mover( + src, + T, + new /datum/pathfinding_metadata( + _max_node_depth = 200, + _id = botcard, + _obstacle = obstacle + ) + ) + +/mob/living/bot/path_found(list/path) + ..() + if(seeking_patrol_path) + seeking_patrol_path = FALSE + patrol_path = path || list() + else + target_path = path + obstacle = null + +/mob/living/bot/path_not_found() + ..() + if(seeking_patrol_path) + seeking_patrol_path = FALSE + patrol_path = list() + else + target_path = list() + if(target && target.loc) + ignore_list |= target + resetTarget() + obstacle = null /mob/living/bot/proc/getPatrolTurf() var/minDist = INFINITY @@ -325,13 +355,18 @@ return /mob/living/bot/proc/calcTargetPath() - target_path = AStar(get_turf(loc), get_turf(target), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, max_target_dist, id = botcard, exclude = obstacle) - if(!target_path) - if(target && target.loc) - ignore_list |= target - resetTarget() - obstacle = null - return + if(waiting_for_path) + return + seeking_patrol_path = FALSE + SSpathfinding.enqueue_mover( + src, + get_turf(target), + new /datum/pathfinding_metadata( + _max_node_depth = 200, + _id = botcard, + _obstacle = obstacle + ) + ) /mob/living/bot/proc/makeStep(var/list/path) if(!path.len) diff --git a/code/modules/mob/living/bot/farmbot.dm b/code/modules/mob/living/bot/farmbot.dm index 4a487bb1a4c2..604fda90e4f3 100644 --- a/code/modules/mob/living/bot/farmbot.dm +++ b/code/modules/mob/living/bot/farmbot.dm @@ -123,23 +123,9 @@ target = source return -/mob/living/bot/farmbot/calcTargetPath() // We need to land NEXT to the tray, because the tray itself is impassable - for(var/trayDir in list(NORTH, SOUTH, EAST, WEST)) - target_path = AStar(get_turf(loc), get_step(get_turf(target), trayDir), TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, max_target_dist, id = botcard) - if(target_path) - break - if(!target_path) - ignore_list |= target - target = null - target_path = list() - return - -/mob/living/bot/farmbot/stepToTarget() // Same reason - var/turf/T = get_turf(target) - if(!target_path.len || !T.Adjacent(target_path[target_path.len])) - calcTargetPath() - makeStep(target_path) - return +// TODO: move adjacent +// /mob/living/bot/farmbot/calcTargetPath() // We need to land NEXT to the tray, because the tray itself is impassable +// /mob/living/bot/farmbot/stepToTarget() // Same reason /mob/living/bot/farmbot/UnarmedAttack(var/atom/A, var/proximity) . = ..() diff --git a/code/modules/mob/mob_automove.dm b/code/modules/mob/mob_automove.dm index ff8d1a6d7bae..01d59ead0a14 100644 --- a/code/modules/mob/mob_automove.dm +++ b/code/modules/mob/mob_automove.dm @@ -6,11 +6,27 @@ _automove_target = null return ..() +/mob/path_found(list/path) + ..() + path.Cut(1, 2) // Remove the first turf since it's going to be our origin. + if(length(path)) + start_automove(path) + +/mob/path_not_found() + ..() + stop_automove() + /// Called by get_movement_delay() to override the current move intent, in cases where an automove has a delay override. /mob/proc/get_automove_delay() var/datum/automove_metadata/metadata = SSautomove.moving_metadata[src] return metadata?.move_delay +/mob/failed_automove() + ..() + stop_automove() + _automove_target = null + return FALSE + /mob/start_automove(target, movement_type, datum/automove_metadata/metadata) _automove_target = target return ..() @@ -18,6 +34,31 @@ // The AI datum may decide to track a target instead of using the mob reference. /mob/get_automove_target(datum/automove_metadata/metadata) . = (istype(ai) && ai.get_automove_target()) || _automove_target || ..() + if(islist(.)) + var/list/path = . + while(length(path) && path[1] == get_turf(src)) + path.Cut(1,2) + if(length(path)) + return path[1] + return null + +/mob/handle_post_automoved(atom/old_loc) + if(istype(ai)) + ai.handle_post_automoved(old_loc) + return + if(!islist(_automove_target) || length(_automove_target) <= 0) + return + var/turf/body_turf = get_turf(src) + if(!istype(body_turf)) + return + var/list/_automove_target_list = _automove_target + if(_automove_target_list[1] != body_turf) + return + if(length(_automove_target_list) > 1) + _automove_target_list.Cut(1, 2) + else + _automove_target_list = null + stop_automove() // We do some early checking here to avoid doing the same checks repeatedly by calling SelfMove(). /mob/can_do_automated_move(variant_move_delay) diff --git a/code/procs/AStar.dm b/code/procs/AStar.dm index 01e4213f805d..fcd760eb1abb 100644 --- a/code/procs/AStar.dm +++ b/code/procs/AStar.dm @@ -61,6 +61,9 @@ length to avoid portals or something i guess?? Not that they're counted right no return a.estimated_cost - b.estimated_cost /proc/AStar(var/start, var/end, adjacent, dist, var/max_nodes, var/max_node_depth = 30, var/min_target_dist = 0, var/min_node_dist, var/id, var/datum/exclude) + + set waitfor = FALSE + var/datum/priority_queue/open = new /datum/priority_queue(/proc/PathWeightCompare) var/list/closed = list() var/list/path @@ -85,13 +88,11 @@ length to avoid portals or something i guess?? Not that they're counted right no path[index--] = current.position break - if(min_node_dist && max_node_depth) - if(call(current.position, min_node_dist)(end) + current.nodes_traversed >= max_node_depth) - continue + if(min_node_dist && max_node_depth && (call(current.position, min_node_dist)(end) + current.nodes_traversed >= max_node_depth)) + continue - if(max_node_depth) - if(current.nodes_traversed >= max_node_depth) - continue + if(max_node_depth && current.nodes_traversed >= max_node_depth) + continue for(var/datum/datum in call(current.position, adjacent)(id)) if(datum == exclude) @@ -115,4 +116,7 @@ length to avoid portals or something i guess?? Not that they're counted right no if(max_nodes && open.Length() > max_nodes) open.Remove(open.Length()) + CHECK_TICK + CHECK_TICK + return path diff --git a/nebula.dme b/nebula.dme index 20ba9b236895..e9cd4adecc18 100644 --- a/nebula.dme +++ b/nebula.dme @@ -281,6 +281,7 @@ #include "code\controllers\subsystems\mapping.dm" #include "code\controllers\subsystems\misc_late.dm" #include "code\controllers\subsystems\overlays.dm" +#include "code\controllers\subsystems\pathfinding.dm" #include "code\controllers\subsystems\plants.dm" #include "code\controllers\subsystems\radiation.dm" #include "code\controllers\subsystems\shuttle.dm"