mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
* might work, but need to do more scent descriptions * very good progress * big commit for madam * unused sprite * comment cleanup * cleaning up descriptions * more scent desc changes * p * 20 seconds Co-authored-by: Jamie D <993128+JamieD1@users.noreply.github.com>
230 lines
8.4 KiB
Plaintext
230 lines
8.4 KiB
Plaintext
/*
|
|
A Star pathfinding 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, moving atom, distance proc, max nodes, maximum node depth, minimum distance to target, adjacent proc, atom id, turfs to exclude, check only simulated)
|
|
|
|
Optional extras to add on (in order):
|
|
Distance proc : the distance used in every A* calculation (length of path and heuristic)
|
|
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.
|
|
Adjacent proc : returns the turfs to consider around the actually processed node
|
|
Simulated only : whether to consider unsimulated turfs or not (used by some Adjacent proc)
|
|
|
|
Also added 'exclude' turf to avoid travelling over; defaults to null
|
|
|
|
Actual Adjacent procs :
|
|
|
|
/turf/proc/reachableAdjacentTurfs : returns reachable turfs in cardinal directions (uses simulated_only)
|
|
|
|
/turf/proc/reachableAdjacentAtmosTurfs : returns turfs in cardinal directions reachable via atmos
|
|
|
|
*/
|
|
#define PF_TIEBREAKER 0.005
|
|
//tiebreker weight.To help to choose between equal paths
|
|
//////////////////////
|
|
//datum/PathNode object
|
|
//////////////////////
|
|
#define MASK_ODD 85
|
|
#define MASK_EVEN 170
|
|
|
|
|
|
//A* nodes variables
|
|
/datum/PathNode
|
|
var/turf/source //turf associated with the PathNode
|
|
var/datum/PathNode/prevNode //link to the parent PathNode
|
|
var/f //A* Node weight (f = g + h)
|
|
var/g //A* movement cost variable
|
|
var/h //A* heuristic variable
|
|
var/nt //count the number of Nodes traversed
|
|
var/bf //bitflag for dir to expand.Some sufficiently advanced motherfuckery
|
|
|
|
/datum/PathNode/New(s,p,pg,ph,pnt,_bf)
|
|
source = s
|
|
prevNode = p
|
|
g = pg
|
|
h = ph
|
|
f = g + h*(1+ PF_TIEBREAKER)
|
|
nt = pnt
|
|
bf = _bf
|
|
|
|
/datum/PathNode/proc/setp(p,pg,ph,pnt)
|
|
prevNode = p
|
|
g = pg
|
|
h = ph
|
|
f = g + h*(1+ PF_TIEBREAKER)
|
|
nt = pnt
|
|
|
|
/datum/PathNode/proc/calc_f()
|
|
f = g + h
|
|
|
|
//////////////////////
|
|
//A* procs
|
|
//////////////////////
|
|
|
|
//the weighting function, used in the A* algorithm
|
|
/proc/PathWeightCompare(datum/PathNode/a, datum/PathNode/b)
|
|
return a.f - b.f
|
|
|
|
//reversed so that the Heap is a MinHeap rather than a MaxHeap
|
|
/proc/HeapPathWeightCompare(datum/PathNode/a, datum/PathNode/b)
|
|
return b.f - a.f
|
|
|
|
//wrapper that returns an empty list if A* failed to find a path
|
|
/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE)
|
|
var/l = SSpathfinder.mobs.getfree(caller)
|
|
while(!l)
|
|
stoplag(3)
|
|
l = SSpathfinder.mobs.getfree(caller)
|
|
var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only, get_best_attempt)
|
|
SSpathfinder.mobs.found(l)
|
|
if(!path)
|
|
path = list()
|
|
return path
|
|
|
|
/proc/cir_get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE)
|
|
var/l = SSpathfinder.circuits.getfree(caller)
|
|
while(!l)
|
|
stoplag(3)
|
|
l = SSpathfinder.circuits.getfree(caller)
|
|
var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only)
|
|
SSpathfinder.circuits.found(l)
|
|
if(!path)
|
|
path = list()
|
|
return path
|
|
|
|
/// Pathfinding for bots
|
|
/proc/AStar(caller, _end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = TRUE, get_best_attempt = FALSE)
|
|
//sanitation
|
|
var/turf/end = get_turf(_end)
|
|
var/turf/start = get_turf(caller)
|
|
if(!start || !end)
|
|
stack_trace("Invalid A* start or destination")
|
|
return FALSE
|
|
if( start.z != end.z || start == end ) //no pathfinding between z levels
|
|
return FALSE
|
|
if(maxnodes)
|
|
//if start turf is farther than maxnodes from end turf, no need to do anything
|
|
if(call(start, dist)(end) > maxnodes)
|
|
return FALSE
|
|
maxnodedepth = maxnodes //no need to consider path longer than maxnodes
|
|
var/datum/Heap/open = new /datum/Heap(/proc/HeapPathWeightCompare) //the open list
|
|
var/list/openc = new() //open list for node check
|
|
var/list/path = null //the returned path, if any
|
|
//initialization
|
|
var/datum/PathNode/cur = new /datum/PathNode(start,null,0,call(start,dist)(end),0,15,1)//current processed turf
|
|
open.Insert(cur)
|
|
openc[start] = cur
|
|
//then run the main loop
|
|
while(!open.IsEmpty() && !path)
|
|
cur = open.Pop() //get the lower f turf in the open list
|
|
//get the lower f node on the open list
|
|
//if we only want to get near the target, check if we're close enough
|
|
var/closeenough
|
|
if(mintargetdist)
|
|
closeenough = call(cur.source,dist)(end) <= mintargetdist
|
|
|
|
|
|
//found the target turf (or close enough), let's create the path to it
|
|
if(cur.source == end || closeenough)
|
|
path = new()
|
|
path.Add(cur.source)
|
|
while(cur.prevNode)
|
|
cur = cur.prevNode
|
|
path.Add(cur.source)
|
|
break
|
|
//get adjacents turfs using the adjacent proc, checking for access with id
|
|
if((!maxnodedepth)||(cur.nt <= maxnodedepth))//if too many steps, don't process that path
|
|
for(var/i = 0 to 3)
|
|
var/f= 1<<i //get cardinal directions.1,2,4,8
|
|
if(cur.bf & f)
|
|
var/T = get_step(cur.source,f)
|
|
if(T != exclude)
|
|
var/datum/PathNode/CN = openc[T] //current checking turf
|
|
var/r=((f & MASK_ODD)<<1)|((f & MASK_EVEN)>>1) //getting reverse direction throught swapping even and odd bits.((f & 01010101)<<1)|((f & 10101010)>>1)
|
|
var/newg = cur.g + call(cur.source,dist)(T)
|
|
if(CN)
|
|
//is already in open list, check if it's a better way from the current turf
|
|
CN.bf &= 15^r //we have no closed, so just cut off exceed dir.00001111 ^ reverse_dir.We don't need to expand to checked turf.
|
|
if((newg < CN.g) )
|
|
if(call(cur.source,adjacent)(caller, T, id, simulated_only))
|
|
CN.setp(cur,newg,CN.h,cur.nt+1)
|
|
open.ReSort(CN)//reorder the changed element in the list
|
|
else
|
|
//is not already in open list, so add it
|
|
if(call(cur.source,adjacent)(caller, T, id, simulated_only))
|
|
CN = new(T,cur,newg,call(T,dist)(end),cur.nt+1,15^r)
|
|
open.Insert(CN)
|
|
openc[T] = CN
|
|
cur.bf = 0
|
|
CHECK_TICK
|
|
|
|
// if(!path && get_best_attempt)
|
|
// path = new()
|
|
// path.Add(cur.source)
|
|
// while(cur.prevNode)
|
|
// cur = cur.prevNode
|
|
// path.Add(cur.source)
|
|
//reverse the path to get it from start to finish
|
|
if(path)
|
|
for(var/i = 1 to round(0.5*path.len))
|
|
path.Swap(i,path.len-i+1)
|
|
openc = null
|
|
//cleaning after us
|
|
return path
|
|
|
|
//Returns adjacent turfs in cardinal directions that are reachable
|
|
//simulated_only controls whether only simulated turfs are considered or not
|
|
|
|
/// Returns a list the src/caller can cross into
|
|
/turf/proc/reachableAdjacentTurfs(caller, ID, simulated_only)
|
|
var/list/L = new()
|
|
var/turf/T
|
|
var/static/space_type_cache = typecacheof(/turf/open/space)
|
|
|
|
for(var/k in 1 to GLOB.cardinals.len)
|
|
T = get_step(src,GLOB.cardinals[k])
|
|
if(!T || (simulated_only && space_type_cache[T.type]))
|
|
continue
|
|
if(!T.density && !LinkBlockedWithAccess(T,caller, ID))
|
|
L.Add(T)
|
|
return L
|
|
|
|
/turf/proc/reachableTurftest(caller, var/turf/T, ID, simulated_only)
|
|
if(T && !T.density && !(simulated_only && SSpathfinder.space_type_cache[T.type]) && !LinkBlockedWithAccess(T,caller, ID))
|
|
return TRUE
|
|
|
|
/// Returns adjacent turfs in cardinal directions that are reachable via atmos
|
|
/turf/proc/reachableAdjacentAtmosTurfs()
|
|
return atmos_adjacent_turfs
|
|
|
|
/// Check if there is a door that needs access in its way
|
|
/turf/proc/LinkBlockedWithAccess(turf/T, caller, ID)
|
|
var/adir = get_dir(src, T)
|
|
var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1)
|
|
for(var/obj/structure/window/W in src)
|
|
if(!W.CanAStarPass(ID, adir))
|
|
return TRUE
|
|
for(var/obj/machinery/door/window/W in src)
|
|
if(!W.CanAStarPass(ID, adir))
|
|
return TRUE
|
|
for(var/obj/machinery/M in src)
|
|
if(!M.CanAStarPass(ID, adir, caller))
|
|
return TRUE
|
|
for(var/obj/machinery/door/firedoor/border_only/W in src)
|
|
if(!W.CanAStarPass(ID, adir, caller))
|
|
return TRUE
|
|
for(var/obj/O in T)
|
|
if(!O.CanAStarPass(ID, rdir, caller))
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
//yog proc
|
|
/turf/proc/reachableTurftestPlayer(caller, turf/T, ID, simulated_only)
|
|
if(T && !T.density && !LinkBlockedWithAccess(T, caller, ID) && !(simulated_only && SSpathfinder.space_type_cache[T.type]))
|
|
return TRUE
|