@name On Star
@inputs 
@outputs 
@inputs Path:array
@outputs C Count XYZ:vector Mode StringConcat:string
@persist C Count Marked:array Mode StringConcat:string

#@inputs Search [Origin Destination]:vector 
@outputs VectorList:array [Origin Destination]:vector StartNode 
@outputs EndNode CurNode 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
@model models/props_junk/PopCan01a.mdl
@outputs Nodes:table [N1 N2 N3]:array
@trigger
#Turn by turn instructions by Captain Maim
### To use, type "OS find<" then part of someone's name
#or "OS node" then tell it to find a node by number "OS find<34" for example
Epsilon=90 #how close to the node do you get before it changes nodes
runOnTick(1)
H1="pyramid"
#H1="prism"
function playernodes() {
    StringConcat=""
    P=players()
    for(C=1,P:count()){
        StringConcat=StringConcat+"<"+C
    }
    for(C=1,P:count()){
        StringC1=StringConcat:replace("<"+C,"")
        Nodes[C,array]=array(P[C,entity]:pos()+vec(0,0,10),C,StringC1)
        holoCreate(C+100,Nodes[C,array][1,vector])
    }
    N1=Nodes[1,array]
    N2=Nodes[2,array]
    N3=Nodes[3,array]
}

HideSearchInfo=0
O=owner()
E=entity()

runOnChat(1)
Chat=O:lastSaid()
Cmd="OS"
Mark="mark"
On1="on"
On0="off"
Brk="<"
Player="play"
Node="node"
Ent="ent"
Npc="npc"
Find="find"
Type="type"
Modetext="Mode set to: "
Point="point"
Brk2=","
Self="self"
Use="use"

CMD=Chat:find(Cmd)
MARK=Chat:find(Mark)
BRK=Chat:find(Brk)
ON1=Chat:find(On1)
ON0=Chat:find(On0)
PLAYER=Chat:find(Player)
NODE=Chat:find(Node)
ENT=Chat:find(Ent)
TYPE=Chat:find(Type)
NPC=Chat:find(Npc)
FIND=Chat:find(Find)
POINT=Chat:find(Point)
BRK2=Chat:find(Brk2)
SELF=Chat:find(Self)
USE=Chat:find(Use)

if(chatClk()&CMD){
    Data=Chat:explode(Brk)[2,string]
    if(!FIND){
        if(PLAYER){Mode=0 print(Modetext+" "+Player)}
        if(NODE){Mode=1 print(Modetext+" "+Node)}
        if(ENT){Mode=2 print(Modetext+" "+Ent)}
        if(NPC){Mode=3 print(Modetext+" "+Npc)}
    }
    if(USE){
        VectorList=Path
        C=1
    }
    if(SELF){print("On Star's Entity ID is: "+entity():id())}
    if(POINT){
        if(BRK){
            XYZ=findPlayerByName(Data):pos()
            if(!XYZ){XYZ=entity(Data:toNumber()):pos()}
            if(BRK2){
                Data2=Data:explode(Brk2)
                XYZ=vec(Data2[1,string]:toNumber(),Data2[2,string]:toNumber(),Data2[3,string]:toNumber())
                print(XYZ)
            }
        }

    }
   if(FIND&BRK){
        
        Origin=O:pos()
        Search=1
        if(NODE){
            
        }
        if(!Mode){
            Target=findPlayerByName(Data)
            print("Plotting path to: "+Target:name())
            Destination=Target:pos()
        }
        if(Mode==1){
           Destination=Nodes[Data:toNumber(),array][1,vector]
            print("Plotting a path to node:"+Data)
        }
        if(Mode==2){
           Destination=entity(Data:toNumber()):pos()
           
        }
        
    } 

}

if(first()){
    print("Welcome to On Star!")
    print("Command word is: "+Cmd)
    print(Cmd+" "+Find+Brk+"Bot01 or <Command Word> <command>'"+Brk+"'<name fragment>")
    print(Node+" sets to node number mode")
    print(Player+" sets to player find mode")
    print("more modes to come")

}
Height=70
Ori=O:pos()+O:forward()*100
Hpos=Ori+vec(0,0,Height)
Blue=vec(0,128,255)
Color=Blue
Count=Path:count()
Red=vec(255,0,0)
Hide=0
if(first()){
    holoCreate(1,Ori+vec(-15,0,Height),vec(1,1,1),ang(-90,0,0),Color)
    holoCreate(2,Hpos,vec(2,0.5,0.5),ang(),Color)
    holoModel(1,H1)
    holoAlpha(1,128)
    holoAlpha(2,128)
    holoParent(1,2)
    if(Hide){
        holoVisible(1,players(),0)
        holoVisible(2,players(),0)
        holoVisible(1,O,1)
        holoVisible(2,O,1)
    }
    
}else{
    if(Count&changed(Count)){C=1}
    if(Count){
        XYZ=VectorList[C,vector]
    }
    holoAng(2,(Hpos-XYZ):toAngle())
    holoPos(2,Hpos)
    if((O:pos():distance(XYZ)<=Epsilon)&C!=Count){C++}
    if(C==Count){
        holoColor(1,Red) 
        holoColor(2,Red)
    }else{
        holoColor(1,Blue)
        holoColor(2,Blue)
    }
}


##########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){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
}
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:pushVector(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]
                    Vars["CLO",table][Vars["LN",number],vector2]=vec2(CurNode,Vars["Depth",number]+1) #vec2(Parent,Depth of parent +1)
                }
            }
        }
    }
}
#This is where the paste quits
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
        
    }
    
}

