JPS Improvements and fixes (#66015)

Extends CanAStarPass to atom level and allows turfs to specify their pass check method to speed this up a bit.
Allows pathing over openspace with lattices/by flying things, otherwise disallowed.

Solves issue with diagonal movement where diagonal movement first step would use turf that was marked as impassable in pathfinder by splitting invalid diagonal move into valid orthogonal ones. In most cases this is pretty inconsequential but for openspace/other dangerous non-dense tiles it's pretty important.
This commit is contained in:
AnturK
2022-04-07 21:57:48 +01:00
committed by GitHub
parent 2a81ac9926
commit 4fe27ddf36
6 changed files with 90 additions and 19 deletions
+7
View File
@@ -64,3 +64,10 @@
* Use instead of `A.loc.loc`.
*/
#define get_area(A) (isarea(A) ? A : get_step(A, 0)?.loc)
/// Turf will be passable if density is 0
#define TURF_PATHING_PASS_DENSITY 0
/// Turf will be passable depending on [CanAStarPass] return value
#define TURF_PATHING_PASS_PROC 1
/// Turf is never passable
#define TURF_PATHING_PASS_NO 2
+55 -3
View File
@@ -17,8 +17,9 @@
* * simulated_only: Whether we consider turfs without atmos simulation (AKA do we want to ignore space)
* * exclude: If we want to avoid a specific turf, like if we're a mulebot who already got blocked by some turf
* * skip_first: Whether or not to delete the first item in the path. This would be done because the first item is the starting tile, which can break movement for some creatures.
* * diagonal_safety: ensures diagonal moves won't use invalid midstep turfs by splitting them into two orthogonal moves if necessary
*/
/proc/get_path_to(caller, end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE)
/proc/get_path_to(caller, end, max_distance = 30, mintargetdist, id=null, simulated_only = TRUE, turf/exclude, skip_first=TRUE, diagonal_safety=TRUE)
if(!caller || !get_turf(end))
return
@@ -28,7 +29,7 @@
l = SSpathfinder.mobs.getfree(caller)
var/list/path
var/datum/pathfind/pathfind_datum = new(caller, end, id, max_distance, mintargetdist, simulated_only, exclude)
var/datum/pathfind/pathfind_datum = new(caller, end, id, max_distance, mintargetdist, simulated_only, exclude, diagonal_safety)
path = pathfind_datum.search()
qdel(pathfind_datum)
@@ -43,6 +44,7 @@
* 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/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.
* We do early next.density check despite it being already checked in LinkBlockedWithAccess for short-circuit performance
*/
#define CAN_STEP(cur_turf, next) (next && !next.density && !(simulated_only && SSpathfinder.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
@@ -120,8 +122,10 @@
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
/// Ensures diagonal moves won't use invalid midstep turfs by splitting them into two orthogonal moves if necessary
var/diagonal_safety = TRUE
/datum/pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid)
/datum/pathfind/New(atom/movable/caller, atom/goal, id, max_distance, mintargetdist, simulated_only, avoid, diagonal_safety)
src.caller = caller
end = get_turf(goal)
open = new /datum/heap(/proc/HeapPathWeightCompare)
@@ -131,6 +135,7 @@
src.mintargetdist = mintargetdist
src.simulated_only = simulated_only
src.avoid = avoid
src.diagonal_safety = diagonal_safety
/**
* search() is the proc you call to kick off and handle the actual pathfinding, and kills the pathfind datum instance when it's done.
@@ -177,6 +182,10 @@
sources = null
qdel(open)
if(diagonal_safety)
path = diagonal_movement_safety()
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/pathfind/proc/search]
@@ -192,6 +201,35 @@
path.Add(iter_turf)
unwind_node = unwind_node.previous_node
/datum/pathfind/proc/diagonal_movement_safety()
if(length(path) < 2)
return
var/list/modified_path = list()
for(var/i in 1 to length(path) - 1)
var/turf/current_turf = path[i]
var/turf/next_turf = path[i+1]
var/movement_dir = get_dir(current_turf, next_turf)
if(!(movement_dir & (movement_dir - 1))) //cardinal movement, no need to verify
modified_path += current_turf
continue
//If default diagonal movement step is invalid, replace with alternative two steps
if(movement_dir & NORTH)
if(!CAN_STEP(current_turf,get_step(current_turf,NORTH)))
modified_path += current_turf
modified_path += get_step(current_turf, movement_dir & ~NORTH)
else
modified_path += current_turf
else
if(!CAN_STEP(current_turf,get_step(current_turf,SOUTH)))
modified_path += current_turf
modified_path += get_step(current_turf, movement_dir & ~SOUTH)
else
modified_path += current_turf
modified_path += path[length(path)]
return modified_path
/**
* For performing lateral scans from a given starting turf.
*
@@ -332,6 +370,8 @@
/**
* For seeing if we can actually move between 2 given turfs while accounting for our access and the caller's pass_flags
*
* Assumes destinantion turf is non-dense - check and shortcircuit in code invoking this proc to avoid overhead.
*
* 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
@@ -352,6 +392,18 @@
var/actual_dir = get_dir(src, destination_turf)
/// These are generally cheaper than looping contents so they go first
switch(destination_turf.pathing_pass_method)
// This is already assumed to be true
//if(TURF_PATHING_PASS_DENSITY)
// if(destination_turf.density)
// return TRUE
if(TURF_PATHING_PASS_PROC)
if(!destination_turf.CanAStarPass(ID, actual_dir , caller))
return TRUE
if(TURF_PATHING_PASS_NO)
return TRUE
// Source border object checks
for(var/obj/structure/window/iter_window in src)
if(!iter_window.CanAStarPass(ID, actual_dir))
+18
View File
@@ -2192,3 +2192,21 @@
new /datum/merger(id, allowed_types, src)
candidate = mergers[id]
return candidate
/**
* This proc is used for telling whether something can pass by this atom in a given direction, for use by the pathfinding system.
*
* Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely
* multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible.
*
* For turfs this will only be used if pathing_pass_method is TURF_PATHING_PASS_PROC
*
* Arguments:
* * ID- An ID card representing what access we have (and thus if we can open things like airlocks or windows to pass through them). The ID card's physical location does not matter, just the reference
* * to_dir- What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions
* * caller- The movable we're checking pass flags for, if we're making any such checks
**/
/atom/proc/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
if(istype(caller) && (caller.pass_flags & pass_flags_self))
return TRUE
. = !density
-16
View File
@@ -209,22 +209,6 @@
/obj/get_dumping_location()
return get_turf(src)
/**
* This proc is used for telling whether something can pass by this object in a given direction, for use by the pathfinding system.
*
* Trying to generate one long path across the station will call this proc on every single object on every single tile that we're seeing if we can move through, likely
* multiple times per tile since we're likely checking if we can access said tile from multiple directions, so keep these as lightweight as possible.
*
* Arguments:
* * ID- An ID card representing what access we have (and thus if we can open things like airlocks or windows to pass through them). The ID card's physical location does not matter, just the reference
* * to_dir- What direction we're trying to move in, relevant for things like directional windows that only block movement in certain directions
* * caller- The movable we're checking pass flags for, if we're making any such checks
**/
/obj/proc/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
if(istype(caller) && (caller.pass_flags & pass_flags_self))
return TRUE
. = !density
/obj/proc/check_uplink_validity()
return 1
+6
View File
@@ -19,6 +19,7 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
overfloor_placed = FALSE
underfloor_accessibility = UNDERFLOOR_INTERACTABLE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
pathing_pass_method = TURF_PATHING_PASS_PROC
var/can_cover_up = TRUE
var/can_build_on = TRUE
@@ -157,6 +158,11 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr
/turf/open/openspace/rust_heretic_act()
return FALSE
/turf/open/openspace/CanAStarPass(obj/item/card/id/ID, to_dir, atom/movable/caller)
if(caller && !caller.can_z_move(DOWN, src, null , ZMOVE_FALL_FLAGS)) //If we can't fall here (flying/lattice), it's fine to path through
return TRUE
return FALSE
/turf/open/openspace/icemoon
name = "ice chasm"
baseturfs = /turf/open/openspace/icemoon
+4
View File
@@ -75,6 +75,10 @@ GLOBAL_LIST_EMPTY(station_turfs)
/// See __DEFINES/construction.dm for RCD_MEMORY_*.
var/rcd_memory
/// How pathing algorithm will check if this turf is passable by itself (not including content checks). By default it's just density check.
/// WARNING: Currently to use a density shortcircuiting this does not support dense turfs with special allow through function
var/pathing_pass_method = TURF_PATHING_PASS_DENSITY
/turf/vv_edit_var(var_name, new_value)
var/static/list/banned_edits = list(NAMEOF(src, x), NAMEOF(src, y), NAMEOF(src, z))
if(var_name in banned_edits)