    This is a pathfinder.

    It finds paths.

    It's intended to be part of the Cronus drone to allow it to navigate anywhere it needs to go (Yes, yes, leave the Cronus hate at the door please), but because it outputs the path in an array of vectors it can be easily adapted to almost any system, so I now release it to the public here with as few kinks as I could manage.

    One of the persistent weaknesses of almost any autonomous creation in gmod to date has been a lack of complex navigation, so I figure this will open some avenues of experimentation for both the Wiremod and Facepunch community.
    Code:

    @name Cronoid pathfinder mk 3
    @inputs [GPS1 GPS2]:vector
    @outputs Path:array [Dest Point Start]:vector Comp

    @persist Deg P Post SC Cla
    @persist File:string
    @persist [Point V Hull]:vector
    @persist [Open Scores S Vecs PP]:array
    @persist [Closed Parents Pop Shorts]:table

    if(first()){
            #Scale value of the pathfinder's grid
        Deg=32
        Hull=vec(1,1,1)*Deg
        
            #Degrees of movement
        #Vecs:pushVector(vec(1,-1,1))
        Vecs:pushVector(vec(1,0,1))
        #Vecs:pushVector(vec(1,1,1))
        #Vecs:pushVector(vec(1,-1,0))
        Vecs:pushVector(vec(1,0,0))
        #Vecs:pushVector(vec(1,1,0))
        #Vecs:pushVector(vec(1,-1,-1))
        Vecs:pushVector(vec(1,0,-1))
        #Vecs:pushVector(vec(1,1,-1))
        Vecs:pushVector(vec(0,-1,1))
        Vecs:pushVector(vec(0,0,1))
        Vecs:pushVector(vec(0,1,1))
        Vecs:pushVector(vec(0,-1,0))
        Vecs:pushVector(vec(0,1,0))
        Vecs:pushVector(vec(0,-1,-1))
        Vecs:pushVector(vec(0,0,-1))
        Vecs:pushVector(vec(0,1,-1))
        #Vecs:pushVector(vec(-1,-1,1))
        Vecs:pushVector(vec(-1,0,1))
        #Vecs:pushVector(vec(-1,1,1))
        Vecs:pushVector(vec(-1,-1,0))
        Vecs:pushVector(vec(-1,0,0))
        Vecs:pushVector(vec(-1,1,0))
        #Vecs:pushVector(vec(-1,-1,-1))
        Vecs:pushVector(vec(-1,0,-1))
        #Vecs:pushVector(vec(-1,1,-1))
            #Because of automatic throttling, this array can be
            #expanded to much greater ranges of movement, but doing so
            #can slow the chip down considerably as it searches through
            #each vector for each individual Point.
        
            #Map shortcut loading
        File="cpath-"+map()
        fileLoad(File)
        
            #Ranger settings
        rangerPersist(1)
        rangerFilter(entity():getConstraints())
        rangerFilter(owner())
        
            #Start and Destination points rounded in to Deg grid
                #Start
        Start=round(GPS2/Hull)*Hull
        
            #Start added to the Open and Scores lists as the earliest
            #vector point to check
        Open[1,vector]=Start
        Scores[1,number]=1
        Pop[Start:toString(),number]=1
        Point=Start
        
            #Destination indicator holo
        holoCreate(1,Dest)
        holoModel(1,"hqcone")
        holoScaleUnits(1,Hull)
        
        Cla=50
        
        runOnTick(1)
    }
    if(!SC & fileLoaded(File)){
        if(fileRead(File)){
            S=glonDecode(fileRead(File))
            
            while(S[1,vector] & S[2,vector]){
                V1=S[1,vector]
                V2=S[2,vector]
                
                V1=round(V1/Hull)*Hull
                V2=round(V2/Hull)*Hull
                
                Shorts[V1:toString(),vector]=V2
                Shorts[V2:toString(),vector]=V1
                
                S:remove(2)
                S:remove(1)
            }
        }
        SC=1
    }
    elseif(!Comp){
                #Destination
                    Off=GPS1-Start
            Dest=round(GPS1/Hull)*Hull
            holoPos(1,Dest)
        
            Des=Point:distance(Dest)
        Sight=rangerOffset(Point,Dest):distance() == Des
        Sight=Sight & Des <= Deg
        
        if(Open:count() & !Sight){
            while(perf()){
                #New Point is the vector in Open with the lowest value 
                #in Scores (K)
            K=Scores:minIndex()
            Point=Open[K,vector]
            
            if(!Closed[Point:toString(),number]){
                while(perf()){
                    P++
                        An=(Dest-Point):toAngle():setPitch(0)
                    Ba=Vecs[P,vector]
                    V=Point+Ba*Hull
                    
                    if(!Closed[V:toString(),number]){
                            #Access check
                                #Can V be reached from Point?
                                #(R:hit()==0)
                        HR=rangerOffset(Point,V)
                        R=!HR:hit()
                        
                            #Drop check
                                #Is there a dangerous drop below V?
                        A=rangerOffset(500,V,vec(0,0,-1)):distance()
                        A=(A<=400)
                        
                            #Jump check
                                #If V is above Point, is there a step?
                        J=rangerOffset(Deg,V,vec(0,0,-1)):hit()
                        J=(V:z()>Point:z() ? J : 1)
                        
                        if(R & A & J){
                                #Movement cost from Point to V
                            G = Deg
                            
                                #Heuristic, guess of distance to Dest 
                                #from V
                            H = V:distance(Dest)
                            
                                #Altitude change, distance vertical 
                                #from Point to V
                            A = Ba:z()
                            
                                #F value, the Score
                            F = G + H + A
                            
                            if(!Pop[V:toString(),number]){
                                Str=V:toString()
                                
                                    #Add V to the Open list
                                Open:pushVector(V)
                                Pop[Str,number]=1
                                    #Add V's F score to the list
                                    #so we can determine it's priority later
                                Scores[Open:count(),number]=F
                                
                                Parents[Str,vector]=Point
                                    #Keeping track of Parents lets us trace
                                    #the route when it completes
                                
                                #Shortcut transmission
                                if(Shorts[Str,vector]){
                                    Sis=Shorts[Str,vector]
                                    
                                    Open:pushVector(Sis)
                                    Scores[Open:count(),number]=F
                                    Parents[Sis:toString(),vector]=V
                                }
                            }
                        }
                        else{
                                #If it doesn't meet those criteria, 
                                #V is blacklisted and added to the
                                #Closed list
                            Closed[V:toString(),number]=1
                        }
                    }
                }
            }
            if(P>=Vecs:count()|Closed[Point:toString(),number]){
                P=0
                
                    #Since we have now checked Point and it's 
                    #surroundings, Point is added to the Closed list 
                    #(to prevent repeat checks) and removed from the 
                    #Open & Scores lists
                Closed[Point:toString(),number]=1
                Open:remove(K)
                Pop[Point:toString(),number]=0
                Scores:remove(K)
            }
            }
        }
    }

            #Path extrapolation and post-processing
    if(Sight & !Comp){
        if(!Path:count()){
            V=Point
            Post=1
        }
            #Create full point route from Parents list
        if(Post==1){
            while(perf() & Path[1,vector]:distance(Start)>Deg){
                V=Parents[V:toString(),vector]
                
                Path:insertVector(1,V)
            }
            if(Path[1,vector]:distance(Start)<=Deg){
                Post++
            }
        }
            #Remove unnecessary points
        elseif(Post==2){
                #Create an array of the vector directions
            if(!PP:count()){
                for(N=1,Path:count()){
                    if(!perf()){break}
                    
                    V=Path[N,vector]
                    V2=Path[N-1,vector]
                    Re=V-V2
                    PP:pushVector(Re)
                }
            }
                #Remove all points whose directions do not change
                #from the previous point
            else{
                for(N=3,PP:count()){
                    if(!perf()){break}
                    
                    V=PP[N,vector]
                    V2=PP[N-1,vector]
                    
                    if(V==V2){
                        PP:remove(N-1)
                        Path:remove(N-1)
                        
                        Ch=1
                    }
                }
                if(!Ch){Post++}
            }
        }
            #Average out points within Deg of eachother
            #This eliminates zigzagging
        elseif(Post==3){
            PP=Path:clone()
            for(N=2,PP:count()){
                V1=PP[N-1,vector]
                V2=PP[N,vector]
                
                Dis=V1:distance(V2)
                
                A=Dis-(Deg/2)
                B=Dis+(Deg/2)
                
                if(inrange(Deg,A,B)){
                    Av=V1
                    Av+=V2
                    Av/=2
                    
                    Path[N-1,vector]=Av
                    Path:remove(N)
                    
                    PP:remove(N-1)
                    
                    Ch=1
                }
            }
            if(!Ch){Post++}
        }
            #Indicate path and remove excess hologram
        else{
            Comp=1
            
            for(N=1,Path:count()){
                if(!holoEntity(N+1)){
                    holoCreate(N+1,Path[N,vector])
                    holoScaleUnits(N+1,Hull)
                }
                else{
                    holoPos(N+1,Path[N,vector])
                }
                holoColor(N+1,vec(0,1,0)*255,255)
            }
            for(M=1,Cla-N+1){
                holoDelete(N+M+1)
            }
        }
    }
    if($GPS1|$GPS2){
        DV=round(GPS1/Hull)*Hull
        SV=round(GPS2/Hull)*Hull
        
        if(DV!=Dest|SV!=Start){
            holoDeleteAll()
            Start=round(GPS2/Hull)*Hull
            
            Closed=table()
            Parents=table()
            Pop=table()
            
            Path=array()
            Open=array()
            Scores=array()
            
            Open[1,vector]=Start
            Scores[1,number]=1
            Pop[Start:toString(),number]=1
            Point=Start
            
                #Destination indicator holo
            holoCreate(1,Dest)
            holoModel(1,"hqcone")
            holoScaleUnits(1,Hull)
            
            Comp=0
        }
    }

    Important: This chip restarts its pathfinding procedures when the GPS inputs change at all, so you shouldn't use it to track moving targets or direct moving vehicles wholesale.

    To do this, you need to input a static pair of vectors, and then when you either reach your destination or Comp = 1 for a moving target input a new static vector of your target's position.

    WARNING!
    By default, this chip has zero water detection and it will steer straight into an ocean if necessary. This is possible to add however rather easily and I'll post a corrected code if there's enough of a demand for it.
    The gridsize is also based on the scale of a crouching player (more or less) but a custom scale is easily entered by altering the Deg and Hull variables in the if(first()) bracket, though an input could be added to do so from an external if needed.

    The chip also accepts pre-mapped shortcuts, which are pairs of vectors recorded on a map using the following developer code.
    Code:

    @name Cronoid pathfinder shortcut developer
    @outputs A:array F:string
    @persist O:entity A2:array
    @persist C En Las LEn LC Min TC [Tel T]:array

    if(first()){
        O=owner()
        holoCreate(1)
        holoModel(1,"hqicosphere2")
        holoAlpha(1,170)
        
        runOnTick(1)
        runOnChat(1)
        
        F="cpath-"+map()+".txt"
        if(!fileLoaded(F)){
            fileLoad(F)
        }
        runOnFile(1)
        hint("==Cronoid Pathfinder Shortcut Dev.==",7)
        hint("All positioning commands require the gravgun.",6)
        hint("Use E to position to your aim point.",6)
        hint("Use mouse1 and mouse2 to draw and push the vector.",6)
        hint("Type /reg to record the vector to the shortcut data.",6)
        hint("Type /store to save the current shortcut data to the file.",6)
        hint("Type /clear to erase the current shortcut data.",6)
        hint("Type /restore to re-upload the shortcut data from archive.",6)
    }
    if(fileClk(F) & fileRead(F)){
        A=glonDecode(fileRead(F))
        A2=A:clone()
        print("Data uploaded, "+A:count()+" vectors extant")
    }
    elseif(!LC){
        findByClass("func_useableladder")
        L=findToArray()
        
        while(L:count()){
            E=L[1,entity]
            Z=E:boxSize():z()/2
            B=E:toWorld(E:boxCenter())
            V=B-vec(0,0,Z)+vec(0,0,16)
            V2=B+vec(0,0,Z)-vec(0,0,16)
            
            A:pushVector(V)
            A:pushVector(V2)
            
            L:remove(1)
        }
        hint("Map ladders collected, "+A:count()+" vectors assessed.",2)
        if(!L:count()){
            LC=1
        }
    }
    elseif(Tel:count() & LC & !TC){
        Las=Min
        LEn=En
        
        #Nearest teleporter detection
        B=owner():shootPos()
        for(N=1,Tel:count()){
            AP=Tel[N,entity]:toWorld(Tel[N,entity]:boxCenter())
            
            D=AP:distance(B)
            
            En=(D<En|N==1 ? D : En)
            Min=($En ? N : Min)
        }
        
        #Teleport detection
        Dis=Tel[Min,entity]:pos():distance(Tel[Las,entity]:pos())
        if(Min!=Las & Dis>500 & T:count()!=Tel:count()){
            if(!(T[Las,number] + T[Min,number])){
                T[Min,number]=Las
                T[Las,number]=Min
                C+=2
                
                G=Tel:count()-C+2
                hint(G+" unmapped teleports remaining.",2)
            }
        }
        
        #Teleporter vector pairing recording
        if(T:count()==Tel:count()){
            TT=table()
            for(N=1,Tel:count()){
                V1=round(Tel[N,entity]:pos())
                V2=round(Tel[T[N,number],entity]:pos())
                
                if(!(TT[V1:toString(),number]+TT[V2:toString(),number])){
                    A:pushVector(V1)
                    A:pushVector(V2)
                    
                    TT[V1:toString(),number]=1
                    TT[V2:toString(),number]=1
                }
            }
            Tel=array()
            TC=1
            hint("All available teleports have been mapped.",4)
        }
    }
    if(LC & !TC & !Tel:count() & findCanQuery()){
        P=findByClass("trigger_teleport")
        Tel=findToArray()
        hint("Now assessing map teleporters.",2)
        hint(P+" teleporters detected.",2)
    }

    if(O:weapon():type()=="weapon_physcannon"){
        if(O:keyUse()){
            V=O:aimPos()
        }
        elseif(O:keyAttack1()){
            V=holoEntity(1):pos()
            V+=(V-O:shootPos()):normalized()
        }
        elseif(O:keyAttack2()){
            V=holoEntity(1):pos()
            V-=(V-O:shootPos()):normalized()
        }
        if(V){holoPos(1,V)}
    }

    Mes=O:lastSaid():lower()
    Com=Mes=="/clear"|Mes=="/reg"|Mes=="/restore"|Mes=="/store"
    Com=Com & chatClk(O)

    hideChat(Com)
    if(Com){
        if(Mes=="/reg"){
            V=round(holoEntity(1):pos())
            
            A:pushVector(V)
            S="Vector "+V:toString()+" (#"+A:count()+") has been added"
            
            M=mod(A:count(),2)
            if(!M){
                S+=", new pairing complete."
            }
            else{
                S+="."
            }
            print(S)
        }
        elseif(Mes=="/restore"){
            A=A2:clone()
            
            print("Data restored from archive copy.")
        }
        elseif(Mes=="/clear"){
            A=array()
            
            print("Data erased, restore to re-upload or store to destroy.")
        }
        else{
            fileWrite(F,glonEncode(A))
            
            print("Data has been saved to "+F+".")
            A2=A:clone()
        }
    }

    The pathfinder automatically uploads the data from the developer, based on the name of the map it's on, every time the chip resets (Note: Not when the pathfinder process restarts, refresh the E2 to refresh the shortcut data). Some map aspects are mapped automatically, the developer alerts you to these aspects. 

    Last edited by Lyinginbedmon; 08-27-2010 at 05:58 PM. Reason: Removed obselete code segment and altered reboot method to eliminate reset() 


    The Olympus Technologies drones, totally not SkyNet in Gmod form.
    Cronus: The Ultimate Drone, definitely SkyNet in Gmod form.
    The gBrain Project, the drone controls system that thinks it's better than you