• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] ChatCommand

Level 23
Joined
Apr 16, 2012
Messages
4,041
Finally something useful from me

JASS:
library ChatCommand uses StringIterator
/*

    Made by: edo494
    Version: 1.2
   
    Requires: StringIterator - http://www.hiveworkshop.com/forums/jass-resources-412/snippet-stringiterator-246143/
   
    This Library provides very easy way to create game modes
    accessed via chat.
   
    There is currently no way to group multiple modes into
    one internally like in dota, where -amap means
    -allmid -allpick, but can be done externally by user.
         
    If you register 2 switches, and both appear in one
    line (-what -ever), it will always trigger them in order
    of appearance in the chat(function registered to swtich -what
    runs, then function registered to swtich -ever runs)
           
    Currently, even if you type something sensible and then
    type some valid switch, it will get triggered. This may
    be filtered, but requires some thinking of doing it inteligently.
    
    Example: "hey boys, its nice to -play with you" will still try
             to run callback to switch "play" with string "with you"
                     
    This resource should be safe for recursive execution, which means
    that if in some way to get to trigger the checker from the event
    registered to this resource, it should work properly
   
    API:
           
    function AddChatCommand takes string modeString, code callBack returns nothing
        - registers new Mode(mode1 in -mode1 etc) and adds callBack object
          into it for custom functionality
        - please note that "-" is not requested, because it is prepended
          to the switch
          Example: AddModeString("what", function ...) will trigger when you
                   type "-what" in chat
                   
    function RemoveChatCommand takes string modeString returns nothing
        - removes the modeString from the system, so it will no longer be
          triggered
        - O(log n), worst case is O(n), where n is number of modeStrings
          registered
                   
    function SetChatCommandDelimiter takes string newDelimiter returns nothing
        - sets new delimiter for the system to look for in the strings
        - this can be any number of characters
          Note: if you use "" or null, it will always consider the whole
          message after the switch as ChatCommandString even if there is
          another Switch inside the line
         
             
    Getters:
   
        native GetTriggerPlayer takes nothing returns player
            - returns the player that entered the chat mode to chat
       
        function GetChatCommand takes nothing returns string
            - returns the string used as switch
              Example: "-what else" returns "what"
           
        function GetChatCommandString takes nothing returns string
            - returns the string that appears after the mode until
              another mode or until the end of string
              Example: "-what else or -if something" will return "else or"
                       or "something", dependsing when you request it.
       
        function GetChatCommandDelimiter takes nothing returns string
            - returns the delimiter string
            - defaults to "-"
             
             
        struct ChatCommand extends array
           
            static method command takes nothing returns string
                - returns the string used as switch(-what returns "what")
           
            static method sring takes nothing returns string
                - returns the string that appears after the mode until
                  another mode or until the end of string
           
            static method operator delimiter takes nothing returns string
                - returns the delimiter string
                - defaults to "-"
           
            static method operator delimiter= takes string newDelimiter returns nothing
                - sets new delimiter for the system to look for in the strings
                - this can be any number of characters
                  Note: if you use "" or null, it will always consider the whole
                  message after the switch as ChatCommandString even if there is
                  another Switch inside the line
*/
    globals
        private string array modes
        private trigger array trig
        private integer modesC = 0
        private string chatStringTrig = null
        private trigger globTrig = CreateTrigger()
        private string chatString = null
        private string modesDelimiter = "-"
    endglobals
   
    struct ChatCommand extends array
        static method command takes nothing returns string
            return chatStringTrig
        endmethod
       
        static method string takes nothing returns string
            return chatString
        endmethod
       
        static method operator delimiter takes nothing returns string
            return modesDelimiter
        endmethod
       
        static method operator delimiter= takes string newDelimiter returns nothing
            set modesDelimiter = newDelimiter
        endmethod
    endstruct
   
    function GetChatCommandString takes nothing returns string
        return chatString
    endfunction
   
    function GetChatCommand takes nothing returns string
        return chatStringTrig
    endfunction
   
    function GetChatCommandDelimiter takes nothing returns string
        return modesDelimiter
    endfunction
   
    function SetChatCommandDelimiter takes string newDelimiter returns nothing
        set modesDelimiter = newDelimiter
    endfunction
   
    function AddChatCommand takes string modeString, code callBack returns nothing
        local integer m = modesC
        if trig[m] == null and callBack != null then
            set trig[m] = CreateTrigger()
        endif
        set modes[m] = modeString
        call TriggerAddCondition(trig[m], Condition(callBack))
        set modesC = m + 1
    endfunction
   
    function RemoveChatCommand takes string modeString returns nothing
        local integer i = modesC - 1
        loop
            if modes[i] == modeString then
                call TriggerClearConditions(trig[i])
                set modes[i] = modes[modesC - 1]
                call DestroyTrigger(trig[i])
                set trig[i] = trig[modesC - 1]
                set modesC = i
                return
            endif
            exitwhen i == 0
            set i = i - 1
        endloop
    endfunction
   
    private module m
        private static method removeStartingSpaces takes string s returns string
            local integer i = 0
            local integer e = StringLength(s)-1
            local boolean b = false
            local string q = ""
            loop
                set q = SubString(s, i, i+1)
                if q != " " then
                    set b = true
                endif
                exitwhen i == e or b
                set i = i + 1
            endloop
            return SubString(s, i, e+1)
        endmethod
       
        private static method removeTrailingSpaces takes string s returns string
            local integer i = 0
            local integer e = StringLength(s) - 1
            local boolean b = false
           
            if e < 0 then
                return s
            endif
            loop
                if SubString(s, e, 1) == " " then
                    set e = e - 1
                else
                    set b = true
                endif
                exitwhen b or e <= 1
            endloop
            return SubString(s, 0, e + 1)
        endmethod
       
        private static method onChatEnter takes nothing returns boolean
            local string entered = GetEventPlayerChatString()
            local StringIterator iter = StringIterator.create(entered)
            local string helper = ""
            local string outer = ""
            local string inner = ""
            local integer looper = 0
            local string lock1 = ""
            local string lock2 = ""
            local string lock3 = ""
            local string endHelper = ""
            local string readDelimiter = ""
            local boolean equals = false
            loop
                call iter.setDelims(" ")
                exitwhen iter.end
                set helper = iter.read()
                set looper = 0
                set readDelimiter = SubString(helper, 0, StringLength(modesDelimiter))
                set equals = readDelimiter == modesDelimiter or /*
                                */ modesDelimiter == "" or modesDelimiter == null
                set outer = SubString(helper, StringLength(modesDelimiter), StringLength(helper))
                if equals then
                    loop
                        if outer == modes[looper] then
                            set lock1 = chatStringTrig
                            set chatStringTrig = outer
                            call iter.setDelims(modesDelimiter)
                            set inner = removeTrailingSpaces(removeStartingSpaces(iter.read()))
                            set lock2 = chatString
                            set lock3 = modesDelimiter
                            set chatString = inner
                            call TriggerEvaluate(trig[looper])
                            set chatStringTrig = lock1
                            set chatString = lock2
                            set modesDelimiter = lock3
                        endif
                        exitwhen looper == modesC - 1
                        set looper = looper + 1
                    endloop
                endif
            endloop
            return false
        endmethod
        private static method onInit takes nothing returns nothing
            local integer i = 0
            call TriggerAddCondition(globTrig, Condition(function thistype.onChatEnter))
            loop
                call TriggerRegisterPlayerChatEvent(globTrig, Player(i), "", false)
                exitwhen i == 15
                set i = i + 1
            endloop
        endmethod
    endmodule
   
    private struct s extends array
        implement m
    endstruct

endlibrary

Examples(ripped from my map):

JASS:
scope UserModeClear initializer init
   
    private function clear takes nothing returns nothing
        if GetLocalPlayer() == GetTriggerPlayer() then
            call ClearTextMessages()
        endif
    endfunction
   
    private function init takes nothing returns nothing
        call AddChatCommand("clear", function clear)
    endfunction
   
endscope

More(a looot more) complex one:

JASS:
scope UserModeCamera initializer init
   
    globals
        private real DEFAULT_DISTANCE = 0
        private constant string CAMERA_HINT = "|cffffff00-camera|r has following options:\n"/*
                                           */+"|cffffff00-camera X|r, where X is number - sets the camera distance to X(Default value is 1906)\n"/*
                                           */+"|cffff0000Note: |rThe distance must be between 100 and 2500\n"/*
                                           */+"|cffffff00-camera fix|r - fixes the distance of camera to certain value\n"/*
                                           */+"|cffff0000Note:|r|cffcccc00If you use |r|cffffff00-camera|r|cffcccc00 fix while the camera is fixed, it reverts the effect|r\n"/*
                                           */+"|cffffff00-camera lock|r - locks the camera to your hero\n"/*
                                           */+"|cffffff00-camera unlock|r - unlocks the camera to free view\n"/*
                                           */+"|cffffff00-camera default|r - sets the distance to default distance\n"/*
                                           */+"|cffffff00-camera toggle|r - toggles the camera mode(if it is locked, it unlocks it and vice versa)\n"/*
                                           */+"|cffffff00-camera help|r - displays this message\n"/*
                                           */+"Note: You can also use |cffffff00-cam|r with the same switches"
                                           
        private constant string CAMERA_ERROR = "|cffff0000Invalid input into -camera|r"
       
        private boolean array cameraLocked
       
        private real array cameraDistances
        private integer instances = 0
        private boolean array cameraFixed
        private timer cameraFix = CreateTimer()
        
        unit array StartingUnits
        integer PlayerCount
    endglobals
   
    //******************************************************//
    /*              Handle Camera things                    */
    /*            Lock camera to main unit                  */
    private function CamLock takes player p returns nothing
        local integer id = GetPlayerId(p)
        local unit u = StartingUnits[id]
        if not cameraLocked[id] then
            if GetLocalPlayer() == p then
                call ClearTextMessages()
            endif
            if GetLocalPlayer() == p then
                call SetCameraTargetController(u, 0, 0, true)
                call DisplayTimedTextToPlayer(p, 0, 0, 10, "|cffffcc00Camera Mode: |rLocked")
            endif
            set cameraLocked[id] = true
        else
            call DisplayTimedTextToPlayer(p, 0, 0, 16, "|cffffcc00Camera|r already locked")
        endif
        set u = null
    endfunction
   
    /*           Unlock camera for free movement            */
    private function CamUnlock takes player p returns nothing
        local integer id = GetPlayerId(p)
        local unit u = StartingUnits[id]
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        local real d = 0
        if cameraLocked[id] then
            if GetLocalPlayer() == p then
                call ClearTextMessages()
            endif
            if GetLocalPlayer() == p then
                set d = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)
                call ResetToGameCamera(0)
                call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, d, 0)
                call SetCameraPosition(x, y)
                call DisplayTimedTextToPlayer(p, 0, 0, 10, "|cffffcc00Camera Mode: |rFree")
            endif
            set cameraLocked[id] = false
        else
            call DisplayTimedTextToPlayer(p, 0, 0, 16, "|cffffcc00Camera|r already unlocked")
        endif
        set u = null
    endfunction
   
    /*                Reset camera for players              */
    private function CamReset takes nothing returns nothing
        local integer i = 0
        loop
            if cameraFixed[i] then
                if GetLocalPlayer() == Player(i) then
                    call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, cameraDistances[i], 0)
                endif
            endif
            exitwhen i == PlayerCount
            set i = i + 1
        endloop
    endfunction
   
    /*             Fix cameras distance for player          */
    private function CamFix takes player p returns nothing
        local integer id = GetPlayerId(p)
        if GetLocalPlayer() == p then
            call ClearTextMessages()
        endif
        if not cameraFixed[id] then
            call DisplayTimedTextToPlayer(p, 0, 0, 16, "Camera has been fixed")
            set instances = instances + 1
            set cameraFixed[id] = true
            if instances == 1 then
                call TimerStart(cameraFix, 0.1, true, function CamReset)
            endif
        else
            call DisplayTimedTextToPlayer(p, 0, 0, 16, "Camera has been un-fixed")
            set instances = instances - 1
            set cameraFixed[id] = false
            if instances == 0 then
                call PauseTimer(cameraFix)
            endif
        endif
    endfunction
   
    /*              Change distance of the Camera           */
    private function CamDist takes integer value, player triggerer returns nothing
        if GetLocalPlayer() == triggerer then
            if value < 100 or value > 2500 then
                if GetLocalPlayer() == triggerer then
                    call ClearTextMessages()
                endif
                call DisplayTimedTextToPlayer(triggerer, 0, 0, 16, "|cffff0000The value after -camera or -cam must be higher or equal to 100 and smaller or equal to 2500|r")
            else
                call SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, value, 1 + (value/DEFAULT_DISTANCE))
                set cameraDistances[GetPlayerId(triggerer)] = value
            endif
        endif
    endfunction
   
    /*              Dispatch entered string                 */
   
    /*  Checking if the entered string is value for zoom,
        lock or unlock commands
    */
    private function CamDispatch takes nothing returns nothing
        local string s = GetChatCommand()
        local string q = ""
        local integer value = -1
        local player p = GetTriggerPlayer()
        set value = S2I(s)
        if S2I(s) != 0 and SubString(s, 0, 1) != "0" then
            call CamDist(value, p)
        elseif s == "fix" then
            call CamFix(p)
        elseif s == "lock" then
            call CamLock(p)
        elseif s == "unlock" then
            call CamUnlock(p)
        elseif s == "default" then
            call CamDist(R2I(DEFAULT_DISTANCE), p)
        elseif s == "toggle" then
            if cameraLocked[GetPlayerId(p)] then
                call CamUnlock(p)
            else
                call CamLock(p)
            endif
        elseif s == "help" then
            if GetLocalPlayer() == p then
                call ClearTextMessages()
            endif
            call DisplayTimedTextToPlayer(p, 0, 0, 30, CAMERA_HINT)
        else
            if GetLocalPlayer() == p then
                call ClearTextMessages()
            endif
            if s != "" or s != null then
                call DisplayTimedTextToPlayer(p, 0, 0, 30, CAMERA_ERROR)
            endif
            call DisplayTimedTextToPlayer(p, 0, 0, 30, CAMERA_HINT)
        endif
    endfunction
   
    private function init takes nothing returns nothing
        local integer i = 0
        call AddChatCommand("camera", function CamDispatch)
        call AddChatCommand("cam", function CamDispatch)
        set DEFAULT_DISTANCE = GetCameraField(CAMERA_FIELD_TARGET_DISTANCE)
        loop
            set cameraDistances[i] = DEFAULT_DISTANCE
            exitwhen i == PlayerCount
            set i = i + 1
        endloop
    endfunction
endscope

To see the first one work just output something using BJDebugMsg and then enter "-clear" into chat as if you were trying to type something to everyone.

To see how the second one works just check global variable CAMERA_HINT. If you dont want to read the entire variable just download it and type -cam help or -camera help to see it displayed ingame.
Please take into consideration that the example is ripped off of my map and following globals are not provided(they are placed in other libraries in my map):

JASS:
integer PlayerCount
unit array StartingUnits



  • struct ChatMode -> struct ChatCommand


  • Overhauled the API:
    • AddChatMode -> AddChatCommand
    • RemoveModeString -> RemoveChatCommand
    • AddChatModeString -> AddChatCommandString
    • SetChatModeDelimiter -> SetChatCommandDelimiter
    • GetChatModeSwtich -> GetChatCommand
    • GetChatModeString -> GetChatCommandString
    • GetChatModeDelimiter -> GetChatCommandDelimiter
    • ChatMode.switch -> ChatMode.command


  • Changed the behaviour of how the ChatModeString is filled. It was kind of glitched and included trailing and leading spaces("-what ever -what" would output " ever " instead of "ever"). This is now fixed
  • Accordingly to the previous mentioned change, I changed the camera example, because the call to RemoveStartingSpaces is no longer needed
  • Optimized the loop quite a bit
  • Delimiter that is used is now configurable, instead of hardcoded to "-"
  • New functions:
    • SetChatModeDelimiter takes string newDelimiter returns nothing
    • GetChatModeDelimiter takes nothing returns string
  • New methods:
    • static method operator delimiter takes nothing returns string
    • static method operator delimiter= takes string newDelimiter returns nothing
  • Added the forgotten function RemoveModeString to the API



  • initial release

 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
yea I can switch it to check for exact match, and use static if, or I could make it turnable during runtime with some checks and boolean value. Static if-ing is overall better for performance and seems less tricky for users to use
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I didnt even know it exists, so to answer your question, there is no relation.

But lets do some comparision:

What mag's does and mine doesnt:

  • his uses structs, whereas mine does not
  • his allows to lock player from executing commands internally, mine does not and you need to check in the callback for player
  • his allows to lock command internally, whereas mine doesnt and requires to check in the callback whether or not it was used before

What mine does and mag's doesnt:

  • allows you to pass additional information to the mode which means you need only 1 mod for multiple things, such as the camera example whereas with mags you will have to do each command separatly. As it seems it would be next to impossible to create -cam X with his system, in mine its trivial

What both do:

  • can get callback registered to mode
  • can execute multiple commands in one line of text, even tho a bit differently
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
updated to version 1.1

added changelog

I forgot to mention that this resource, even if not needed most likely, is recursivelly safe, so if you somehow trigger chat event with switches while another one is running, it should work properly(not tested)
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
version 1.2 is now Up!:

Overhauled the API:
  • AddChatMode -> AddChatCommand
  • RemoveModeString -> RemoveChatCommand
  • AddChatModeString -> AddChatCommandString
  • SetChatModeDelimiter -> SetChatCommandDelimiter
  • GetChatModeSwtich -> GetChatCommand
  • GetChatModeString -> GetChatCommandString
  • GetChatModeDelimiter -> GetChatCommandDelimiter
  • ChatMode.switch -> ChatMode.command
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
ou, the struct should've most likely been called ChatCommand, dont know why I called it ChatMode when even the snippet has been renamed :D

Change:

- renamed struct ChatMode -> ChatCommand

edit: apparently it is called ChatModes, can you rename it to ChatCommands, because the library is called that way now too
 
Top