1. Evolution complete! Make Darwin proud and go vote in the Techtree Contest #12 - Poll.
    Dismiss Notice
  2. Icon Contest #17 - Results are out! Step by to congratulate our winners!
    Dismiss Notice
  3. Succumb to the whispers and join our Texturing Contest #29 - Old Gods!
    Dismiss Notice
  4. The results for Texturing Contest #28 are out! Step by to congratulate our winners!
    Dismiss Notice
  5. We've created the Staff Job Openings thread. We're currently in need of icon, video production, and social/multimedia positions to be filled. Thank you!
    Dismiss Notice
  6. On May 20th a new law about privacy and data processing comes into work in the EU. I am no lawyer and I need help figuring out if we comply and if not, what we must do about it. Please message me if you can provide any assistance. Read more. Ralle
    Dismiss Notice

[vJASS] Command Utilities

Discussion in 'Submissions' started by Pinzu, Aug 1, 2018.

  1. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    994
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    The purpose of this library is to make the creation of commands faster and easier to manage by providing additional API geared towards that end. Among other things you can register commands, disable them for certain players, or all players, and define new player string identification.

    Each player has a struct storing information generated each time a player enters a chat command. A central feature of the update cycle is to divide every accepted chat message into an array of words making it easier to make string comparisions for commands with multiple arguments.

    Library
    Code (vJASS):

    library CommandUtils uses Table
    /*
        =========================================================================
        Command Utilities version 1.2.
        Latest version: hiveworkshop.com/threads/command-utilities.307554/#post-3282208
        =========================================================================
        This libary provides a struct for each player, which stores information generated each time a
        player enters a chat command. It's purpose is to make the creation of commands faster and easier
        to manage by providing a few additional API functions geared towards that end.
        Credits:
        -   Made by Pinzu
     
        Requirements
        -   Table by Bribe
                hiveworkshop.com/threads/snippet-new-table.188084/
        Configuration:
        - Simply copy Bribe's Table and this library into your map
        - Modify the configurables to your likig in the global block. You can add multiple command
            prefixes if you scroll down to the onInit method inside Command struct.
        API
        public PlayerChat array lastPlayerChat      Struct holding data gathered from last registered
                                                    command for each player [0-23]
        ----------------
        The Command struct is set up as a double linked list, which means it's perfect to iterate through them all.
        There are more public methods available than those listed. However, it's not recommended that you use them over the
        functions provided below.
     
        struct Command
     
            static thistype head
     
            static thistype tail
     
            thistype next
     
            thistype prev
     
            method addTrigger takes trigger t returns nothing
                Adds a trigger to be executed when the command is detected.
     
            method disableForPlayer takes integer pid returns nothing
                Disables a command for a given player by player-id.
     
            method enableForPlayer takes integer pid returns nothing
                Enables a command for a given player by player-id.
     
            method isDisabledForPlayer takes player p returns boolean
                Returns true if a command is disabled for a given player.
     
        PlayerChat struct is the heart of the system. Every time a player enters a chat string the system handles it by looping through it
        and defining all words in an array. It can then be used to get arguments in a command trigger.
     
        struct PlayerChat
            string str                              Holds the entire entered chat string
     
            string subStr                            Substring after the first matching command
     
            string array word[]                     Array with the words found in the entered chat string
     
            integer array wordStart[]                The starting index in the chat string containing the entered word
     
            integer array wordEnd[]                    The ending index in the chat string containing the entered word
     
            integer wordCount                        Number of words found in the entered chat string
     
            integer length                          Length of chat string
     
     
            method wordLength takes integer index returns integer
                Returns the lenth of a word with a given index.
            method wordLower takes integer index returns string
                Returns a word transformed into lower case
     
            method wordUpper takes integer index returns string
                Returns a word transformed into upper case
     
     
        function StrToPlayer takes string s returns player p
            Converts a string containg a player number or name into a player. Red is denoted as
            either "0" or "1" depending on your configuration (default is 1).
     
        function StrContainsDigit takes string s returns boolean
            returns true if a string contains a digit
        function StrIsInt takes string s returns boolean
            Returns true if a string can be converted to a integer
     
        function StrIsReal takes string s returns boolean
            Returns true if a string can be converted to a real
     
        function StrIsBoolean takes string s returns boolean
            Returns true if a string is a boolean.
     
        function S2B takes string s returns boolean
            Converts a string to a boolean, will return true if the string matches "true".
     
        function RegisterCommand takes trigger trg, string cmd returns Command
            Adds a command to a certain trigger, returning the created command.
     
        function TrgRegisterCommand takes trigger t, string cmd, code c returns Command
            Wrapper to make command registration faster, returns the registrated command.
     
        function DeregisterCommand takes string cmd returns nothing
            Removes a command from the system.
     
        function EnablePlayerCommand takes string cmd, player p returns boolean
            Enables a given command for a player, returns false if the command doesn't exist
     
        function DisablePlayerCommand takes string cmd, player p returns boolean
            Disables a given command for a player, returns false if the command doesn't exist
     
        function SavePlayerId takes string identifier, player p returns nothing
            Creates a custom player identification.
        function RemovePlayerId takes string identifier returns nothing
            Removes a player identification.
     
        function GetLastPlayerChat takes player p returns PlayerChat
            Wrapper for the global variable lastPlayerChat, returns a struct containing data gathered
            from the last registrated command by player.
     
        function EnableCommands takes nothing returns nothing
            Enables the system event handling
     
        function DisableCommands takes nothing returns nothing
            Disable the system event handling
    */

        globals
     
            // CONFIGURABLES \\
     
            /*  Filter prefix, this is the default chat-prefix that determines when the LastPlayerChat should update.
                If set to "" it will update after every entered chat message. If you want to have multiple you must scroll down
                and manually add other prefixes. If you only want to manage commands of a certain type you can set it to for example "-" */

     
            private constant string     FILTER_PREFIX          = "" // Default is none
     
            /*  Numbers of maximum words the system handles, should be a relatively high number. */
     
            private constant integer    MAX_WORDS                  = 300
     
            /*     This is used to allow player names as valid default identification, example: "1" being Player red. */
     
            private constant boolean     ACK_PLAYER_NAME            = true
     
            /* If you want to use real-event detection for when the system has handled chat strings you need to modify this variable to your own and replace it in the code too. */
     
            public real cmd_chat_event
     
            // INTERNAL VARIABLES \\
     
            private PlayerChat array        lastPlayerChat
            private Table                     table
            private trigger                 chatTrg
            private constant real            RESET_CHAT_EVENT    = 0
            private constant real            ON_CHAT_EVENT        = 1
     
        endglobals
        struct Command
            readonly static thistype head = 0
            readonly static thistype tail  = 0
            readonly thistype next
            readonly thistype prev
            readonly  string str
            private Table cmdTable
            private integer trgSize
     
     
            static method create takes trigger trg, string cmd returns thistype
                local thistype this = .allocate()
                local integer cid = StringHash(cmd)
                set this.trgSize = 0
                set this.cmdTable = Table.create()
                set table[cid] = this
                set this.str = cmd
                call this.addTrigger(trg)
                if head == 0 then
                    set head = this
                    set tail = this
                else
                    set this.prev = tail
                    set tail.next = this
                    set tail = this
                endif
                return this
            endmethod
     
            method destroy takes nothing returns nothing
                if this == head and this == tail then
                    set head = 0
                    set tail = 0
                elseif this == head then
                    set head.next.prev = 0
                    set head = head.next
                elseif this == tail then
                    set tail.prev.next = 0
                    set tail = tail.prev
                else
                    set this.prev.next = this.next
                    set this.next.prev = this.prev
                endif
                call .cmdTable.destroy()
                call .deallocate()
            endmethod
     
            method addTrigger takes trigger t returns nothing
                set cmdTable.trigger[.trgSize] = t
                set .trgSize = .trgSize + 1
            endmethod
     
            method disableForPlayer takes integer pid returns nothing
                set cmdTable.boolean[pid] = true
            endmethod
     
            method enableForPlayer takes integer pid returns nothing
                call cmdTable.remove(pid)
            endmethod
     
            method isDisabledForPlayer takes player p returns boolean
                return cmdTable.boolean.has(GetPlayerId(p))
            endmethod
     
            method exec takes player p returns nothing
                local integer i = 0
                if .isDisabledForPlayer(p) then
                    return
                endif
                loop
                    exitwhen i == .trgSize
                    call TriggerExecute(.cmdTable.trigger[i])
                    set i = i + 1
                endloop
            endmethod
        endstruct
     
        struct PlayerChat
            readonly string str                              // Entered ChatString
            readonly string subStr                            // SubString after the command label
            readonly string array word[MAX_WORDS]             // A array containing each detected word
            readonly integer array wordStart[MAX_WORDS]        // Starting index of each word
            readonly integer array wordEnd[MAX_WORDS]        // Ending index of each word
            readonly integer wordCount                        // Number of words
            readonly integer length                          // Length of EnteredChatString
     
            private method update takes string chat returns nothing
                local string char = ""
                local integer i = 0
                loop
                    exitwhen i == .wordCount
                    set .word[i] = null
                    set .wordStart[i] = 0
                    set .wordEnd[i] = 0
                    set i = i + 1
                endloop
                set .length = StringLength(chat)
                set .str = chat    
                set .wordCount = 0
                set .wordStart[0] = 0
                set i = 0
                loop
                    exitwhen i > .length
                    set char = SubString(chat, i, i + 1)
                    if char != " "  then
                        set .word[.wordCount] = .word[.wordCount] + char
                    else
                        set .wordEnd[.wordCount] = i
                        set .wordCount = .wordCount + 1
                        set .wordStart[.wordCount] = i + 1
                    endif
                    set i = i + 1
                endloop
                set .subStr = SubString(.str, .wordStart[1], .length)
                set .wordEnd[.wordCount] = i - 1
                set .wordCount = .wordCount + 1
            endmethod
     
            private static method onPlayerChatEvent takes nothing returns nothing
                local Command command
                local player p = GetTriggerPlayer()
                local integer pid = GetPlayerId(p)
                call lastPlayerChat[pid].update(GetEventPlayerChatString())
                //! runtextmacro CHAT_EVENT_REG("cmd_chat_event")        // Replace cmd_chat_event with your variable
                //! textmacro_once CHAT_EVENT_REG takes VAR
                set $VAR$ = RESET_CHAT_EVENT
                set $VAR$ = ON_CHAT_EVENT
                //! endtextmacro
                set command = table[StringHash(lastPlayerChat[pid].word[0])]
                if (command == 0) then
                    return
                endif
                call command.exec(p)
                set p = null
            endmethod
     
            private static method onInit takes nothing returns nothing
                local player p
                local integer i = 0
                set chatTrg = CreateTrigger()
                set table = Table.create()
                loop
                    set p = Player(i)
                    if (GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING and /*
                    */
    GetPlayerController(p) == MAP_CONTROL_USER) then
     
                        /* You can add multiple prefix here */
     
                        /*CONFIG*/  call TriggerRegisterPlayerChatEvent(chatTrg, p, FILTER_PREFIX, false)
     
                        set lastPlayerChat[i] = PlayerChat.create()
                    endif
                    // Save player number as a default identifier
                    set table.player[StringHash(I2S(i + 1))] = p
                    set i = i + 1
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                endloop
                call TriggerAddAction(chatTrg, function thistype.onPlayerChatEvent)
                set p = null
            endmethod
     
            method wordLength takes integer index returns integer
                return .wordEnd[index] - .wordStart[index]
            endmethod
            method wordLower takes integer index returns string
                return StringCase(.word[index], false)
            endmethod
     
            method wordUpper takes integer index returns string
                return StringCase(.word[index], true)
            endmethod
        endstruct
     
        function StrToPlayer takes string s returns player p
            local player p = table.player[StringHash(s)]
            local integer i = 0
            if p != null then
                return p
            endif
            if ACK_PLAYER_NAME then
                loop
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                    set p = Player(i)
                    if s == GetPlayerName(p) then
                        return  p
                    endif
                    set i = i + 1
                endloop
            endif
            return null
        endfunction
     
        private function isDigit takes string c returns boolean
            return c == "0" or c == "1" or c == "2" or c == "3" or c == "4" or c == "5" or /*
                */
    c == "6" or c == "7" or c == "8" or c == "9"
        endfunction
        function StrIsReal takes string s returns boolean
            local integer i = 0
            local integer dots = 0
            local string char
            loop
                exitwhen i == StringLength(s)
                set char = SubString(s, i, i + 1)
                if char == "." then
                    set dots = dots + 1
                    if dots > 1 then
                        return false
                    endif
                elseif not isDigit(char) then
                    return false
                endif
                set i = i + 1
            endloop
            return true
        endfunction
        function StrIsInt takes string s returns boolean
            local integer i = 0
            local string char
            loop
                exitwhen i == StringLength(s)
                set char = SubString(s, i, i + 1)
                if not isDigit(char) and not (i == 0 and char == "-") then
                    return false
                endif
                set i = i + 1
            endloop
            return true
        endfunction
     
        function StrIsBoolean takes string s returns boolean
            set s = StringCase(s, false)
            return s == "true" or s == "false"
        endfunction
     
        function S2B takes string s returns boolean
            set s = StringCase(s, false)
            if s == "true" then
                return true
            endif
            return false
        endfunction
        function StrContainsDigit takes string s returns boolean
            local integer length = StringLength(s)
            local integer i = 0
            local string c
            set i = 0
            loop
                exitwhen i == length
                set c = SubString(s, i, i + 1)
                if isDigit(c) then
                    return true
                endif
                set i = i + 1
            endloop
            return false
        endfunction
        function SavePlayerId takes string identifier, player p returns nothing
            set table.player[StringHash(identifier)] = p
        endfunction
        function RemovePlayerId takes string identifier returns nothing
            call table.player.remove(StringHash(identifier))
        endfunction
     
        function EnableCommands takes nothing returns nothing
            call EnableTrigger(chatTrg)
        endfunction
        function DisableCommands takes nothing returns nothing
            call DisableTrigger(chatTrg)
        endfunction
     
        function CommandExists takes string cmd returns boolean
            return table.has(StringHash(cmd))
        endfunction
     
        function IsCommandEnabledForPlayer takes string cmd, player p returns boolean
            local Command command  = table[StringHash(cmd)]
            return not command.isDisabledForPlayer(p)
        endfunction
     
        function EnablePlayerCommand takes string cmd, player p returns boolean
            local Command command  = table[StringHash(cmd)]
            if (command != 0) then
                call command.enableForPlayer(GetPlayerId(p))
                return true
            endif
            return false
        endfunction
        function DisablePlayerCommand takes string cmd, player p returns boolean
            local Command command  = table[StringHash(cmd)]
            if (command != 0) then
                call command.disableForPlayer(GetPlayerId(p))
                return true
            endif
            return false
        endfunction
        function RegisterCommand takes trigger trg, string cmd returns Command
            local integer cid = StringHash(cmd)
            local Command command  = table[cid]
            if (command != 0) then
                call command.addTrigger(trg)
            else
                call Command.create(trg, cmd)
            endif
            return command
        endfunction
        function TrgRegisterCommand takes trigger t, string cmd, code c returns Command
            call TriggerAddAction(t, c)
            return RegisterCommand(t, cmd)
        endfunction
        function DeregisterCommand takes string cmd returns nothing
            local integer cid = StringHash(cmd)
            local Command command = table[cid]
            if (command != 0) then
                call table.remove(cid)
                call command.destroy()
            endif
        endfunction
        function GetLastPlayerChat takes player p returns PlayerChat
            return lastPlayerChat[GetPlayerId(p)]
        endfunction
    endlibrary

     


    Setup
    How to register commands
    call TrgRegisterCommand(CreateTrigger(),"-hello", function SayHello)

    Or if you need to manipulate the command.
    set command = TrgRegisterCommand(CreateTrigger(),"-hello", function SayHello)

    Note that a command must consist of one and only one word, combinations such as "-set name" need to be handled by registering the function to "-set" and then checking the parameter for chat.word[1] == "name" for the other part of the string. Also note that one command key can have multiple triggers associated to it.

    How to setup or remove player identification
    call SavePlayerId("red", Player(0))

    Some identifications such as 1 for Player 1 are default to the system, if you don't want to include them you must remove them manually.
    call RemovePlayerid("1")



    Misc sample usage
    Code (vJASS):

    scope Command initializer Init
        //-enable [command] [playernumber]
        private function EnableCommand takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local integer i
            if chat.wordCount >= 3 then
                set p = StrToPlayer(chat.word[2])
                if (p == null) then
                    call BJDebugMsg("Invalid player number")
                    return
                endif
                call EnablePlayerCommand(chat.word[1], p)
                call BJDebugMsg("Enabling command '" + chat.word[1] + "' for " + GetPlayerName(p))
            endif
        endfunction
        //-disable [command] [playernumber]
        private function DisableCommand takes nothing returns nothing
            local PlayerChat chat = GetLastPlayerChat(GetTriggerPlayer())
            local integer i
            local player p
            local boolean result
            if chat.wordCount >= 3 and StrIsInt(chat.word[2]) then
                set p = StrToPlayer(chat.word[2])
                if (p == null) then
                    debug call BJDebugMsg("Invalid player number")
                    return
                endif
                // You can also use the return value to give a response on weather
                // the command was valid or not
                if DisablePlayerCommand(chat.word[1], p) then
                    call BJDebugMsg("Disabling command '" + chat.word[1] + "' for " + GetPlayerName(p))
                else
                    call BJDebugMsg("There is no such command")
                endif
            else
                call BJDebugMsg("Full command: -disable [command] [playernumber]")
            endif
        endfunction
        //-name [playername]
        private function ChangeName takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local integer i = 1
            // Here we are excluding names containing digits and also checking that the number of
            // entered words are at least 2.
            if (chat.wordCount >= 2 or not StrContainsDigit(chat.subStr)) then
                call SetPlayerName(p, chat.subStr)
            endif
        endfunction
        //-spawn [x] [y] [facing]
        private function SpawnFooty takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local real x
            local real y
            local real f
            // Here we are checking for a proper command length and that word 1, 2 and 3 are reals before
            // creating the footman.
            if (chat.wordCount >= 4 and StrIsReal(chat.word[1]) and StrIsReal(chat.word[2]) and StrIsReal(chat.word[3])) then
                set x = S2R(chat.word[1])
                set y = S2R(chat.word[2])
                set f = S2R(chat.word[3])
                call CreateUnit(p, 'hfoo', x, y, f)
            endif
        endfunction
       private function SpawnGhoul takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local real x
            local real y
            local real f
            // Here we are checking for a proper command length and that word 1, 2 and 3 are reals
            if (chat.wordCount >= 4 and StrIsReal(chat.word[1]) and StrIsReal(chat.word[2]) and StrIsReal(chat.word[3])) then
                set x = S2R(chat.word[1])
                set y = S2R(chat.word[2])
                set f = S2R(chat.word[3])
                call CreateUnit(p, 'ugho', x, y, f)
            endif
        endfunction
        //-give [playernumber] [amount] [resource]
        private function Give takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local player p2
            // Here we are making sure the word count match the command
            if (chat.wordCount != 4) then
                debug call BJDebugMsg("Invalid entry. Example use: -give 1 gold 500")
                return
            endif
            // Here we are making sure that the playernumber exists
            set p2 = StrToPlayer(chat.word[1])
            if (p2 == null) then
                debug call BJDebugMsg("Invalid player number")
                return
            endif
            if not StrIsInt(chat.word[2]) then
                debug call BJDebugMsg("No amount specified")
                return
            endif
            if (chat.word[3] == "gold" or chat.word[3] == "g") then
                call SetPlayerState(p2, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p2, PLAYER_STATE_RESOURCE_GOLD) + S2I(chat.word[2]))
            elseif (chat.word[3] == "wood" or chat.word[3] == "lumber" or chat.word[3] == "l" or chat.word[3] == "w") then
                call SetPlayerState(p2, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p2, PLAYER_STATE_RESOURCE_LUMBER) + S2I(chat.word[2]))
            else
                debug call BJDebugMsg("Invalid resource specified")
            endif
        endfunction
        // Checkcs to see if word [1] is a real
        private function IsReal takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local real x = S2R(chat.word[1])
            if (StrIsReal(chat.word[1])) then
                call BJDebugMsg("Is Real [" + chat.word[1] + "] = " + R2S(x))
            else
                call BJDebugMsg("Not Real [" + chat.word[1] + "] = " + R2S(x))
            endif
        endfunction
        // Checkcs to see if word [1] is a integer
        private function IsInt takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local integer x = S2I(chat.word[1])
            if (StrIsInt(chat.word[1])) then
                call BJDebugMsg("Is Integer [" + chat.word[1] + "] = " + I2S(x))
            else
                call BJDebugMsg("Not Integer [" + chat.word[1] + "] = " + I2S(x))
            endif
        endfunction
        // Checkcs to see if word [1] contains a digit
        private function ContainsNum takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            local integer x = S2I(chat.word[1])
            if (StrContainsDigit(chat.word[1])) then
                call BJDebugMsg("Has a digit [" + chat.word[1] + "]")
            else
                call BJDebugMsg("No digit [" + chat.word[1] + "]")
            endif
        endfunction
        private function SeeSubString takes nothing returns nothing
        local PlayerChat chat = GetLastPlayerChat(GetTriggerPlayer())
            call BJDebugMsg("Substring: '" + chat.subStr + "'")
        endfunction
        private function Mute takes nothing returns nothing
        if IsCommandEnabledForPlayer("-spawn", GetTriggerPlayer()) then
            call BJDebugMsg("Command is enabled")
        else
            call BJDebugMsg("Command is disabled")
        endif
        endfunction
        private function Commands takes nothing returns nothing
            local PlayerChat chat = GetLastPlayerChat(GetTriggerPlayer())
            local Command cmd = Command.head
            local player p = StrToPlayer(chat.word[1])
            loop
                exitwhen cmd == 0
                if p != null then
                    if not cmd.isDisabledForPlayer(p) then
                        call BJDebugMsg("Enabled: " + cmd.str)
                    else
                        call BJDebugMsg("Disabled: " + cmd.str)
                    endif
                elseif p == null then
                    call BJDebugMsg(cmd.str)
                endif
                set cmd = cmd.next
            endloop
        endfunction
        private function Init takes nothing returns nothing
            local player p
            local trigger t = CreateTrigger()
            call RegisterCommand(t, "-name")
            call TriggerAddAction(t, function ChangeName)
            // call DeregisterCommand(t) // deregistering "-name" in this case.
            // Here we are creating two commands that will execute at the same time
            call TrgRegisterCommand(CreateTrigger(), "-spawn", function SpawnFooty)
            call TrgRegisterCommand(CreateTrigger(), "-spawn", function SpawnGhoul)
            call TrgRegisterCommand(CreateTrigger(), "-give", function Give)
            call TrgRegisterCommand(CreateTrigger(), "-real", function IsReal)
            call TrgRegisterCommand(CreateTrigger(), "-int", function IsInt)
            call TrgRegisterCommand(CreateTrigger(), "-num", function ContainsNum)
            call TrgRegisterCommand(CreateTrigger(), "-enable", function EnableCommand)
            call TrgRegisterCommand(CreateTrigger(), "-disable", function DisableCommand)
            call TrgRegisterCommand(CreateTrigger(), "-sub", function SeeSubString)
            call TrgRegisterCommand(CreateTrigger(), "-mute", function Mute)
     
            call TrgRegisterCommand(CreateTrigger(), "-commands", function Commands)

            // Custom player identifier for red
            set p = Player(0)
            call SavePlayerId("red", p)
            // These are included per default and would have to be disabled in system configuration
            call SavePlayerId(GetPlayerName(p), p)
            call SavePlayerId("1", p)
            set p = Player(1)
            call SavePlayerId("blue", Player(1))
            // and so on...
            call RemovePlayerId("blue")
        call DeregisterCommand("-real")
           // call TrgRegisterCommand(CreateTrigger(), "-real", function IsReal)
           // call TrgRegisterCommand(CreateTrigger(), "-int2", function IsInt)
        endfunction
    endscope
     


    Writing a function to display all commands that are avaialble for a player

    1) Register the command as usual
    call TrgRegisterCommand(CreateTrigger(), "-commands", function Commands)

    2) Then write something like this to iterate through the commands. This function will either print player specific information, or list all commands if no player was specified.
    Code (vJASS):

        private function Commands takes nothing returns nothing
            local PlayerChat chat = GetLastPlayerChat(GetTriggerPlayer())
            local Command cmd = Command.head
            local player p = StrToPlayer(chat.word[1])
            loop
                exitwhen cmd == 0
                if p != null then
                    if not cmd.isDisabledForPlayer(p) then
                        call BJDebugMsg("Enabled: " + cmd.str)
                    else
                        call BJDebugMsg("Disabled: " + cmd.str)
                    endif
                elseif p == null then
                    call BJDebugMsg(cmd.str)
                endif
                set cmd = cmd.next
            endloop
        endfunction
    If you want to store more info in your commands, simply modify the public variables in the Command struct to suit your needs.



    How to handle all chat events

    1) Change the configurable: Set FILTER_PREFIX = ""
    2) Scroll down in the library code until you find the method "onPlayerChatEvent", then change "udg_chat_event" to your global variable name.
    Code (vJASS):

    private static method onPlayerChatEvent takes nothing returns nothing
                local Command command
                local player p = GetTriggerPlayer()
                local integer pid = GetPlayerId(p)
                call lastPlayerChat[pid].update(GetEventPlayerChatString())
                //! runtextmacro CHAT_EVENT_REG("udg_chat_event")
                //! textmacro CHAT_EVENT_REG takes VAR
                set $VAR$ = RESET_CHAT_EVENT
                set $VAR$ = ON_CHAT_EVENT
                //! endtextmacro
                set command = table[StringHash(lastPlayerChat[pid].word[0])]
                if (command == 0) then
                    return
                endif
                call command.exec(p)
                set p = null
            endmethod

    3) Implement it in your chat trigger. It should look something like this:
    Code (vJASS):

    scope ChatEvent initializer Init

        private function OnChatEvent takes nothing returns nothing
            local player p = GetTriggerPlayer()
            local PlayerChat chat = GetLastPlayerChat(p)
            call BJDebugMsg(GetPlayerName(p) + ": " + chat.str)
        endfunction
        private function Init takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterVariableEvent(t, "udg_chat_event", EQUAL, 1)
            call TriggerAddAction(t, function OnChatEvent)
        endfunction
    endscope
    You can now use the library with any chat event that you have in your map. As an example you could use it to make a chat system that changes certain words.
     
    Last edited: Aug 19, 2018 at 9:04 PM
  2. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,622
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    For the example script, you may want to use the "debug" keyword or add your own "debug" boolean to the system in order to prevent in-game messages intended for a tester to appear for all players at all times.

    You're also not using switchers on your "-give" functions, which means you're running code despite knowing there are invalid values preventing anything from displaying correctly. Either use "elseif" or use "return" to skip the code below.

    I'm only saying this because people who use your resource are going to base their stuff on your demo code. One of many takeaways I've had from Damage Engine was the sheer number of people taking my demo code and making small modifications of it to create their own stuff. It's less likely to be encountered in the vJass community, but it's something I'm conscious of as a resource developer.

    In regards to the core system, "GetLastPlayerChat" takes an integer. Based on the function name, I would assume it takes a player.

    The rest of the code looks compact, to the point, without unnecessary stuff. I'm not a fan of limiting it to 4 characters, however. I feel this should be customizable.
     
  3. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    994
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    First, thanks for your feedback.
    • I'm not sure of the 4 character limit you are talking about, are you refering to the documentation error perhaps (saw a random 4 showing up there)? ^^
    • I'll revert it back to taking a player again.
    • I'll see what cleanup can be done do the sample code, didn't really want to provide a whole host of stuff as it's just examples of API usage.
    • I could use your input on if
      SetCommandHelp
      and
      GetCommandHelp
      should be omitted or if I should perhaps extend this feature to also include the possibility of listing all commands? The current utility of it seem somewhat questionable at best.
    • Maybe I should include an already existing StringLib for the StrIsReal, StrIsDigit,StrContainsDigit (there are a few others I could think would be useful here aswell) which then becomes the question.
    Reference: [Documentation] String Type

    -----------------------------------------------

    I made the suggested changes and added features relating to player usage. You can now define custom play identification such as "red" or use default ones, such as"1" and "WorldEditor". Added a new function for CommandRegistration to make setup require less lines of code. Finally i added a function to enable/disable the trigger firing the commands. I also removed the feature of saving a string containing help information to a command as it appeared like a useless feature to me.

    * I'm also considering moving the word[] variable into a table instead and also storing the start position of each word (and possibly end) so that combining words can be done easier using substring, but maybe this is overkill. I could use some help defining such operators.

    * Should I create a trigger for cleaning up when a player leaves or just have that as a callable function? Or ignore it altogether as it's pretty static throughout the game.

    Any suggestions of features that are missing or improvement to the code is most welcome, as always.


    Changelog v 1.0.2

    - Added: DisablePlayerCommand - disables command for a given player
    - Added: EnablePlayerCommand - enables a command for a given player
    - Added: StrToPlayer - converts a string identifier into a player e.g. "1", "WorldEditor" or
    custom definition.
    - Added: TrgRegisterCommand - an alternative command registration, everything in one line.
    - Added: SavePlayerId - allows customization of player identification.
    - Added: RemovePlayerId - removes a stored player identification.
    - Added: function to disable/enable all commands.
    - Removed: SetCommandHelp and GetCommandHelp which returned a human readable
    - Moved: The main code was moved into the struct Command.
    - Updated: Sample code got some additions and touch ups
     
    Last edited: Aug 8, 2018
  4. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    994
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    One could make a GUI version of this by creating a wrapper for the setup of all commands and then using an array for each player. The only problem would be the 2D stuff [player][word]. An idea to solve that could be to just omit player and only having lastPlayer.data instead of player[].data. This could be useful I think, but I'm unsure if it's worth making tbh, as they do like to copy and paste alot.

    I've decided to keep the PlayerChat update even if a command is not entered as it can be useful in chat systems and such. Currently the system can't take in empty commands, this I think is a flaw but i don't see any way to handle it as you might want to have multiple blanks triggered at the same time where as the current setup would only allow one such "command" to be stored if implemented, but perhaps that would be acceptable. Or maybe I should just ignore this 'issue'.

    I created a list containing all commands that could be useful if one would want to iterate through them all, but I decided to not implement it at this point. Could be useful if you want to display all commands or all commands available to a certain player. What do you guys think of this?

    Update: 1.1.
    Most changes were done internally but some usage differences are:

    - Changed all member variables of PlayerChat to readonly and added wordStart[] and wordEnd[] which denotes the start and end points of a given word. Exmaple usage:
     set temp = SubString(playerChat.str, playerChat.wordStart[2], playerChat.wordEnd[4])
    which would combine word 2, 3 and 4.
     set temp = SubString(playerChat.str, playerChat.wordStart[2], playerChat.strLength)
    combines everything from the start of word 2 to the end.
    - Reduced the maximum number of words to 400.
    - Methods added to PlayerChat:
    method wordLength takes integer index returns integer

    method wordLower takes integer index returns string

    method wordUpper takes integer index returns string

    - Methods removed from PlayerChat:
    method getWord takes integer index returns string 
    (as there is already a word[] public variable for that).


    Is it worth spending time making this possible?
    Code (vJASS):

        call TrgRegisterCommand(CreateTrigger(), "-quit", function QuitJob)
        call TrgRegisterCommand(CreateTrigger(), "-quit", function QuitSchool)


    Currently it will only maintain a 1 to 1 link between command and trigger and only the last registrated command will be executed. Ideally both of these should run, which can be achieved by storing the triggers in a list.

    Another limitation is that you can only register commands by the first word as a prefix but what if you want to have commands like this "-quit school" and "-quit job" be registrated separetly. It would be easy to handle with if-statements inside a universal "-quit" command, but is that ideal for the user?
     
    Last edited: Aug 13, 2018
  5. Jampion

    Jampion

    JASS Reviewer

    Joined:
    Mar 25, 2016
    Messages:
    1,245
    Resources:
    0
    Resources:
    0
    StrIsReal returns true for strings like "0asd". Is this intended?
    Also StrIsReal(".5") = true, but StrIsReal(".0") = false

    Displaying a list of commands a player can use would be useful. One could implement a -help or -commands command with this.

    It's not bad to have this, as this is how normal triggers and events work as well. I would add it, if it is easily doable.

    Not an issue imo. You can just use the second word as argument and then act accordingly.
     
  6. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    994
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    Thanks for your feedback. I'll look into it the things you suggested.

    StrIsReal("0asd") works as that is how S2R functions, "5asd" is 5 but asd5 is 0. I'll fix ".0" aswell. If you think it's better to deny any values with none digits i could do that but I don't see why this is important? I just wanted to filter away clear misstakes such as "asdsa" returning 0. ^^

    But the reason I didn't include a list of commands, was because that let's take the command "-give", is not very useful information if the command has multiple arguments which the system doesn't handle, in this example we have "-give [player] [amount] [resource]" would just return "-give". It's not clear to me what one would use it for unless you also return SetCommandTooltip GetCommandTooltip?

    Will also add: CommandIsEnabledForPlayer(command, player) and CommandExists(command). That way one could add a dummy command "-" and show error messages for commands that are disabled for specifc players.
     
    Last edited: Aug 15, 2018 at 8:01 AM
  7. Pinzu

    Pinzu

    Joined:
    Nov 30, 2007
    Messages:
    994
    Resources:
    2
    Spells:
    1
    Tutorials:
    1
    Resources:
    2
    Changelog 1.2
    - Changed: Now supports multiple triggers being attached to the same command
    - Changed: RegisterCommand and TrgRegisterCommand now returns the registered command struct.
    - Changed: Commands are now double linked nodes that you can iterate through using head, tail, next and prev.
    - Added: function StrIsBoolean takes string s returns boolean
    - Added: function S2B takes string s returns boolean
    - Added: function CommandExists takes string cmd returns boolean
    - Added: function IsCommandEnabledForPlayer takes string cmd, player p returns boolean
    - Added: Real-variable to notify of chat events that have been handled by the system
    - Fixed: Bug related to disabling/enabling commands for individual players
    - Fixed: StrIsReal and StrIsInt now works as you'd expect it to


    I now consider this (almost) complete unless anyone has any suggestions of what could be improved.

    There is also an argument that StrIsInt, StrIsBoolean, StrIsReal, S2B, ContainsDigit should be moved from the core of the library but. I'll just keep them there for now as I found them to be pretty useful when writing command conditions.


    Known Issues:
    - If you write multiple " " characters after each other they'll be detected as words. This means that commands that are correct but have been dislocated due to double spacing wont work, as well as the illogical fact that words can be empty.
    - If you deregister a command without having stored the reference to the trigger it used, you will create a memory leak (perhaps trigger creation should be done internally, rather than having the user provide it?)
    - PlayerChat.substr currently forfills no clear purpose, as one could use wordStart[1] and wordEnd[wordCunt] to achieve the same thing. Same argument can be made for length, these could effectivly be made into operators instead.
    - When player leaves the PlayerChat struct is not deallocated.
    - Perhaps all the command handling related stuff should be moved inside the Command struct

    set cmd = RegisterCommand(trg, "-mycommand")
    ->
    set cmd = Command.register("-mycommand",  function MyFunction)

    set cmd = TrgRegisterCommand(trg, "-mycommand", function MyFunc)
    -> Removed
    call DeregisterCommand("-mycommand")
    ->
    call Command.deregister("-mycommand")

    call EnablePlayerCommand("-mycommand", someplayer)
    ->
    call Command.enablePlayer("-mycommand", someplayer)

    call DisablePlayerCommand("-mycommand", someplayer)
    ->
    call Command.disablePlayer("-mycommand", someplayer)

    call EnableCommands()
    ->
    call Command.enable()

    call DisableCommands()
    ->
    call Command.disable()


    Also I'm not sure if GetLastPlayerchat(whichplayer) needs to have a player argument, as i could just have the system remeber which player it was or even have a variable inside PlayerStruct to get the player (so this operation is not needed twice).
     
    Last edited: Aug 20, 2018 at 7:20 AM