@name (F.R.E.D.) v6
#Flying Robotic Eradicator Drone 
@inputs 
#Fred's core
@outputs FireDelayed #Wire the turret to this!
@outputs [XYZ Height TXYZ Goal]:vector TD Search Completed P Fire  [NODE RandLink]:array
@outputs Laser:entity T:entity Master Error Self [Origin Destination]:vector 
@outputs [StartNode GoalNode CurNode PrevNode]:number #Search engine
@outputs [VectorList Target]:array LOS  #VectorList is the output from the search engine
#move behavior
@persist P ChEr Counter ChP Scanning DistanceT:array
#Networking 
@persist Master Self T:entity
#Pathfinding persists
#@persist Search #how to begin a search
@persist [Error CurNode GoalNode StartNode Search SearchC]:number #useful values
@persist [VectorList]:array CChanged #output path
@persist [Vars Nodes]:table #Nodes can be used for random navigation, Vars should just be left as is
#@persist [Origin Destination]:vector #Input coordinates  #DO NOT PERSIST!
@trigger 
@model models/props_combine/combine_binocular01.mdl
#@model models/props_junk/PopCan01a.mdl
Erase=0 #set this to 1 to erase the entire FRED6 network, Do this if they need a reset, then set back to 0.

if(duped()|dupefinished()){reset()}
interval(200) #50 is optimal frequency
runOnChat(1)
Chat=owner():lastSaid()
E=entity() #the chip
S=entity() #the thing you want to move
EC=S:isConstrainedTo()
FloatHeight=60 #how high does it hover
Hostile=0 #Looking at dormant drones causes them to attack
PathFreq=2000 #how frequently should a nodegraph search be made?
Proxy=60 #how close to a node till changing nodes
DelNodes=0 #delete nodegraph from global variables if all units die?
Harmless=0 #enable or disable FRED's weapon 
OwnerLoyalty=0 #will it ignore it's owner
FireDelay=50 #how many ms will be between shots
Vagabond=0 #disable targeting so FRED must roam aimlessly
MinDist=20 #anti-wall humping, how close is a wall before FRED issues another search
HideSearchInfo=0 #make FRED stop telling you about how he's found a path
NPCKILL=1 #Kill NPCS instead of players
SpeedAlt=60 #slow FRED down, set to 'Speed' to neutralize this effect 40 is a good slow speed.
AutoUnfreeze=1 #Unfreeze when first spawned?

if(EC){EC:setMass(1)}
Com="FRED"
Get="get"
Brk="<"
Menu="menu"
PK="PK"
NK="NK"

#Hostility Mode
if(Hostile&!T){
    Pl=players()
    for(C=1,Pl:count()){
        AimE=Pl[C,entity]:aimEntity()
        if(!(EC:isFrozen()|S:isFrozen())|(AimE==S)|(AimE==EC)){
            T=Pl[C,entity]
            
        }
    }
}
#End of Hostility Mode

gSetGroup("Network") #give your unit network a unique name.
if(duped()){reset()}
runOnLast(1)
Diag=0 #this prints network reports to chat
NetMax=20 #this is the maximum number of networked units, surplus units are removed automatically.
ID="ID"

MasterInterval=500 #frequency units check for updates on who is the master unit.
HiveMind=1 #1 enables a client/server AKA master/slave mode (used for hiveminds.) 0 limits use to simple network ID generation.
Brk="<" #separation or "break" character, used to create a unique string to help mass search the network table with.

######### "D-Link" Self Organizing Network Code#########
if(!Self){
    EID=entity():id()
    EIDS=toString(EID)
    ScanN=gGetStr(ID) 
    NetT=vonDecodeTable(ScanN) 
    
    for(C=1,NetMax){if(!ScanN:find(Brk+C)){Self=C break}} #locate a hole and take that slot
    if(HiveMind){
        for(C=1,NetMax){if(ScanN:find(Brk+C)){Master=C break}} #locate first unit and mark that unit
        if(!Master){Master=Self}
    }
    if(!Self){
        if(Diag){print("Self Destructing Excess Clones!")} 
        selfDestructAll()
    }
    NetT[Brk+Self,number]=EID
    if(Self==Master&HiveMind){gSetNum("master",Self)}
    gSetStr(ID,vonEncode(NetT))
    if(Diag){print("Self="+Self)} #you can remove this if you want it's diagnostic
}

###########Cleanup and authority passing code for networking#######
if(last()){
    NetT=vonDecodeTable(gGetStr(ID))
    NetT:remove(Brk+Self)
    if(Self==Master&HiveMind){ #passing the torch
        for(C=1,NetMax){
            if(NetT[Brk+C,number]){
                gSetNum("master",C)
                Master=0
                if(Diag){print("Unit "+Self+" is passing leadership to "+C)}
                break
            }
        }
        if(Master){ #if there is no unit to pass the torch to
            gSetNum("master",0) 
            if(DelNodes){gSetStr("Nodes","")}
            if(Diag){print("Unit "+Self+" was unable to pass leadership.")}
        }
    }
    gSetStr(ID,vonEncode(NetT))
    if(Diag){print("Unit "+Self+" Deleted")} #diagnostic non critical
}

if(Erase){gSetStr(ID,"")}
#periodically checks to see if the master has passed the torch
if(HiveMind){
    timer("master",MasterInterval)
    if(clk("master")){Master=gGetNum("master")}
    
    #Add your own targeting code below
    if(findCanQuery()){
        #This collpases the unit spread and organizes their orders.        
        NetT=vonDecodeTable(gGetStr(ID))
        Units=array() 
        Orders=table()
        for(C=1,NetMax){if(NetT[Brk+C,number]){Units:pushNumber(C)}} #collapse the spread of units on the network 
        Orders=invert(Units) #we want the collapsed units indexed by self ID
        
        if(Master==Self){ #actions for the network master
############your find functions here################

           #findInSphere(entity():pos(),8000) #demo only
           #findIncludeClass("prop_physics")
            if(!Vagabond&!NPCKILL){
              Target=players() 
            }
            if(NPCKILL){
              findInSphere(S:pos(),10000)
              findIncludeClass("npc_")
              Dump=findToArray()
              Target=array()
              for(C=1,Dump:count()){
                Target[C,number]=Dump[C,entity]:id()
              }
              #print("bloop!")
            }
            if(OwnerLoyalty){
                for(C=1,Target:count()){
                    if(Target[C,entity]==owner()){
                        Target:remove(C)
                    }
                }
            }
            if(!Target:count()){
                HoldFire=1
            }else{
                HoldFire=0
            }
            #Target=findToArray()
            #Target=array()
            
#############then let this command the hive#########
            
            gSetStr("Target",vonEncode(Target)) #issues orders to slaves
        }else{Target=vonDecode(gGetStr("Target"))} #all slaves take orders
            if(Target[Orders[toString(Self),number],entity]){
                T=Target[Orders[toString(Self),number],entity] #if there are more units than targets the surplus units are idle.
            }else{
                T=entity(Target[Orders[toString(Self),number],number]) #if there are more units than targets the surplus units are idle.
            }
    }
}
#########End of D-Link Network Code#######

####Laser Sight  Prevents needless weapons fire ######
rangerFilter(S)
rangerFilter(S:getConstraints())
Sight=rangerOffset(4000,S:pos(),S:forward())
LaserDist=Sight:distance()
Laser=Sight:entity() #Detects what it's aiming at
if(!Harmless&!HoldFire){
    Fire=(((Laser==T))|((T:aimEntity()==S)|(T:aimEntity()==EC)))&!(EC:isFrozen()|S:isFrozen())&!(S:isPlayerHolding()|EC:isPlayerHolding())
    
}
if(Harmless|HoldFire){
   Fire=0   
}
##############Can I see the target?##########
List=array()
List=S:getConstraints()
List:pushEntity(S)
rangerFilter(List)
LOS=rangerOffset(S:pos(),T:pos()+vec(0,0,40)):hit() #Line Of Sight trace

#LOSRange=rangerOffset(S:pos(),S:pos()+S:forward()*MinDist):distance()
#if(P==VectorList:count()&LOS){timer("Pathfind",PathFreq)}else{TXYZ=T:pos()}
#if(MinDist<LOSRange){timer("Pathfind",100)}


#########################################################################
######Dynamic weapon detection, if FRED can emit bullets, he will. ######
#ifdef entity:shootTo(vector,number,number,number)    
    if(Fire&clk("Fire")){E:shootTo(E:forward(),0,100,1)}
#endif


#####################################################################
#########Behavioral Code How it responds to Pathing Error codes######
#####################################################################


########Setup things for path following##############
if((clk("Pathfind")&Nodes:count())&LOS){
    timer("Path",50)
    Search=1 #begin another path search
    VectorList=array()
    Vars=table()
    P=1 #set path to index 1
}


##Set Origin and Destination variables for path finding
Origin=S:pos()
if(!T:pos()|!T:isAlive()){T=noentity()}
if(T){
    #print("I found someone!")
    Destination=round(T:pos())
}#else{
#    Destination=round(S:pos())
#}

##############reset things for next path search###########
if(!P){P=1} #Make sure the Point count is not 0
if((Error==4)&T){Search=1 timer("Path",2000)}
if(Completed&Search|clk("Path")){
    Search=0 
}



##### Wander Code ########
if(!T|(Error==1)|(Error==3)){ #Wander, if target is off nodegraph, wander the graph
    ChEr=changed(Error) #change in error
    timer("int",100) #interval
    if(T){timer("research",10000) D=1}
    if(clk("research")|Scanning){
        #print("scanning for player")
        DistanceT=array()
        Scanning=1
        
        while(D<=Nodes:count()&perf()){
            DistanceT:pushNumber(T:pos():distance(Nodes[D,array][1,vector]))
            D++
        }
        rangerIgnoreWorld(0)
        rangerHitEntities(0)
        rangerHitWater(0)
        LOSTest=rangerOffset(T:pos(),Nodes[DistanceT:minIndex(),array][1,vector]):hit()
        if(!LOSTest){
            Search=1
            Scanning=0
        }
    }
    if((ChEr&((Error==1)|(Error==3)))|(!T&(Error==4)&clk("int"))){
        P=1
        timer("Path",2000)
        Search=1
        Destination=Nodes[randint(1,Nodes:count()),array][1,vector]
    }
    
    if(S:pos():distance(VectorList[1,vector])<Proxy){
        if(!HideSearchInfo){ 
            #print("wandering aimlessly")
        }
        NODE=Nodes[CurNode,array] #take current Node, start node
        DPrevNode=PrevNode #Double Previous Node
        PrevNode=CurNode #Previous Node
        CurNode=NODE[randint(3,NODE:count()),number]
        if(DPrevNode==CurNode){ #prevents back tracking
            CurNode=NODE[randint(3,NODE:count()),number]
        }
        RandLink=Nodes[CurNode,array] #pick a random linked node
        VectorList=array(RandLink[1,vector]) #take that random nodes vector and plug it into the array    
    }

}
##############END WANDER CODE############

###############################################
#########if path is found to target, go get it!
if(!Error&VectorList:count()&VectorList[P,vector]){
    
    if(S:pos():distance(VectorList[P,vector])<Proxy){
        if(!HideSearchInfo){print("P= "+P+" "+round(VectorList[P,vector]))}
        #if(P<VectorList:count()){
            
        if(P==1){printTable(VectorList)}
        P++ 
        #}
        #stoptimer("nodetimeout")
        #timer("nodetimeout",10000)
    }
    ######Can I see the next node?#######
    #rangerFilter(S)
    #rangerFilter(S:getConstraints():pushEntity(S))
    #LOSN=rangerOffset(S:pos(),VectorList[P,vector]):hit()
    #if(LOSN){P--}
    TXYZ=VectorList[P,vector]
    holoCreate(3,TXYZ,vec(0.5,0.5,2)*2)
    holoColor(3,vec(255,0,0))
    holoCreate(2,VectorList[P,vector],vec(1,1,1)*2)
    holoColor(2,vec(0,0,255))
}



if(clk("nodetimeout")){
    P=1
    Search=1
    timer("Path",50)    
}




####################Begin search at the end of search##############
#if(Completed){timer("Path",2000)}

###################################################################
#If I get to the end of my path and you're not there, search again
###################################################################
if((P>VectorList:count()&LOS)|(Chat=="find me"&chatClk())){ 
        P=1
        Search=1
        Vars=table()
        timer("Path",1000)

    
}
#if(!LOS){TXYZ=T:pos()}  #renable this later! This is still good!

#CComplete=changed(Completed)
#[if(CComplete&Completed){
    print("Completed Path search")
    #holoDeleteAll()
    for(C=1,VectorList:count()){
        holoCreate(C+20,VectorList[C,vector])
        holoColor(C+20,vec(255,255,0))   
    }
    
}]#

#if(!TXYZ){
    #TXYZ=VectorList[P,vector]
    #if(!TXYZ){TXYZ=VectorList[P-1,vector]}
    #if(!TXYZ&T){TXYZ=T:pos()}
    #TPos=T:pos()
#} #make sure it doesn't run to the origin

######################################################
###############End behavior code######################
######################################################

#ifdef entity:propFreeze(number) 
if(first()&AutoUnfreeze){
    Unfreeze=entity():getConstraints()
    entity():propFreeze(0)
    for(C=1,Unfreeze:count()){
        Unfreeze[C,entity]:propFreeze(0)
    }
}
#endif


#############Movement Code####################
##########Hover ranger Makes FRED hover#######
rangerFilter(S)
Ground=rangerOffset(20000,S:pos(),vec(0,0,-1)):position():z()
TD=(T&(T:pos():distance(S:pos())>200))|(!T&VectorList[1,vector]) #Target Disable
#if((LaserDist<20)&!VectorList:count()&(Error==6)){TD=0} #do this when too close to an obsticle

#set height to 
Height=vec(S:pos():x()*!TD,S:pos():y()*!TD,!TD*Ground+FloatHeight)


XYZ=Height+TXYZ*TD   #sets the essential movement code, hover if no destination move to TXYZ if destination

#rangerFilter(S)
#rangerHitEntities(0)
#XYZValid=rangerOffset(1000,S:pos(),S:forward()):hit()

#if(!XYZValid){
   # print("I can see it!")
    #P--
#}
#[
holoCreate(1,XYZ)
holoColor(1,vec(255,0,0))
holoCreate(2,TXYZ,vec(0,0,255))
]#

############ The Quaternian Turret Code, this makes it aim #######
if(T){ #where does FRED aim?
    Dir = S:pos()-(T:shootPos()+T:vel()/(10))
}else{
    Dir=S:pos()-vec(TXYZ:x(),TXYZ:y(),S:pos():z())
}
Pos = S:toWorld(vec(0,0,0))

AltAng = S:angles()
Q = quat(Dir:toAngle())*qRotation(vec(0,0,1),180)
S:applyTorque((150*S:toLocal(rotationVector(Q/quat(S))+S:pos()) - 12*S:angVelVector())*S:inertia())


######## Movement Code, this controls its speed and fly to an XYZ #########
if(S:pos():distance(XYZ)>200){
    Speed=6*(S:mass()/1)
    
}else{
    Speed=4*(S:mass()/2)
}
Speed=SpeedAlt #remove this is for testing only
#Speed=2000
holoCreate(1,XYZ)

S:applyForce(Speed*(XYZ-S:pos())-(S:vel()/2)*S:mass())
#####################End Movement Code################



##########Breadth First Pathfinding AI############
###
#variables used by the search engine: 
#@inputs Search [Origin Destination]:vector
#@outputs VectorList:array Completed Error #the final path, some useful numbers
#@persist [CurNode SearchC GoalNode StartNode]:number #some useful numbers
#@persist [Vars Nodes]:table #Vars includes all internal varaibles, Nodes is the nodegraph itself

#Usage: to start a search, set search=1 set Origin and Destination and set Completed=0
#For repeated searches reset Search=0 and Completed=0 If no origin or Destination are given a 
#default destination and origin are presumed. The C variable is used to count.
HideSearchInfo=0 #silent mode?
####
#errors
#Error=0 It worked! 
#Error=1 No end node found
#Error=2 No start node found
#Error=3 Start and End nodes are on separate nodegraph fragments
#Error=4 No search made
#Error=5 No start or end node
#Error=6 Search in progress
if(first()|duped()){

    Vars=table()
    Error=4
    Vars=table() #stores all our array variables
    Vars["SEARCHING",number]=0
    Vars["Filename",string]="nodegraphs/Nodegraph_6_"+map()+".txt"
    Vars["Offset",vector]=vec(0,0,10) #How high off the ground should a node be placed
    runOnFile(1)
    if(gGetStr("Nodes")){
        Nodes=vonDecodeTable(gGetStr("Nodes"))

    }else{
    runOnFile(1)
    fileLoad(Vars["Filename",string])
    print("loading "+Vars["Filename",string])
    }
}
if(fileLoaded()&!Nodes:count()&fileName()==Vars["Filename",string]){
    Read=fileRead()
    gSetStr("Nodes",Read)
    Nodes=vonDecodeTable(Read)
    
}

SearchC=changed(Search)
if(Nodes:count()&Search&(SearchC|~Search)){
    Vars["SEARCHING",number]=1 #trigger Search
    Vars["Begin",number]=1 #this only runs once when searching to prevent the goal from moving.
    Vars["Open",array]=array() #Numerical Node ID list of nodes to visit
    Vars["Closed",table]=table() #numerical table of node's visited
    Vars["Start",array]=array() #one use to find the starting podsition's nearest node
    Vars["StartK",array]=array() #saves node IDs for start array
    Vars["End",array]=array()   #one use to find the ending position's nearest node
    Vars["EndK",array]=array() #preserves the node IDs for end array
    Vars["NodeList",array]=array() #final path by nodes
    VectorList=array() #final path by vectors 
    timer("start",1)
    Vars["CurNode",number]=0
    Vars["Completed",number]=0
    Vars["Ready",number]=0
    Vars["Next",number]=0
    Vars["N",number]=1
    Vars["S",number]=1
    if(Origin:isInWorld()&Destination:isInWorld()){
        if(!Origin){
            Vars["Self",vector]=round(owner():pos())+Vars["Offset",vector]
        }else{
            Vars["Self",vector]=round(Origin+Vars["Offset",vector])
        }
        if(!Destination){
            #Vars["Goal",vector]=round(owner():aimPos())+Vars["Offset",vector]
        }else{
            Vars["Goal",vector]=round(Destination+Vars["Offset",vector])
        }
        if(!HideSearchInfo){print("Starting search")}
        Error=6
    }else{Error=5}
}
if(Vars["SEARCHING",number]&(clk("next")|clk("start")|clk("continue"))){
    ######Let's find the start and end nodes that
    ######have Line of Sight to our target objects
    if(Vars["Begin",number]){
        #perpetuates the while statement if it's running "hot"
        if(Vars["N",number]<=Nodes:count()){
            timer("start",20)
        }else{
            Vars["Begin",number]=0 
            Vars["Next",number]=1
        }
        #Finds the nearest nodes to the start and your goal vectors
        
        while(Vars["N",number]<=Nodes:count()&perf()){
                Vars["V",vector]=Nodes[Vars["N",number],array][1,vector]
                if(Vars["V",vector]){
                    Vars["Start",array]:pushNumber(round(Vars["V",vector]:distance(Vars["Self",vector])))
                    Vars["StartK",array]:pushNumber(Vars["N",number])
                    Vars["End",array]:pushNumber(round(Vars["V",vector]:distance(Vars["Goal",vector])))
                }
                Vars["N",number]=Vars["N",number]+1
        }  
        
    }
    if(Vars["Next",number]){
        #Time to do ranger tests! 
        Vars["EndK",array]=Vars["StartK",array]:clone()
        rangerIgnoreWorld(0)
        rangerHitEntities(0)
        rangerHitWater(0)

        ##Test all nodes to see if we have LOS, first node with LOS wins
        if(Vars["S",number]<=Vars["Start",array]:count()&!perf()){timer("start",20)}

        while(Vars["S",number]<=Vars["Start",array]:count()&perf()){
            Vars["Min",number]=Vars["StartK",array][Vars["Start",array]:minIndex(),number]
            Vars["Trace",number]=rangerOffset(Vars["Self",vector],Nodes[Vars["Min",number],array][1,vector]):hit()
            if(!Vars["Trace",number]){
                CurNode=Vars["Min",number]
                break
            }else{
                Vars["StartK",array]:remove(Vars["Start",array]:minIndex())
                Vars["Start",array]:remove(Vars["Start",array]:minIndex())
            }
        Vars["S",number]=Vars["S",number]+1
        }
        
        if(Vars["E",number]<=Vars["End",array]:count()&!perf()){timer("start",20)}
       while(Vars["E",number]<Vars["End",array]:count()&perf()){
            Vars["Min",number]=Vars["EndK",array][Vars["End",array]:minIndex(),number]
           
            if(!rangerOffset(Vars["Goal",vector],Nodes[Vars["Min",number],array][1,vector]):hit()){
                GoalNode=Vars["Min",number]
                    break
            }else{
                Vars["EndK",array]:remove(Vars["End",array]:minIndex())
                Vars["End",array]:remove(Vars["End",array]:minIndex())
            }
       Vars["E",number]=Vars["E",number]+1
        }#Now that we've done the least ranger tests needed we move on. 
        if(!CurNode){
            Error=2
        }
        if(!GoalNode){
            Error=1
        }
        if(!CurNode&!GoalNode){Error=5} #report that both nodes were not found
        if(CurNode){
            StartNode=CurNode #This is used when reconstructing the path
            Vars["Open",array]:pushNumber(CurNode)
            Vars["Depth",number]=1 #This sets the search's starting depth
            CLO=Vars["Closed",table]
            CLO[CurNode,vector2]=vec2(CurNode,Vars["Depth",number])
            Vars["NodeList",array]:pushNumber(CurNode)
        }
        if(CurNode&GoalNode){
            Vars["Ready",number]=1
            Vars["Next",number]=0
        }else{Vars["Ready",number]=0 Vars["SEARCHING",number]=0}
    }#End Next
    Vars["BFS",number]=Vars["Open",array]:count()&Vars["Ready",number]&!Vars["Next",number]
    if(Vars["BFS",number]){ ##BFS=Bredth First Search
        timer("continue",50)
    }
    
    while(Vars["BFS",number]&perf()){###Time to do the actual path search
        CurNode=Vars["Open",array]:shiftNumber() #moves the first item off the list
        if(!CurNode){print("Error preforming search, open list depleted early.") Error=3 break}
        Vars["Depth",number]=Vars["Closed",table][CurNode,vector2]:y() #Current Node's depth
        if(CurNode==GoalNode){
            if(!HideSearchInfo){print("search complete")}
            Completed=1
            Vars["SEARCHING",number]=0
            Vars["Ready",number]=0
            timer("Parse",20)
            Vars["NodeList",array]=array()
            VectorList=array()
            Vars["NodeList",array]:unshiftNumber(GoalNode)
            VectorList:unshiftVector(Nodes[GoalNode,array][1,vector]) #don't forget the endnode
            VectorList:pushVector(Vars["Goal",vector]) #push the target's last known location into the list
            break
        }else{
            Vars["Links",array]=array()
            Vars["Links",array]=Nodes[CurNode,array]
            for(C=3,Vars["Links",array]:count()){
                Vars["LN",number]=Vars["Links",array][C,number]
                if(!Vars["Closed",table][Vars["LN",number],vector2]){ #keeps us from duplicating node info
                    Vars["Open",array]:pushNumber(Vars["LN",number]) #pushes a nodeID to be scanned
                    Vars["CLO",table]=Vars["Closed",table]
#####This is where paste ends#########       Vars["CLO",table][Vars["LN",number],vector2]=vec2(CurNode,Vars["Depth",number
                    Vars["CLO",table][Vars["LN",number],vector2]=vec2(CurNode,Vars["Depth",number]+1) #vec2(Parent,Depth of parent +1)
                }
            }
        }
    }
}
if(clk("Parse")){ 

    if(CurNode!=StartNode){timer("Parse",1)} #enable this when you got it working right!
    while(CurNode!=StartNode&perf()){
        if(CurNode){
            CurNode=Vars["Closed",table][CurNode,vector2]:x() #set the next node to the parent of the current

            Vars["NodeList",array]:unshiftNumber(CurNode)

            VectorList:unshiftVector(Nodes[CurNode,array][1,vector])  
        }
    }
    if(CurNode==StartNode){
        stoptimer("Parse")
        if(!HideSearchInfo){print("SUCCESS! PATH FOUND!")}
        NodeList=Vars["NodeList",array]
        Error=0
        print("Node List:")
        printTable(NodeList)
    }
    
}
