Files
CHOMPStation2/code/_helpers/pathfinding_ch/astar.dm
Cadyn ecd8125771 Ai update (#8023)
Co-authored-by: silicons <2003111+silicons@users.noreply.github.com>
Co-authored-by: silicons <no@you.cat>
2024-03-31 13:38:23 +02:00

259 lines
8.1 KiB
Plaintext

//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2023 Citadel Station developers. *//
/// visualization; obviously slow as hell
// #define ASTAR_DEBUGGING
#ifdef ASTAR_DEBUGGING
#warn ASTAR pathfinding visualizations enabled
/// visualization delay
GLOBAL_VAR_INIT(astar_visualization_delay, 0.05 SECONDS)
/// how long to persist the visuals
GLOBAL_VAR_INIT(astar_visualization_persist, 3 SECONDS)
#define ASTAR_VISUAL_COLOR_CLOSED "#ff4444"
#define ASTAR_VISUAL_COLOR_OUT_OF_BOUNDS "#555555"
#define ASTAR_VISUAL_COLOR_OPEN "#4444ff"
#define ASTAR_VISUAL_COLOR_CURRENT "#ffff00"
#define ASTAR_VISUAL_COLOR_FOUND "#00ff00"
#define ASTAR_TRACE_COLOR_REDIRECTED "#7777ff"
/proc/astar_wipe_colors_after(list/turf/turfs, time)
set waitfor = FALSE
astar_wipe_colors_after_sleeping(turfs, time)
/proc/astar_wipe_colors_after_sleeping(list/turf/turfs, time)
sleep(time)
for(var/turf/T in turfs)
T.color = null
T.maptext = null
T.overlays.len = 0
/proc/get_astar_scan_overlay(dir, forwards, color)
var/image/I = new
I.icon = icon('icons/screen/debug/pathfinding.dmi', "jps_scan", dir)
I.appearance_flags = KEEP_APART | RESET_ALPHA | RESET_COLOR | RESET_TRANSFORM
I.plane = OBJ_PLANE
I.color = color
if(dir & NORTH)
I.pixel_y = forwards? 16 : -16
else if(dir & SOUTH)
I.pixel_y = forwards? -16 : 16
if(dir & EAST)
I.pixel_x = forwards? 16 : -16
else if(dir & WEST)
I.pixel_x = forwards? -16 : 16
return I
#endif
/// this is almost a megabyte
#define ASTAR_SANE_NODE_LIMIT 15000
/datum/astar_node
/// turf
var/turf/pos
/// previous
var/datum/astar_node/prev
/// our score
var/score
/// our inherent cost
var/weight
/// node depth to get to here
var/depth
/// cost to get here from prev - built off of prev
var/cost
/datum/astar_node/New(turf/pos, datum/astar_node/prev, score, weight, depth, cost)
src.pos = pos
src.prev = prev
src.score = score
src.weight = weight
src.depth = depth
src.cost = cost
/proc/cmp_astar_node(datum/astar_node/A, datum/astar_node/B)
return A.score - B.score
#define ASTAR_HEURISTIC_CALL(TURF) (isnull(context)? call(heuristic_call)(TURF, goal) : call(context, heuristic_call)(TURF, goal))
#define ASTAR_ADJACENCY_CALL(A, B) (isnull(context)? call(adjacency_call)(A, B, actor, src) : call(context, adjacency_call)(A, B, actor, src))
#define ASTAR_HEURISTIC_WEIGHT 1.2
#ifdef ASTAR_DEBUGGING
#define ASTAR_HELL_DEFINE(TURF, DIR) \
if(!isnull(TURF)) { \
if(ASTAR_ADJACENCY_CALL(current, considering)) { \
considering_cost = top.cost + considering.path_weight; \
considering_score = ASTAR_HEURISTIC_CALL(considering) * ASTAR_HEURISTIC_WEIGHT + considering_cost; \
considering_node = node_by_turf[considering]; \
if(isnull(considering_node)) { \
considering_node = new /datum/astar_node(considering, top, considering_score, considering_cost, top.depth + 1, considering_cost); \
open.enqueue(considering_node); \
node_by_turf[considering] = considering_node; \
turfs_got_colored[considering] = TRUE; \
considering.color = ASTAR_VISUAL_COLOR_OPEN; \
considering.maptext = MAPTEXT("[top.depth + 1], [considering_cost], [considering_score]"); \
considering.overlays += get_astar_scan_overlay(DIR); \
} \
else { \
if(considering_node.cost > considering_cost) { \
considering_node.cost = considering_cost; \
considering_node.depth = top.depth + 1; \
considering_node.pos.maptext = MAPTEXT("X [top.depth + 1], [considering_cost], [considering_score]"); \
considering.overlays += get_astar_scan_overlay(DIR, TRUE, ASTAR_TRACE_COLOR_REDIRECTED); \
considering_node.prev = top; \
} \
} \
} \
}
#else
#define ASTAR_HELL_DEFINE(TURF, DIR) \
if(!isnull(TURF)) { \
if(ASTAR_ADJACENCY_CALL(current, considering)) { \
considering_cost = top.cost + considering.path_weight; \
considering_score = ASTAR_HEURISTIC_CALL(considering) * ASTAR_HEURISTIC_WEIGHT + considering_cost; \
considering_node = node_by_turf[considering]; \
if(isnull(considering_node)) { \
considering_node = new /datum/astar_node(considering, top, considering_score, considering_cost, top.depth + 1, considering_cost); \
open.enqueue(considering_node); \
node_by_turf[considering] = considering_node; \
} \
else { \
if(considering_node.cost > considering_cost) { \
considering_node.cost = considering_cost; \
considering_node.depth = top.depth + 1; \
considering_node.prev = top; \
} \
} \
} \
}
#endif
/**
* AStar
* * Non uniform grids
* * Slower than JPS
* * Inherently cardinals-only
* * Node limit is manhattan, so 128 is a lot less than BYOND's get_dist(128).
*/
/datum/pathfinding/astar
/datum/pathfinding/astar/search()
ASSERT(isturf(src.start) && isturf(src.goal) && src.start.z == src.goal.z)
if(src.start == src.goal)
return list()
// too far away
if(get_manhattan_dist(src.start, src.goal) > max_path_length)
return null
#ifdef ASTAR_DEBUGGING
var/list/turf/turfs_got_colored = list()
#endif
// cache for sanic speed
var/max_depth = src.max_path_length
var/turf/goal = src.goal
var/target_distance = src.target_distance
var/atom/movable/actor = src.actor
var/adjacency_call = src.adjacency_call
var/heuristic_call = src.heuristic_call
var/datum/context = src.context
// add operating vars
var/turf/current
var/turf/considering
var/considering_score
var/considering_cost
var/datum/astar_node/considering_node
var/list/node_by_turf = list()
// make queue
var/datum/priority_queue/open = new /datum/priority_queue(/proc/cmp_astar_node)
// add initial node
var/datum/astar_node/initial_node = new(start, null, ASTAR_HEURISTIC_CALL(start), 0, 0, 0)
open.enqueue(initial_node)
node_by_turf[start] = initial_node
#ifdef ASTAR_DEBUGGING
turfs_got_colored[start] = TRUE
start.color = ASTAR_VISUAL_COLOR_OPEN
#endif
while(length(open.array))
// get best node
var/datum/astar_node/top = open.dequeue()
current = top.pos
#ifdef ASTAR_DEBUGGING
top.pos.color = ASTAR_VISUAL_COLOR_CURRENT
turfs_got_colored[top.pos] = TRUE
sleep(GLOB.astar_visualization_delay)
#else
CHECK_TICK
#endif
// get distance and check completion
if(get_dist(current, goal) <= target_distance && (target_distance != 1 || !require_adjacency_when_going_adjacent || current.TurfAdjacency(goal)))
// found; build path end to start of nodes
var/list/path_built = list()
while(top)
path_built += top.pos
#ifdef ASTAR_DEBUGGING
top.pos.color = ASTAR_VISUAL_COLOR_FOUND
turfs_got_colored[top] = TRUE
#endif
top = top.prev
// reverse
var/head = 1
var/tail = length(path_built)
while(head < tail)
path_built.Swap(head++, tail--)
#ifdef ASTAR_DEBUGGING
astar_wipe_colors_after(turfs_got_colored, GLOB.astar_visualization_persist)
#endif
return path_built
// too deep, abort
if(top.depth + get_dist(current, goal) > max_depth)
#ifdef ASTAR_DEBUGGING
top.pos.color = ASTAR_VISUAL_COLOR_OUT_OF_BOUNDS
turfs_got_colored[top.pos] = TRUE
#endif
continue
considering = get_step(current, NORTH)
ASTAR_HELL_DEFINE(considering, NORTH)
considering = get_step(current, SOUTH)
ASTAR_HELL_DEFINE(considering, SOUTH)
considering = get_step(current, EAST)
ASTAR_HELL_DEFINE(considering, EAST)
considering = get_step(current, WEST)
ASTAR_HELL_DEFINE(considering, WEST)
#ifdef ASTAR_DEBUGGING
top.pos.color = ASTAR_VISUAL_COLOR_CLOSED
turfs_got_colored[top.pos] = TRUE
#endif
if(length(open.array) > ASTAR_SANE_NODE_LIMIT)
#ifdef ASTAR_DEBUGGING
astar_wipe_colors_after(turfs_got_colored, GLOB.astar_visualization_persist)
#endif
CRASH("A* hit node limit - something went horribly wrong! args: [json_encode(args)]; vars: [json_encode(vars)]")
#ifdef ASTAR_DEBUGGING
astar_wipe_colors_after(turfs_got_colored, GLOB.astar_visualization_persist)
#endif
#undef ASTAR_HELL_DEFINE
#undef ASTAR_HEURISTIC_CALL
#undef ASTAR_ADJACENCY_CALL
#undef ASTAR_SANE_NODE_LIMIT
#undef ASTAR_HEURISTIC_WEIGHT
#ifdef ASTAR_DEBUGGING
#undef ASTAR_DEBUGGING
#undef ASTAR_VISUAL_COLOR_CLOSED
#undef ASTAR_VISUAL_COLOR_OPEN
#undef ASTAR_VISUAL_COLOR_CURRENT
#undef ASTAR_VISUAL_COLOR_FOUND
#endif