From bd6b5a68701c55e55caa9e9d97b726f19d8740fe Mon Sep 17 00:00:00 2001 From: Menshin Date: Sat, 12 Jul 2014 21:58:32 +0200 Subject: [PATCH] Refined the implementation of the AStar algorithm : *Reorganised and commented used procs for better visibility/maintenance *Redid the PriorityQueue class *Changed the euclidian distance to the byond one (so that we don't calculate float and square root when it's not necessary) *Made the AStar algorithm use the closed list instead of just filling it *Some optimization in the open list sorting : last entered is first in case of f ties, slightly less tiles are checked now *Changes the order directions are checked when adding adjacents turfs to make for more 'realistic' path (read less 'drunk pathing' with lots of diagonals) *Fixed some turfs densities not being check for diagonal movement *Closed firedoors are now correctly seens as blocked by the algorithm *Cleanbots now only moves in cardinal directions, like other bots. *Updated the info text at the beginning of the file a bit *Absolutely unrelated : simplified the turf.Bless() proc Conflicts: code/__HELPERS/unsorted.dm code/defines/procs/AStar.dm code/game/machinery/bots/bots.dm code/game/machinery/bots/cleanbot.dm code/game/machinery/doors/firedoor.dm code/game/machinery/doors/windowdoor.dm code/game/turfs/turf.dm --- code/__HELPERS/unsorted.dm | 130 ++++++---- code/defines/procs/AStar.dm | 325 ++++++++++++++---------- code/game/machinery/bots/bots.dm | 71 +----- code/game/machinery/bots/cleanbot.dm | 5 +- code/game/machinery/bots/ed209bot.dm | 4 - code/game/machinery/bots/floorbot.dm | 4 - code/game/machinery/bots/medbot.dm | 5 - code/game/machinery/bots/secbot.dm | 28 +- code/game/machinery/doors/door.dm | 4 + code/game/machinery/doors/firedoor.dm | 4 + code/game/machinery/doors/windowdoor.dm | 5 + code/game/turfs/turf.dm | 121 +++++++-- 12 files changed, 396 insertions(+), 310 deletions(-) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f5e4c25a300..febcc061e06 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -150,25 +150,82 @@ Turf and target are seperate in case you want to teleport some distance from a t return destination +/////////////////// +//A* helpers procs +/////////////////// +// Returns true if a link between A and B is blocked +// Movement through doors allowed if ID has access +/proc/LinkBlockedWithAccess(turf/A, turf/B, obj/item/weapon/card/id/ID) -/proc/LinkBlocked(turf/A, turf/B) if(A == null || B == null) return 1 var/adir = get_dir(A,B) var/rdir = get_dir(B,A) - if((adir & (NORTH|SOUTH)) && (adir & (EAST|WEST))) // diagonal - var/iStep = get_step(A,adir&(NORTH|SOUTH)) - if(!LinkBlocked(A,iStep) && !LinkBlocked(iStep,B)) return 0 + if(adir & (adir-1)) // diagonal + var/turf/iStep = get_step(A,adir&(NORTH|SOUTH)) + if(!iStep.density && !LinkBlockedWithAccess(A,iStep, ID) && !LinkBlockedWithAccess(iStep,B,ID)) + return 0 + + var/turf/pStep = get_step(A,adir&(EAST|WEST)) + if(!pStep.density && !LinkBlockedWithAccess(A,pStep,ID) && !LinkBlockedWithAccess(pStep,B,ID)) + return 0 + + return 1 + + if(DirBlockedWithAccess(A,adir, ID)) + return 1 + + if(DirBlockedWithAccess(B,rdir, ID)) + return 1 + + for(var/obj/O in B) + if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER)) + return 1 + + return 0 + +// Returns true if direction is blocked from loc +// Checks doors against access with given ID +/proc/DirBlockedWithAccess(turf/loc,var/dir,var/obj/item/weapon/card/id/ID) + for(var/obj/structure/window/D in loc) + if(!D.density) continue + if(D.dir == SOUTHWEST) return 1 //full-tile window + if(D.dir == dir) return 1 //matching border window + + for(var/obj/machinery/door/D in loc) + if(!D.CanAStarPass(ID,dir)) + return 1 + return 0 + +// Returns true if a link between A and B is blocked +// Movement through doors allowed if door is open +/proc/LinkBlocked(turf/A, turf/B) + if(A == null || B == null) + return 1 + var/adir = get_dir(A,B) + var/rdir = get_dir(B,A) + if(adir & (adir-1)) //diagonal + var/turf/iStep = get_step(A,adir & (NORTH|SOUTH)) //check the north/south component + if(!iStep.density && !LinkBlocked(A,iStep) && !LinkBlocked(iStep,B)) + return 0 + + var/turf/pStep = get_step(A,adir & (EAST|WEST)) //check the east/west component + if(!pStep.density && !LinkBlocked(A,pStep) && !LinkBlocked(pStep,B)) + return 0 - var/pStep = get_step(A,adir&(EAST|WEST)) - if(!LinkBlocked(A,pStep) && !LinkBlocked(pStep,B)) return 0 return 1 if(DirBlocked(A,adir)) return 1 if(DirBlocked(B,rdir)) return 1 + + for(var/obj/O in B) + if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER)) + return 1 + return 0 - +// Returns true if direction is blocked from loc +// Checks if doors are open /proc/DirBlocked(turf/loc,var/dir) for(var/obj/structure/window/D in loc) if(!D.density) continue @@ -176,18 +233,12 @@ Turf and target are seperate in case you want to teleport some distance from a t if(D.dir == dir) return 1 for(var/obj/machinery/door/D in loc) - if(!D.density) continue - if(istype(D, /obj/machinery/door/window)) - if((dir & SOUTH) && (D.dir & (EAST|WEST))) return 1 - if((dir & EAST ) && (D.dir & (NORTH|SOUTH))) return 1 - else return 1 // it's a real, air blocking door + if(!D.density)//if the door is open + continue + else return 1 // if closed, it's a real, air blocking door return 0 -/proc/TurfBlockedNonWindow(turf/loc) - for(var/obj/O in loc) - if(O.density && !istype(O, /obj/structure/window)) - return 1 - return 0 +///////////////////////////////////////////////////////////////////////// /proc/sign(x) return x!=0?x/abs(x):0 @@ -1555,41 +1606,6 @@ proc/check_target_facings(mob/living/initator, mob/living/target) return 3 -/proc/texttospeechstrip(var/t_in) - var/t_out = "" - for(var/i=1, i<=length(t_in), i++) - var/ascii_char = text2ascii(t_in,i) - switch(ascii_char) - // A .. Z - if(65 to 90) //Uppercase Letters - if(lentext(t_out) <= 150) - t_out += ascii2text(ascii_char) - // a .. z - if(97 to 122) //Lowercase Letters - if(lentext(t_out) <= 150) - t_out += ascii2text(ascii_char) - // 0 .. 9 - if(48 to 57) //Numbers - if(lentext(t_out) <= 150) - t_out += ascii2text(ascii_char) - // ` , - . ! ? : ' - if(39,44,45,46,33,63,58,96,60,62) //Common name punctuation - if(lentext(t_out) <= 150) - t_out += ascii2text(ascii_char) - //Space - if(32) - if(lentext(t_out) <= 150) - t_out += ascii2text(ascii_char) - return t_out -/var/lastspeak = "" - - -/mob/proc/texttospeech(var/text, var/speed, var/pitch, var/accent, var/voice, var/echo) - text = texttospeechstrip(text) - lastspeak = text - ext_python("voice.py", "\"[accent]\" \"[voice]\" \"[pitch]\" \"[echo]\" \"[speed]\" \"[text]\" \"[src.ckey]\"") - - atom/proc/GetTypeInAllContents(typepath) var/list/processing_list = list(src) var/list/processed = list() @@ -1609,4 +1625,12 @@ atom/proc/GetTypeInAllContents(typepath) processed |= A - return found + return found + +/proc/random_step(atom/movable/AM, steps, chance) + var/initial_chance = chance + while(steps > 0) + if(prob(chance)) + step(AM, pick(alldirs)) + chance = max(chance - (initial_chance / steps), 0) + steps-- diff --git a/code/defines/procs/AStar.dm b/code/defines/procs/AStar.dm index 0af47ba12c6..acf759e4f9b 100644 --- a/code/defines/procs/AStar.dm +++ b/code/defines/procs/AStar.dm @@ -1,5 +1,3 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31 - /* 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 @@ -14,10 +12,6 @@ So an example use might be: src.path_list = AStar(src.loc, target.loc, /turf/proc/AdjacentTurfs, /turf/proc/Distance) -Note: The path is returned starting at the END node, so i wrote reverselist to reverse it for ease of use. - -src.path_list = reverselist(src.pathlist) - 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. @@ -36,150 +30,209 @@ length to avoid portals or something i guess?? Not that they're counted right no // Also added 'exclude' turf to avoid travelling over; defaults to null +//Currently, there's four main ways to call AStar +// +// 1) adjacent = "/turf/proc/AdjacentTurfsWithAccess" and distance = "/turf/proc/Distance" +// Seeks a path moving in all directions (including diagonal) and checking for the correct id to get through doors +// +// 2) adjacent = "/turf/proc/CardinalTurfsWithAccess" and distance = "/turf/proc/Distance_cardinal" +// Seeks a path moving only in cardinal directions and checking if for the correct id to get through doors +// Used by most bots, including Beepsky +// +// 3) adjacent = "/turf/proc/AdjacentTurfs" and distance = "/turf/proc/Distance" +// Same as 1), but don't check for ID. Can get only get through open doors +// +// 4) adjacent = "/turf/proc/AdjacentTurfsSpace" and distance = "/turf/proc/Distance" +// Same as 1), but check all turf, including unsimulated -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 +////////////////////// +//PriorityQueue object +////////////////////// - Dequeue() - if(!L.len) return 0 - . = L[1] - Remove(1) +//an ordered list, using the cmp proc to weight the list elements +/PriorityQueue + var/list/L //the actual queue + var/cmp //the weight function used to order the queue - 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 +/PriorityQueue/New(compare) + L = new() + cmp = compare -datum - var/bestF -proc - PathWeightCompare(PathNode/a, PathNode/b) - return a.f - b.f +/PriorityQueue/proc/IsEmpty() + return !L.len - AStar(start,end,adjacent,dist,maxnodes,maxnodedepth = 30,mintargetdist,minnodedist,id=null, var/turf/exclude=null) +//add an element in the list, +//immediatly ordering it to its position using Insertion sort +/PriorityQueue/proc/Enqueue(var/atom/A) + var/i + L.Add(A) + i = L.len -1 + while(i > 0 && call(cmp)(L[i],A) >= 0) //place the element at it's right position using the compare proc + L.Swap(i,i+1) //last inserted element being first in case of ties (optimization) + i-- -// 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 +//removes and returns the first element in the queue +/PriorityQueue/proc/Dequeue() + if(!L.len) + return 0 + . = L[1] + Remove(.) + return . - open.Enqueue(new /PathNode(start,null,0,call(start,dist)(end))) +//removes an element +/PriorityQueue/proc/Remove(var/atom/A) + return L.Remove(A) - while(!open.IsEmpty() && !path) - { - var/PathNode/cur = open.Dequeue() - closed.Add(cur.source) +//returns a copy of the elements list +/PriorityQueue/proc/List() + var/list/ret = L.Copy() + return ret - var/closeenough - if(mintargetdist) - closeenough = call(cur.source,dist)(end) <= mintargetdist +//return the position of an element or 0 if not found +/PriorityQueue/proc/Seek(var/atom/A) + return L.Find(A) - if(cur.source == end || closeenough) - path = new() +//return the element at the i_th position +/PriorityQueue/proc/Get(var/i) + if(i > L.len || i < 1) + return 0 + return L[i] + +//replace the passed element at it's right position using the cmp proc +/PriorityQueue/proc/ReSort(var/atom/A) + var/i = Seek(A) + if(i == 0) + return + while(i < L.len && call(cmp)(L[i],L[i+1]) > 0) + L.Swap(i,i+1) + i++ + while(i > 1 && call(cmp)(L[i],L[i-1]) <= 0) //last inserted element being first in case of ties (optimization) + L.Swap(i,i-1) + i-- + +////////////////////// +//PathNode object +////////////////////// + +//A* nodes variables +/PathNode + var/turf/source //turf associated with the PathNode + var/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 + +/PathNode/New(s,p,pg,ph,pnt) + source = s + prevNode = p + g = pg + h = ph + f = g + h + source.PNode = src + nt = pnt + +/PathNode/proc/calc_f() + f = g + h + +////////////////////// +//A* procs +////////////////////// + +//the weighting function, used in the A* algorithm +proc/PathWeightCompare(PathNode/a, PathNode/b) + return a.f - b.f + +//search if there's a PathNode that points to turf T in the Priority Queue +proc/SeekTurf(var/PriorityQueue/Queue, var/turf/T) + var/i = 1 + var/PathNode/PN + while(i < Queue.L.len + 1) + PN = Queue.L[i] + if(PN.source == T) + return i + i++ + return 0 + +//the actual algorithm +proc/AStar(start,end,adjacent,dist,maxnodes,maxnodedepth = 30,mintargetdist,minnodedist,id=null, var/turf/exclude=null) + var/PriorityQueue/open = new /PriorityQueue(/proc/PathWeightCompare) //the open list, ordered using the PathWeightCompare proc, from lower f to higher + var/list/closed = new() //the closed list + var/list/path = null //the returned path, if any + var/PathNode/cur //current processed turf + + //sanitation + start = get_turf(start) + if(!start) + return 0 + + //initialization + open.Enqueue(new /PathNode(start,null,0,call(start,dist)(end),0)) + + //then run the main loop + while(!open.IsEmpty() && !path) + { + //get the lower f node on the open list + cur = open.Dequeue() //get the lower f turf in the open list + closed.Add(cur.source) //and tell we've processed it + + //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) - while(cur.prevNode) - cur = cur.prevNode - path.Add(cur.source) - break + 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 + //IMPLEMENTATION TO FINISH + //do we really need this minnodedist ??? + /*if(minnodedist && maxnodedepth) + if(call(cur.source,minnodedist)(end) + 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 + //get adjacents turfs using the adjacent proc, checking for access with id + var/list/L = call(cur.source,adjacent)(id,closed) - 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) - } + for(var/turf/T in L) + if(T == exclude) + continue - 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) + var/newg = cur.g + call(cur.source,dist)(T) + if(!T.PNode) //is not already in open list, so add it + open.Enqueue(new /PathNode(T,cur,newg,call(T,dist)(end),cur.nt+1)) + else //is already in open list, check if it's a better way from the current turf + if(newg < T.PNode.g) + T.PNode.prevNode = cur + T.PNode.g = newg + T.PNode.calc_f() + open.ReSort(T.PNode)//reorder the changed element in the list - if(path) - for(var/i = 1; i <= path.len/2; i++) - path.Swap(i,path.len-i+1) + } - return path + //cleaning after us + for(var/PathNode/PN in open.L) + PN.source.PNode = null + for(var/turf/T in closed) + T.PNode = null + + //if the path is longer than maxnodes, then don't return it + if(path && maxnodes && path.len > (maxnodes + 1)) + return 0 + + //reverse the path to get it from start to finish + if(path) + for(var/i = 1; i <= path.len/2; i++) + path.Swap(i,path.len-i+1) + + return path diff --git a/code/game/machinery/bots/bots.dm b/code/game/machinery/bots/bots.dm index 1e0694f4404..b0016ddf1ce 100644 --- a/code/game/machinery/bots/bots.dm +++ b/code/game/machinery/bots/bots.dm @@ -157,69 +157,10 @@ src.add_hiddenprint(user) src.attack_hand(user) -/******************************************************************/ -// Navigation procs -// Used for A-star pathfinding +/obj/machinery/bot/proc/speak(var/message) + if((!src.on) || (!message)) + return + for(var/mob/O in hearers(src, null)) + O.show_message("[src] beeps, \"[message]\"",2) + return - -// Returns the surrounding cardinal turfs with open links -// Including through doors openable with the ID -/turf/proc/CardinalTurfsWithAccess(var/obj/item/weapon/card/id/ID) - var/L[] = new() - - // for(var/turf/simulated/t in oview(src,1)) - - for(var/d in cardinal) - var/turf/simulated/T = get_step(src, d) - if(istype(T) && !T.density) - if(!LinkBlockedWithAccess(src, T, ID)) - L.Add(T) - return L - - -// Returns true if a link between A and B is blocked -// Movement through doors allowed if ID has access -/proc/LinkBlockedWithAccess(turf/A, turf/B, obj/item/weapon/card/id/ID) - - if(A == null || B == null) return 1 - var/adir = get_dir(A,B) - var/rdir = get_dir(B,A) - if((adir & (NORTH|SOUTH)) && (adir & (EAST|WEST))) // diagonal - var/iStep = get_step(A,adir&(NORTH|SOUTH)) - if(!LinkBlockedWithAccess(A,iStep, ID) && !LinkBlockedWithAccess(iStep,B,ID)) - return 0 - - var/pStep = get_step(A,adir&(EAST|WEST)) - if(!LinkBlockedWithAccess(A,pStep,ID) && !LinkBlockedWithAccess(pStep,B,ID)) - return 0 - return 1 - - if(DirBlockedWithAccess(A,adir, ID)) - return 1 - - if(DirBlockedWithAccess(B,rdir, ID)) - return 1 - - for(var/obj/O in B) - if(O.density && !istype(O, /obj/machinery/door) && !(O.flags & ON_BORDER)) - return 1 - - return 0 - -// Returns true if direction is blocked from loc -// Checks doors against access with given ID -/proc/DirBlockedWithAccess(turf/loc,var/dir,var/obj/item/weapon/card/id/ID) - for(var/obj/structure/window/D in loc) - if(!D.density) continue - if(D.is_fulltile()) return 1 - if(D.dir == dir) return 1 - - for(var/obj/machinery/door/D in loc) - if(!D.density) continue - if(istype(D, /obj/machinery/door/window)) - if( dir & D.dir ) return !D.check_access(ID) - - //if((dir & SOUTH) && (D.dir & (EAST|WEST))) return !D.check_access(ID) - //if((dir & EAST ) && (D.dir & (NORTH|SOUTH))) return !D.check_access(ID) - else return !D.check_access(ID) // it's a real, air blocking door - return 0 diff --git a/code/game/machinery/bots/cleanbot.dm b/code/game/machinery/bots/cleanbot.dm index 74751b7793d..396d657c96c 100644 --- a/code/game/machinery/bots/cleanbot.dm +++ b/code/game/machinery/bots/cleanbot.dm @@ -237,8 +237,9 @@ text("[src.oddbutton ? "Yes" : "No" if(target && path.len == 0) spawn(0) if(!src || !target) return - src.path = AStar(src.loc, src.target.loc, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance, 0, 30, id=botcard) - if (!path) path = list() + src.path = AStar(src.loc, src.target.loc, /turf/proc/CardinalTurfsWithAccess, /turf/proc/Distance_cardinal, 0, 30) + if(!src.path) + src.path = list() if(src.path.len == 0) src.oldtarget = src.target target.targeted_by = null diff --git a/code/game/machinery/bots/ed209bot.dm b/code/game/machinery/bots/ed209bot.dm index c5ebde6ba05..ae959bb42b0 100644 --- a/code/game/machinery/bots/ed209bot.dm +++ b/code/game/machinery/bots/ed209bot.dm @@ -709,10 +709,6 @@ Auto Patrol: []"}, M:loc = T */ -/obj/machinery/bot/ed209/proc/speak(var/message) - for(var/mob/O in hearers(src, null)) - O.show_message("[src] beeps, \"[message]\"",2) - return /obj/machinery/bot/ed209/explode() walk_to(src,0) diff --git a/code/game/machinery/bots/floorbot.dm b/code/game/machinery/bots/floorbot.dm index c2acf2f2ab9..78c418adb8c 100644 --- a/code/game/machinery/bots/floorbot.dm +++ b/code/game/machinery/bots/floorbot.dm @@ -120,10 +120,6 @@ var/global/list/floorbot_targets=list() return -/obj/machinery/bot/floorbot/proc/speak(var/message) - for(var/mob/O in hearers(src, null)) - O.show_message("[src] beeps, \"[message]\"",2) - return /obj/machinery/bot/floorbot/attackby(var/obj/item/W , mob/user as mob) if(istype(W, /obj/item/stack/tile/plasteel)) diff --git a/code/game/machinery/bots/medbot.dm b/code/game/machinery/bots/medbot.dm index a377a8a7425..5096b6a1554 100644 --- a/code/game/machinery/bots/medbot.dm +++ b/code/game/machinery/bots/medbot.dm @@ -446,11 +446,6 @@ return -/obj/machinery/bot/medbot/proc/speak(var/message) - if((!src.on) || (!message)) - return - visible_message("[src] beeps, \"[message]\"") - return /obj/machinery/bot/medbot/bullet_act(var/obj/item/projectile/Proj) if(Proj.flag == "taser") diff --git a/code/game/machinery/bots/secbot.dm b/code/game/machinery/bots/secbot.dm index 0d633fe1b45..c2b4f9d25e1 100644 --- a/code/game/machinery/bots/secbot.dm +++ b/code/game/machinery/bots/secbot.dm @@ -83,17 +83,16 @@ -/obj/machinery/bot/secbot - New() - ..() - src.icon_state = "secbot[src.on]" - spawn(3) - src.botcard = new /obj/item/weapon/card/id(src) - var/datum/job/detective/J = new/datum/job/detective - src.botcard.access = J.get_access() - if(radio_controller) - radio_controller.add_object(src, control_freq, filter = RADIO_SECBOT) - radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) +/obj/machinery/bot/secbot/New() + ..() + src.icon_state = "secbot[src.on]" + spawn(3) + src.botcard = new /obj/item/weapon/card/id(src) + var/datum/job/detective/J = new /datum/job/detective + src.botcard.access = J.get_access() + if(radio_controller) + radio_controller.add_object(src, control_freq, filter = RADIO_SECBOT) + radio_controller.add_object(src, beacon_freq, filter = RADIO_NAVBEACONS) /obj/machinery/bot/secbot/turn_on() @@ -252,7 +251,7 @@ Auto Patrol: []"}, src.target = null src.last_found = world.time src.frustration = 0 - src.mode = 0 + src.mode = SECBOT_IDLE walk_to(src,0) if(target) // make sure target exists @@ -394,7 +393,6 @@ Auto Patrol: []"}, // perform a single patrol step - /obj/machinery/bot/secbot/proc/patrol_step() if(loc == patrol_target) // reached target @@ -667,10 +665,6 @@ Auto Patrol: []"}, M:loc = T */ -/obj/machinery/bot/secbot/proc/speak(var/message) - for(var/mob/O in hearers(src, null)) - O.show_message("[src] beeps, \"[message]\"",2) - return /obj/machinery/bot/secbot/explode() diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index c174b3817f1..f7410d589f0 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -99,6 +99,10 @@ return !density +//used in the AStar algorithm to determinate if the turf the door is on is passable +/obj/machinery/door/proc/CanAStarPass(var/obj/item/weapon/card/id/ID) + return !density || check_access(ID) + /obj/machinery/door/proc/bumpopen(mob/user as mob) if(operating) return if(user.last_airflow > world.time - vsc.airflow_delay) //Fakkit diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 5a86a20259a..b213eff32d2 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -276,3 +276,7 @@ /obj/machinery/door/firedoor/multi_tile/triple icon = 'icons/obj/doors/DoorHazard3x1.dmi' width = 3 + +//used in the AStar algorithm to determinate if the turf the door is on is passable +/obj/machinery/door/firedoor/CanAStarPass() + return !density diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 7a247f37614..4108711b507 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -74,6 +74,11 @@ else return 1 + +//used in the AStar algorithm to determinate if the turf the door is on is passable +/obj/machinery/door/window/CanAStarPass(var/obj/item/weapon/card/id/ID, var/to_dir) + return !density || (dir != to_dir) || check_access(ID) + /obj/machinery/door/window/CheckExit(atom/movable/mover as mob|obj, turf/target as turf) if(istype(mover) && mover.checkpass(PASSGLASS)) return 1 diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 6289b6dd255..07c144f1c08 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -26,6 +26,8 @@ var/has_resources var/list/resources + var/PathNode/PNode = null //associated PathNode in the A* algorithm + /turf/New() ..() for(var/atom/movable/AM as mob|obj in src) @@ -336,31 +338,102 @@ return flags |= NOJAUNT -/turf/proc/AdjacentTurfs() - var/L[] = new() - for(var/turf/simulated/t in oview(src,1)) - if(!t.density) - if(!LinkBlocked(src, t) && !TurfBlockedNonWindow(t)) - L.Add(t) - return L -/turf/proc/Distance(turf/t) - if(get_dist(src,t) == 1) - var/cost = (src.x - t.x) * (src.x - t.x) + (src.y - t.y) * (src.y - t.y) - cost *= (pathweight+t.pathweight)/2 - return cost - else - return get_dist(src,t) -/turf/proc/AdjacentTurfsSpace() - var/L[] = new() - for(var/turf/t in oview(src,1)) - if(!t.density) - if(!LinkBlocked(src, t) && !TurfBlockedNonWindow(t)) - L.Add(t) +///////////////////////////////////////////////////////////////////////// +// Navigation procs +// Used for A-star pathfinding +//////////////////////////////////////////////////////////////////////// + +/////////////////////////// +//Cardinal only movements +/////////////////////////// + +// Returns the surrounding cardinal turfs with open links +// Including through doors openable with the ID +/turf/proc/CardinalTurfsWithAccess(var/obj/item/weapon/card/id/ID) + var/list/L = new() + var/turf/simulated/T + + for(var/dir in cardinal) + T = get_step(src, dir) + if(istype(T) && !T.density) + if(!LinkBlockedWithAccess(src, T, ID)) + L.Add(T) return L -// This Distance proc assumes that only cardinal movement is +// Returns the surrounding cardinal turfs with open links +// Don't check for ID, doors passable only if open +/turf/proc/CardinalTurfs() + var/list/L = new() + var/turf/simulated/T + + for(var/dir in cardinal) + T = get_step(src, dir) + if(istype(T) && !T.density) + if(!LinkBlocked(src, T)) + L.Add(T) + return L + +/////////////////////////// +//All directions movements +/////////////////////////// + +// Returns the surrounding simulated turfs with open links +// Including through doors openable with the ID +/turf/proc/AdjacentTurfsWithAccess(var/obj/item/weapon/card/id/ID = null,var/list/closed)//check access if one is passed + var/list/L = new() + var/turf/simulated/T + for(var/dir in list(NORTHWEST,NORTHEAST,SOUTHEAST,SOUTHWEST,NORTH,EAST,SOUTH,WEST)) //arbitrarily ordered list to favor non-diagonal moves in case of ties + T = get_step(src,dir) + if(T in closed) //turf already proceeded in A* + continue + if(istype(T) && !T.density) + if(!LinkBlockedWithAccess(src, T, ID)) + L.Add(T) + return L + +//Idem, but don't check for ID and goes through open doors +/turf/proc/AdjacentTurfs(var/list/closed) + var/list/L = new() + var/turf/simulated/T + for(var/dir in list(NORTHWEST,NORTHEAST,SOUTHEAST,SOUTHWEST,NORTH,EAST,SOUTH,WEST)) //arbitrarily ordered list to favor non-diagonal moves in case of ties + T = get_step(src,dir) + if(T in closed) //turf already proceeded by A* + continue + if(istype(T) && !T.density) + if(!LinkBlocked(src, T)) + L.Add(T) + return L + +// check for all turfs, including unsimulated ones +/turf/proc/AdjacentTurfsSpace(var/obj/item/weapon/card/id/ID = null, var/list/closed)//check access if one is passed + var/list/L = new() + var/turf/T + for(var/dir in list(NORTHWEST,NORTHEAST,SOUTHEAST,SOUTHWEST,NORTH,EAST,SOUTH,WEST)) //arbitrarily ordered list to favor non-diagonal moves in case of ties + T = get_step(src,dir) + if(T in closed) //turf already proceeded by A* + continue + if(istype(T) && !T.density) + if(!ID) + if(!LinkBlocked(src, T)) + L.Add(T) + else + if(!LinkBlockedWithAccess(src, T, ID)) + L.Add(T) + return L + +////////////////////////////// +//Distance procs +////////////////////////////// + +//Distance associates with all directions movement +/turf/proc/Distance(var/turf/T) + return get_dist(src,T) + +// This Distance proc assumes that only cardinal movement is // possible. It results in more efficient (CPU-wise) pathing // for bots and anything else that only moves in cardinal dirs. -/turf/proc/Distance_cardinal(turf/t) - if(!src || !t) return 0 - return abs(src.x - t.x) + abs(src.y - t.y) +/turf/proc/Distance_cardinal(turf/T) + if(!src || !T) return 0 + return abs(src.x - T.x) + abs(src.y - T.y) + +//////////////////////////////////////////////////// \ No newline at end of file