diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index ed468f8b18..e32d5cbcec 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -75,6 +75,7 @@ #define INIT_ORDER_LIGHTING -20 #define INIT_ORDER_SHUTTLE -21 #define INIT_ORDER_SQUEAK -40 +#define INIT_ORDER_PATH -50 #define INIT_ORDER_PERSISTENCE -100 // Subsystem fire priority, from lowest to highest priority diff --git a/code/__HELPERS/AStar.dm b/code/__HELPERS/AStar.dm index 37ca3bb082..c575a4b3d2 100644 --- a/code/__HELPERS/AStar.dm +++ b/code/__HELPERS/AStar.dm @@ -23,10 +23,14 @@ Actual Adjacent procs : /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 @@ -36,13 +40,22 @@ Actual Adjacent procs : 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) +/datum/PathNode/New(s,p,pg,ph,pnt,_bf) source = s prevNode = p g = pg h = ph - f = g + h + 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() @@ -61,117 +74,114 @@ Actual Adjacent procs : 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/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) +/proc/get_path_to(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1) + var/l = SSpathfinder.getfree(caller) + while(!l) + stoplag(3) + l = SSpathfinder.getfree(caller) var/list/path = AStar(caller, end, dist, maxnodes, maxnodedepth, mintargetdist, adjacent,id, exclude, simulated_only) + + SSpathfinder.found(l) if(!path) path = list() + return path -//the actual algorithm -/proc/AStar(caller, end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableAdjacentTurfs, id=null, turf/exclude=null, simulated_only = 1) - var/list/pnodelist = list() +/proc/AStar(caller, var/turf/end, dist, maxnodes, maxnodedepth = 30, mintargetdist, adjacent = /turf/proc/reachableTurftest, id=null, turf/exclude=null, simulated_only = 1) //sanitation - var/start = get_turf(caller) - if(!start) + var/turf/start = get_turf(caller) + if((!start)||(start.z != end.z)||(start == end)) //no pathfinding between z levels return 0 - if(maxnodes) //if start turf is farther than maxnodes from end turf, no need to do anything if(call(start, dist)(end) > maxnodes) return 0 maxnodedepth = maxnodes //no need to consider path longer than maxnodes - var/datum/Heap/open = new /datum/Heap(/proc/HeapPathWeightCompare) //the open list - var/list/closed = new() //the closed list + var/list/openc = new() //open list for node check var/list/path = null //the returned path, if any - var/datum/PathNode/cur //current processed turf - //initialization - open.Insert(new /datum/PathNode(start,null,0,call(start,dist)(end),0)) - + 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) - //get the lower f node on the open list cur = open.Pop() //get the lower f turf in the open list - closed.Add(cur.source) //and tell we've processed it - + //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 - //if too many steps, abandon that path if(maxnodedepth && (cur.nt > maxnodedepth)) continue - //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 - var/list/L = call(cur.source,adjacent)(caller,id, simulated_only) - for(var/turf/T in L) - if(T == exclude || (T in closed)) - continue - - var/newg = cur.g + call(cur.source,dist)(T) - - var/datum/PathNode/P = pnodelist[T] - if(!P) - //is not already in open list, so add it - var/datum/PathNode/newnode = new /datum/PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1) - open.Insert(newnode) - pnodelist[T] = newnode - else //is already in open list, check if it's a better way from the current turf - if(newg < P.g) - P.prevNode = cur - P.g = (newg * L.len / 9) - P.calc_f() - P.nt = cur.nt + 1 - open.ReSort(P)//reorder the changed element in the list + for(var/i = 0 to 3) + var/f= 1<>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 - - - //cleaning after us - pnodelist = null - //reverse the path to get it from start to finish if(path) - for(var/i = 1; i <= path.len/2; i++) + 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 + /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/dir in GLOB.cardinals) - T = get_step(src,dir) + 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 /turf/proc/LinkBlockedWithAccess(turf/T, caller, ID) var/adir = get_dir(src, T) - var/rdir = get_dir(T, src) - + var/rdir = ((adir & MASK_ODD)<<1)|((adir & MASK_EVEN)>>1) for(var/obj/structure/window/W in src) if(!W.CanAStarPass(ID, adir)) return 1 diff --git a/code/__HELPERS/heap.dm b/code/__HELPERS/heap.dm index ce03b482e5..916e7fc05c 100644 --- a/code/__HELPERS/heap.dm +++ b/code/__HELPERS/heap.dm @@ -29,8 +29,8 @@ L[1] = L[L.len] L.Cut(L.len) - - Sink(1) + if(L.len) + Sink(1) //Get a node up to its right position in the heap /datum/Heap/proc/Swim(var/index) @@ -73,3 +73,4 @@ /datum/Heap/proc/List() . = L.Copy() + diff --git a/code/controllers/subsystem/pathfinder.dm b/code/controllers/subsystem/pathfinder.dm new file mode 100644 index 0000000000..9bcdc4595e --- /dev/null +++ b/code/controllers/subsystem/pathfinder.dm @@ -0,0 +1,38 @@ +SUBSYSTEM_DEF(pathfinder) + name = "pathfinder" + init_order = INIT_ORDER_PATH + flags = SS_NO_FIRE + var/lcount = 10 + var/run + var/free + var/list/flow + var/static/space_type_cache + +/datum/controller/subsystem/pathfinder/Initialize() + space_type_cache = typecacheof(/turf/open/space) + run = 0 + free = 1 + flow = new() + flow.len=lcount + +/datum/controller/subsystem/pathfinder/proc/getfree(atom/M) + if(run < lcount) + run += 1 + while(flow[free]) + CHECK_TICK + free = (free % lcount) + 1 + var/t = addtimer(CALLBACK(SSpathfinder, /datum/controller/subsystem/pathfinder.proc/toolong, free), 150, TIMER_STOPPABLE) + flow[free] = t + flow[t] = M + return free + else + return 0 + +/datum/controller/subsystem/pathfinder/proc/toolong(l) + log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]") + found(l) + +/datum/controller/subsystem/pathfinder/proc/found(l) + deltimer(flow[l]) + flow[l] = null + run -= 1 diff --git a/tgstation.dme b/tgstation.dme index e5ff8ed7b1..1e4e9f0811 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -262,6 +262,7 @@ #include "code\controllers\subsystem\overlays.dm" #include "code\controllers\subsystem\pai.dm" #include "code\controllers\subsystem\parallax.dm" +#include "code\controllers\subsystem\pathfinder.dm" #include "code\controllers\subsystem\persistence.dm" #include "code\controllers\subsystem\radiation.dm" #include "code\controllers\subsystem\radio.dm"