mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 12:41:09 +01:00
+10
-6
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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))
|
||||
|
||||
+126
-171
@@ -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.
|
||||
|
||||
@@ -201,7 +201,7 @@ var/global/mulebot_count = 0
|
||||
if(5,6)
|
||||
dat += "<span class='average'>Calculating navigation path</span>"
|
||||
if(7)
|
||||
dat += "<span class='bad'>Unable to locate destination</span>"
|
||||
dat += "<span class='bad'>Unable to reach destination</span>"
|
||||
dat += "</div>"
|
||||
|
||||
dat += "<b>Current Load:</b> [load ? load.name : "<i>none</i>"]<BR>"
|
||||
@@ -225,7 +225,7 @@ var/global/mulebot_count = 0
|
||||
|
||||
if(load)
|
||||
dat += "<A href='byond://?src=\ref[src];op=unload'>Unload Now</A><BR>"
|
||||
dat += "<div class='notice'>The maintenance hatch is closed</div>"
|
||||
dat += "<div class='notice'>The maintenance hatch is closed.</div>"
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user