@name Nodegraph Editor
@inputs Path:array Completed Search Searcher:wirelink
@outputs Nodes:table Filename:string HE Holo:array C RECORDING LINKING EDITING SCANNING CurrentLink Link:array
@outputs SearchIt SNode:array #Proxy:array # [Close Opened]:vector 
@persist C B D F P HE HEP Loading Hologrammed Resolution # Time 
@persist RECORDING LINKING EDITING Hold SCANNING Validate
@persist [Cyan White Green Red Blue Yellow Orange Scale Offset Size]:vector
@persist [Co Brk Scan Filename]:string 
@persist [OldPath OPA LinkStr LinkVec Proxy]:array
@persist Nodes:table
@trigger
#nodegraph editor by Captain Maim (STEAM_0_1_8017844)
#Simple usage,  type "NG load" to load the map if it hasn't already loaded on spawn. 
#"NG edit on" to edit nodegraph. 
#"NG save" to save progress
#"NG help" for additional assistance.
#in console type:
#wire_holograms_max 600
#wire_holograms_burst_amount 600
PathHighlight=0 #highlight a found path in orange? Disable for if 

ModelName="hq_rcube_thin"

runOnChat(1)
runOnFile(1)
O=owner() #Who does this chip respond to?
timer("searchit",500)
if(clk("searchit")){
    SearchIt=O:keyUse()&O:weapon():type():find("weapon_crowbar")
}
if(Searcher:entity()){
    Completed=Searcher["Completed",number]
    Search=Searcher["SEARCHING",number]
    Path=Searcher["NodeList",array]
    Closed=Searcher["Closed",table]
    Open=Searcher["Open",table]
    Searcher["Nodes",table]=Nodes
    Searcher["Search",number]=SearchIt
    
}
#Function messages
RecMsg="Recording Path: "
EditMsg="Manual Node Editing Mode: "
LinkMsg="Automatically Linking Nodes"


#Chat key word list
Com="NG" #Node graph
Record="record"
Links="link"
Edit="edit"
Load="load"
Save="save"
On1="on"
On0="off"
Help="help"
Valid="validate"
Type="type"
Brk="<"

#Chat key word check list
Chat=O:lastSaid()
COM=Chat:find(Com)
RECORD=Chat:find(Record)
LINKS=Chat:find(Links)
EDIT=Chat:find(Edit)
LOAD=Chat:find(Load)
SAVE=Chat:find(Save)
ON1=Chat:find(On1)
ON0=Chat:find(On0)
HELP=Chat:find(Help)
VALID=Chat:find(Valid)
TYPE=Chat:find(Type)
BRK=Chat:find(Brk)

#Chat Commannd Tree, this triggers various functions via chat
if(chatClk()&COM){
    if(RECORD){
        if(ON1){
            RECORDING=1 
            print(RecMsg+On1)
            SCANNING=0
        }
        elseif(ON0){
            RECORDING=0 print(RecMsg+On0)
            SCANNING=1
        }
    }
    if(LINKS){
        LINKING=1
        print(LinkMsg)
    }
    if(EDIT){
        print("type help with this command to get help.")
        if(ON1){EDITING=1 print(EditMsg+On1)}
        elseif(ON0){EDITING=0 print(EditMsg+On0)}
        if(HELP){
            print("Manual Node Editing Mode: All actions have a 1 second timer to prevent accidents.")
            print("Hold Physgun to edit links:")
            print("To edit links take physgun, aim at first node, hold use and right click. Then aim at second node and hold use and left click. This toggles symeterical linking.")
            print("To toggle asymeterical linking crouch while preforming the second action stated above.")
            print("Hold Gravity gun to edit nodes")
            print("To move nodes: look at a node hologram so it's blue, holding use, press right click to pickup node, use+left click to drop where you look.")
            print("To add an unlinked node, look where you want the node to be and hold use + left and right click.")
        }
    }
    if(LOAD){
        LOADING=1
    }
    if(SAVE){
        SAVING=1
    }
    if(VALID){
        Validate=1
        F=0
    }
    if(TYPE){
        if(BRK){
            Kind=Chat:explode(Brk)[2,string]
            print("need to finalize the node types")
        }else{
            print("0-No modifier")
            print("1-walk   (human sized things only)")
            print("2-crouch (human or small things only)")
            print("3-jump   (human or flying things only)")
            print("4-button  (linked to something, node follows entity ID, if no entity is connected it's a press to continue)")
            print("5-elevator (node follows entity, linked to a button or used directly)")
            print("6-hide    (a place to hide)")
            print("7-snipe   (a place to snipe form)")
            print("8-door    (linked to a button or opened directly)")
            print("9-narrow  (nothing wider than a person should travel this way.)")
            print("10-ladder (human or flying things)")
            print("11-breakable (for secret walls and things.)")
            print("12-jump+crouch")
            print("13-Car track (where can cars go)")
        }
    }
    
}


if(first()){
    Filename="nodegraphs/Nodegraph_5_"+map()+".txt"
    Offset=vec(0,0,10) #How high off the ground will nodes be placed
    Resolution=800 #Used for creating and processing nodegraphs
    Nodes=table() #The master nodegraph variable
    Scan="Scan" #Timer word
    Size=vec(1,1,1)*0.5  #Hologram size
    Scale=vec(1,1,1)*0.5 #I think this is the same as Size
    
    timer(Scan,20)
    SCANNING=1
    
    #hologram colors
    Brightness=255
    Cyan=vec(0,1,1)*Brightness
    White=vec(1,1,1)*Brightness
    Green=vec(0,1,0)*Brightness
    Red=vec(1,0,0)*Brightness
    Blue=vec(0,0,1)*Brightness
    Orange=vec(1,0.5,0)*Brightness
    Yellow=vec(1,1,0)*Brightness
    
    P=1
    
    #string formating for links

}


#Save the nodegraph
if(SAVING){
    print("Saving "+Nodes:count()+" nodes") 
    fileWrite(Filename,glonEncode(Nodes))
}

if(fileCanLoad()&LOADING){
    fileLoad(Filename)
    Nodes=table()    
    #Time=curtime()
    #NodeKeys=array()
    SCANNING=0
    stoptimer(Scan)
    print("Loading: "+Filename)
    Loading=1  
    LOADING=0 
}



if(fileLoading()&Loading){
    print("Node Graph File is loading...")
    Loading=0
    Hologrammed=0
}

if(clk("load")|fileLoaded()&fileName()==Filename&!Hologrammed){   
    
    #if(!Nodes){Nodes=glonDecodeTable(fileRead(Filename))}
    if(!Nodes){
        #Loadedin=curtime()-Time
        #print("Node Graph loaded in: "+Loadedin+" seconds.")
        #Read=fileRead()
        Nodes=glonDecodeTable(fileRead())
        #print("ops "+opcounter())
           
    }
    #if(isSinglePlayer()){
        Count=Nodes:count()
        if(B==1){print("reading "+Count+" nodes.")}
        Color=Cyan    
        if(B<=Count){timer("load",20)}
        else{
            Hologrammed=1
            SCANNING=1
            Loading=0
            timer(Scan,1000)
        }
        while(B<=Count&perf()){
           if(Nodes[B,array][1,vector]){
                
                print("Loading Node: "+B+" which is "+B+"/"+Count)
                holoCreate(B,Nodes[B,array][1,vector],Size,ang(),Color)
                holoModel(B,ModelName,1)
            }    
            B++ 
        }   
    #}

}

Proximaty=0
if(Proximaty&Nodes:count()){
    if(P<=Nodes:count()){timer("proxy",20)}
    while(P<=Nodes:count()&perf()){
        Proxy[P,number]=O:pos():distance(Nodes[P,array][1,vector])
        
        P++
    }
    if(P>Nodes:count()){
        holoDeleteAll()
        Proxy1=Proxy:clone()
        timer("update",5000)
        for(C=1,5){
            
            Min=Proxy1:minIndex()
            Proxy1:remove(Min)
            #print("Spawning hologram "+Min)
            holoCreate(Min,Nodes[Min,array][1,vector],Size,ang(),Cyan)
            holoModel(Min,ModelName,1)
        }
    }
}
if(clk("update")){P=1}


#this is used to tell the below function what you LOOKED at last
if(clk("wait")){
    HEP=HE #HE= Holo Entity  HEP= Holo Entity Previous
}

if(clk(Scan)&SCANNING){
    timer("wait",findCanQuery()*3)
    timer(Scan,findCanQuery())
    findInCone(O:shootPos(),O:eye(),500,5)
    findIncludeClass("gmod_wire_hologram")
    Holo=findToArray()
    HC=Holo:count()
    HE=holoIndex(Holo[1,entity])
    if(HC){
        HES=toString(HE)
        holoColor(HE,Blue)
        holoScale(HE,Scale*2)
        NS=HE
        #Link=Nodes["S"+NS,string]:replace(NS+Co,""):explode(Brk)
        Link=Nodes[NS,array]
        #printTable(Link)
        if(Link[2,number]!=NS){Link:insertNumber(2,NS) print("Correcting Node: "+NS)}
        for(C=3,Link:count()){
            #print("Node: "+Link[C,string])
            holoColor(Link[C,number],Red)
            holoScale(Link[C,number],Scale*2)
        }
    }elseif(HEP){
        NS=HEP
        Link=Nodes[NS,array]
        holoColor(HEP,White)
        holoScale(HEP,Scale)
        for(C=3,Link:count()){
            holoColor(Link[C,number],White)
            holoScale(Link[C,number],Scale)
        }
    }
}
if(PathHighlight){
    
    #this highlights the path found
    if(Path:count()&Completed&!Search){
        OldPath=Path:clone()
        for(C=1,Path:count()){
            holoColor(Path[C,number],Orange)
            holoScale(Path[C,number],Scale*2)
        }
    }
    #
    #if(Path:count()&Search&!Completed){
    #    for(C=1,Path:count()){
    #        holoColor(Path[C,number],Red)
    #        holoColor(Path[C,number],Scale*2)
    #    }
    #}
    #this unhighlights the path when a new search is started
    if(OldPath:count()&Search&changed(Search)){
        for(C=1,Path:count()){
            holoColor(Path[C,number],White)
            holoScale(Path[C,number],Scale)
        }    
    }
}


#automatic node linking function
if(LINKING){
    
    if(chatClk()){
        print("Linking In Progress")
#[        LinkVec=array()
        
        B=1 #B is my first order counting variable 
        D=1 #D is my second order counting variable
        timer("link",20)
        for(C=1,Nodes:count()){
            LinkVec:pushVector(Nodes[C,array][1,vector]
            
        }

        foreach(K,V:vector=Nodes){
            LinkVec:pushVector(V)
            LinkStr:pushString("S"+K)
        }
]#
    }
}
if(clk("link")){
        
    NCo=LinkVec:count() #Node Count
    if(B<=NCo){timer("link",75)}
    #print("Processing Node: "+B+"/"+NCo)
    while(B<=NCo&perf()){ 
                V=LinkVec[B,vector]
                V1=LinkVec[D,vector] 
            if((D<=NCo)&(B!=D)){
                rangerHitEntities(1)
                #rangerFilter(HitThis) #array of valid targets
                if((round(V:distance(V1))<=Resolution)){ #checks for nodes within X of self node
                    Ranger=rangerOffset(V,V1):hit() #tests LOS between nodes, 0=clear, 1=obstructed
                    #Linkstr=Brk+K+Co+K1 #link string denotes self and linked nodes
                    Bk="<"
                    Here=Nodes[B,array]:concat(Bk)+Bk
                    Here1=Bk+D+Bk
                    There=Nodes[D,array]:concat(Bk)+Bk
                    There1=Bk+B+Bk

                    if(!Ranger){
                        if(!Here:find(Here1)&!There:find(There1)){ #checks clear LOS and no redundancy
                            Here2=Nodes[B,array]
                            Here2:pushNumber(D)
                            There2=Nodes[D,array]
                            There2:pushNumber(B)
                            Nodes[B,array]=Here2
                            Nodes[D,array]=There2
                        }
                    }
                    
                }
            }
            D++
        if(D>NCo){B++ D=1 CurrentLink=B}
    }
    if(B>NCo){print("Nodes Linked!") LINKING=0}
}


NC=Nodes:count() #Node Count

OP=round(O:pos()+Offset)  #Owner Position


#Owner Position Array, logs the last X locations the player was at over the previous executions
if(RECORDING){
    if(OPA:count()>=20){OPA:shiftVector()}
    OPA:pushVector(OP) #owner position over time
    timer("record",1)
}



#Auto node generation, no links
if(((clk("record")|clk("delay"))&RECORD)){
    
    #Jump Or Ladder detection
    JOL=!O:isOnGround()&!O:inNoclip()
    #On Ground
    OG=O:isOnGround()
    timer("record",50)

    
    #if(!Ladder){timer("ladder",20) Ladder=1}    
    
    #OL=changed(OnLadder) #On Ladder
    #if(OL){print("Accel="+$ZVel)}    
    
    I1=Resolution
    I2=I1-100
    findInSphere(O:pos(),I1+5)
    findSortByDistance(O:pos())
    findIncludeClass("hologram")
    Holo=findToArray()
    
    Color=Cyan
    HP=holoIndex(Holo[1,entity]) #Holographic Proximatey number
    timer("delay",100)    
    if(NC){
        NC1=NC+1
        Dist=Nodes[HP,array][1,vector]:distance(OP)
        rangerHitEntities(1)
        rangerIgnoreWorld(0)
        rangerFilter(O)
        OLS=rangerOffset(OP,Nodes[HP,array][1,vector]):hit() #Owner Line of Sight
        
    }

    if(!NC){
        Nodes[1,array]=array(OP,1)
        
        holoCreate(1,OP,Size,ang(),Color)
        holoModel(1,ModelName,1)
        
    }elseif(((Dist>I2)&(Dist<I1))|OLS){
        if(OLS){
            D=0
            
            for(C=1,OPA:count(),1){
                
                OLS=rangerOffset(OPA[C,vector],Nodes[HP,array][1,vector]):hit()
                if(!OLS){D++}

                if(D==10*!OLS){OP=OPA[C,vector] break print("C: "+C+" "+OLS)}
            }
        }
        
        
        NCS=NC1#Node Count String
        #this records the node and displays a hologram
        
        if((OG&HP&(Nodes[NC,array][1,vector]:distance(OP)>I2)|!OLS)&clk("delay")){
            print("Waypoint Recorded")
            Nodes[NC1,array]=array(OP,NC1)
            holoCreate(NC1,OP,Size,ang(),Color)
            holoModel(NC1,ModelName,1)
        }
    }
}


#Enable Node Editing
if(EDITING){
    Editing=1 
    timer("edit",1)
    if(chatClk()){print("edit mode")}
}else{Editing=0}


#move nodes, delete nodes, link nodes, manual placement, etc... 
if(Editing&clk("edit")){
timer("edit",1000)
    Wep=O:weapon():type()
    Crouch=O:isCrouch()
    Noclip=O:inNoclip()
    A1=O:keyAttack1()
    A2=O:keyAttack2()
    Use=O:keyUse()
    AimEnt=O:aimEntity()
    AimPos=round(O:aimPos()+Offset)
    Cannon=Wep:find("physcannon")
    Phys=Wep:find("physgun")
    
    #link/unlink just these 2 nodes, creates or removes a symeterical link
    if(Phys&Use&HE&!Crouch){
        if(A2&!A1){
            Hold=HE
            print("Selecting Node: "+HE)
        }
        if(A1&!A2){
            Bk="<"
            S2O=Bk+HE+Bk #Self node To Other node
            O2S=Bk+Hold+Bk #Other node to Self node
            SNode=Nodes[Hold,array] #Self Node
            ONode=Nodes[HE,array] #Other Node
                            
            Check=(SNode:concat(Bk,2)+Bk):find(S2O)
            
            
            
            
            if(Check){
                for(C=3,SNode:count()){
                    if(SNode[C,number]==HE){SNode:remove(C)}
                }
                for(C=3,ONode:count()){
                    if(ONode[C,number]==Hold){ONode:remove(C)}
                }
                
                
                print("Nodes: "+Hold+" and "+HE+" have been unlinked.")
                print(Nodes[Hold,array]:concat(Bk,2)+"\n"+Nodes[HE,array]:concat(Bk,2))
            }else{
                SNode:pushNumber(HE) 
                ONode:pushNumber(Hold)
                print("Nodes: "+Hold+" and "+HE+" have been linked.")
                print(Nodes[Hold,array]:concat(Bk,2)+"\n"+Nodes[HE,array]:concat(Bk,2))
            }
            
        }
    }
    #create half links, asymetrically linked nodes
    if(Phys&HE&Crouch){
        if(A1){
            Bk="<"
            print("Preparing 1 way link with node: "+Hold)
            S2O=Bk+HE+Bk #Self node To Other node
            O2S=Bk+Hold+Bk #Other node to Self node
            SNode=Nodes[Hold,array] #Self Node
            ONode=Nodes[HE,array] #Other Node
            Check=(SNode:concat(Bk,2)+Bk):find(S2O)
            
            if(!Check){
                SNode:pushNumber(HE)
            }
            Check=(ONode:concat(Bk,2)+Bk):find(O2S)
            if(Check){
                for(C=3,ONode:count()){
                    if(ONode[C,number]==Hold){ONode:remove(C)}
                }
            }
            print("Node "+Hold+" is half linked to "+HE)
            print(Nodes[Hold,array]:concat(Bk,2)+"\n"+Nodes[HE,array]:concat(Bk,2))
        }
    }
    
    #Pick up and move nodes
    if(Cannon&Use){
        #Pickup node
        if(A2&!A1&HE){Hold=HE print("Node: "+HE+" picked up")}
        #Place node
        if(A1&!A2){
            print("Placing Node: "+Hold)
            SNode=Nodes[Hold,array]
            SNode[1,vector]=AimPos
            holoPos(Hold,AimPos)
            #findInSphere(AimPos,Resolution)
            #findIncludeClass("hologram")
            #Hologram=findToArray()
            #if(!Hologram:count()){print("You've placed an orphan node!")}
        }
        if(A1&A2){
            NC1=NC+1
            Nodes[NC1,array]=array(AimPos,NC1)
            
            
            print("Creating Node: "+NC1)
            holoCreate(NC1,AimPos,Size,ang(),Yellow)
            holoModel(NC1,ModelName,1)
            #findInSphere(AimPos,Resolution)
            #findIncludeClass("hologram")
            #Hologram=findToArray()
            #if(!Hologram:count()){print("You've placed an orphan node!")}
        }
    }
}


#if(HE){
#    Close=Closed[toString(HE),vector]
#    Opened=Open[toString(HE),vector]
#}

