mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 10:12:45 +00:00
Ai update (#8023)
Co-authored-by: silicons <2003111+silicons@users.noreply.github.com> Co-authored-by: silicons <no@you.cat>
This commit is contained in:
52
code/__defines/_flags/turf_flags_ch.dm
Normal file
52
code/__defines/_flags/turf_flags_ch.dm
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//* /turf_flags var on /turf
|
||||||
|
/// This is used in literally one place, turf.dm, to block ethwereal jaunt.
|
||||||
|
#define NO_JAUNT (1<<0)
|
||||||
|
/// Unused reservation turf
|
||||||
|
#define UNUSED_RESERVATION_TURF (1<<2)
|
||||||
|
/// queued for planet turf addition
|
||||||
|
#define TURF_PLANET_QUEUED (1<<3)
|
||||||
|
/// registered to a planet
|
||||||
|
#define TURF_PLANET_REGISTERED (1<<4)
|
||||||
|
/// queued for ZAS rebuild
|
||||||
|
#define TURF_ZONE_REBUILD_QUEUED (1<<5)
|
||||||
|
|
||||||
|
///CITMAIN TURF FLAGS - Completely unused
|
||||||
|
/*
|
||||||
|
/// If a turf can be made dirty at roundstart. This is also used in areas.
|
||||||
|
#define CAN_BE_DIRTY (1<<3)
|
||||||
|
/// Should this tile be cleaned up and reinserted into an excited group?
|
||||||
|
#define EXCITED_CLEANUP (1<<4)
|
||||||
|
/// Blocks lava rivers being generated on the turf
|
||||||
|
#define NO_LAVA_GEN (1<<5)
|
||||||
|
/// Blocks ruins spawning on the turf
|
||||||
|
#define NO_RUINS (1<<6)
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
DEFINE_BITFIELD(turf_flags, list(
|
||||||
|
BITFIELD(NO_JAUNT),
|
||||||
|
BITFIELD(UNUSED_RESERVATION_TURF),
|
||||||
|
BITFIELD(TURF_PLANET_QUEUED),
|
||||||
|
BITFIELD(TURF_PLANET_REGISTERED),
|
||||||
|
BITFIELD(TURF_ZONE_REBUILD_QUEUED),
|
||||||
|
))
|
||||||
|
*/
|
||||||
|
//* /turf_path_danger var on /turf
|
||||||
|
/// lava, fire, etc
|
||||||
|
#define TURF_PATH_DANGER_BURN (1<<0)
|
||||||
|
/// openspace, chasms, etc
|
||||||
|
#define TURF_PATH_DANGER_FALL (1<<1)
|
||||||
|
/// will just fucking obliterate you
|
||||||
|
#define TURF_PATH_DANGER_ANNIHILATION (1<<2)
|
||||||
|
/// this, is literally space.
|
||||||
|
#define TURF_PATH_DANGER_SPACE (1<<3)
|
||||||
|
/*
|
||||||
|
DEFINE_SHARED_BITFIELD(turf_path_danger, list(
|
||||||
|
"turf_path_danger",
|
||||||
|
"turf_path_danger_ignore",
|
||||||
|
), list(
|
||||||
|
BITFIELD(TURF_PATH_DANGER_BURN),
|
||||||
|
BITFIELD(TURF_PATH_DANGER_FALL),
|
||||||
|
BITFIELD(TURF_PATH_DANGER_ANNIHILATION),
|
||||||
|
BITFIELD(TURF_PATH_DANGER_SPACE),
|
||||||
|
))
|
||||||
|
*/
|
||||||
@@ -4,3 +4,5 @@
|
|||||||
#define COMSIG_BELLY_UPDATE_VORE_FX "update_vore_fx"
|
#define COMSIG_BELLY_UPDATE_VORE_FX "update_vore_fx"
|
||||||
///from /obj/belly/process()
|
///from /obj/belly/process()
|
||||||
#define COMSIG_BELLY_UPDATE_PREY_LOOP "update_prey_loop"
|
#define COMSIG_BELLY_UPDATE_PREY_LOOP "update_prey_loop"
|
||||||
|
/// COMSIG used to get messages where they need to go
|
||||||
|
#define COMSIG_VISIBLE_MESSAGE "visible_message"
|
||||||
|
|||||||
37
code/_helpers/distance_ch.dm
Normal file
37
code/_helpers/distance_ch.dm
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* checks distance from one thing to another but automatically resolving for turf / nesting
|
||||||
|
*/
|
||||||
|
/proc/in_range_of(atom/A, atom/B, dist = 1)
|
||||||
|
return game_range_to(A, B) <= dist
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets real dist from A to B, including resolving for turf. if not the same Z, returns infinity.
|
||||||
|
*/
|
||||||
|
/proc/game_range_to(atom/A, atom/B)
|
||||||
|
A = get_turf(A)
|
||||||
|
B = get_turf(B)
|
||||||
|
return A.z == B.z? get_dist(A, B) : INFINITY
|
||||||
|
|
||||||
|
/**
|
||||||
|
* real dist because byond dist doesn't go above 127 :/
|
||||||
|
*
|
||||||
|
* accepts **TURFS**
|
||||||
|
*/
|
||||||
|
/proc/get_chebyshev_dist(turf/A, turf/B)
|
||||||
|
return max(abs(A.x - B.x), abs(A.y - B.y))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* real euclidean dist
|
||||||
|
*
|
||||||
|
* accepts **TURFS**
|
||||||
|
*/
|
||||||
|
/proc/get_euclidean_dist(turf/A, turf/B)
|
||||||
|
return sqrt((A.x - B.x) ** 2 + (A.y - B.y) ** 2)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* real taxicab dist
|
||||||
|
*
|
||||||
|
* accepts **TURFS**
|
||||||
|
*/
|
||||||
|
/proc/get_manhattan_dist(turf/A, turf/B)
|
||||||
|
return abs(A.x - B.x) + abs(A.y - B.y)
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
|
|
||||||
return heard
|
return heard
|
||||||
|
|
||||||
|
|
||||||
/proc/isStationLevel(var/level)
|
/proc/isStationLevel(var/level)
|
||||||
return level in using_map.station_levels
|
return level in using_map.station_levels
|
||||||
|
|
||||||
|
|||||||
128
code/_helpers/graphs/astar_ch.dm
Normal file
128
code/_helpers/graphs/astar_ch.dm
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
// todo: DO NOT FUCKING USE THIS
|
||||||
|
// it is *EXTREMELY* inefficient, and scales up quadratically in time complexity
|
||||||
|
// DO NOT USE THIS UNTIL IT IS REWRITTEN
|
||||||
|
// notably that "bad node trimming" is actually horrifying.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Star pathfinding algorithm
|
||||||
|
*
|
||||||
|
* This file's AStar should not be used generally; it's the generic graph search algorithm, as opposed
|
||||||
|
* to the optimized turf-grid-only search algorithm.
|
||||||
|
*
|
||||||
|
* Returns a list of tiles forming a path from A to B, taking dense objects as well as walls, and the orientation of
|
||||||
|
* windows along the route into account.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Use:
|
||||||
|
* your_list = AStar(start location, end location, adjacent turf proc, distance proc)
|
||||||
|
* For the adjacent turf proc i wrote:
|
||||||
|
* /turf/proc/AdjacentTurfs
|
||||||
|
* And for the distance one i wrote:
|
||||||
|
* /turf/proc/Distance
|
||||||
|
*
|
||||||
|
* So an example use might be:
|
||||||
|
*
|
||||||
|
* src.path_list = AStar(src.loc, target.loc, TYPE_PROC_REF(/turf, AdjacentTurfs), TYPE_PROC_REF(/turf, Distance))
|
||||||
|
*
|
||||||
|
* Note: The path is returned starting at the END node, so i wrote reverselist to reverse it for ease of use.
|
||||||
|
*
|
||||||
|
* src.path_list = reverselist(src.pathlist)
|
||||||
|
*
|
||||||
|
* Then to start on the path, all you need to do it:
|
||||||
|
* Step_to(src, src.path_list[1])
|
||||||
|
* src.path_list -= src.path_list[1] or equivilent to remove that node from the list.
|
||||||
|
*
|
||||||
|
* Optional extras to add on (in order):
|
||||||
|
* MaxNodes: The maximum number of nodes the returned path can be (0 = infinite)
|
||||||
|
* Maxnodedepth: The maximum number of nodes to search (default: 30, 0 = infinite)
|
||||||
|
* Mintargetdist: Minimum distance to the target before path returns, could be used to get
|
||||||
|
* near a target, but not right to it - for an AI mob with a gun, for example.
|
||||||
|
* Minnodedist: Minimum number of nodes to return in the path, could be used to give a path a minimum
|
||||||
|
* length to avoid portals or something i guess?? Not that they're counted right now but w/e.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Modified to provide ID argument - supplied to 'adjacent' proc, defaults to null
|
||||||
|
// Used for checking if route exists through a door which can be opened
|
||||||
|
|
||||||
|
// Also added 'exclude' turf to avoid travelling over; defaults to null
|
||||||
|
|
||||||
|
/datum/graph_astar_node
|
||||||
|
var/datum/position
|
||||||
|
var/datum/graph_astar_node/previous_node
|
||||||
|
|
||||||
|
var/best_estimated_cost
|
||||||
|
var/estimated_cost
|
||||||
|
var/known_cost
|
||||||
|
var/cost
|
||||||
|
var/nodes_traversed
|
||||||
|
|
||||||
|
/datum/graph_astar_node/New(_position, _previous_node, _known_cost, _cost, _nodes_traversed)
|
||||||
|
position = _position
|
||||||
|
previous_node = _previous_node
|
||||||
|
|
||||||
|
known_cost = _known_cost
|
||||||
|
cost = _cost
|
||||||
|
estimated_cost = cost + known_cost
|
||||||
|
|
||||||
|
best_estimated_cost = estimated_cost
|
||||||
|
nodes_traversed = _nodes_traversed
|
||||||
|
|
||||||
|
/proc/cmp_graph_astar_node(datum/graph_astar_node/a, datum/graph_astar_node/b)
|
||||||
|
return a.estimated_cost - b.estimated_cost
|
||||||
|
|
||||||
|
/proc/graph_astar(start, end, adjacent, dist, max_nodes, max_node_depth = 30, min_target_dist = 0, min_node_dist, id, datum/exclude)
|
||||||
|
var/datum/priority_queue/open = new /datum/priority_queue(/proc/cmp_graph_astar_node)
|
||||||
|
var/list/closed = list()
|
||||||
|
var/list/path
|
||||||
|
var/list/path_node_by_position = list()
|
||||||
|
start = get_turf(start)
|
||||||
|
if(!start)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
open.enqueue(new /datum/graph_astar_node(start, null, 0, call(start, dist)(end), 0))
|
||||||
|
|
||||||
|
while(!open.is_empty() && !path)
|
||||||
|
var/datum/graph_astar_node/current = open.dequeue()
|
||||||
|
closed.Add(current.position)
|
||||||
|
|
||||||
|
if(current.position == end || call(current.position, dist)(end) <= min_target_dist)
|
||||||
|
path = new /list(current.nodes_traversed + 1)
|
||||||
|
path[path.len] = current.position
|
||||||
|
var/index = path.len - 1
|
||||||
|
|
||||||
|
while(current.previous_node)
|
||||||
|
current = current.previous_node
|
||||||
|
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(max_node_depth)
|
||||||
|
if(current.nodes_traversed >= max_node_depth)
|
||||||
|
continue
|
||||||
|
|
||||||
|
for(var/datum/datum in call(current.position, adjacent)(id))
|
||||||
|
if(datum == exclude)
|
||||||
|
continue
|
||||||
|
|
||||||
|
var/best_estimated_cost = current.estimated_cost + call(current.position, dist)(datum)
|
||||||
|
|
||||||
|
//handle removal of sub-par positions
|
||||||
|
if(datum in path_node_by_position)
|
||||||
|
var/datum/graph_astar_node/target = path_node_by_position[datum]
|
||||||
|
if(target.best_estimated_cost)
|
||||||
|
if(best_estimated_cost + call(datum, dist)(end) < target.best_estimated_cost)
|
||||||
|
open.remove_entry(target)
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
|
||||||
|
var/datum/graph_astar_node/next_node = new (datum, current, best_estimated_cost, call(datum, dist)(end), current.nodes_traversed + 1)
|
||||||
|
path_node_by_position[datum] = next_node
|
||||||
|
open.enqueue(next_node)
|
||||||
|
|
||||||
|
if(max_nodes && length(open.array) > max_nodes)
|
||||||
|
open.remove_index(length(open.array))
|
||||||
|
|
||||||
|
return path
|
||||||
397
code/_helpers/legacy_tg_path_ch.dm
Normal file
397
code/_helpers/legacy_tg_path_ch.dm
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
/**
|
||||||
|
* This file contains the stuff you need for using JPS (Jump Point Search) pathing, an alternative to A* that skips
|
||||||
|
* over large numbers of uninteresting tiles resulting in much quicker pathfinding solutions. Mind that diagonals
|
||||||
|
* cost the same as cardinal moves currently, so paths may look a bit strange, but should still be optimal.
|
||||||
|
*/
|
||||||
|
//////////////////////
|
||||||
|
//datum/tg_heap object
|
||||||
|
//////////////////////
|
||||||
|
|
||||||
|
/datum/tg_heap
|
||||||
|
var/list/L
|
||||||
|
var/cmp
|
||||||
|
|
||||||
|
/datum/tg_heap/New(compare)
|
||||||
|
L = new()
|
||||||
|
cmp = compare
|
||||||
|
|
||||||
|
/datum/tg_heap/Destroy(force, ...)
|
||||||
|
for(var/i in L) // because this is before the list helpers are loaded
|
||||||
|
qdel(i)
|
||||||
|
L = null
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/tg_heap/proc/is_empty()
|
||||||
|
return !length(L)
|
||||||
|
|
||||||
|
//insert and place at its position a new node in the heap
|
||||||
|
/datum/tg_heap/proc/insert(A)
|
||||||
|
|
||||||
|
L.Add(A)
|
||||||
|
swim(length(L))
|
||||||
|
|
||||||
|
//removes and returns the first element of the heap
|
||||||
|
//(i.e the max or the min dependant on the comparison function)
|
||||||
|
/datum/tg_heap/proc/pop()
|
||||||
|
if(!length(L))
|
||||||
|
return 0
|
||||||
|
. = L[1]
|
||||||
|
|
||||||
|
L[1] = L[length(L)]
|
||||||
|
L.Cut(length(L))
|
||||||
|
if(length(L))
|
||||||
|
sink(1)
|
||||||
|
|
||||||
|
//Get a node up to its right position in the heap
|
||||||
|
/datum/tg_heap/proc/swim(index)
|
||||||
|
var/parent = round(index * 0.5)
|
||||||
|
|
||||||
|
while(parent > 0 && (call(cmp)(L[index],L[parent]) > 0))
|
||||||
|
L.Swap(index,parent)
|
||||||
|
index = parent
|
||||||
|
parent = round(index * 0.5)
|
||||||
|
|
||||||
|
//Get a node down to its right position in the heap
|
||||||
|
/datum/tg_heap/proc/sink(index)
|
||||||
|
var/g_child = get_greater_child(index)
|
||||||
|
|
||||||
|
while(g_child > 0 && (call(cmp)(L[index],L[g_child]) < 0))
|
||||||
|
L.Swap(index,g_child)
|
||||||
|
index = g_child
|
||||||
|
g_child = get_greater_child(index)
|
||||||
|
|
||||||
|
//Returns the greater (relative to the comparison proc) of a node children
|
||||||
|
//or 0 if there's no child
|
||||||
|
/datum/tg_heap/proc/get_greater_child(index)
|
||||||
|
if(index * 2 > length(L))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if(index * 2 + 1 > length(L))
|
||||||
|
return index * 2
|
||||||
|
|
||||||
|
if(call(cmp)(L[index * 2],L[index * 2 + 1]) < 0)
|
||||||
|
return index * 2 + 1
|
||||||
|
else
|
||||||
|
return index * 2
|
||||||
|
|
||||||
|
//Replaces a given node so it verify the heap condition
|
||||||
|
/datum/tg_heap/proc/resort(A)
|
||||||
|
var/index = L.Find(A)
|
||||||
|
|
||||||
|
swim(index)
|
||||||
|
sink(index)
|
||||||
|
|
||||||
|
/datum/tg_heap/proc/List()
|
||||||
|
. = L.Copy()
|
||||||
|
|
||||||
|
GLOBAL_LIST_INIT(legacy_tg_space_type_cache, typecacheof(/turf/space))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper macro to see if it's possible to step from the first turf into the second one, minding things like door access and directional windows.
|
||||||
|
* Note that this can only be used inside the [datum/tg_jps_pathfind][pathfind datum] since it uses variables from said datum.
|
||||||
|
* If you really want to optimize things, optimize this, cuz this gets called a lot.
|
||||||
|
*/
|
||||||
|
#define CAN_STEP(cur_turf, next) (next && !next.density && !(simulated_only && GLOB.legacy_tg_space_type_cache[next.type]) && !cur_turf.LinkBlockedWithAccess(next,caller, id) && (next != avoid))
|
||||||
|
/// Another helper macro for JPS, for telling when a node has forced neighbors that need expanding
|
||||||
|
#define STEP_NOT_HERE_BUT_THERE(cur_turf, dirA, dirB) ((!CAN_STEP(cur_turf, get_step(cur_turf, dirA)) && CAN_STEP(cur_turf, get_step(cur_turf, dirB))))
|
||||||
|
|
||||||
|
/// The JPS Node datum represents a turf that we find interesting enough to add to the open list and possibly search for new tiles from
|
||||||
|
/datum/tg_jps_node
|
||||||
|
/// The turf associated with this node
|
||||||
|
var/turf/tile
|
||||||
|
/// The node we just came from
|
||||||
|
var/datum/tg_jps_node/previous_node
|
||||||
|
/// The A* node weight (f_value = number_of_tiles + heuristic)
|
||||||
|
var/f_value
|
||||||
|
/// The A* node heuristic (a rough estimate of how far we are from the goal)
|
||||||
|
var/heuristic
|
||||||
|
/// How many steps it's taken to get here from the start (currently pulling double duty as steps taken & cost to get here, since all moves incl diagonals cost 1 rn)
|
||||||
|
var/number_tiles
|
||||||
|
/// How many steps it took to get here from the last node
|
||||||
|
var/jumps
|
||||||
|
/// Nodes store the endgoal so they can process their heuristic without a reference to the pathfind datum
|
||||||
|
var/turf/node_goal
|
||||||
|
|
||||||
|
/datum/tg_jps_node/New(turf/our_tile, datum/tg_jps_node/incoming_previous_node, jumps_taken, turf/incoming_goal)
|
||||||
|
tile = our_tile
|
||||||
|
jumps = jumps_taken
|
||||||
|
if(incoming_goal) // if we have the goal argument, this must be the first/starting node
|
||||||
|
node_goal = incoming_goal
|
||||||
|
else if(incoming_previous_node) // if we have the parent, this is from a direct lateral/diagonal scan, we can fill it all out now
|
||||||
|
previous_node = incoming_previous_node
|
||||||
|
number_tiles = previous_node.number_tiles + jumps
|
||||||
|
node_goal = previous_node.node_goal
|
||||||
|
heuristic = get_dist(tile, node_goal)
|
||||||
|
f_value = number_tiles + heuristic
|
||||||
|
// otherwise, no parent node means this is from a subscan lateral scan, so we just need the tile for now until we call [datum/jps/proc/update_parent] on it
|
||||||
|
|
||||||
|
/datum/tg_jps_node/Destroy(force, ...)
|
||||||
|
previous_node = null
|
||||||
|
return ..()
|
||||||
|
|
||||||
|
/datum/tg_jps_node/proc/update_parent(datum/tg_jps_node/new_parent)
|
||||||
|
previous_node = new_parent
|
||||||
|
node_goal = previous_node.node_goal
|
||||||
|
jumps = get_dist(tile, previous_node.tile)
|
||||||
|
number_tiles = previous_node.number_tiles + jumps
|
||||||
|
heuristic = get_dist(tile, node_goal)
|
||||||
|
f_value = number_tiles + heuristic
|
||||||
|
|
||||||
|
/// TODO: Macro this to reduce proc overhead
|
||||||
|
/proc/TGHeapPathWeightCompare(datum/tg_jps_node/a, datum/tg_jps_node/b)
|
||||||
|
return b.f_value - a.f_value
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The datum used to handle the JPS pathfinding, completely self-contained.
|
||||||
|
*/
|
||||||
|
/datum/tg_jps_pathfind
|
||||||
|
/// The thing that we're actually trying to path for
|
||||||
|
var/atom/movable/caller
|
||||||
|
/// The turf where we started at
|
||||||
|
var/turf/start
|
||||||
|
/// The turf we're trying to path to (note that this won't track a moving target)
|
||||||
|
var/turf/end
|
||||||
|
/// The open list/stack we pop nodes out from (TODO: make this a normal list and macro-ize the heap operations to reduce proc overhead)
|
||||||
|
var/datum/tg_heap/open
|
||||||
|
///An assoc list that serves as the closed list & tracks what turfs came from where. Key is the turf, and the value is what turf it came from
|
||||||
|
var/list/sources
|
||||||
|
/// The list we compile at the end if successful to pass back
|
||||||
|
var/list/path
|
||||||
|
|
||||||
|
// general pathfinding vars/args
|
||||||
|
/// An ID card representing what access we have and what doors we can open. Its location relative to the pathing atom is irrelevant
|
||||||
|
var/obj/item/weapon/card/id/id
|
||||||
|
/// How far away we have to get to the end target before we can call it quits
|
||||||
|
var/mintargetdist = 0
|
||||||
|
/// I don't know what this does vs , but they limit how far we can search before giving up on a path
|
||||||
|
var/max_distance = 30
|
||||||
|
/// Space is big and empty, if this is TRUE then we ignore pathing through unsimulated tiles
|
||||||
|
var/simulated_only
|
||||||
|
/// A specific turf we're avoiding, like if a mulebot is being blocked by someone t-posing in a doorway we're trying to get through
|
||||||
|
var/turf/avoid
|
||||||
|
|
||||||
|
/datum/tg_jps_pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid)
|
||||||
|
src.caller = caller
|
||||||
|
end = get_turf(goal)
|
||||||
|
open = new /datum/tg_heap(GLOBAL_PROC_REF(TGHeapPathWeightCompare))
|
||||||
|
sources = new()
|
||||||
|
src.id = id
|
||||||
|
src.max_distance = max_distance
|
||||||
|
src.mintargetdist = mintargetdist
|
||||||
|
src.simulated_only = simulated_only
|
||||||
|
src.avoid = avoid
|
||||||
|
|
||||||
|
/**
|
||||||
|
* search() is the proc you call to kick off and handle the actual pathfinding, and kills the pathfind datum instance when it's done.
|
||||||
|
*
|
||||||
|
* If a valid path was found, it's returned as a list. If invalid or cross-z-level params are entered, or if there's no valid path found, we
|
||||||
|
* return null, which [/proc/get_path_to] translates to an empty list (notable for simple bots, who need empty lists)
|
||||||
|
*/
|
||||||
|
/datum/tg_jps_pathfind/proc/search()
|
||||||
|
start = get_turf(caller)
|
||||||
|
if(!start || !end)
|
||||||
|
stack_trace("Invalid A* start or destination")
|
||||||
|
return
|
||||||
|
if(start.z != end.z || start == end ) //no pathfinding between z levels
|
||||||
|
return
|
||||||
|
if(max_distance && (max_distance < get_dist(start, end))) //if start turf is farther than max_distance from end turf, no need to do anything
|
||||||
|
return
|
||||||
|
|
||||||
|
//initialization
|
||||||
|
var/datum/tg_jps_node/current_processed_node = new (start, -1, 0, end)
|
||||||
|
open.insert(current_processed_node)
|
||||||
|
sources[start] = start // i'm sure this is fine
|
||||||
|
|
||||||
|
//then run the main loop
|
||||||
|
while(!open.is_empty() && !path)
|
||||||
|
if(!caller)
|
||||||
|
return
|
||||||
|
current_processed_node = open.pop() //get the lower f_value turf in the open list
|
||||||
|
if(max_distance && (current_processed_node.number_tiles > max_distance))//if too many steps, don't process that path
|
||||||
|
continue
|
||||||
|
|
||||||
|
var/turf/current_turf = current_processed_node.tile
|
||||||
|
for(var/scan_direction in list(EAST, WEST, NORTH, SOUTH))
|
||||||
|
lateral_scan_spec(current_turf, scan_direction, current_processed_node)
|
||||||
|
|
||||||
|
for(var/scan_direction in list(NORTHEAST, SOUTHEAST, NORTHWEST, SOUTHWEST))
|
||||||
|
diag_scan_spec(current_turf, scan_direction, current_processed_node)
|
||||||
|
|
||||||
|
CHECK_TICK
|
||||||
|
|
||||||
|
//we're done! reverse the path to get it from start to finish
|
||||||
|
if(path)
|
||||||
|
for(var/i = 1 to round(0.5 * length(path)))
|
||||||
|
path.Swap(i, length(path) - i + 1)
|
||||||
|
|
||||||
|
sources = null
|
||||||
|
qdel(open)
|
||||||
|
return path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when we've hit the goal with the node that represents the last tile,
|
||||||
|
* then sets the path var to that path so it can be returned by [datum/tg_jps_pathfind/proc/search]
|
||||||
|
*/
|
||||||
|
/datum/tg_jps_pathfind/proc/unwind_path(datum/tg_jps_node/unwind_node)
|
||||||
|
path = new()
|
||||||
|
var/turf/iter_turf = unwind_node.tile
|
||||||
|
path.Add(iter_turf)
|
||||||
|
|
||||||
|
while(unwind_node.previous_node)
|
||||||
|
var/dir_goal = get_dir(iter_turf, unwind_node.previous_node.tile)
|
||||||
|
for(var/i = 1 to unwind_node.jumps)
|
||||||
|
iter_turf = get_step(iter_turf,dir_goal)
|
||||||
|
path.Add(iter_turf)
|
||||||
|
unwind_node = unwind_node.previous_node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For performing lateral scans from a given starting turf.
|
||||||
|
*
|
||||||
|
* These scans are called from both the main search loop, as well as subscans for diagonal scans, and they treat finding interesting turfs slightly differently.
|
||||||
|
* If we're doing a normal lateral scan, we already have a parent node supplied, so we just create the new node and immediately insert it into the heap, ezpz.
|
||||||
|
* If we're part of a subscan, we still need for the diagonal scan to generate a parent node, so we return a node datum with just the turf and let the diag scan
|
||||||
|
* proc handle transferring the values and inserting them into the heap.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* * original_turf: What turf did we start this scan at?
|
||||||
|
* * heading: What direction are we going in? Obviously, should be cardinal
|
||||||
|
* * parent_node: Only given for normal lateral scans, if we don't have one, we're a diagonal subscan.
|
||||||
|
*/
|
||||||
|
/datum/tg_jps_pathfind/proc/lateral_scan_spec(turf/original_turf, heading, datum/tg_jps_node/parent_node)
|
||||||
|
var/steps_taken = 0
|
||||||
|
|
||||||
|
var/turf/current_turf = original_turf
|
||||||
|
var/turf/lag_turf = original_turf
|
||||||
|
|
||||||
|
while(TRUE)
|
||||||
|
if(path)
|
||||||
|
return
|
||||||
|
lag_turf = current_turf
|
||||||
|
current_turf = get_step(current_turf, heading)
|
||||||
|
steps_taken++
|
||||||
|
if(!CAN_STEP(lag_turf, current_turf))
|
||||||
|
return
|
||||||
|
|
||||||
|
if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist)))
|
||||||
|
var/datum/tg_jps_node/final_node = new(current_turf, parent_node, steps_taken)
|
||||||
|
sources[current_turf] = original_turf
|
||||||
|
if(parent_node) // if this is a direct lateral scan we can wrap up, if it's a subscan from a diag, we need to let the diag make their node first, then finish
|
||||||
|
unwind_path(final_node)
|
||||||
|
return final_node
|
||||||
|
else if(sources[current_turf]) // already visited, essentially in the closed list
|
||||||
|
return
|
||||||
|
else
|
||||||
|
sources[current_turf] = original_turf
|
||||||
|
|
||||||
|
if(parent_node && parent_node.number_tiles + steps_taken > max_distance)
|
||||||
|
return
|
||||||
|
|
||||||
|
var/interesting = FALSE // have we found a forced neighbor that would make us add this turf to the open list?
|
||||||
|
|
||||||
|
switch(heading)
|
||||||
|
if(NORTH)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, EAST, NORTHEAST))
|
||||||
|
interesting = TRUE
|
||||||
|
if(SOUTH)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, SOUTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, EAST, SOUTHEAST))
|
||||||
|
interesting = TRUE
|
||||||
|
if(EAST)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHEAST))
|
||||||
|
interesting = TRUE
|
||||||
|
if(WEST)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHWEST))
|
||||||
|
interesting = TRUE
|
||||||
|
|
||||||
|
if(interesting)
|
||||||
|
var/datum/tg_jps_node/newnode = new(current_turf, parent_node, steps_taken)
|
||||||
|
if(parent_node) // if we're a diagonal subscan, we'll handle adding ourselves to the heap in the diag
|
||||||
|
open.insert(newnode)
|
||||||
|
return newnode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For performing diagonal scans from a given starting turf.
|
||||||
|
*
|
||||||
|
* Unlike lateral scans, these only are called from the main search loop, so we don't need to worry about returning anything,
|
||||||
|
* though we do need to handle the return values of our lateral subscans of course.
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* * original_turf: What turf did we start this scan at?
|
||||||
|
* * heading: What direction are we going in? Obviously, should be diagonal
|
||||||
|
* * parent_node: We should always have a parent node for diagonals
|
||||||
|
*/
|
||||||
|
/datum/tg_jps_pathfind/proc/diag_scan_spec(turf/original_turf, heading, datum/tg_jps_node/parent_node)
|
||||||
|
var/steps_taken = 0
|
||||||
|
var/turf/current_turf = original_turf
|
||||||
|
var/turf/lag_turf = original_turf
|
||||||
|
|
||||||
|
while(TRUE)
|
||||||
|
if(path)
|
||||||
|
return
|
||||||
|
lag_turf = current_turf
|
||||||
|
current_turf = get_step(current_turf, heading)
|
||||||
|
steps_taken++
|
||||||
|
if(!CAN_STEP(lag_turf, current_turf))
|
||||||
|
return
|
||||||
|
|
||||||
|
if(current_turf == end || (mintargetdist && (get_dist(current_turf, end) <= mintargetdist)))
|
||||||
|
var/datum/tg_jps_node/final_node = new(current_turf, parent_node, steps_taken)
|
||||||
|
sources[current_turf] = original_turf
|
||||||
|
unwind_path(final_node)
|
||||||
|
return
|
||||||
|
else if(sources[current_turf]) // already visited, essentially in the closed list
|
||||||
|
return
|
||||||
|
else
|
||||||
|
sources[current_turf] = original_turf
|
||||||
|
|
||||||
|
if(parent_node.number_tiles + steps_taken > max_distance)
|
||||||
|
return
|
||||||
|
|
||||||
|
var/interesting = FALSE // have we found a forced neighbor that would make us add this turf to the open list?
|
||||||
|
var/datum/tg_jps_node/possible_child_node // otherwise, did one of our lateral subscans turn up something?
|
||||||
|
|
||||||
|
switch(heading)
|
||||||
|
if(NORTHWEST)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, EAST, NORTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHWEST))
|
||||||
|
interesting = TRUE
|
||||||
|
else
|
||||||
|
possible_child_node = (lateral_scan_spec(current_turf, WEST) || lateral_scan_spec(current_turf, NORTH))
|
||||||
|
if(NORTHEAST)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, NORTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, SOUTH, SOUTHEAST))
|
||||||
|
interesting = TRUE
|
||||||
|
else
|
||||||
|
possible_child_node = (lateral_scan_spec(current_turf, EAST) || lateral_scan_spec(current_turf, NORTH))
|
||||||
|
if(SOUTHWEST)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, EAST, SOUTHEAST) || STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHWEST))
|
||||||
|
interesting = TRUE
|
||||||
|
else
|
||||||
|
possible_child_node = (lateral_scan_spec(current_turf, SOUTH) || lateral_scan_spec(current_turf, WEST))
|
||||||
|
if(SOUTHEAST)
|
||||||
|
if(STEP_NOT_HERE_BUT_THERE(current_turf, WEST, SOUTHWEST) || STEP_NOT_HERE_BUT_THERE(current_turf, NORTH, NORTHEAST))
|
||||||
|
interesting = TRUE
|
||||||
|
else
|
||||||
|
possible_child_node = (lateral_scan_spec(current_turf, SOUTH) || lateral_scan_spec(current_turf, EAST))
|
||||||
|
|
||||||
|
if(interesting || possible_child_node)
|
||||||
|
var/datum/tg_jps_node/newnode = new(current_turf, parent_node, steps_taken)
|
||||||
|
open.insert(newnode)
|
||||||
|
if(possible_child_node)
|
||||||
|
possible_child_node.update_parent(newnode)
|
||||||
|
open.insert(possible_child_node)
|
||||||
|
if(possible_child_node.tile == end || (mintargetdist && (get_dist(possible_child_node.tile, end) <= mintargetdist)))
|
||||||
|
unwind_path(possible_child_node)
|
||||||
|
return
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For seeing if we can actually move between 2 given turfs while accounting for our access and the caller's pass_flags
|
||||||
|
*
|
||||||
|
* Arguments:
|
||||||
|
* * caller: The movable, if one exists, being used for mobility checks to see what tiles it can reach
|
||||||
|
* * ID: An ID card that decides if we can gain access to doors that would otherwise block a turf
|
||||||
|
* * simulated_only: Do we only worry about turfs with simulated atmos, most notably things that aren't space?
|
||||||
|
*/
|
||||||
|
/turf/proc/LinkBlockedWithAccess(turf/destination_turf, caller, ID)
|
||||||
|
var/static/datum/pathfinding/whatever = new
|
||||||
|
return !global.default_pathfinding_adjacency(src, destination_turf, GLOB.generic_pathfinding_actor, whatever)
|
||||||
|
|
||||||
|
#undef CAN_STEP
|
||||||
|
#undef STEP_NOT_HERE_BUT_THERE
|
||||||
258
code/_helpers/pathfinding_ch/astar.dm
Normal file
258
code/_helpers/pathfinding_ch/astar.dm
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
//* 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
|
||||||
199
code/_helpers/pathfinding_ch/common.dm
Normal file
199
code/_helpers/pathfinding_ch/common.dm
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
//* This file is explicitly licensed under the MIT license. *//
|
||||||
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default object used during pathfinder checks
|
||||||
|
*/
|
||||||
|
GLOBAL_DATUM_INIT(generic_pathfinding_actor, /atom/movable/pathfinding_predicate, new)
|
||||||
|
|
||||||
|
/atom/movable/pathfinding_predicate
|
||||||
|
invisibility = INVISIBILITY_ABSTRACT
|
||||||
|
//pass_flags = ATOM_PASS_CLICK
|
||||||
|
//pass_flags_self = NONE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* datum used for pathfinding
|
||||||
|
*
|
||||||
|
* pathfinding is a specific version of otherwise generic graph/grid searches
|
||||||
|
* we only path via cardinals due to ss13's movement treating diagonals as two cardinal moves
|
||||||
|
* pixel movement is explicitly non-supported at this time
|
||||||
|
*
|
||||||
|
* for overmaps / similar pixel-move-ish tasks, please write a new pathfinding system if you want
|
||||||
|
* accurate results.
|
||||||
|
*/
|
||||||
|
/datum/pathfinding
|
||||||
|
//* basics
|
||||||
|
/// thing trying to get a path
|
||||||
|
var/atom/movable/actor
|
||||||
|
/// start turf
|
||||||
|
var/turf/start
|
||||||
|
/// goal turf
|
||||||
|
var/turf/goal
|
||||||
|
|
||||||
|
//* options
|
||||||
|
/// how far away to the end we want to get; 0 = get ontop of the tile, 1 = get adjacent to the tile
|
||||||
|
/// keep in mind that pathing with 0 to a dense object is usually going to fail!
|
||||||
|
/// this is in byond distance, *not* pathfinding distance
|
||||||
|
/// this means that 1 tile away diagonally = 1, 2 diagonally away = 2, etc.
|
||||||
|
var/target_distance
|
||||||
|
/// if target distance is one, we require adjacency
|
||||||
|
var/require_adjacency_when_going_adjacent = TRUE
|
||||||
|
/// how far away total we can search
|
||||||
|
/// this is not distance from source we want to go, this is how far away we can *search*
|
||||||
|
/// (the former might be the case for some algorithms, though).
|
||||||
|
/// this should not be used to limit pathfinding max distance / path distance
|
||||||
|
/// this just tells the algorithm when it should give up
|
||||||
|
/// different algorithms respond differently to this.
|
||||||
|
var/max_path_length
|
||||||
|
/// context to call adjacency/distance call on
|
||||||
|
/// null = global proc
|
||||||
|
var/datum/context
|
||||||
|
/// checks if we can go to a turf
|
||||||
|
/// defaults to default density / canpass / etc checks
|
||||||
|
/// called with (turf/A, turf/B, atom/movable/actor, datum/pathfinding/pathfinding)
|
||||||
|
/// it should return the distance to that turf
|
||||||
|
var/adjacency_call = /proc/default_pathfinding_adjacency
|
||||||
|
/// checks distance from turf to target / end turf
|
||||||
|
/// defaults to just get dist
|
||||||
|
/// called with (turf/current, turf/goal)
|
||||||
|
var/heuristic_call = /proc/default_pathfinding_heuristic
|
||||||
|
/// danger flags to ignore
|
||||||
|
var/turf_path_danger_ignore = NONE
|
||||||
|
|
||||||
|
//* ss13-specific things
|
||||||
|
/// access list ; used to get through doors and other objects if set
|
||||||
|
var/list/ss13_with_access
|
||||||
|
|
||||||
|
/datum/pathfinding/New(atom/movable/actor, turf/start, turf/goal, target_distance, max_path_length)
|
||||||
|
src.actor = actor
|
||||||
|
src.start = start
|
||||||
|
src.goal = goal
|
||||||
|
src.target_distance = target_distance
|
||||||
|
src.max_path_length = max_path_length
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns raw list of nodes returned by algorithm
|
||||||
|
*/
|
||||||
|
/datum/pathfinding/proc/search()
|
||||||
|
RETURN_TYPE(/list)
|
||||||
|
CRASH("Not implemented on base type.")
|
||||||
|
|
||||||
|
/datum/pathfinding/proc/debug_log_string()
|
||||||
|
return json_encode(vars)
|
||||||
|
|
||||||
|
/datum/pathfinding_context
|
||||||
|
|
||||||
|
/datum/pathfinding_context/proc/adjacency(turf/A, turf/B, atom/movable/actor, datum/pathfinding/search)
|
||||||
|
return default_pathfinding_adjacency(A, B, actor, search)
|
||||||
|
|
||||||
|
/datum/pathfinding_context/proc/heuristic(turf/current, turf/goal)
|
||||||
|
return default_pathfinding_heuristic(current, goal)
|
||||||
|
|
||||||
|
/datum/pathfinding_context/ignoring
|
||||||
|
/// ignore typecache
|
||||||
|
var/list/turf_ignore_typecache
|
||||||
|
/// ignore instance cache
|
||||||
|
var/list/turf_ignore_cache
|
||||||
|
|
||||||
|
/datum/pathfinding_context/ignoring/adjacency(turf/A, turf/B, atom/movable/actor, datum/pathfinding/search)
|
||||||
|
if(!isnull(turf_ignore_typecache) && turf_ignore_typecache[B.type])
|
||||||
|
return FALSE
|
||||||
|
if(!isnull(turf_ignore_cache) && turf_ignore_cache[B.type])
|
||||||
|
return FALSE
|
||||||
|
return default_pathfinding_adjacency(A, B, actor, search)
|
||||||
|
|
||||||
|
//* ENSURE BELOW PROCS MATCH EACH OTHER IN THEIR PAIRS *//
|
||||||
|
//* This allows for fast default implementations while *//
|
||||||
|
//* allowing for advanced checks when a pathfinding *//
|
||||||
|
//* context is supplied. *//
|
||||||
|
|
||||||
|
/proc/default_pathfinding_adjacency(turf/A, turf/B, atom/movable/actor, datum/pathfinding/search)
|
||||||
|
// we really need to optimize this furthur
|
||||||
|
// this currently catches abstract stuff like lighting objects
|
||||||
|
// not great for performance.
|
||||||
|
|
||||||
|
if(B.density)
|
||||||
|
return FALSE
|
||||||
|
if((B.turf_path_danger & search.turf_path_danger_ignore) != B.turf_path_danger)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/dir = get_dir(A, B)
|
||||||
|
|
||||||
|
if(dir & (dir - 1))
|
||||||
|
var/td1 = dir & (NORTH|SOUTH)
|
||||||
|
var/td2 = dir & (EAST|WEST)
|
||||||
|
var/turf/scan = get_step(A, td1)
|
||||||
|
if(!isnull(scan) && default_pathfinding_adjacency(A, scan, actor, search) && default_pathfinding_adjacency(scan, B, actor, search))
|
||||||
|
return TRUE
|
||||||
|
scan = get_step(A, td2)
|
||||||
|
if(!isnull(scan) && default_pathfinding_adjacency(A, scan, actor, search) && default_pathfinding_adjacency(scan, B, actor, search))
|
||||||
|
return TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/rdir = turn(dir, 180)
|
||||||
|
|
||||||
|
for(var/atom/movable/AM as anything in A)
|
||||||
|
if(!AM.can_pathfinding_exit(actor, dir, search))
|
||||||
|
return FALSE
|
||||||
|
for(var/atom/movable/AM as anything in B)
|
||||||
|
if(!AM.can_pathfinding_enter(actor, rdir, search))
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/proc/default_pathfinding_heuristic(turf/current, turf/goal)
|
||||||
|
return max(abs(current.x - goal.x), abs(current.y - goal.y))
|
||||||
|
|
||||||
|
/proc/jps_pathfinding_adjacency(turf/A, turf/B, atom/movable/actor, datum/pathfinding/search)
|
||||||
|
// we really need to optimize this furthur
|
||||||
|
// this currently catches abstract stuff like lighting objects
|
||||||
|
// not great for performance.
|
||||||
|
|
||||||
|
if(B.density)
|
||||||
|
return FALSE
|
||||||
|
if((B.turf_path_danger & search.turf_path_danger_ignore) != B.turf_path_danger)
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
var/dir = get_dir(A, B)
|
||||||
|
|
||||||
|
if(dir & (dir - 1))
|
||||||
|
var/td1 = dir & (NORTH|SOUTH)
|
||||||
|
var/td2 = dir & (EAST|WEST)
|
||||||
|
var/turf/scan = get_step(A, td1)
|
||||||
|
if(!isnull(scan) && jps_pathfinding_adjacency(A, scan, actor, search) && jps_pathfinding_adjacency(scan, B, actor, search))
|
||||||
|
return TRUE
|
||||||
|
scan = get_step(A, td2)
|
||||||
|
if(!isnull(scan) && jps_pathfinding_adjacency(A, scan, actor, search) && jps_pathfinding_adjacency(scan, B, actor, search))
|
||||||
|
return TRUE
|
||||||
|
return FALSE
|
||||||
|
|
||||||
|
for(var/atom/movable/AM as anything in B)
|
||||||
|
if(!AM.can_pathfinding_pass(actor, search))
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a pretty hot proc used during pathfinding to see if something
|
||||||
|
* should be able to pass through this movable in a certain direction.
|
||||||
|
*
|
||||||
|
* dir is where they're coming from
|
||||||
|
*/
|
||||||
|
/atom/movable/proc/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return !density /*|| (pass_flags_self & actor.pass_flags)*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a pretty hot proc used during pathfinding to see if something
|
||||||
|
* should be able to pass out of this movable in a certain direction.
|
||||||
|
*
|
||||||
|
* dir is where they're going to
|
||||||
|
*/
|
||||||
|
/atom/movable/proc/can_pathfinding_exit(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return !(flags & ON_BORDER) || !density /*|| (pass_flags_self & actor.pass_flags)*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* basically, non directional pathfinding enter/exit checks
|
||||||
|
*
|
||||||
|
* this is used for JPS because it does not at all play nicely with situations where one direction
|
||||||
|
* is blocked and another isn't.
|
||||||
|
*/
|
||||||
|
/atom/movable/proc/can_pathfinding_pass(atom/movable/actor, datum/pathfinding/search)
|
||||||
|
return !density /*|| (pass_flags_self & actor.pass_flags)*/
|
||||||
603
code/_helpers/pathfinding_ch/jps.dm
Normal file
603
code/_helpers/pathfinding_ch/jps.dm
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
//* This file is explicitly licensed under the MIT license. *//
|
||||||
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
||||||
|
|
||||||
|
/// visualization; obviously slow as hell
|
||||||
|
/// JPS visualization is currently not nearly as perfect as A*'s.
|
||||||
|
/// notably is sometimes marks stuff closed that isn't because of the weird backtracking stuff I put in.
|
||||||
|
// #define JPS_DEBUGGING
|
||||||
|
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
|
||||||
|
#warn JPS pathfinding visualizations enabled
|
||||||
|
/// visualization delay
|
||||||
|
GLOBAL_VAR_INIT(jps_visualization_delay, 0.05 SECONDS)
|
||||||
|
/// how long to persist the visuals
|
||||||
|
GLOBAL_VAR_INIT(jps_visualization_persist, 3 SECONDS)
|
||||||
|
/// visualize nodes or finished path
|
||||||
|
GLOBAL_VAR_INIT(jps_visualization_resolve, TRUE)
|
||||||
|
|
||||||
|
/proc/get_jps_scan_overlay(dir, forwards)
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
#define JPS_VISUAL_DELAY 10 SECONDS
|
||||||
|
#define JPS_VISUAL_COLOR_CLOSED "#ff3333"
|
||||||
|
#define JPS_VISUAL_COLOR_OUT_OF_BOUNDS "#555555"
|
||||||
|
#define JPS_VISUAL_COLOR_OPEN "#7777ff"
|
||||||
|
#define JPS_VISUAL_COLOR_FOUND "#33ff33"
|
||||||
|
#define JPS_VISUAL_COLOR_CURRENT "#ffff00"
|
||||||
|
#define JPS_VISUAL_COLOR_INTERMEDIATE "#ff00ff"
|
||||||
|
|
||||||
|
/proc/jps_wipe_colors_after(list/turf/turfs, time)
|
||||||
|
set waitfor = FALSE
|
||||||
|
jps_wipe_colors_after_sleeping(turfs, time)
|
||||||
|
|
||||||
|
/proc/jps_wipe_colors_after_sleeping(list/turf/turfs, time)
|
||||||
|
sleep(time)
|
||||||
|
for(var/turf/T in turfs)
|
||||||
|
T.color = null
|
||||||
|
T.maptext = null
|
||||||
|
// lol just cut all this is a debug proc anyways
|
||||||
|
T.overlays.len = 0
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/datum/jps_node
|
||||||
|
/// our turf
|
||||||
|
var/turf/pos
|
||||||
|
/// previous node
|
||||||
|
var/datum/jps_node/prev
|
||||||
|
|
||||||
|
/// our heuristic to goal
|
||||||
|
var/heuristic
|
||||||
|
/// our node depth - for jps, this is just the amount turfs passed to go from start to here.
|
||||||
|
var/depth
|
||||||
|
/// our jump direction
|
||||||
|
var/dir
|
||||||
|
/// our score - built from heuristic and cost
|
||||||
|
var/score
|
||||||
|
|
||||||
|
/datum/jps_node/New(turf/pos, datum/jps_node/prev, heuristic, depth, dir)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
ASSERT(isturf(pos))
|
||||||
|
#endif
|
||||||
|
src.pos = pos
|
||||||
|
src.prev = prev
|
||||||
|
src.heuristic = heuristic
|
||||||
|
src.depth = depth
|
||||||
|
src.dir = dir
|
||||||
|
|
||||||
|
src.score = depth + heuristic
|
||||||
|
|
||||||
|
/proc/cmp_jps_node(datum/jps_node/A, datum/jps_node/B)
|
||||||
|
return A.score - B.score
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPS (jump point search)
|
||||||
|
*
|
||||||
|
* * flat routes
|
||||||
|
* * inherently emits diagonals
|
||||||
|
* * emits a bunch nodes to walk to instead of a clear path
|
||||||
|
* * all tiles are treated as 1 distance - including diagonals.
|
||||||
|
* * max_dist is *really* weird. It uses JPs path lengths, so, you probably need it a good bit higher than your target distance.
|
||||||
|
* * jps cannot handle turfs that allow in one dir only at all. for precision navigation in those cases, you'll need A*.
|
||||||
|
*/
|
||||||
|
/datum/pathfinding/jps
|
||||||
|
adjacency_call = /proc/jps_pathfinding_adjacency
|
||||||
|
|
||||||
|
/datum/pathfinding/jps/search()
|
||||||
|
//* define ops
|
||||||
|
#define JPS_HEURISTIC_CALL(TURF) (isnull(context)? call(heuristic_call)(TURF, goal) : call(context, heuristic_call)(TURF, goal))
|
||||||
|
#define JPS_ADJACENCY_CALL(A, B) (isnull(context)? call(adjacency_call)(A, B, actor, src) : call(context, adjacency_call)(A, B, actor, src))
|
||||||
|
//* preliminary checks
|
||||||
|
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_chebyshev_dist(src.start, src.goal) > max_path_length)
|
||||||
|
return null
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
//* set up debugging vars
|
||||||
|
// turf associated to how many open nodes are on it; once 0, it becomes closed. if setting to something other than closed, set to -1.
|
||||||
|
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
|
||||||
|
if(SSpathfinder.pathfinding_cycle >= SHORT_REAL_LIMIT)
|
||||||
|
SSpathfinder.pathfinding_cycle = 0
|
||||||
|
// our cycle. used to determine if a turf was pathed on by us. in theory, this isn't entirely collision resistant,
|
||||||
|
// but i don't really care :>
|
||||||
|
var/cycle = ++SSpathfinder.pathfinding_cycle
|
||||||
|
//* variables - run
|
||||||
|
// open priority queue
|
||||||
|
var/datum/priority_queue/open = new /datum/priority_queue(/proc/cmp_jps_node)
|
||||||
|
// used when creating a node if we need to reference it
|
||||||
|
var/datum/jps_node/node_creating
|
||||||
|
// the top node that we fetch at start of cycle
|
||||||
|
var/datum/jps_node/node_top
|
||||||
|
// turf of top node
|
||||||
|
var/turf/node_top_pos
|
||||||
|
// dir of top node
|
||||||
|
var/node_top_dir
|
||||||
|
//* variables - diagonal scan
|
||||||
|
// turf we're on right now
|
||||||
|
var/turf/dscan_current
|
||||||
|
// turf we're about to hop to
|
||||||
|
var/turf/dscan_next
|
||||||
|
// side dir 1 for cardinal scan
|
||||||
|
var/dscan_dir1
|
||||||
|
// side dir 2 for cardinal scan
|
||||||
|
var/dscan_dir2
|
||||||
|
// did a forced neighbor get detected in either cardinal scan
|
||||||
|
var/dscan_pass
|
||||||
|
// current number of steps in the scan
|
||||||
|
var/dscan_steps
|
||||||
|
// where we started at, steps wise, so we can properly trim by depth
|
||||||
|
var/dscan_initial
|
||||||
|
// diagonal node - this is held here because if we get a potential spot on cardinal we need to immediately
|
||||||
|
// make the diagonal node
|
||||||
|
var/datum/jps_node/dscan_node
|
||||||
|
//* variables - cardinal scan
|
||||||
|
// turf we're on right now
|
||||||
|
var/turf/cscan_current
|
||||||
|
// turf we're about to hop to
|
||||||
|
var/turf/cscan_next
|
||||||
|
// turf we were on last so we can make a node there when we have a forced neighbor
|
||||||
|
var/turf/cscan_last
|
||||||
|
// turf we're scanning to side
|
||||||
|
var/turf/cscan_turf1
|
||||||
|
// turf we're scanning to side
|
||||||
|
var/turf/cscan_turf2
|
||||||
|
// perpendicular dir 1
|
||||||
|
var/cscan_dir1
|
||||||
|
// perpendicular dir 2
|
||||||
|
var/cscan_dir2
|
||||||
|
// perpendicular dir 1 didn't fail
|
||||||
|
var/cscan_dir1_pass
|
||||||
|
// perpendicular dir 2 didn't fail
|
||||||
|
var/cscan_dir2_pass
|
||||||
|
// did a forced neighbor get detected?
|
||||||
|
var/cscan_pass
|
||||||
|
// current number of steps in the scan
|
||||||
|
var/cscan_steps
|
||||||
|
// where we started at, steps wise, so we can properly trim by depth
|
||||||
|
var/cscan_initial
|
||||||
|
//* start
|
||||||
|
// get start heuristic
|
||||||
|
var/start_heuristic = JPS_HEURISTIC_CALL(start)
|
||||||
|
// for best case, we estimate the 'right' dir to go at first
|
||||||
|
var/start_dir = jps_estimate_dir(start, goal)
|
||||||
|
// dir being checked
|
||||||
|
var/start_check_dir
|
||||||
|
// turf being checked
|
||||||
|
var/turf/start_check
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
turfs_got_colored[start] = 8
|
||||||
|
start.color = JPS_VISUAL_COLOR_OPEN
|
||||||
|
#define JPS_START_DIR(DIR) \
|
||||||
|
start_check_dir = DIR ; \
|
||||||
|
start_check = get_step(start, start_check_dir); \
|
||||||
|
if(!isnull(start_check) && JPS_ADJACENCY_CALL(start, start_check)) { \
|
||||||
|
start.overlays += get_jps_scan_overlay(DIR, TRUE); \
|
||||||
|
node_creating = new /datum/jps_node(start, null, start_heuristic, 0, start_check_dir) ; \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define JPS_START_DIR(DIR) \
|
||||||
|
start_check_dir = DIR ; \
|
||||||
|
start_check = get_step(start, start_check_dir); \
|
||||||
|
if(!isnull(start_check) && JPS_ADJACENCY_CALL(start, start_check)) { \
|
||||||
|
node_creating = new /datum/jps_node(start, null, start_heuristic, 0, start_check_dir) ; \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
JPS_START_DIR(start_dir)
|
||||||
|
JPS_START_DIR(turn(start_dir, 45))
|
||||||
|
JPS_START_DIR(turn(start_dir, -45))
|
||||||
|
JPS_START_DIR(turn(start_dir, 90))
|
||||||
|
JPS_START_DIR(turn(start_dir, -90))
|
||||||
|
JPS_START_DIR(turn(start_dir, 135))
|
||||||
|
JPS_START_DIR(turn(start_dir, -135))
|
||||||
|
JPS_START_DIR(turn(start_dir, 180))
|
||||||
|
//* define completion check
|
||||||
|
#define JPS_COMPLETION_CHECK(TURF) (get_dist(TURF, goal) <= target_distance && (target_distance != 1 || !require_adjacency_when_going_adjacent || TURF.TurfAdjacency(goal)))
|
||||||
|
//* define cardinal scan helpers
|
||||||
|
#define JPS_CARDINAL_DURING_DIAGONAL (node_top_dir & (node_top_dir - 1))
|
||||||
|
//* define cardinal scan
|
||||||
|
// things to note:
|
||||||
|
// - unlike diagonal / cardinal scan branches, this does not
|
||||||
|
// skip the first tile. this is because when it's used in a diagonal
|
||||||
|
// scan, it outright should not be skipping the first tile.
|
||||||
|
// order of ops:
|
||||||
|
// - check out of bounds/depth
|
||||||
|
// - check completion
|
||||||
|
// - place debug overlays
|
||||||
|
// - check sides and mark pass/fail; if it was already failing, mark the cpass fail and make diagonal nodes
|
||||||
|
// - if cpass failed, we also want to make our cardinal nodes
|
||||||
|
// - if any node is made, ensure that we are either not in diagonal mode, or if we are, the diagonal node was created
|
||||||
|
// - check and go to next turf
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
#define JPS_CARDINAL_SCAN(TURF, DIR) \
|
||||||
|
cscan_dir1 = turn(DIR, 90); \
|
||||||
|
cscan_dir2 = turn(DIR, -90); \
|
||||||
|
cscan_steps = 0; \
|
||||||
|
cscan_pass = TRUE; \
|
||||||
|
cscan_dir1_pass = TRUE; \
|
||||||
|
cscan_dir2_pass = TRUE; \
|
||||||
|
cscan_current = TURF; \
|
||||||
|
cscan_last = null; \
|
||||||
|
cscan_initial = JPS_CARDINAL_DURING_DIAGONAL? node_top.depth + dscan_steps : node_top.depth; \
|
||||||
|
do { \
|
||||||
|
if(cscan_steps + cscan_initial + get_dist(cscan_current, goal) > max_depth) { \
|
||||||
|
cscan_current.color = JPS_VISUAL_COLOR_OUT_OF_BOUNDS; \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
if(JPS_COMPLETION_CHECK(cscan_current)) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_current, dscan_node, JPS_HEURISTIC_CALL(cscan_current), dscan_node.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_current, node_top, JPS_HEURISTIC_CALL(cscan_current), node_top.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
return jps_unwind_path(node_creating, turfs_got_colored); \
|
||||||
|
} \
|
||||||
|
turfs_got_colored[cscan_current] = turfs_got_colored[cscan_current] || 0; \
|
||||||
|
cscan_current.overlays += get_jps_scan_overlay(DIR, JPS_CARDINAL_DURING_DIAGONAL); \
|
||||||
|
cscan_turf1 = get_step(cscan_current, cscan_dir1); \
|
||||||
|
cscan_turf2 = get_step(cscan_current, cscan_dir2); \
|
||||||
|
if(!isnull(cscan_turf1)) { \
|
||||||
|
if(!JPS_ADJACENCY_CALL(cscan_current, cscan_turf1)) { \
|
||||||
|
cscan_dir1_pass = FALSE ; \
|
||||||
|
} \
|
||||||
|
else if(cscan_dir1_pass == FALSE) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, dscan_node, JPS_HEURISTIC_CALL(cscan_last), dscan_node.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, node_top, JPS_HEURISTIC_CALL(cscan_last), node_top.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
turfs_got_colored[cscan_last] = turfs_got_colored[cscan_last] + 1; \
|
||||||
|
cscan_last.color = JPS_VISUAL_COLOR_OPEN; \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
cscan_pass = FALSE; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
if(!isnull(cscan_turf2)) { \
|
||||||
|
if(!JPS_ADJACENCY_CALL(cscan_current, cscan_turf2)) { \
|
||||||
|
cscan_dir2_pass = FALSE ; \
|
||||||
|
} \
|
||||||
|
else if(cscan_dir2_pass == FALSE) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, dscan_node, JPS_HEURISTIC_CALL(cscan_last), dscan_node.depth + cscan_steps - 1, DIR | cscan_dir2); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, node_top, JPS_HEURISTIC_CALL(cscan_last), node_top.depth + cscan_steps - 1, DIR | cscan_dir2); \
|
||||||
|
} \
|
||||||
|
turfs_got_colored[cscan_last] = turfs_got_colored[cscan_last] + 1; \
|
||||||
|
cscan_last.color = JPS_VISUAL_COLOR_OPEN; \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
cscan_pass = FALSE; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
if(!cscan_pass) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, dscan_node, JPS_HEURISTIC_CALL(cscan_last), dscan_node.depth + cscan_steps - 1, DIR); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, node_top, JPS_HEURISTIC_CALL(cscan_last), node_top.depth + cscan_steps - 1, DIR); \
|
||||||
|
} \
|
||||||
|
turfs_got_colored[cscan_last] = turfs_got_colored[cscan_last] + 1; \
|
||||||
|
cscan_last.color = JPS_VISUAL_COLOR_OPEN; \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
cscan_next = get_step(cscan_current, DIR); \
|
||||||
|
if(isnull(cscan_next) || (cscan_next.pathfinding_cycle == cycle) || !JPS_ADJACENCY_CALL(cscan_current, cscan_next)) { \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
cscan_current.pathfinding_cycle = cycle; \
|
||||||
|
cscan_last = cscan_current; \
|
||||||
|
cscan_current = cscan_next; \
|
||||||
|
cscan_steps++; \
|
||||||
|
} \
|
||||||
|
while(TRUE);
|
||||||
|
#else
|
||||||
|
#define JPS_CARDINAL_SCAN(TURF, DIR) \
|
||||||
|
cscan_dir1 = turn(DIR, 90); \
|
||||||
|
cscan_dir2 = turn(DIR, -90); \
|
||||||
|
cscan_steps = 0; \
|
||||||
|
cscan_pass = TRUE; \
|
||||||
|
cscan_dir1_pass = TRUE; \
|
||||||
|
cscan_dir2_pass = TRUE; \
|
||||||
|
cscan_current = TURF; \
|
||||||
|
cscan_last = null; \
|
||||||
|
cscan_initial = JPS_CARDINAL_DURING_DIAGONAL? node_top.depth + dscan_steps : node_top.depth; \
|
||||||
|
do { \
|
||||||
|
if(cscan_steps + cscan_initial + get_dist(cscan_current, goal) > max_depth) { \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
if(JPS_COMPLETION_CHECK(cscan_current)) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_current, dscan_node, JPS_HEURISTIC_CALL(cscan_current), dscan_node.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_current, node_top, JPS_HEURISTIC_CALL(cscan_current), node_top.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
return jps_unwind_path(node_creating); \
|
||||||
|
} \
|
||||||
|
cscan_turf1 = get_step(cscan_current, cscan_dir1); \
|
||||||
|
cscan_turf2 = get_step(cscan_current, cscan_dir2); \
|
||||||
|
if(!isnull(cscan_turf1)) { \
|
||||||
|
if(!JPS_ADJACENCY_CALL(cscan_current, cscan_turf1)) { \
|
||||||
|
cscan_dir1_pass = FALSE ; \
|
||||||
|
} \
|
||||||
|
else if(cscan_dir1_pass == FALSE) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, dscan_node, JPS_HEURISTIC_CALL(cscan_last), dscan_node.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, node_top, JPS_HEURISTIC_CALL(cscan_last), node_top.depth + cscan_steps - 1, DIR | cscan_dir1); \
|
||||||
|
} \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
cscan_pass = FALSE; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
if(!isnull(cscan_turf2)) { \
|
||||||
|
if(!JPS_ADJACENCY_CALL(cscan_current, cscan_turf2)) { \
|
||||||
|
cscan_dir2_pass = FALSE ; \
|
||||||
|
} \
|
||||||
|
else if(cscan_dir2_pass == FALSE) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, dscan_node, JPS_HEURISTIC_CALL(cscan_last), dscan_node.depth + cscan_steps - 1, DIR | cscan_dir2); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, node_top, JPS_HEURISTIC_CALL(cscan_last), node_top.depth + cscan_steps - 1, DIR | cscan_dir2); \
|
||||||
|
} \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
cscan_pass = FALSE; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
if(!cscan_pass) { \
|
||||||
|
if(JPS_CARDINAL_DURING_DIAGONAL && isnull(dscan_node)) { \
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir); \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, dscan_node, JPS_HEURISTIC_CALL(cscan_last), dscan_node.depth + cscan_steps - 1, DIR); \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
node_creating = new /datum/jps_node(cscan_last, node_top, JPS_HEURISTIC_CALL(cscan_last), node_top.depth + cscan_steps - 1, DIR); \
|
||||||
|
} \
|
||||||
|
open.enqueue(node_creating); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
cscan_next = get_step(cscan_current, DIR); \
|
||||||
|
if(isnull(cscan_next) || (cscan_next.pathfinding_cycle == cycle) || !JPS_ADJACENCY_CALL(cscan_current, cscan_next)) { \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
cscan_current.pathfinding_cycle = cycle; \
|
||||||
|
cscan_last = cscan_current; \
|
||||||
|
cscan_current = cscan_next; \
|
||||||
|
cscan_steps++; \
|
||||||
|
} \
|
||||||
|
while(TRUE);
|
||||||
|
#endif
|
||||||
|
//* loop
|
||||||
|
while(length(open.array))
|
||||||
|
node_top = open.dequeue()
|
||||||
|
node_top_pos = node_top.pos
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
node_top.pos.color = JPS_VISUAL_COLOR_CURRENT
|
||||||
|
sleep(GLOB.jps_visualization_delay)
|
||||||
|
#else
|
||||||
|
CHECK_TICK
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get distance and check completion
|
||||||
|
if(JPS_COMPLETION_CHECK(node_top_pos))
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
return jps_unwind_path(node_top, turfs_got_colored)
|
||||||
|
#else
|
||||||
|
return jps_unwind_path(node_top)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// too deep, abort
|
||||||
|
if(node_top.depth + get_dist(node_top_pos, goal) >= max_depth)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
node_top.pos.color = JPS_VISUAL_COLOR_OUT_OF_BOUNDS
|
||||||
|
turfs_got_colored[node_top.pos] = turfs_got_colored[node_top.pos] || 0
|
||||||
|
#endif
|
||||||
|
continue
|
||||||
|
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
if(!(turfs_got_colored[node_top.pos] -= 1))
|
||||||
|
node_top.pos.color = JPS_VISUAL_COLOR_CLOSED
|
||||||
|
else if(turfs_got_colored[node_top.pos] > 0)
|
||||||
|
node_top.pos.color = JPS_VISUAL_COLOR_OPEN
|
||||||
|
node_top_pos.maptext = MAPTEXT("d [node_top.depth]<br>s [node_top.score]<br>o [max(turfs_got_colored[node_top.pos], 0)]")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get dir and run based on dir
|
||||||
|
node_top_dir = node_top.dir
|
||||||
|
if(node_top_dir & (node_top_dir - 1))
|
||||||
|
// node is diagonal
|
||||||
|
dscan_dir1 = turn(node_top_dir, -45)
|
||||||
|
dscan_dir2 = turn(node_top_dir, 45)
|
||||||
|
dscan_node = null
|
||||||
|
dscan_current = node_top_pos
|
||||||
|
dscan_pass = TRUE
|
||||||
|
dscan_steps = 0
|
||||||
|
dscan_initial = node_top.depth
|
||||||
|
do
|
||||||
|
// check if we're out of bounds
|
||||||
|
if(dscan_steps + dscan_initial + get_dist(dscan_current, goal) > max_depth)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
dscan_current.color = JPS_VISUAL_COLOR_OUT_OF_BOUNDS
|
||||||
|
turfs_got_colored[dscan_current] = -1
|
||||||
|
#endif
|
||||||
|
break
|
||||||
|
// get next turf
|
||||||
|
// we don't do current turf because it's assumed already ran
|
||||||
|
dscan_next = get_step(dscan_current, node_top_dir)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
dscan_current.overlays += get_jps_scan_overlay(node_top_dir, TRUE)
|
||||||
|
turfs_got_colored[dscan_current] = turfs_got_colored[dscan_current] || 0
|
||||||
|
#endif
|
||||||
|
// check it's 1. there and 2. we haven't checked it yet and
|
||||||
|
// 3. we can reach it; if not this is just pointless
|
||||||
|
if(isnull(dscan_next) || (dscan_next.pathfinding_cycle == cycle) || !JPS_ADJACENCY_CALL(dscan_current, dscan_next))
|
||||||
|
break
|
||||||
|
// move up
|
||||||
|
dscan_current = dscan_next
|
||||||
|
++dscan_steps
|
||||||
|
// check if it's close enough to goal
|
||||||
|
if(JPS_COMPLETION_CHECK(dscan_current))
|
||||||
|
node_creating = new(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
return jps_unwind_path(node_creating, turfs_got_colored)
|
||||||
|
#else
|
||||||
|
return jps_unwind_path(node_creating)
|
||||||
|
#endif
|
||||||
|
// perform the two cardinal scans
|
||||||
|
JPS_CARDINAL_SCAN(dscan_current, dscan_dir1)
|
||||||
|
if(!cscan_pass)
|
||||||
|
dscan_pass = FALSE
|
||||||
|
JPS_CARDINAL_SCAN(dscan_current, dscan_dir2)
|
||||||
|
if(!cscan_pass)
|
||||||
|
dscan_pass = FALSE
|
||||||
|
// check if scans did anything; if so, inject the diagonal node, which should already be
|
||||||
|
// proper linked with the created cardinal nodes
|
||||||
|
if(!dscan_pass)
|
||||||
|
if(isnull(dscan_node))
|
||||||
|
dscan_node = new /datum/jps_node(dscan_current, node_top, JPS_HEURISTIC_CALL(dscan_current), node_top.depth + dscan_steps, node_top_dir)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
dscan_current.color = JPS_VISUAL_COLOR_OPEN
|
||||||
|
turfs_got_colored[dscan_current] = turfs_got_colored[dscan_current] + 1
|
||||||
|
#endif
|
||||||
|
open.enqueue(dscan_node)
|
||||||
|
break
|
||||||
|
// set pathfinder cycle to prevent re-iteration of the same turfs
|
||||||
|
dscan_current.pathfinding_cycle = cycle
|
||||||
|
while(TRUE)
|
||||||
|
else
|
||||||
|
// node is cardinal
|
||||||
|
// check that it's valid and not blocked
|
||||||
|
cscan_current = get_step(node_top_pos, node_top_dir)
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
cscan_current.overlays += get_jps_scan_overlay(node_top_dir, TRUE)
|
||||||
|
turfs_got_colored[cscan_current] = turfs_got_colored[cscan_current] || 0
|
||||||
|
#endif
|
||||||
|
// check it's 1. there and 2. we haven't checked it yet and
|
||||||
|
// 3. we can reach it; if not this is just pointless
|
||||||
|
if(isnull(cscan_current) || (cscan_current.pathfinding_cycle == cycle) || !JPS_ADJACENCY_CALL(node_top_pos, cscan_current))
|
||||||
|
else
|
||||||
|
// perform iteration
|
||||||
|
JPS_CARDINAL_SCAN(cscan_current, node_top_dir)
|
||||||
|
|
||||||
|
//* clean up debugging
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
jps_wipe_colors_after(turfs_got_colored, GLOB.jps_visualization_persist)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//* clean up defines
|
||||||
|
#undef JPS_START_DIR
|
||||||
|
#undef JPS_COMPLETION_CHECK
|
||||||
|
#undef JPS_CARDINAL_DURING_DIAGONAL
|
||||||
|
#undef JPS_CARDINAL_SCAN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The proc used to grab the nodes back in order from start to finish after the algorithm runs.
|
||||||
|
*/
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
/datum/pathfinding/jps/proc/jps_unwind_path(datum/jps_node/top, list/turfs_got_colored)
|
||||||
|
#else
|
||||||
|
/datum/pathfinding/jps/proc/jps_unwind_path(datum/jps_node/top)
|
||||||
|
#endif
|
||||||
|
// found; build path end to start of nodes
|
||||||
|
var/list/path_built = list()
|
||||||
|
while(top)
|
||||||
|
path_built += top.pos
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
top.pos.color = GLOB.jps_visualization_resolve? JPS_VISUAL_COLOR_INTERMEDIATE : JPS_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 JPS_DEBUGGING
|
||||||
|
if(GLOB.jps_visualization_resolve)
|
||||||
|
for(var/turf/T in jps_output_turfs(path_built))
|
||||||
|
T.color = JPS_VISUAL_COLOR_FOUND
|
||||||
|
turfs_got_colored[top] = TRUE
|
||||||
|
jps_wipe_colors_after(turfs_got_colored, GLOB.jps_visualization_persist)
|
||||||
|
#endif
|
||||||
|
return path_built
|
||||||
|
|
||||||
|
/datum/pathfinding/jps/proc/jps_estimate_dir(turf/start, turf/goal)
|
||||||
|
var/dx = abs(start.x - goal.x)
|
||||||
|
var/dy = abs(start.y - goal.y)
|
||||||
|
if(dx > dy)
|
||||||
|
return get_dir(start, goal) & (EAST|WEST)
|
||||||
|
else
|
||||||
|
return get_dir(start, goal) & (NORTH|SOUTH)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* takes a list of turf nodes from JPS return and converts it into a proper list of turfs to walk
|
||||||
|
*/
|
||||||
|
/proc/jps_output_turfs(list/turf/nodes)
|
||||||
|
if(isnull(nodes))
|
||||||
|
return
|
||||||
|
. = list()
|
||||||
|
switch(length(nodes))
|
||||||
|
if(0)
|
||||||
|
return
|
||||||
|
if(1)
|
||||||
|
return list(nodes[1])
|
||||||
|
var/index = 1
|
||||||
|
while(index < length(nodes))
|
||||||
|
var/turf/current = nodes[index]
|
||||||
|
var/turf/next = nodes[index + 1]
|
||||||
|
var/safety = get_dist(current, next)
|
||||||
|
while(current && current != next)
|
||||||
|
. += current
|
||||||
|
current = get_step_towards(current, next)
|
||||||
|
if(!safety--)
|
||||||
|
CRASH("failed jps output processing due to running out of safety, that shouldn't be possible")
|
||||||
|
++index
|
||||||
|
|
||||||
|
. += nodes[index]
|
||||||
|
|
||||||
|
#ifdef JPS_DEBUGGING
|
||||||
|
#undef JPS_DEBUGGING
|
||||||
|
|
||||||
|
#undef JPS_VISUAL_COLOR_CLOSED
|
||||||
|
#undef JPS_VISUAL_COLOR_OPEN
|
||||||
|
#undef JPS_VISUAL_COLOR_CURRENT
|
||||||
|
#undef JPS_VISUAL_COLOR_FOUND
|
||||||
|
#endif
|
||||||
89
code/_helpers/priority_queue_ch.dm
Normal file
89
code/_helpers/priority_queue_ch.dm
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
//* This file is explicitly licensed under the MIT license. *//
|
||||||
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array-backed priority queue.
|
||||||
|
*
|
||||||
|
* The "front" of the queue is popped first; check comparators.dm for what this means.
|
||||||
|
*/
|
||||||
|
/datum/priority_queue
|
||||||
|
/// comparaison function
|
||||||
|
var/procpath/comparison
|
||||||
|
/// internal array
|
||||||
|
var/list/array = list()
|
||||||
|
|
||||||
|
/datum/priority_queue/New(cmp)
|
||||||
|
src.comparison = cmp
|
||||||
|
array = list()
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/is_empty()
|
||||||
|
return length(array) == 0
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/enqueue(entry)
|
||||||
|
array += entry
|
||||||
|
bubble_up(length(array))
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/dequeue()
|
||||||
|
if(length(array) == 0)
|
||||||
|
return null
|
||||||
|
. = array[1]
|
||||||
|
array.Swap(1, length(array))
|
||||||
|
--array.len
|
||||||
|
bubble_down(1)
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/peek()
|
||||||
|
return length(array)? array[1] : null
|
||||||
|
|
||||||
|
// todo: define this
|
||||||
|
/datum/priority_queue/proc/bubble_up(index)
|
||||||
|
while(index >= 2 && call(comparison)(array[index], array[index / 2]) < 0)
|
||||||
|
array.Swap(index, index / 2)
|
||||||
|
index /= 2
|
||||||
|
|
||||||
|
// todo: define this
|
||||||
|
/datum/priority_queue/proc/bubble_down(index)
|
||||||
|
var/length = length(array)
|
||||||
|
var/next = index * 2
|
||||||
|
while(next <= length)
|
||||||
|
// left always exists, right doesn't necessarily exist
|
||||||
|
if(call(comparison)(array[next], array[index]) < 0)
|
||||||
|
if(next < length && call(comparison)(array[next], array[next + 1]) > 0)
|
||||||
|
array.Swap(index, next + 1)
|
||||||
|
index = next + 1
|
||||||
|
else
|
||||||
|
array.Swap(index, next)
|
||||||
|
index = next
|
||||||
|
else if(next < length && call(comparison)(array[next + 1], array[index]) < 0)
|
||||||
|
array.Swap(index, next + 1)
|
||||||
|
index = next + 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
next = index * 2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns copy of list of entries in no particular order
|
||||||
|
*/
|
||||||
|
/datum/priority_queue/proc/flattened()
|
||||||
|
return array.Copy()
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/remove_index(index)
|
||||||
|
var/length = length(array)
|
||||||
|
if(!index || index > length)
|
||||||
|
return
|
||||||
|
if(index == length)
|
||||||
|
. = array[index]
|
||||||
|
--array.len
|
||||||
|
return
|
||||||
|
. = array[index]
|
||||||
|
array.Swap(index, length)
|
||||||
|
--array.len
|
||||||
|
bubble_down(index)
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/find(entry)
|
||||||
|
return array.Find(entry)
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/remove_entry(entry)
|
||||||
|
return remove_index(array.Find(entry))
|
||||||
|
|
||||||
|
/datum/priority_queue/proc/size()
|
||||||
|
return length(array)
|
||||||
@@ -141,4 +141,57 @@ Quick adjacency (to turf):
|
|||||||
useless. Throwpass may later need to be removed and replaced with a passcheck (bitfield on movable atom passflags).
|
useless. Throwpass may later need to be removed and replaced with a passcheck (bitfield on movable atom passflags).
|
||||||
|
|
||||||
Since I don't want to complicate the click code rework by messing with unrelated systems it won't be changed here.
|
Since I don't want to complicate the click code rework by messing with unrelated systems it won't be changed here.
|
||||||
*/
|
*/
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
///True if the dir is north or south, false therwise
|
||||||
|
#define NSCOMPONENT(d) (d&(NORTH|SOUTH))
|
||||||
|
///True if the dir is east/west, false otherwise
|
||||||
|
#define EWCOMPONENT(d) (d&(EAST|WEST))
|
||||||
|
/**
|
||||||
|
* Turf adjacency
|
||||||
|
*
|
||||||
|
* - Always true if you're in the same turf
|
||||||
|
* - If you're vertically/horizontally adjacent, ensure there's no border obects
|
||||||
|
* - If you're diagonally adjacent, ensure you can pass to it with mutually adjacent squares
|
||||||
|
*/
|
||||||
|
/turf/proc/TurfAdjacency(turf/neighbor_turf, atom/target, atom/movable/mover)
|
||||||
|
if(neighbor_turf == src)
|
||||||
|
return TRUE
|
||||||
|
if(get_dist(src, neighbor_turf) > 1 || z != neighbor_turf.z)
|
||||||
|
return FALSE
|
||||||
|
// non diagonal
|
||||||
|
if(neighbor_turf.x == x || neighbor_turf.y == y)
|
||||||
|
return ClickCross(get_dir(src, neighbor_turf), TRUE, target, mover) && neighbor_turf.ClickCross(get_dir(neighbor_turf, src), TRUE, target, mover)
|
||||||
|
|
||||||
|
// diagonal
|
||||||
|
var/reverse_dir = get_dir(neighbor_turf, src)
|
||||||
|
var/d1 = NSCOMPONENT(reverse_dir)
|
||||||
|
var/d2 = EWCOMPONENT(reverse_dir)
|
||||||
|
var/turf/checking
|
||||||
|
|
||||||
|
// because byond's parser is awful and doesn't let us skip lines on ifs with comments after '\'s,
|
||||||
|
// we're going to comment above:
|
||||||
|
// criteria in order for both are:
|
||||||
|
// - not dense
|
||||||
|
// - could leave target
|
||||||
|
// - could go from diagonal to self
|
||||||
|
// - could go from diagonal to target
|
||||||
|
// - could leave self
|
||||||
|
checking = get_step(neighbor_turf, d1)
|
||||||
|
if(!checking.density && \
|
||||||
|
neighbor_turf.ClickCross(d1, TRUE, target, mover) && \
|
||||||
|
checking.ClickCross(d2, FALSE, target, mover) && \
|
||||||
|
checking.ClickCross(turn(d1, 180), FALSE, target, mover) && \
|
||||||
|
ClickCross(turn(d2, 180), TRUE, target, mover))
|
||||||
|
return TRUE
|
||||||
|
checking = get_step(neighbor_turf, d2)
|
||||||
|
if(!checking.density && \
|
||||||
|
neighbor_turf.ClickCross(d2, TRUE, target, mover) && \
|
||||||
|
checking.ClickCross(d1, FALSE, target, mover) && \
|
||||||
|
checking.ClickCross(turn(d2, 180), FALSE, target, mover) && \
|
||||||
|
ClickCross(turn(d1, 180), TRUE, target, mover))
|
||||||
|
return TRUE
|
||||||
|
return FALSE
|
||||||
|
#undef NSCOMPONENT
|
||||||
|
#undef EWCOMPONENT
|
||||||
|
//CHOMPEdit End
|
||||||
|
|||||||
135
code/controllers/subsystems/pathfinder_ch.dm
Normal file
135
code/controllers/subsystems/pathfinder_ch.dm
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
//* This file is explicitly licensed under the MIT license. *//
|
||||||
|
//* Copyright (c) 2023 Citadel Station developers. *//
|
||||||
|
|
||||||
|
#define PATHFINDER_TIMEOUT 50
|
||||||
|
|
||||||
|
SUBSYSTEM_DEF(pathfinder)
|
||||||
|
name = "Pathfinder"
|
||||||
|
flags = SS_NO_INIT | SS_NO_FIRE
|
||||||
|
|
||||||
|
/// pathfinding mutex - most algorithms depend on this
|
||||||
|
/// multi "threading" in byond just adds overhead
|
||||||
|
/// from everything trying to re-queue their executions
|
||||||
|
/// for this reason, much like with maploading,
|
||||||
|
/// it's somewhat pointless to have more than one operation going
|
||||||
|
/// at a time
|
||||||
|
var/pathfinding_mutex = FALSE
|
||||||
|
/// pathfinding calls blocked
|
||||||
|
var/pathfinding_blocked = 0
|
||||||
|
/// pathfinding cycle - this is usable because of the mutex
|
||||||
|
/// this is used in place of a closed list in algorithms like JPS
|
||||||
|
/// to maximize performance.
|
||||||
|
var/tmp/pathfinding_cycle = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* be aware that this emits a set of disjunct nodes
|
||||||
|
* use [jps_output_turfs()] to convert them into a proper turf path list.
|
||||||
|
*
|
||||||
|
* Please see [code/__HELPERS/pathfinding/jps.dm] for details on what JPS does/is.
|
||||||
|
*/
|
||||||
|
/datum/controller/subsystem/pathfinder/proc/get_path_jps(atom/movable/actor = GLOB.generic_pathfinding_actor, turf/goal, turf/start = get_turf(actor), target_distance = 1, max_path_length = 128)
|
||||||
|
var/datum/pathfinding/jps/instance = new(actor, start, goal, target_distance, max_path_length)
|
||||||
|
return run_pathfinding(instance)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Please see [code/__HELPERS/pathfinding/astar.dm] for details on what JPS does/is.
|
||||||
|
*/
|
||||||
|
/datum/controller/subsystem/pathfinder/proc/get_path_astar(atom/movable/actor = GLOB.generic_pathfinding_actor, turf/goal, turf/start = get_turf(actor), target_distance = 1, max_path_length = 128)
|
||||||
|
var/datum/pathfinding/astar/instance = new(actor, start, goal, target_distance, max_path_length)
|
||||||
|
return run_pathfinding(instance)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/pathfinder/proc/default_ai_pathfinding(datum/ai_holder/holder, turf/goal, min_dist = 1, max_path = 128)
|
||||||
|
var/datum/pathfinding/astar/instance = new(holder.holder, get_turf(holder.holder), goal, min_dist, max_path * 2)
|
||||||
|
var/obj/item/weapon/card/id/potential_id = holder.holder.GetIdCard()
|
||||||
|
if(!isnull(potential_id))
|
||||||
|
instance.ss13_with_access = potential_id.access?.Copy()
|
||||||
|
return run_pathfinding(instance)
|
||||||
|
|
||||||
|
/datum/controller/subsystem/pathfinder/proc/default_circuit_pathfinding(obj/item/device/electronic_assembly/assembly, turf/goal, min_dist = 1, max_path = 128, var/list/access)
|
||||||
|
var/datum/pathfinding/jps/instance = new(assembly, get_turf(assembly), goal, min_dist, max_path)
|
||||||
|
instance.ss13_with_access = access.Copy()
|
||||||
|
return jps_output_turfs(run_pathfinding(instance))
|
||||||
|
|
||||||
|
/datum/controller/subsystem/pathfinder/proc/default_bot_pathfinding(mob/living/bot/bot, turf/goal, min_dist = 1, max_path = 128)
|
||||||
|
var/datum/pathfinding/jps/instance = new(bot, get_turf(bot), goal, min_dist, max_path)
|
||||||
|
instance.ss13_with_access = bot.botcard.access?.Copy()
|
||||||
|
return jps_output_turfs(run_pathfinding(instance))
|
||||||
|
|
||||||
|
/datum/controller/subsystem/pathfinder/proc/run_pathfinding(datum/pathfinding/instance)
|
||||||
|
var/started = world.time
|
||||||
|
++pathfinding_blocked
|
||||||
|
if(pathfinding_blocked < 10)
|
||||||
|
while(pathfinding_mutex)
|
||||||
|
stoplag(1)
|
||||||
|
if(world.time > started + PATHFINDER_TIMEOUT)
|
||||||
|
stack_trace("pathfinder timeout; check debug logs.")
|
||||||
|
log_debug("pathfinder timeout of instance with debug variables [instance.debug_log_string()]")
|
||||||
|
return
|
||||||
|
else
|
||||||
|
while(pathfinding_mutex)
|
||||||
|
stoplag(3)
|
||||||
|
if(world.time > started + PATHFINDER_TIMEOUT)
|
||||||
|
stack_trace("pathfinder timeout; check debug logs.")
|
||||||
|
log_debug("pathfinder timeout of instance with debug variables [instance.debug_log_string()]")
|
||||||
|
return
|
||||||
|
--pathfinding_blocked
|
||||||
|
pathfinding_mutex = TRUE
|
||||||
|
. = instance.search()
|
||||||
|
if(world.time > started + PATHFINDER_TIMEOUT)
|
||||||
|
stack_trace("pathfinder timeout; check debug logs.")
|
||||||
|
log_debug("pathfinder timeout of instance with debug variables [instance.debug_log_string()]")
|
||||||
|
pathfinding_mutex = FALSE
|
||||||
|
|
||||||
|
#undef PATHFINDER_TIMEOUT
|
||||||
|
|
||||||
|
/proc/astar_debug(turf/target)
|
||||||
|
if(isnull(target))
|
||||||
|
return
|
||||||
|
return SSpathfinder.get_path_astar(usr, target, get_turf(usr))
|
||||||
|
|
||||||
|
/proc/jps_debug(turf/target)
|
||||||
|
if(isnull(target))
|
||||||
|
return
|
||||||
|
return SSpathfinder.get_path_jps(usr, target, get_turf(usr))
|
||||||
|
|
||||||
|
/proc/old_astar_debug(turf/target)
|
||||||
|
if(isnull(target))
|
||||||
|
return
|
||||||
|
return graph_astar(get_turf(usr), target, TYPE_PROC_REF(/turf, CardinalTurfsWithAccess), TYPE_PROC_REF(/turf, Distance), 0, 128, 1)
|
||||||
|
|
||||||
|
/proc/old_jps_debug(turf/target)
|
||||||
|
var/turf/start = get_turf(usr)
|
||||||
|
var/atom/movable/delegate_for_tg = new(start)
|
||||||
|
var/datum/tg_jps_pathfind/tg_instance = new(delegate_for_tg, target, null, 128, 1, FALSE, null)
|
||||||
|
return tg_instance.search()
|
||||||
|
|
||||||
|
/proc/pathfinding_run_all(turf/start = get_turf(usr), turf/goal)
|
||||||
|
var/pass_silicons_astar = SSpathfinder.get_path_astar(goal = goal, start = start, target_distance = 1, max_path_length = 256)
|
||||||
|
var/pass_silicons_jps = SSpathfinder.get_path_jps(goal = goal, start = start, target_distance = 1, max_path_length = 256)
|
||||||
|
// old astar has been cut because it's such horrible code it's not worth benchmarking against the other 3.
|
||||||
|
// var/pass_old_astar = graph_astar(
|
||||||
|
// start,
|
||||||
|
// goal,
|
||||||
|
// TYPE_PROC_REF(/turf, CardinalTurfsWithAccess),
|
||||||
|
// TYPE_PROC_REF(/turf, Distance),
|
||||||
|
// 0,
|
||||||
|
// 128,
|
||||||
|
// 1,
|
||||||
|
// )
|
||||||
|
var/atom/movable/delegate_for_tg = new(start)
|
||||||
|
var/datum/tg_jps_pathfind/tg_instance = new(delegate_for_tg, goal, null, 256, 1, FALSE, null)
|
||||||
|
var/pass_tg_jps = tg_instance.search()
|
||||||
|
pass_silicons_astar = !!length(pass_silicons_astar)
|
||||||
|
pass_silicons_jps = !!length(pass_silicons_jps)
|
||||||
|
// pass_old_astar = !!length(pass_old_astar)
|
||||||
|
pass_tg_jps = !!length(pass_tg_jps)
|
||||||
|
if(pass_silicons_astar != pass_silicons_jps || pass_silicons_jps != pass_tg_jps)
|
||||||
|
log_and_message_admins("turf pair [COORD(start)], [COORD(goal)] mismatch silicons-astar [pass_silicons_astar] silicons-jps [pass_silicons_jps] tg-jps [pass_tg_jps]")
|
||||||
|
else
|
||||||
|
log_and_message_admins("turf pair [COORD(start)], [COORD(goal)] succeeded")
|
||||||
|
|
||||||
|
/proc/pathfinding_run_benchmark(times = 1000, turf/source = get_turf(usr))
|
||||||
|
var/list/turf/nearby = RANGE_TURFS(100, source)
|
||||||
|
for(var/i in 1 to min(times, 10000))
|
||||||
|
var/turf/picked = pick(nearby)
|
||||||
|
pathfinding_run_all(source, picked)
|
||||||
@@ -522,37 +522,10 @@
|
|||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
return 0
|
return 0
|
||||||
|
//CHOMPEdit Begin
|
||||||
// Show a message to all mobs and objects in sight of this atom
|
|
||||||
// Use for objects performing visible actions
|
|
||||||
// message is output to anyone who can see, e.g. "The [src] does something!"
|
|
||||||
// blind_message (optional) is what blind people will hear e.g. "You hear something!"
|
|
||||||
/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view, var/runemessage = "<span style='font-size: 1.5em'>👁</span>")
|
/atom/proc/visible_message(var/message, var/blind_message, var/list/exclude_mobs, var/range = world.view, var/runemessage = "<span style='font-size: 1.5em'>👁</span>")
|
||||||
|
SEND_GLOBAL_SIGNAL(COMSIG_VISIBLE_MESSAGE, src, message, blind_message, exclude_mobs, range, runemessage, isbelly(loc))
|
||||||
//VOREStation Edit
|
//CHOMPEdit End
|
||||||
var/list/see
|
|
||||||
if(isbelly(loc))
|
|
||||||
var/obj/belly/B = loc
|
|
||||||
see = B.get_mobs_and_objs_in_belly()
|
|
||||||
else
|
|
||||||
see = get_mobs_and_objs_in_view_fast(get_turf(src), range, remote_ghosts = FALSE)
|
|
||||||
//VOREStation Edit End
|
|
||||||
|
|
||||||
var/list/seeing_mobs = see["mobs"]
|
|
||||||
var/list/seeing_objs = see["objs"]
|
|
||||||
if(LAZYLEN(exclude_mobs))
|
|
||||||
seeing_mobs -= exclude_mobs
|
|
||||||
|
|
||||||
for(var/obj/O as anything in seeing_objs)
|
|
||||||
O.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE)
|
|
||||||
for(var/mob/M as anything in seeing_mobs)
|
|
||||||
if(M.see_invisible >= invisibility && MOB_CAN_SEE_PLANE(M, plane))
|
|
||||||
M.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE)
|
|
||||||
if(runemessage != -1)
|
|
||||||
M.create_chat_message(src, "[runemessage]", FALSE, list("emote"), audible = FALSE)
|
|
||||||
else if(blind_message)
|
|
||||||
M.show_message(blind_message, AUDIBLE_MESSAGE)
|
|
||||||
|
|
||||||
// Show a message to all mobs and objects in earshot of this atom
|
// Show a message to all mobs and objects in earshot of this atom
|
||||||
// Use for objects performing audible actions
|
// Use for objects performing audible actions
|
||||||
// message is the message output to anyone who can hear.
|
// message is the message output to anyone who can hear.
|
||||||
|
|||||||
@@ -60,3 +60,6 @@
|
|||||||
|
|
||||||
/atom/movable/proc/exit_belly(obj/belly/B)
|
/atom/movable/proc/exit_belly(obj/belly/B)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
/atom/movable/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
|
||||||
|
return
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ var/global/list/possible_changeling_IDs = list("Alpha","Beta","Gamma","Delta","E
|
|||||||
to_chat(src, "<span class='warning'>We cannot reach \the [M] with a sting!</span>")
|
to_chat(src, "<span class='warning'>We cannot reach \the [M] with a sting!</span>")
|
||||||
return 0 //One is inside, the other is outside something.
|
return 0 //One is inside, the other is outside something.
|
||||||
// Maximum queued turfs set to 25; I don't *think* anything raises sting_range above 2, but if it does the 25 may need raising
|
// Maximum queued turfs set to 25; I don't *think* anything raises sting_range above 2, but if it does the 25 may need raising
|
||||||
if(!AStar(src.loc, M.loc, /turf/proc/AdjacentTurfsRangedSting, /turf/proc/Distance, max_nodes=25, max_node_depth=sting_range)) //If we can't find a path, fail
|
if(!SSpathfinder.get_path_jps(src, get_turf(src), get_turf(M), max_path_length = 25)) //CHOMPEdit
|
||||||
to_chat(src, "<span class='warning'>We cannot find a path to sting \the [M] by!</span>")
|
to_chat(src, "<span class='warning'>We cannot find a path to sting \the [M] by!</span>")
|
||||||
return 0
|
return 0
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
/obj/machinery/door/airlock/scp
|
/obj/machinery/door/airlock/scp
|
||||||
name = "SCP Access"
|
name = "SCP Access"
|
||||||
icon = 'icons/obj/doors/SCPdoor.dmi'
|
icon = 'icons/obj/doors/SCPdoor.dmi'
|
||||||
//req_one_access = list(access_maint_tunnels)
|
//req_one_access = list(access_maint_tunnels)
|
||||||
open_sound_powered = 'sound/machines/scp1o.ogg'
|
open_sound_powered = 'sound/machines/scp1o.ogg'
|
||||||
close_sound_powered = 'sound/machines/scp1c.ogg'
|
close_sound_powered = 'sound/machines/scp1c.ogg'
|
||||||
|
|
||||||
|
/obj/machinery/door/airlock/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return ..() || (has_access(req_access, req_one_access, search.ss13_with_access) && !locked && !inoperable())
|
||||||
|
|||||||
@@ -5,3 +5,9 @@
|
|||||||
glass = 1
|
glass = 1
|
||||||
open_sound = 'sound/machines/firewide1o.ogg'
|
open_sound = 'sound/machines/firewide1o.ogg'
|
||||||
close_sound = 'sound/machines/firewide1c.ogg'
|
close_sound = 'sound/machines/firewide1c.ogg'
|
||||||
|
|
||||||
|
/obj/machinery/door/firedoor/border_only/can_pathfinding_exit(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return (src.dir != dir) || ..()
|
||||||
|
|
||||||
|
/obj/machinery/door/firedoor/border_only/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return (src.dir != dir) || ..()
|
||||||
|
|||||||
@@ -89,7 +89,13 @@
|
|||||||
if(get_dir(mover, target) == reverse_dir[dir]) // From elsewhere to here, can't move against our dir
|
if(get_dir(mover, target) == reverse_dir[dir]) // From elsewhere to here, can't move against our dir
|
||||||
return !density
|
return !density
|
||||||
return TRUE
|
return TRUE
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
/obj/machinery/door/window/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return (src.dir != dir) || ..() || (has_access(req_access, req_one_access, search.ss13_with_access) && !inoperable())
|
||||||
|
|
||||||
|
/obj/machinery/door/window/can_pathfinding_exit(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return (src.dir != dir) || ..() || (has_access(req_access, req_one_access, search.ss13_with_access) && !inoperable())
|
||||||
|
//CHOMPEdit End
|
||||||
/obj/machinery/door/window/Uncross(atom/movable/mover, turf/target)
|
/obj/machinery/door/window/Uncross(atom/movable/mover, turf/target)
|
||||||
if(istype(mover) && mover.checkpass(PASSGLASS))
|
if(istype(mover) && mover.checkpass(PASSGLASS))
|
||||||
return TRUE
|
return TRUE
|
||||||
@@ -97,6 +103,7 @@
|
|||||||
return !density
|
return !density
|
||||||
return TRUE
|
return TRUE
|
||||||
|
|
||||||
|
|
||||||
/obj/machinery/door/window/CanZASPass(turf/T, is_zone)
|
/obj/machinery/door/window/CanZASPass(turf/T, is_zone)
|
||||||
if(get_dir(T, loc) == turn(dir, 180))
|
if(get_dir(T, loc) == turn(dir, 180))
|
||||||
if(is_zone) // No merging allowed.
|
if(is_zone) // No merging allowed.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
SPECIES_TESHARI = 'icons/mob/species/teshari/id.dmi'
|
SPECIES_TESHARI = 'icons/mob/species/teshari/id.dmi'
|
||||||
)
|
)
|
||||||
|
|
||||||
var/access = list()
|
var/list/access = list() //CHOMPEdit
|
||||||
var/registered_name = "Unknown" // The name registered_name on the card
|
var/registered_name = "Unknown" // The name registered_name on the card
|
||||||
slot_flags = SLOT_ID | SLOT_EARS
|
slot_flags = SLOT_ID | SLOT_EARS
|
||||||
|
|
||||||
|
|||||||
@@ -186,10 +186,10 @@
|
|||||||
|
|
||||||
/obj/proc/see_emote(mob/M as mob, text, var/emote_type)
|
/obj/proc/see_emote(mob/M as mob, text, var/emote_type)
|
||||||
return
|
return
|
||||||
|
/* CHOMP Removal
|
||||||
/obj/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
|
/obj/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
|
||||||
return
|
return
|
||||||
|
*/
|
||||||
// Used to mark a turf as containing objects that are dangerous to step onto.
|
// Used to mark a turf as containing objects that are dangerous to step onto.
|
||||||
/obj/proc/register_dangerous_to_step()
|
/obj/proc/register_dangerous_to_step()
|
||||||
var/turf/T = get_turf(src)
|
var/turf/T = get_turf(src)
|
||||||
|
|||||||
@@ -27,7 +27,20 @@
|
|||||||
return
|
return
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
/obj/structure/plasticflaps/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
if(isliving(actor))
|
||||||
|
var/mob/living/L = actor
|
||||||
|
if(isbot(L))
|
||||||
|
return TRUE
|
||||||
|
if(L.can_ventcrawl())
|
||||||
|
return TRUE
|
||||||
|
if(L.mob_size <= MOB_TINY)
|
||||||
|
return TRUE
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
//return isnull(actor.pulling)? TRUE : can_pathfinding_enter(actor.pulling, dir, search)
|
||||||
|
//CHOMPEdit End
|
||||||
/obj/structure/plasticflaps/CanPass(atom/A, turf/T)
|
/obj/structure/plasticflaps/CanPass(atom/A, turf/T)
|
||||||
if(istype(A) && A.checkpass(PASSGLASS))
|
if(istype(A) && A.checkpass(PASSGLASS))
|
||||||
return prob(60)
|
return prob(60)
|
||||||
@@ -65,4 +78,4 @@
|
|||||||
name = "airtight plastic flaps"
|
name = "airtight plastic flaps"
|
||||||
desc = "Heavy duty, airtight, plastic flaps. Have extra safety installed, preventing passage of living beings."
|
desc = "Heavy duty, airtight, plastic flaps. Have extra safety installed, preventing passage of living beings."
|
||||||
can_atmos_pass = ATMOS_PASS_NO
|
can_atmos_pass = ATMOS_PASS_NO
|
||||||
can_pass_lying = FALSE
|
can_pass_lying = FALSE
|
||||||
|
|||||||
@@ -116,7 +116,13 @@
|
|||||||
take_damage(proj_damage)
|
take_damage(proj_damage)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
/obj/structure/window/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return ..() || (!fulltile && (src.dir) != dir)
|
||||||
|
|
||||||
|
/obj/structure/window/can_pathfinding_exit(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
return ..() || (!fulltile && (src.dir != dir))
|
||||||
|
//CHOMPEdit End
|
||||||
/obj/structure/window/ex_act(severity)
|
/obj/structure/window/ex_act(severity)
|
||||||
switch(severity)
|
switch(severity)
|
||||||
if(1.0)
|
if(1.0)
|
||||||
@@ -703,4 +709,4 @@
|
|||||||
qdel(src)
|
qdel(src)
|
||||||
return TRUE
|
return TRUE
|
||||||
return FALSE
|
return FALSE
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,6 +12,18 @@
|
|||||||
var/nitrogen = 0
|
var/nitrogen = 0
|
||||||
var/phoron = 0
|
var/phoron = 0
|
||||||
|
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
//* Movement / Pathfinding
|
||||||
|
/// How much the turf slows down movement, if any.
|
||||||
|
var/slowdown = 0
|
||||||
|
/// Pathfinding cost
|
||||||
|
var/path_weight = 1
|
||||||
|
/// danger flags to avoid
|
||||||
|
var/turf_path_danger = NONE
|
||||||
|
/// pathfinding id - used to avoid needing a big closed list to iterate through every cycle of jps
|
||||||
|
var/pathfinding_cycle
|
||||||
|
//CHOMPEdit End
|
||||||
|
|
||||||
//Properties for airtight tiles (/wall)
|
//Properties for airtight tiles (/wall)
|
||||||
var/thermal_conductivity = 0.05
|
var/thermal_conductivity = 0.05
|
||||||
var/heat_capacity = 1
|
var/heat_capacity = 1
|
||||||
@@ -49,8 +61,8 @@
|
|||||||
directional_opacity = ALL_CARDINALS
|
directional_opacity = ALL_CARDINALS
|
||||||
|
|
||||||
//Pathfinding related
|
//Pathfinding related
|
||||||
if(movement_cost && pathweight == 1) // This updates pathweight automatically.
|
if(movement_cost && path_weight == 1) // This updates pathweight automatically. //CHOMPEdit
|
||||||
pathweight = movement_cost
|
path_weight = movement_cost
|
||||||
|
|
||||||
var/turf/Ab = GetAbove(src)
|
var/turf/Ab = GetAbove(src)
|
||||||
if(Ab)
|
if(Ab)
|
||||||
@@ -262,7 +274,7 @@
|
|||||||
/turf/proc/Distance(turf/t)
|
/turf/proc/Distance(turf/t)
|
||||||
if(get_dist(src,t) == 1)
|
if(get_dist(src,t) == 1)
|
||||||
var/cost = (src.x - t.x) * (src.x - t.x) + (src.y - t.y) * (src.y - t.y)
|
var/cost = (src.x - t.x) * (src.x - t.x) + (src.y - t.y) * (src.y - t.y)
|
||||||
cost *= (pathweight+t.pathweight)/2
|
cost *= ((isnull(path_weight)? slowdown : path_weight) + (isnull(t.path_weight)? t.slowdown : t.path_weight))/2 //CHOMPEdit
|
||||||
return cost
|
return cost
|
||||||
else
|
else
|
||||||
return get_dist(src,t)
|
return get_dist(src,t)
|
||||||
|
|||||||
@@ -65,4 +65,4 @@
|
|||||||
return FALSE
|
return FALSE
|
||||||
if(get_dist(holder, leader) > follow_distance)
|
if(get_dist(holder, leader) > follow_distance)
|
||||||
return TRUE
|
return TRUE
|
||||||
return FALSE
|
return FALSE
|
||||||
|
|||||||
@@ -191,4 +191,4 @@
|
|||||||
directions += L.target_up
|
directions += L.target_up
|
||||||
|
|
||||||
if(directions.len)
|
if(directions.len)
|
||||||
L.climbLadder(holder, pick(directions))
|
L.climbLadder(holder, pick(directions))
|
||||||
|
|||||||
@@ -29,6 +29,12 @@
|
|||||||
if(!A)
|
if(!A)
|
||||||
ai_log("calculate_path() : Called without an atom. Exiting.",AI_LOG_WARNING)
|
ai_log("calculate_path() : Called without an atom. Exiting.",AI_LOG_WARNING)
|
||||||
return
|
return
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
var/turf/T = get_turf(A)
|
||||||
|
if(!istype(T) || T.z != holder.z)
|
||||||
|
ai_log("calculate_path() : Called with invalid destination. Exiting.",AI_LOG_WARNING)
|
||||||
|
return
|
||||||
|
//CHOMPEdit End
|
||||||
|
|
||||||
if(!use_astar) // If we don't use A* then this is pointless.
|
if(!use_astar) // If we don't use A* then this is pointless.
|
||||||
ai_log("calculate_path() : Not using A*, Exiting.", AI_LOG_DEBUG)
|
ai_log("calculate_path() : Not using A*, Exiting.", AI_LOG_DEBUG)
|
||||||
@@ -42,7 +48,7 @@
|
|||||||
/datum/ai_holder/proc/get_path(var/turf/target,var/get_to = 1, var/max_distance = world.view*6)
|
/datum/ai_holder/proc/get_path(var/turf/target,var/get_to = 1, var/max_distance = world.view*6)
|
||||||
ai_log("get_path() : Entering.",AI_LOG_DEBUG)
|
ai_log("get_path() : Entering.",AI_LOG_DEBUG)
|
||||||
forget_path()
|
forget_path()
|
||||||
var/list/new_path = AStar(get_turf(holder.loc), target, astar_adjacent_proc, /turf/proc/Distance, min_target_dist = get_to, max_node_depth = max_distance, id = holder.IGetID(), exclude = obstacles)
|
var/list/new_path = SSpathfinder.default_ai_pathfinding(src, target, get_to) //CHOMPEdit
|
||||||
|
|
||||||
if(new_path && new_path.len)
|
if(new_path && new_path.len)
|
||||||
path = new_path
|
path = new_path
|
||||||
@@ -55,4 +61,4 @@
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
ai_log("get_path() : Exiting.", AI_LOG_DEBUG)
|
ai_log("get_path() : Exiting.", AI_LOG_DEBUG)
|
||||||
return path.len
|
return path.len
|
||||||
|
|||||||
@@ -30,16 +30,19 @@
|
|||||||
|
|
||||||
// A lot of this is based off of /TG/'s AI code.
|
// A lot of this is based off of /TG/'s AI code.
|
||||||
|
|
||||||
|
//CHOMPEdit Begin
|
||||||
// Step 1, find out what we can see.
|
// Step 1, find out what we can see.
|
||||||
/datum/ai_holder/proc/list_targets()
|
/datum/ai_holder/proc/list_targets()
|
||||||
. = ohearers(vision_range, holder)
|
. = hearers(vision_range, holder) - holder // Remove ourselves to prevent suicidal decisions. ~ SRC is the ai_holder.
|
||||||
. -= dview_mob // Not the dview mob!
|
|
||||||
|
|
||||||
var/static/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha, /obj/structure/blob))
|
var/static/list/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/mecha))
|
||||||
|
var/static/list/ignore = typecacheof(list(/mob/observer))
|
||||||
|
|
||||||
for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines))
|
for(var/HM in typecache_filter_list(range(vision_range, holder), hostile_machines))
|
||||||
if(can_see(holder, HM, vision_range))
|
if(can_see(holder, HM, vision_range))
|
||||||
. += HM
|
. += HM
|
||||||
|
. = typecache_filter_list_reverse(., ignore)
|
||||||
|
//CHOMPEdit End
|
||||||
|
|
||||||
// Step 2, filter down possible targets to things we actually care about.
|
// Step 2, filter down possible targets to things we actually care about.
|
||||||
/datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
|
/datum/ai_holder/proc/find_target(var/list/possible_targets, var/has_targets_list = FALSE)
|
||||||
@@ -123,8 +126,7 @@
|
|||||||
return closest_targets
|
return closest_targets
|
||||||
|
|
||||||
/datum/ai_holder/proc/can_attack(atom/movable/the_target, var/vision_required = TRUE)
|
/datum/ai_holder/proc/can_attack(atom/movable/the_target, var/vision_required = TRUE)
|
||||||
if(!can_see_target(the_target) && vision_required)
|
//CHOMP Removal (optimizing by making most intense check last)
|
||||||
return FALSE
|
|
||||||
if(!belly_attack)
|
if(!belly_attack)
|
||||||
if(isbelly(holder.loc))
|
if(isbelly(holder.loc))
|
||||||
return FALSE
|
return FALSE
|
||||||
@@ -185,6 +187,17 @@
|
|||||||
return TRUE
|
return TRUE
|
||||||
// return FALSE
|
// return FALSE
|
||||||
|
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
//It may seem a bit funny to define a proc above and then immediately override it in the same file
|
||||||
|
//But this is basically layering the checks so that the vision check will always come last
|
||||||
|
/datum/ai_holder/can_attack(atom/movable/the_target, var/vision_required = TRUE)
|
||||||
|
if(!..())
|
||||||
|
return FALSE
|
||||||
|
if(vision_required && !can_see_target(the_target))
|
||||||
|
return FALSE
|
||||||
|
return TRUE
|
||||||
|
//CHOMPEdit End
|
||||||
|
|
||||||
// 'Soft' loss of target. They may still exist, we still have some info about them maybe.
|
// 'Soft' loss of target. They may still exist, we still have some info about them maybe.
|
||||||
/datum/ai_holder/proc/lose_target()
|
/datum/ai_holder/proc/lose_target()
|
||||||
ai_log("lose_target() : Entering.", AI_LOG_TRACE)
|
ai_log("lose_target() : Entering.", AI_LOG_TRACE)
|
||||||
|
|||||||
@@ -31,4 +31,51 @@
|
|||||||
|
|
||||||
set_pin_data(IC_OUTPUT, 1, desired_dir)
|
set_pin_data(IC_OUTPUT, 1, desired_dir)
|
||||||
push_data()
|
push_data()
|
||||||
activate_pin(2)
|
activate_pin(2) //CHOMPEdit
|
||||||
|
|
||||||
|
//CHOMPEdit Begin
|
||||||
|
/obj/item/integrated_circuit/smart/advanced_pathfinder
|
||||||
|
name = "advanced pathfinder"
|
||||||
|
desc = "This circuit uses a complex processor for long-range pathfinding."
|
||||||
|
extended_desc = "This circuit uses absolute coordinates to find its target. A path will be generated to the target, taking obstacles into account, \
|
||||||
|
and pathing around any instances of said input. The passkey provided from a card reader is used to calculate a valid path through airlocks."
|
||||||
|
icon_state = "numberpad"
|
||||||
|
complexity = 40
|
||||||
|
cooldown_per_use = 50
|
||||||
|
inputs = list("X target" = IC_PINTYPE_NUMBER,"Y target" = IC_PINTYPE_NUMBER,"obstacle" = IC_PINTYPE_REF,"access" = IC_PINTYPE_STRING)
|
||||||
|
outputs = list("X" = IC_PINTYPE_LIST,"Y" = IC_PINTYPE_LIST)
|
||||||
|
activators = list("calculate path" = IC_PINTYPE_PULSE_IN, "on calculated" = IC_PINTYPE_PULSE_OUT,"not calculated" = IC_PINTYPE_PULSE_OUT)
|
||||||
|
spawn_flags = IC_SPAWN_RESEARCH
|
||||||
|
power_draw_per_use = 80
|
||||||
|
var/obj/item/weapon/card/id/idc
|
||||||
|
|
||||||
|
/obj/item/integrated_circuit/smart/advanced_pathfinder/Initialize(mapload)
|
||||||
|
.=..()
|
||||||
|
idc = new(src)
|
||||||
|
|
||||||
|
/obj/item/integrated_circuit/smart/advanced_pathfinder/do_work()
|
||||||
|
if(!assembly)
|
||||||
|
activate_pin(3)
|
||||||
|
return
|
||||||
|
//idc.access = assembly.access_card.access
|
||||||
|
var/turf/a_loc = get_turf(assembly)
|
||||||
|
|
||||||
|
var/turf/target_turf = locate(get_pin_data(IC_INPUT, 1), get_pin_data(IC_INPUT, 2), a_loc.z)
|
||||||
|
var/list/P = SSpathfinder.default_circuit_pathfinding(src, target_turf, 0, 200)
|
||||||
|
|
||||||
|
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)
|
||||||
|
//CHOMPEdit End
|
||||||
|
|||||||
@@ -334,7 +334,7 @@
|
|||||||
/mob/living/bot/proc/startPatrol()
|
/mob/living/bot/proc/startPatrol()
|
||||||
var/turf/T = getPatrolTurf()
|
var/turf/T = getPatrolTurf()
|
||||||
if(T)
|
if(T)
|
||||||
patrol_path = AStar(get_turf(loc), T, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, max_patrol_dist, id = botcard, exclude = obstacle)
|
target_path = SSpathfinder.default_bot_pathfinding(src, T, 1) //CHOMPEdit
|
||||||
if(!patrol_path)
|
if(!patrol_path)
|
||||||
patrol_path = list()
|
patrol_path = list()
|
||||||
obstacle = null
|
obstacle = null
|
||||||
@@ -366,7 +366,7 @@
|
|||||||
return
|
return
|
||||||
|
|
||||||
/mob/living/bot/proc/calcTargetPath()
|
/mob/living/bot/proc/calcTargetPath()
|
||||||
target_path = AStar(get_turf(loc), get_turf(target), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, max_target_dist, id = botcard, exclude = obstacle)
|
target_path = SSpathfinder.default_bot_pathfinding(src, get_turf(target), 1) //CHOMPEdit
|
||||||
if(!target_path)
|
if(!target_path)
|
||||||
if(target && target.loc)
|
if(target && target.loc)
|
||||||
ignore_list |= target
|
ignore_list |= target
|
||||||
|
|||||||
@@ -155,10 +155,9 @@
|
|||||||
if(++times_idle == 150) turn_off() //VOREStation Add - Idle shutoff time
|
if(++times_idle == 150) turn_off() //VOREStation Add - Idle shutoff time
|
||||||
|
|
||||||
/mob/living/bot/farmbot/calcTargetPath() // We need to land NEXT to the tray, because the tray itself is impassable
|
/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))
|
if(isnull(target))
|
||||||
target_path = AStar(get_turf(loc), get_step(get_turf(target), trayDir), /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, max_target_dist, id = botcard)
|
return
|
||||||
if(target_path)
|
target_path = SSpathfinder.default_bot_pathfinding(src, get_turf(target), 1, 32) //CHOMPEdit
|
||||||
break
|
|
||||||
if(!target_path)
|
if(!target_path)
|
||||||
ignore_list |= target
|
ignore_list |= target
|
||||||
target = null
|
target = null
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
for(var/mob/M in range(T, 2))
|
for(var/mob/M in range(T, 2))
|
||||||
if(M.faction == faction) // Don't grenade our friends
|
if(M.faction == faction) // Don't grenade our friends
|
||||||
return FALSE
|
return FALSE
|
||||||
if(M in oview(src, special_attack_max_range)) // And lets check if we can actually see at least two people before we throw a grenade
|
if(M!=src && can_see(M)) // And lets check if we can actually see at least two people before we throw a grenade //CHOMPEdit dear god
|
||||||
if(!M.stat) // Dead things don't warrant a grenade
|
if(!M.stat) // Dead things don't warrant a grenade
|
||||||
mob_count ++
|
mob_count ++
|
||||||
if(mob_count < 2)
|
if(mob_count < 2)
|
||||||
@@ -88,6 +88,7 @@
|
|||||||
wander = TRUE // ... but "patrol" a little.
|
wander = TRUE // ... but "patrol" a little.
|
||||||
intelligence_level = AI_SMART // Also knows not to walk while confused if it risks death.
|
intelligence_level = AI_SMART // Also knows not to walk while confused if it risks death.
|
||||||
threaten_delay = 30 SECONDS // Mercs will give you 30 seconds to leave or get shot.
|
threaten_delay = 30 SECONDS // Mercs will give you 30 seconds to leave or get shot.
|
||||||
|
use_astar = TRUE //CHOMPEdit
|
||||||
|
|
||||||
/datum/ai_holder/simple_mob/merc/ranged
|
/datum/ai_holder/simple_mob/merc/ranged
|
||||||
pointblank = TRUE // They get close? Just shoot 'em!
|
pointblank = TRUE // They get close? Just shoot 'em!
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
. = ..()
|
. = ..()
|
||||||
//return QDEL_HINT_HARDDEL_NOW Just keep track of mob references. They delete SO much faster now.
|
//return QDEL_HINT_HARDDEL_NOW Just keep track of mob references. They delete SO much faster now.
|
||||||
|
|
||||||
/mob/proc/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2)
|
/mob/show_message(msg, type, alt, alt_type)//Message, type of message (1 or 2), alternative message, alt message type (1 or 2) //CHOMPEdit show_message() moved to /atom/movable
|
||||||
var/time = say_timestamp()
|
var/time = say_timestamp()
|
||||||
|
|
||||||
if(!client && !teleop) return
|
if(!client && !teleop) return
|
||||||
|
|||||||
13
code/modules/mob/mob_movement_ch.dm
Normal file
13
code/modules/mob/mob_movement_ch.dm
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/mob/living/can_pathfinding_enter(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
// mobs are ignored by pathfinding for now
|
||||||
|
// in the future we'll need a way for mobs to not collide into
|
||||||
|
// each other during cooperative AI actions
|
||||||
|
// or even for say, mulebots moving around humans when blocked.
|
||||||
|
return TRUE
|
||||||
|
|
||||||
|
/mob/living/can_pathfinding_exit(atom/movable/actor, dir, datum/pathfinding/search)
|
||||||
|
// mobs are ignored by pathfinding for now
|
||||||
|
// in the future we'll need a way for mobs to not collide into
|
||||||
|
// each other during cooperative AI actions
|
||||||
|
// or even for say, mulebots moving around humans when blocked.
|
||||||
|
return TRUE
|
||||||
@@ -55,6 +55,7 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
|
|||||||
can_build_into_floor = TRUE
|
can_build_into_floor = TRUE
|
||||||
can_dirty = FALSE // It's open space
|
can_dirty = FALSE // It's open space
|
||||||
can_start_dirty = FALSE
|
can_start_dirty = FALSE
|
||||||
|
turf_path_danger = TURF_PATH_DANGER_FALL //CHOMPEdit
|
||||||
|
|
||||||
/turf/simulated/open/vacuum
|
/turf/simulated/open/vacuum
|
||||||
oxygen = 0
|
oxygen = 0
|
||||||
|
|||||||
@@ -671,3 +671,9 @@
|
|||||||
var/obj/item/I = thing
|
var/obj/item/I = thing
|
||||||
surrounding.Add(get_belly_surrounding(I.contents))
|
surrounding.Add(get_belly_surrounding(I.contents))
|
||||||
return surrounding
|
return surrounding
|
||||||
|
|
||||||
|
/obj/belly/proc/effective_emote_hearers()
|
||||||
|
. = list(loc)
|
||||||
|
for(var/atom/movable/AM as anything in contents)
|
||||||
|
//if(AM.atom_flags & ATOM_HEAR)
|
||||||
|
. += AM
|
||||||
|
|||||||
BIN
icons/screen/debug/pathfinding.dmi
Normal file
BIN
icons/screen/debug/pathfinding.dmi
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 473 B |
134
modular_chomp/code/modules/client/hearable.dm
Normal file
134
modular_chomp/code/modules/client/hearable.dm
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// visible_message is way too fucking expensive because it recursively searches through turf contents to find something which should actually recieve the message.
|
||||||
|
// so I'm sick of it and I'm making a component to keep track of anything that needs visible_message
|
||||||
|
// note: this is seperate from normal Say(), objects that need to listen to dialogue don't need this component.
|
||||||
|
// Love and sorry if this horribly breaks - Cadyn
|
||||||
|
/datum/component/hearer
|
||||||
|
var/atom/movable/parent_atom
|
||||||
|
|
||||||
|
/datum/component/hearer/RegisterWithParent()
|
||||||
|
. = ..()
|
||||||
|
parent_atom = parent
|
||||||
|
if(!istype(parent_atom))
|
||||||
|
CRASH("hearer intialized on non-atom")
|
||||||
|
RegisterSignal(SSdcs, COMSIG_VISIBLE_MESSAGE, PROC_REF(on_message))
|
||||||
|
|
||||||
|
/datum/component/hearer/UnregisterFromParent()
|
||||||
|
. = ..()
|
||||||
|
parent_atom = null
|
||||||
|
UnregisterSignal(SSdcs, COMSIG_VISIBLE_MESSAGE)
|
||||||
|
|
||||||
|
/datum/component/hearer/proc/on_message(var/dcs,var/atom/source, var/message, var/blind_message, var/list/exclude_mobs, var/range, var/runemessage = "<span style='font-size: 1.5em'>👁</span>", var/inbelly)
|
||||||
|
if(!AreConnectedZLevels(source.z,parent_atom.z))
|
||||||
|
return
|
||||||
|
if(inbelly && !(parent_atom.loc == source.loc))
|
||||||
|
return
|
||||||
|
if(parent_atom in exclude_mobs)
|
||||||
|
return
|
||||||
|
|
||||||
|
var/turf/source_turf = get_turf(source)
|
||||||
|
var/turf/parent_turf = get_turf(parent_atom)
|
||||||
|
|
||||||
|
if(!istype(source_turf) || !istype(parent_turf))
|
||||||
|
return
|
||||||
|
|
||||||
|
//Most expensive checks last
|
||||||
|
if(get_dist(source_turf,parent_turf) > range)
|
||||||
|
return
|
||||||
|
|
||||||
|
if(source_turf.z != parent_turf.z)
|
||||||
|
if(source_turf.z > parent_turf.z)
|
||||||
|
var/turf/curturf = GetAbove(parent_turf)
|
||||||
|
while(isopenspace(curturf) && curturf.z != source_turf.z)
|
||||||
|
curturf = GetAbove(curturf)
|
||||||
|
|
||||||
|
if(!isopenspace(curturf)) //Last also has to be open space
|
||||||
|
return
|
||||||
|
|
||||||
|
if(curturf.z != source_turf.z)
|
||||||
|
return
|
||||||
|
else
|
||||||
|
var/turf/curturf = GetAbove(source_turf)
|
||||||
|
|
||||||
|
while(isopenspace(curturf) && curturf.z != parent_turf.z)
|
||||||
|
curturf = GetAbove(curturf)
|
||||||
|
|
||||||
|
if(!isopenspace(curturf)) //Last also has to be open space
|
||||||
|
return
|
||||||
|
|
||||||
|
if(curturf.z != parent_turf.z)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if(ismob(parent_atom))
|
||||||
|
var/mob/M = parent_atom
|
||||||
|
if(!M.ckey)
|
||||||
|
return
|
||||||
|
if((M.see_invisible >= source.invisibility) && MOB_CAN_SEE_PLANE(M, source.plane))
|
||||||
|
M.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE)
|
||||||
|
if(runemessage != -1)
|
||||||
|
M.create_chat_message(source, "[runemessage]", FALSE, list("emote"), audible = FALSE)
|
||||||
|
else if(blind_message)
|
||||||
|
M.show_message(blind_message, AUDIBLE_MESSAGE)
|
||||||
|
else
|
||||||
|
parent_atom.show_message(message, VISIBLE_MESSAGE, blind_message, AUDIBLE_MESSAGE)
|
||||||
|
|
||||||
|
//Atom definition (base)
|
||||||
|
|
||||||
|
/atom/movable
|
||||||
|
var/datum/component/hearer/hearer
|
||||||
|
|
||||||
|
/atom/movable/Destroy()
|
||||||
|
if(hearer) QDEL_NULL(hearer)
|
||||||
|
. = ..()
|
||||||
|
|
||||||
|
//Mob definition
|
||||||
|
|
||||||
|
/mob/Login()
|
||||||
|
. = ..()
|
||||||
|
if(!hearer) hearer = AddComponent(/datum/component/hearer) //Only add these to mobs that have a client at some point in time
|
||||||
|
|
||||||
|
//Overmap ship definition (I have no idea why they use show_message?? but ok)
|
||||||
|
|
||||||
|
/obj/effect/overmap/visitable/ship/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
//Holopad definition
|
||||||
|
|
||||||
|
/obj/machinery/hologram/holopad/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
//UAV definition
|
||||||
|
|
||||||
|
/obj/item/device/uav/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
//AIcard definition
|
||||||
|
|
||||||
|
/obj/item/device/aicard/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
//Tape recorder definition
|
||||||
|
|
||||||
|
/obj/item/device/taperecorder/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
//Portal definition
|
||||||
|
|
||||||
|
/obj/effect/map_effect/portal/master/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
//Communicator definition
|
||||||
|
|
||||||
|
/obj/item/device/communicator/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
|
|
||||||
|
/obj/item/device/paicard/Initialize()
|
||||||
|
. = ..()
|
||||||
|
hearer = AddComponent(/datum/component/hearer)
|
||||||
6
tools/build/build-testing.ps1
Normal file
6
tools/build/build-testing.ps1
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Set-Variable -Name "basedir" -Value "$PSScriptRoot/../.."
|
||||||
|
|
||||||
|
(Get-Content "$basedir/vorestation.dme").Replace('#include "maps\southern_cross\southern_cross.dm"', '#include "maps\virgo_minitest\virgo_minitest.dm"') | Set-Content "$basedir/vorestation.dme"
|
||||||
|
& "$basedir/tools/build/build.bat"
|
||||||
|
(Get-Content "$basedir/vorestation.dme").Replace('#include "maps\virgo_minitest\virgo_minitest.dm"', '#include "maps\southern_cross\southern_cross.dm"') | Set-Content "$basedir/vorestation.dme"
|
||||||
|
Read-Host -Prompt "Press any key to continue"
|
||||||
@@ -127,6 +127,7 @@
|
|||||||
#include "code\__defines\wires.dm"
|
#include "code\__defines\wires.dm"
|
||||||
#include "code\__defines\xenoarcheaology.dm"
|
#include "code\__defines\xenoarcheaology.dm"
|
||||||
#include "code\__defines\ZAS.dm"
|
#include "code\__defines\ZAS.dm"
|
||||||
|
#include "code\__defines\_flags\turf_flags_ch.dm"
|
||||||
#include "code\__defines\dcs\flags.dm"
|
#include "code\__defines\dcs\flags.dm"
|
||||||
#include "code\__defines\dcs\helpers.dm"
|
#include "code\__defines\dcs\helpers.dm"
|
||||||
#include "code\__defines\dcs\signals.dm"
|
#include "code\__defines\dcs\signals.dm"
|
||||||
@@ -150,6 +151,7 @@
|
|||||||
#include "code\_helpers\_lists.dm"
|
#include "code\_helpers\_lists.dm"
|
||||||
#include "code\_helpers\atmospherics.dm"
|
#include "code\_helpers\atmospherics.dm"
|
||||||
#include "code\_helpers\atom_movables.dm"
|
#include "code\_helpers\atom_movables.dm"
|
||||||
|
#include "code\_helpers\distance_ch.dm"
|
||||||
#include "code\_helpers\events.dm"
|
#include "code\_helpers\events.dm"
|
||||||
#include "code\_helpers\files.dm"
|
#include "code\_helpers\files.dm"
|
||||||
#include "code\_helpers\game.dm"
|
#include "code\_helpers\game.dm"
|
||||||
@@ -159,12 +161,14 @@
|
|||||||
#include "code\_helpers\icons.dm"
|
#include "code\_helpers\icons.dm"
|
||||||
#include "code\_helpers\icons_procs.dm"
|
#include "code\_helpers\icons_procs.dm"
|
||||||
#include "code\_helpers\icons_vr.dm"
|
#include "code\_helpers\icons_vr.dm"
|
||||||
|
#include "code\_helpers\legacy_tg_path_ch.dm"
|
||||||
#include "code\_helpers\lighting.dm"
|
#include "code\_helpers\lighting.dm"
|
||||||
#include "code\_helpers\logging.dm"
|
#include "code\_helpers\logging.dm"
|
||||||
#include "code\_helpers\logging_vr.dm"
|
#include "code\_helpers\logging_vr.dm"
|
||||||
#include "code\_helpers\matrices.dm"
|
#include "code\_helpers\matrices.dm"
|
||||||
#include "code\_helpers\mobs.dm"
|
#include "code\_helpers\mobs.dm"
|
||||||
#include "code\_helpers\names.dm"
|
#include "code\_helpers\names.dm"
|
||||||
|
#include "code\_helpers\priority_queue_ch.dm"
|
||||||
#include "code\_helpers\sanitize_values.dm"
|
#include "code\_helpers\sanitize_values.dm"
|
||||||
#include "code\_helpers\shell.dm"
|
#include "code\_helpers\shell.dm"
|
||||||
#include "code\_helpers\storage.dm"
|
#include "code\_helpers\storage.dm"
|
||||||
@@ -180,8 +184,12 @@
|
|||||||
#include "code\_helpers\view.dm"
|
#include "code\_helpers\view.dm"
|
||||||
#include "code\_helpers\visual_filters.dm"
|
#include "code\_helpers\visual_filters.dm"
|
||||||
#include "code\_helpers\widelists_ch.dm"
|
#include "code\_helpers\widelists_ch.dm"
|
||||||
|
#include "code\_helpers\graphs\astar_ch.dm"
|
||||||
#include "code\_helpers\icons\flatten.dm"
|
#include "code\_helpers\icons\flatten.dm"
|
||||||
#include "code\_helpers\logging\ui.dm"
|
#include "code\_helpers\logging\ui.dm"
|
||||||
|
#include "code\_helpers\pathfinding_ch\astar.dm"
|
||||||
|
#include "code\_helpers\pathfinding_ch\common.dm"
|
||||||
|
#include "code\_helpers\pathfinding_ch\jps.dm"
|
||||||
#include "code\_helpers\sorts\__main.dm"
|
#include "code\_helpers\sorts\__main.dm"
|
||||||
#include "code\_helpers\sorts\comparators.dm"
|
#include "code\_helpers\sorts\comparators.dm"
|
||||||
#include "code\_helpers\sorts\TimSort.dm"
|
#include "code\_helpers\sorts\TimSort.dm"
|
||||||
@@ -320,6 +328,7 @@
|
|||||||
#include "code\controllers\subsystems\nightshift.dm"
|
#include "code\controllers\subsystems\nightshift.dm"
|
||||||
#include "code\controllers\subsystems\overlays.dm"
|
#include "code\controllers\subsystems\overlays.dm"
|
||||||
#include "code\controllers\subsystems\overmap_renamer_vr.dm"
|
#include "code\controllers\subsystems\overmap_renamer_vr.dm"
|
||||||
|
#include "code\controllers\subsystems\pathfinder_ch.dm"
|
||||||
#include "code\controllers\subsystems\persist_vr.dm"
|
#include "code\controllers\subsystems\persist_vr.dm"
|
||||||
#include "code\controllers\subsystems\persistence.dm"
|
#include "code\controllers\subsystems\persistence.dm"
|
||||||
#include "code\controllers\subsystems\ping.dm"
|
#include "code\controllers\subsystems\ping.dm"
|
||||||
@@ -2935,6 +2944,7 @@
|
|||||||
#include "code\modules\mob\mob_helpers.dm"
|
#include "code\modules\mob\mob_helpers.dm"
|
||||||
#include "code\modules\mob\mob_helpers_vr.dm"
|
#include "code\modules\mob\mob_helpers_vr.dm"
|
||||||
#include "code\modules\mob\mob_movement.dm"
|
#include "code\modules\mob\mob_movement.dm"
|
||||||
|
#include "code\modules\mob\mob_movement_ch.dm"
|
||||||
#include "code\modules\mob\mob_planes.dm"
|
#include "code\modules\mob\mob_planes.dm"
|
||||||
#include "code\modules\mob\mob_planes_vr.dm"
|
#include "code\modules\mob\mob_planes_vr.dm"
|
||||||
#include "code\modules\mob\mob_transformation_simple.dm"
|
#include "code\modules\mob\mob_transformation_simple.dm"
|
||||||
@@ -4719,6 +4729,7 @@
|
|||||||
#include "modular_chomp\code\modules\artifice\deadringer.dm"
|
#include "modular_chomp\code\modules\artifice\deadringer.dm"
|
||||||
#include "modular_chomp\code\modules\balloon_alert\balloon_alert.dm"
|
#include "modular_chomp\code\modules\balloon_alert\balloon_alert.dm"
|
||||||
#include "modular_chomp\code\modules\casino\casino_map_atoms.dm"
|
#include "modular_chomp\code\modules\casino\casino_map_atoms.dm"
|
||||||
|
#include "modular_chomp\code\modules\client\hearable.dm"
|
||||||
#include "modular_chomp\code\modules\client\preferences.dm"
|
#include "modular_chomp\code\modules\client\preferences.dm"
|
||||||
#include "modular_chomp\code\modules\client\preferences_spawnpoints.dm"
|
#include "modular_chomp\code\modules\client\preferences_spawnpoints.dm"
|
||||||
#include "modular_chomp\code\modules\client\preference_setup\global\setting_datums.dm"
|
#include "modular_chomp\code\modules\client\preference_setup\global\setting_datums.dm"
|
||||||
|
|||||||
Reference in New Issue
Block a user