@name Music Board v2
@persist [ TRUE FALSE ]
@persist [ E ]:entity 
@persist [ BarStart BarEnd BarCount ] [ Board BoardLength Reader Tempo Power Loop ]
@persist [ Time ]

if (first()) {
    
    E = entity()
    
    TRUE = 1        #// Final variable representing the boolean value, True (Do not change).
    FALSE = 0       #// Final variable representing the boolean value, False (Do not change).
    
    Board = 0       #// The hologram index of the Music Board (Do not change).
    Reader = 1      #// The hologram index of the Reader Bar (Do not change).
    
    Time = 1        #// The variable representing time (increasing positively), defaulted to 1 (Do not change).
    Tempo = 250     #// The variable representing the tempo delay (in milliseconds).
    
    Power = FALSE   #// The variable representing the Music Board's on/off state, defaulted to off.
    Loop = FALSE    #// The variable representing the Music Board's loop state, defaulted to off.
    
    #**
    #* Toggles the Power on or off depending on Bool (boolean value, 1 - on, 0 - off).
    #**
    function void togglePower(Bool:number) {
        Power = Bool
        Time = 1
    }
    #**
    #* Checks whether the Music Board is powered on or off and returns a boolean value (1 - on, 0 - off).
    #**
    function number isOn() {
        return Power    
    }
    #**
    #* Toggles the Music Board to Loop or not based on a boolean value (1 - true, 0 - false).
    #**
    function void toggleLoop(Bool:number) {
        Loop = Bool    
    }
    #**
    #* Checks whether the Music Board is set to loop again and retuns a boolean value (1 - true, 0 - false).
    #**
    function number isLooped() {
        return Loop    
    }
    #**
    #* Sets the length of the Music Board in Garry's Mod units.
    #**
    function void setBoardLength(Length:number) {
        BoardLength = Length   
    }    
    #**
    #* Gets the length of the Music Board in Garry's Mod units.
    #**
    function number getBoardLength() {
        return BoardLength    
    }   
    #**
    #* Sets the first bar hologram's index position.
    #** 
    function void setBarStart(Index:number) {
        BarStart = Index    
    }    
    #**
    #* Gets the first bar hologram's index position.
    #**
    function number getBarStart() {
        return BarStart    
    }   
    #**
    #* Sets the last bar hologram's index position.
    #** 
    function void setBarEnd(Index:number) {
        BarEnd = Index   
    }
    #**
    #* Gets the last bar hologram's index position.
    #**
    function number getBarEnd() {
        return BarEnd   
    }  
    #**
    #* Sets the amount of bars, given the first and last bar hologram's index.
    #** 
    function void setBarCount(StartIndex:number, EndIndex:number) {
        setBarStart(StartIndex)
        setBarEnd(EndIndex)
        BarCount = ( getBarEnd() - getBarStart() )    
    }    
    #**
    #* Gets the amount of bar holograms.
    #**
    function number getBarCount() {
        return BarCount    
    }    
    #**
    #* Checks if the entity has typed something in chat and returns a boolean value (1 - true, 0 - false).
    #**
    function number entity:isTalking() {
        return chatClk(This)    
    }
    #**
    #* Returns the player entity's last typed words and returns them in an array.
    #**
    function array entity:getChat() {
        return This:lastSaid():explode(" ")
    }
    
    #**
    #* Creates the Music Board given the first bar hologram's index (Start), last bar hologram's index (End),
    #* the amount of bar holograms (Count), and the length of the Music Board (Length).
    #**
    function void createBoard(Start, End, Count, Length) {
        holoCreate( Board )
        holoPos( Board, E:toWorld( vec( Length / 2, 0, 0) ) )
        holoScaleUnits( Board, vec( Length, 100, 1 ) )
        holoColor( Board, vec(0,100,200) )
        
        holoCreate( Reader )
        holoPos( Reader, E:toWorld(vec(0,0,1)) )
        holoScaleUnits( Reader, vec(1, 101, 1) )
        holoColor( Reader, vec(255,255,255) )
        
        for (I = Start, End) {
            holoCreate( I )
            holoScaleUnits( I, vec(1, 101, 2) )    
            holoPos( I, E:toWorld( vec( (Length / Count) * (I - Start), 0, 0 ) ) )
            holoColor( I, vec( 25, 25, 25 ) )
        }
    }
    
    #**
    #* Deletes all holograms when resetting the Music Board's attributes, eliminating any remaining holograms.
    #**
    function void resetHolos() {
        holoDeleteAll()   
    }
    
    #**
    #* Parses a player entity's chat (in the form of an array) for specific commands.
    #* If a chat word matches one of the command words, the corresponding function is called from the switch statement.
    #**
    function void parseChat(Chat:array) {
        local Commands = array("start", "end", "length", "on", "off", "loop")
        
        for (I = 1, Chat:count()) {
            local Word = Chat[I,string]:lower()
            
            for (J = 1, Commands:count()) {
                local Command = Commands[J,string]:lower()
                
                if (Word == Command) {
                    hideChat(TRUE)
                    
                    switch(Word) {
                        case "start", setBarStart(Chat[I + 1, string]:toNumber()), break
                        case "end", setBarEnd(Chat[I + 1, string]:toNumber()), break
                        case "length", setBoardLength(Chat[I + 1, string]:toNumber()), break
                        case "on", togglePower(TRUE), break
                        case "off", togglePower(FALSE), break
                        case "loop", toggleLoop(Chat[I + 1, string]:toNumber()), break
                    }
                }
            }   
        }         
    }
    
    #**
    #* Checks if the owner has typed in chat and parses the words for any commands.
    #* If the player formats the Music Board's attributes and powers it on, the Music Board will
    #* be created/reset.
    #**
    function void checkChat() {
        
        runOnChat(1)
        
        if (owner():isTalking()) { 
            parseChat(owner():getChat())  
            
            local IsFormatted = (getBarStart() > 0 & getBarEnd() > 0)
            
            if (IsFormatted & !isOn()) {
                resetHolos()
                setBarCount(getBarStart(), getBarEnd()) 
                createBoard(getBarStart(), getBarEnd(), getBarCount(), getBoardLength())        
            }    
        }    
    }
    
    #**
    #* Moves the Reader bar across the Music Board (unfinished).
    #* If looping is enabled, the Reader bar will start back at the beginning and loop through infitinitely.
    #**
    function void readMusic() {  
        interval(Tempo) #// The time delay between each Reader bar movement
        local Pos = E:toWorld(vec(Time * (getBoardLength() / getBarCount()) / 8, 0, 1)) #// The amount the bar moves per Time unit
        holoPos( Reader, Pos )
        
        Time++
        
        if ((Time * (getBoardLength() / getBarCount())) / 8 > getBoardLength()) {
            if (isLooped() == FALSE) { 
                togglePower(FALSE)    
            }
            Time = 1
        }
    }
    
    #**
    #* Runs the program by parsing player chat for commands, creating the Music Board, and reading the music.
    #** 
    function void run() {
        checkChat()    
                
        if (isOn() == TRUE) {
            readMusic()    
        }
    }
}

run()
