diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 70cf55ecbf6..a08e6827d48 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -134,12 +134,16 @@ proc/listclearnulls(list/list)
L.Cut(picked,picked+1) //Cut is far more efficient that Remove()
//Returns the top(last) element from the list and removes it from the list (typical stack function)
-/proc/pop(list/listfrom)
- if (listfrom.len > 0)
- var/picked = listfrom[listfrom.len]
- listfrom.len--
- return picked
- return null
+/proc/pop(list/L)
+ if(L.len)
+ . = L[L.len]
+ L.len--
+
+/proc/sorted_insert(list/L, thing, comparator)
+ var/pos = L.len
+ while(pos > 0 && call(comparator)(thing, L[pos]) > 0)
+ pos--
+ L.Insert(pos+1, thing)
/*
* Sorting
diff --git a/code/controllers/lighting_controller.dm b/code/controllers/lighting_controller.dm
index 503fc10c93c..3428503e2d6 100644
--- a/code/controllers/lighting_controller.dm
+++ b/code/controllers/lighting_controller.dm
@@ -117,7 +117,7 @@ datum/controller/lighting/proc/Recover()
var/msg = "## DEBUG: [time2text(world.timeofday)] lighting_controller restarted. Reports:\n"
for(var/varname in lighting_controller.vars)
switch(varname)
- if("tag","bestF","type","parent_type","vars") continue
+ if("tag","type","parent_type","vars") continue
else
var/varval1 = lighting_controller.vars[varname]
var/varval2 = vars[varname]
diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm
index fb369946c34..1481225d5af 100644
--- a/code/controllers/master_controller.dm
+++ b/code/controllers/master_controller.dm
@@ -287,7 +287,7 @@ datum/controller/game_controller/proc/Recover() //Mostly a placeholder for now.
var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n"
for(var/varname in master_controller.vars)
switch(varname)
- if("tag","bestF","type","parent_type","vars") continue
+ if("tag","type","parent_type","vars") continue
else
var/varval = master_controller.vars[varname]
if(istype(varval,/datum))
diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm
index 0af47ba12c6..3c97935a29d 100644
--- a/code/defines/procs/AStar.dm
+++ b/code/defines/procs/AStar.dm
@@ -1,185 +1,140 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
+/proc/cmp_pathnodes(pathnode/A, pathnode/B)
+ return (A.f_score - B.f_score)
-/*
-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, adjacent turf proc, distance proc)
-For the adjacent turf proc i wrote:
+pathnode
+ var/turf/me //turf
+ var/pathnode/parent //pathnode this turf was discovered from
+ var/f_score //total path-length of this turf
+ var/g_score //length of path to this turf
+
+ New(m,p,f,g)
+ me = m
+ parent = p
+ f_score = f
+ g_score = g
+
+/********************
+/A* pathfinding algorithm
+/This version updated May 2013 by Yvarov
+/Returns a list of turfs along the discovered path, in order from start to finish.
+/
+/A* is more complex than can be easily explained here, but basically
+/it checks tiles, assigning a score f = g + h where
+/g = distance from start & h = minimum distance to end, and repeats with
+/new tiles starting with the smallest f_score. It's capable of finding
+/a path (assuming one exists), without getting stuck in wrong turns or
+/unnecessarily checking unpromising tiles.
+/
+/The syntax for calling remains roughly the same as the version this replaces,
+/minus two arguments (ones that were never given by any of
+/the procs that called it anyway).
+
+Possible procs to use for adjacency (there may be others):
/turf/proc/AdjacentTurfs
-And for the distance one i wrote:
-/turf/proc/Distance
-So an example use might be:
+/turf/proc/AdjacentTurfsSpace
+/turf/proc/CardinalTurfsWithAccess (best for most occasions, four-directional)
-src.path_list = AStar(src.loc, target.loc, /turf/proc/AdjacentTurfs, /turf/proc/Distance)
+Possible procs to use for adjacency:
+/turf/proc/Distance_cardinal (requires four-directional adjacency proc)
+/turf/proc/Distance (requires eight-directional adjacency proc)
-Note: The path is returned starting at the END node, so i wrote reverselist to reverse it for ease of use.
+Arguments:
+/turf/start_turf: The turf to start the path from.
+/turf/dest_turf: The turf to finish the path on.
+adjacent_proc: The proc used to find adjacent tiles. When called, id is provided as an argument.
+ For the best path to be found, it must only output tiles that could be reached in
+ one move AND aren't occupied by something else, like a wall.
+distance_proc: Formula to use for calculating distance.
+maxnodes: Maximum _depth_ of nodes to examine; currently used only by disease.
+ Not equal to the number of nodes to examine (lpct, internal) or the maximum path
+ length returned (max_length below).
+max_length: If the path is longer, return only this many elements of it (starting from the front).
+ It can only be shortened after finding the whole path, so doesn't save any time,
+ but as different values were provided throughout the code it's kept for compatibility.
+id: Supplied to the adjacency proc for doors/etc. Default is null.
+/var/turf/exclude: Turfs to avoid when finding a path. Default is null.
-src.path_list = reverselist(src.pathlist)
+Nodes not kept at all from previous version (which were never supplied):
+"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."
+*********************/
+proc/AStar(turf/start_turf, turf/dest_turf, adjacent_proc, distance_proc, maxnodes, max_length, id=null, var/turf/exclude=null)
+ set background=1 //allows proc to sleep should it take more than a single frame to complete
+
+ var/const/DEPTH_BIAS = 0.5 //preference of discovered paths. decrease with extreme caution and don't set >1!
+ //var/evaluations = 0 //Total number of tiles examined, including discards and duplicates. Used for debugging
+ var/lpct = 0 //Loop counter - turfs properly examined. Debugging and infinite loop prevention.
+ var/list/checked = list() //list of pathnodes already examined
+ var/list/priorityqueue = list() //list of pathnodes not yet examined, sorted descending by f_score
+ var/pathnode/current //current pathnode being examined
+ var/next_g //calculated g_score of a potential turf
+ var/next_f //calculated f_score of a potential turf
+ var/skip //used for internal logic branching, don't touch.
+ var/pathnode/temp //temporary pathnode used for comparison, don't touch.
+
+ var/list/path = list() //return value
-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.
+ //sanity check to avoid a huge waste of CPU time on impossible tasks
+ for(var/obj/O in dest_turf)
+ if(O.density && (!istype(O, /obj/machinery/door) || !(O.flags & ON_BORDER))) //something is blocking the destination
+ return path
+
+ priorityqueue += new /pathnode(start_turf, null, call(start_turf,distance_proc)(dest_turf), 0)//add the starting turf and manhattan distance to the queue!
-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.
-*/
+ for(lpct=0, lpct<1800, lpct++) //maximum for DEPTH_BIAS=0.5 is 958 loops to Research Division (originally 2743)
+ if(!priorityqueue.len) break
-// 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
+ current = pop(priorityqueue)
+ checked.Add(current)
-// Also added 'exclude' turf to avoid travelling over; defaults to null
+ next_g = current.g_score + DEPTH_BIAS
+ //if(maxnodes && next_g > (maxnodes*DEPTH_BIAS)) continue
+ if(!current) //no more possibilities, or an unhandled error
+ //world << "Impossible path"
+ return path //FAILURE
-PriorityQueue
- var/L[]
- var/cmp
- New(compare)
- L = new()
- cmp = compare
- proc
- IsEmpty()
- return !L.len
- Enqueue(d)
- var/i
- var/j
- L.Add(d)
- i = L.len
- j = i>>1
- while(i > 1 && call(cmp)(L[j],L[i]) > 0)
- L.Swap(i,j)
- i = j
- j >>= 1
+ if(current.me == dest_turf) //reached the destination
+ while(current) //recreate path by looping through parents
+ path.Insert(1, current.me)
+ current = current.parent
+ //world << "Path found! ([path.len] steps, [lpct] loops, [evaluations] evaluations)"
- Dequeue()
- if(!L.len) return 0
- . = L[1]
- Remove(1)
+ path.len = min(path.len, max_length-1)
+ return path //SUCCESS
- Remove(i)
- if(i > L.len) return 0
- L.Swap(i,L.len)
- L.Cut(L.len)
- if(i < L.len)
- _Fix(i)
- _Fix(i)
- var/child = i + i
- var/item = L[i]
- while(child <= L.len)
- if(child + 1 <= L.len && call(cmp)(L[child],L[child + 1]) > 0)
- child++
- if(call(cmp)(item,L[child]) > 0)
- L[i] = L[child]
- i = child
- else
- break
- child = i + i
- L[i] = item
- List()
- var/ret[] = new()
- var/copy = L.Copy()
- while(!IsEmpty())
- ret.Add(Dequeue())
- L = copy
- return ret
- RemoveItem(i)
- var/ind = L.Find(i)
- if(ind)
- Remove(ind)
-PathNode
- var/datum/source
- var/PathNode/prevNode
- var/f
- var/g
- var/h
- var/nt // Nodes traversed
- New(s,p,pg,ph,pnt)
- source = s
- prevNode = p
- g = pg
- h = ph
- f = g + h
- source.bestF = f
- nt = pnt
+ var/adjacent = call(current.me,adjacent_proc)(id)
+ for(var/next_turf in adjacent)
+ //evaluations++
+ if(next_turf == exclude)
+ continue
+ skip = 0
+ next_f = next_g + call(next_turf,distance_proc)(dest_turf)
+ var/i = 0
+ while(i < checked.len && !skip)
+ i++
+ temp = checked[i]
+ if(temp.me == next_turf) //if this turf has already been checked
+ skip = 1
+ if(temp.f_score > next_f) //BUT the current path to it is better
+ checked[i] = new /pathnode(next_turf, current, next_f, next_g)
+ //then update it
+ if(!skip) //if it hasn't been checked
+ i = 0
+ while(i < priorityqueue.len && !skip)
+ i++
+ temp = priorityqueue[i]
+ if(temp.me == next_turf) //if this turf was already on the openlist
+ if(temp.f_score > next_f) //BUT the current path to it is better
+ priorityqueue.Cut(i,i+1) //then remove it
+ skip = 2
+ else
+ skip = 1 //otherwise leave it and don't add this version
+ if(!(skip % 2))
+ sorted_insert(priorityqueue, new /pathnode(next_turf, current, next_f, next_g), /proc/cmp_pathnodes)
-datum
- var/bestF
-proc
- PathWeightCompare(PathNode/a, PathNode/b)
- return a.f - b.f
-
- AStar(start,end,adjacent,dist,maxnodes,maxnodedepth = 30,mintargetdist,minnodedist,id=null, var/turf/exclude=null)
-
-// world << "A*: [start] [end] [adjacent] [dist] [maxnodes] [maxnodedepth] [mintargetdist], [minnodedist] [id]"
- var/PriorityQueue/open = new /PriorityQueue(/proc/PathWeightCompare)
- var/closed[] = new()
- var/path[]
- start = get_turf(start)
- if(!start) return 0
-
- open.Enqueue(new /PathNode(start,null,0,call(start,dist)(end)))
-
- while(!open.IsEmpty() && !path)
- {
- var/PathNode/cur = open.Dequeue()
- closed.Add(cur.source)
-
- var/closeenough
- if(mintargetdist)
- closeenough = call(cur.source,dist)(end) <= mintargetdist
-
- if(cur.source == end || closeenough)
- path = new()
- path.Add(cur.source)
- while(cur.prevNode)
- cur = cur.prevNode
- path.Add(cur.source)
- break
-
- var/L[] = call(cur.source,adjacent)(id)
- if(minnodedist && maxnodedepth)
- if(call(cur.source,minnodedist)(end) + cur.nt >= maxnodedepth)
- continue
- else if(maxnodedepth)
- if(cur.nt >= maxnodedepth)
- continue
-
- for(var/datum/d in L)
- if(d == exclude)
- continue
- var/ng = cur.g + call(cur.source,dist)(d)
- if(d.bestF)
- if(ng + call(d,dist)(end) < d.bestF)
- for(var/i = 1; i <= open.L.len; i++)
- var/PathNode/n = open.L[i]
- if(n.source == d)
- open.Remove(i)
- break
- else
- continue
-
- open.Enqueue(new /PathNode(d,cur,ng,call(d,dist)(end),cur.nt+1))
- if(maxnodes && open.L.len > maxnodes)
- open.L.Cut(open.L.len)
- }
-
- var/PathNode/temp
- while(!open.IsEmpty())
- temp = open.Dequeue()
- temp.source.bestF = 0
- while(closed.len)
- temp = closed[closed.len]
- temp.bestF = 0
- closed.Cut(closed.len)
-
- if(path)
- for(var/i = 1; i <= path.len/2; i++)
- path.Swap(i,path.len-i+1)
-
- return path
+ //world << "Didn't find a path within [evaluations] evaluations and [lpct] loops."
+ return path //FAILURE
+ //This return should only be reached if the path is too long, and the while loop gives up.
diff --git a/code/game/machinery/bots/mulebot.dm b/code/game/machinery/bots/mulebot.dm
index 8f56a805db5..cecbf903b3e 100644
--- a/code/game/machinery/bots/mulebot.dm
+++ b/code/game/machinery/bots/mulebot.dm
@@ -201,7 +201,7 @@ var/global/mulebot_count = 0
if(5,6)
dat += "Calculating navigation path"
if(7)
- dat += "Unable to locate destination"
+ dat += "Unable to reach destination"
dat += ""
dat += "Current Load: [load ? load.name : "none"]
"
@@ -225,7 +225,7 @@ var/global/mulebot_count = 0
if(load)
dat += "Unload Now
"
- dat += "