Custom Commands

Back to lessons

Text-based Custom Commands:

At this point, you should be able to code most simple spells. There is another significant use of triggers to look at besides spells, and that is text commands. For this example, let's say we want to add a -zoom command to the game. Zoom is useful because WC3's default camera zoom is pretty obnoxious for most maps.

Trigger setup:



The trigger will be set up in the same way as when we have spell casting, but this time we'll use a different native to add the Event. Recall that before, we used TriggerRegisterAnyUnitEventBJ to add a Unit Event, then specified that it was a Casting unit event. This time, we'll use TriggerRegisterPlayerChatEvent.

native TriggerRegisterPlayerChatEvent takes trigger whichTrigger, player whichPlayer, string chatMessageToDetect, boolean exactMatchOnly returns event

Take a look at the arguments above.
- whichTrigger: Same as before. The trigger to add this event to.
- whichPlayer: This is new. Before, the event worked for *any* unit. Now, we need to specify *one* specific player for this event to work for.
- chatMessageToDetect: Self-explanatory. The chat message.
- exactMatchOnly: This is for if you want it to detect substrings. More on this later.

Let's first think about what the last two arguments will be since they are the most important.
For chatMessageToDetect: we want to make a zoom command, so -zoom sounds like a good string to detect. For this argument, we will use "-zoom ". The space at the end is so that it will only detect the command if something is written after it (in this case, the amount to zoom by).
For exactMatchOnly: well, we don't want it to *only* match when exactly "-zoom " is typed. We want it to match if the player types, for example, "-zoom 2000". Therefore this must be false.


An immediate issue is presented: this can only add an event for one player. We want the command to work for all players. We could copy and paste this, type something like: call TriggerRegisterPlayerChatEvent(t, Player(0) ...)
call TriggerRegisterPlayerChatEvent(t, Player(1) ...)
...
call TriggerRegisterPlayerChatEvent(t, Player(11) ...)


But that gets a little tiring. This looks like a perfect opportunity to practice looping. First note that Player(num) will get the player at slot num. In VJASS, this starts at zero. So, Player(0) returns red. We want to loop from zero, to the last player (here we'll say 11), adding this event for each player. Try writing the loop structure out yourself before looking at my solution. [Review loops if needed](L5TriggersBranch.md) since these are one of the absolute most important coding concepts and you must be good at writing them.

After writing the loop, place it in your function with your trigger. Compare yours with what I did.

My solution:
function onInit takes nothing returns nothing
    local integer i = 0
    local trigger t = CreateTrigger()
    loop
        exitwhen i > 11
        call TriggerRegisterPlayerChatEvent(t, Player(i), "-zoom ", false)
        set i = i + 1
    endloop
    call TriggerAddAction(t,function onZoom)
    set t = null
endfunction


I chose to write my trigger without a condition. We will see why soon.
So, if you wrote something like this, then your trigger will be set up correctly. All that's left to do is write the onZoom command.

Substrings:



Now in the actions, we need a way to check how much the user wants to zoom by. Let's take an example command: "-zoom 2000". In this case, the user wants to zoom by 2000. We need a way to extract this value from the user entered string, and for this we need a substring. Maybe you've used substrings in GUI. If so - VJASS substrings are different, so re-learn what to do here.

Substrings provide a way to separate a string into different parts. For example, we may separate this string into two parts: first the command "-zoom " and second, everything past that space which in this case is "2000".

The native for a substring is:

native SubString takes string source, integer start, integer end returns string

The arguments are:
    - source: the original string to separate
    - start: the character to start separating at
    - end: the character to end separation at
Note: characters are zero indexed. For example, the character "-" is at inded zero above.
The first thing we're going to do is map out each character in the string "-zoom 2000" to what the index is going to be.

-zoom 2000
"-" : index 0
"z" : index 1
"o" : index 2
"o" : index 3
"m" : index 4
" " : index 5
"2" : index 6
"0" : index 7
"0" : index 8
"0" : index 9
If we want to get the first part of the string "-zoom ", we start at index 0 and end at index 6 (not included).
If we want to get the second part of the string "2000", we start at index 6 and end at the end of the string. We can type "999" for this last index to get everything past this.
Therefore, the SubString commands will be like this:

local string src = GetEventPlayerChatString() // gets entered string
local string firstPart = SubString(src,0,6) // gets first part, for example "-zoom "
local string secondPart = SubString(src,6,999) // gets anything past the space, for example "2000"


After getting these parts of the string, the first thing we want to do is make sure that the user actually entered "-zoom " as the first part of the string. We already detected that they entered "-zoom " but that could be anywhere in the string. For example if the user types something like:

"If you want to zoom the camera, type -zoom (number)!"

They clearly don't want to zoom themselves, they're just explaining it. So, first check the first part and ensure that they want to zoom:

if(firstPart == "-zoom ")then

endif


Now inside the if we want to zoom the camera. The function for this is:

function SetCameraFieldForPlayer takes player whichPlayer, camerafield whichField, real value, real duration returns nothing

The arguments:
    - whichPlayer: The player to zoom for. In this case, the player who typed the message: GetTriggerPlayer()
    - whichField: What to adjust for the player. In this case, we want the camera distance: CAMERA_FIELD_TARGET_DISTANCE
    - value: What to adjust the distance to. We want to adjust to the user entered amount, converted to a real: S2R(secondPart)
    - duration: The time to adjust it over. Set to 0 for instant.


Now: combine all this together to get the final system. scope Zoom initializer onInit

    function onZoom takes nothing returns nothing
        local string src = GetEventPlayerChatString()
        local string firstPart = SubString(src,0,6)
        local string secondPart = SubString(src,6,999)
        call BJDebugMsg(firstPart)
        call BJDebugMsg(secondPart)
         if(firstPart == "-zoom ")then
             call BJDebugMsg("Activating")
            call SetCameraFieldForPlayer(GetTriggerPlayer(),CAMERA_FIELD_TARGET_DISTANCE,S2R(secondPart),0)
        endif
    endfunction

    function onInit takes nothing returns nothing
        local integer i = 0
        local trigger t = CreateTrigger()
        loop
            exitwhen i > 11
            call TriggerRegisterPlayerChatEvent(t, Player(i), "-zoom ", false)
            set i = i + 1
        endloop
        call TriggerAddAction(t,function onZoom)
        set t = null
    endfunction

endscope


And we're done!
No need to null strings like we do other objects.

To review:
- TriggerRegisterPlayerChatEvent for each player
- Get first and second part of command
- Ensure that the first part is what you expected
- Convert the second part to whatever data-type you need and so something with it.

Back to lessons