mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
Co-authored-by: silicons <2003111+silicons@users.noreply.github.com> Co-authored-by: silicons <no@you.cat>
259 lines
8.1 KiB
Plaintext
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
|