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 += "
The maintenance hatch is closed
" + dat += "
The maintenance hatch is closed.
" else if(!ai) @@ -590,7 +590,7 @@ var/global/mulebot_count = 0 blockcount++ mode = 4 if(blockcount == 3) - src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") + src.visible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) if(blockcount > 5) // attempt 5 times before recomputing @@ -608,7 +608,7 @@ var/global/mulebot_count = 0 return return else - src.visible_message("[src] makes an annoyed buzzing sound", "You hear an electronic buzzing sound.") + src.visible_message("[src] makes an annoyed buzzing sound.", "You hear an electronic buzzing sound.") playsound(src.loc, 'sound/machines/buzz-two.ogg', 50, 0) //world << "Bad turf." mode = 5