• 🏆 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!

Registering an event for a trigger

Status
Not open for further replies.
Level 15
Joined
Aug 7, 2013
Messages
1,337
Hi,

I have learned how to register a condition and set of actions to a trigger, but how does one register an event?

e.g.

JASS:
gg_trg_myTrig = CreateTrigger()
call TriggerAddCondition(gg_trg_myTrig, function myCondition1, ...)
call TriggerAddAction(gg_trg_myTrig, function myAction1, ...)

But how does one add an event that drives the trigger? My understanding is that the above trigger would just fire off every trigger cycle? Should there not be something like TriggerAddEvent(gg_trg_myTrig, event e1, ...) ?

For example, I want the above trigger to fire off only when a player enters a chat string.
 

Cokemonkey11

Spell Reviewer
Level 30
Joined
May 9, 2006
Messages
3,537
There are different kinds of events, and they don't accept handlers in the form of code or boolexpr

So, you must use one of the specific event natives.

In this case you want:

TriggerRegisterPlayerChatEvent

Edit: By the way, you can always use the GUI trigger editor to add an event from a list, and then use "Edit -> Convert to Custom Text" to see the jass used in the trigger.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
all events that can be registered to triggers are in form of functions(every one of them start with TriggerRegister)
one such function is:
call TriggerRegisterPlayerUnitEvent(trigger, EVENT_PLAYER_UNIT_DEATH)
Im not 100% sure about number of arguments, Im on mobile

the function above is the same as GUI event: Generic Unit - Any unit dies
 

Cokemonkey11

Spell Reviewer
Level 30
Joined
May 9, 2006
Messages
3,537
all events that can be registered to triggers are in form of functions
one such function is:
call TriggerRegisterPlayerUnitEvent(trigger, EVENT_PLAYER_UNIT_DEATH)
Im not 100% sure about number of arguments, Im on mobile

the function above is the same as GUI event: Generic Unit - Any unit dies

TriggerRegisterPlayerUnitEvent takes a specific player as an argument. To work for all players one should use TriggerRegisterAnyUnitEventBJ
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
TriggerRegisterPlayerUnitEvent takes a specific player as an argument. To work for all players one should use TriggerRegisterAnyUnitEventBJ

I figured it would be a bit more complete if there was an Event type.


If there is no call to a TriggerRegisterEvent function, then does this mean the trigger will fire off continuously every trigger cycle?

Does this call need to be the initialize function, or can you dynamically add events to triggers (and how about conditions and actions too?).
 

Cokemonkey11

Spell Reviewer
Level 30
Joined
May 9, 2006
Messages
3,537
I figured it would be a bit more complete if there was an Event type.

There is an event datatype, but it would be highly unusual that you'd want to keep track of them. I can only think of one example.

If there is no call to a TriggerRegisterEvent function, then does this mean the trigger will fire off continuously every trigger cycle?
If a trigger isn't associated with an event, it will never fire, unless the trigger is otherwise executed by TriggerEvaluate or TriggerExecute.

Does this call need to be the initialize function, or can you dynamically add events to triggers (and how about conditions and actions too?).

You can dynamically add events, conditions, and actions to triggers; this is called dynamic triggering.

It's easy to mess up dynamic triggering if you're not experienced with it, and there are some (unavoidable I think?) leaks associated with it, so in most cases it's recommended to avoid this.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
you can dynamically create, destroy triggers, add conditions, remove conditions, add actions, remove action, add events. You cannot remove events dynamically, thats why its hard to make efficient DDS, because you need to destroy the trigger and create it again and add events for all units back to the trigger

if you never call TriggerRegister...., the trigger never runs from events, and can only be run by TriggerEvaluate(trigger) and TriggerExecute(trigger)

edit: coke, you post again like less than 1 minute before me :D

also, event datatype is returned from function TriggerRegister... but they are pretty much useless
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
you can dynamically create, destroy triggers, add conditions, remove conditions, add actions, remove action, add events. You cannot remove events dynamically, thats why its hard to make efficient DDS, because you need to destroy the trigger and create it again and add events for all units back to the trigger

if you never call TriggerRegister...., the trigger never runs from events, and can only be run by TriggerEvaluate(trigger) and TriggerExecute(trigger)

edit: coke, you post again like less than 1 minute before me :D

also, event datatype is returned from function TriggerRegister... but they are pretty much useless

I am confused then because this trigger works but I didn't do TriggerRegisterPlayerChatEvent...

JASS:
scope TestCommands initializer init

globals
    private player owner
    private integer length
    private string curS
    private integer val
endglobals

private function main takes nothing returns nothing
    //Var Setting
    set owner = GetTriggerPlayer()
    set curS = StringCase(GetEventPlayerChatString(), false)
    set length = StringLength(curS)

    //hello
    if curS == "-hello" then
        call DisplayTimedTextToPlayer(owner, 0, 0, 15, "-hello success!")
    endif
endfunction

private function init takes nothing returns nothing
    set gg_trg_TestCommands = CreateTrigger()
    call TriggerAddAction(gg_trg_TestCommands, function main)
endfunction

endscope
 
I am confused then because this trigger works but I didn't do TriggerRegisterPlayerChatEvent...

There's probably another GUI trigger entitled "TestCommands" with the event already there.

you should be using local triggers inside scopes/libraries.

JASS:
local trigger t = CreateTrigger()
call TriggerRegisterSomeEvent(t)
call TriggerAddAction(t, code)
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
There's probably another GUI trigger entitled "TestCommands" with the event already there.

you should be using local triggers inside scopes/libraries.

JASS:
local trigger t = CreateTrigger()
call TriggerRegisterSomeEvent(t)
call TriggerAddAction(t, code)

Not to my knowledge does such a GUI trigger exist--the entire map is in JASS. I can change the name of the trigger to any random string, and it still runs...

Thank you for the the heads up on using local triggers.
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
Paste your scope from above into an empty map and you can verify that another script is interfering with it.

If you change the trigger to a local instead of gg_... it should also fix the problem, since the local trigger won't have any events added to it externally.

What is the difference between making a local trigger and giving it any old name versus the prefix "gg_trg_myTrig?"
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
gg_trg_mytrig is a global trigger variable made with each trigger you add in the trigger editor.
It also has a minor leak as it is not nulled if only used once to register.
The only time you need a global trigger is when you want to turn on the trigger after it is turned off.

Also don't use triggerAddAction. It creates a new thread every time it is run.

Use triggerAddCondition and run everything from there.
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
gg_trg_mytrig is a global trigger variable made with each trigger you add in the trigger editor.
It also has a minor leak as it is not nulled if only used once to register.
The only time you need a global trigger is when you want to turn on the trigger after it is turned off.

Also don't use triggerAddAction. It creates a new thread every time it is run.

Use triggerAddCondition and run everything from there.

Well surely the thread will eventually terminate, so how is that a bad thing. More threads would speed up computations wouldn't they?

Also I tried this native native TriggerRegisterPlayerChatEvent but it asks for too many arguments--I just want the trigger to fire when a player enters any string into the chat. What's the way to do that?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Like this.

JASS:
        call TriggerRegisterPlayerChatEvent( t, Player(0), "Message", true)

There is nothing to compute.
It is like this.
thread one
examine condition. If true start thread 2.
It is pointless to start a new thread for that since thread 1 ends right after thread 2 starts. I believe TriggerAddAction also does trigger evaluations when it is run. TriggerAddCondition does not.
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
Like this.

JASS:
        call TriggerRegisterPlayerChatEvent( t, Player(0), "Message", true)

There is nothing to compute.
It is like this.
thread one
examine condition. If true start thread 2.
It is pointless to start a new thread for that since thread 1 ends right after thread 2 starts. I believe TriggerAddAction also does trigger evaluations when it is run. TriggerAddCondition does not.

Then what was the reasoning behind such an architecture?

But in your example, wouldn't this only fire off if what the player types in is == "Message" ?

Also, how do we handle triggering with multiple events, e.g.
e1. A player enters message equal to "-info"
AND
e2. A player selects a unit

Do we just make two calls, e.g. TriggerRegisterEvent1(trig, ...) and TriggerRegisterEvent2(trig, ...)?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
The player chat message above is for player 1 red, the true means exact match. the string Message is the string it looks for.

You can add multiple trigger events to one trigger but not in the manner you want.
Example i also want to add player 2 blue to that trigger i do this.
JASS:
call TriggerRegisterPlayerChatEvent( t, Player(0), "Message", true) // Player 1 red
call TriggerRegisterPlayerChatEvent( t, Player(1), "Message", true) // Player 2 blue

To do something like you want you need 2 triggers and a timer / boolean array.
When player enters the chat message you change boolean to true and activate a timer.
When timer ends say .5 seconds set boolean to false.
In the select unit event check if it is the unit you want and that the triggering player is the player you want then check if boolean is true and do the actions.

There are 2 ways to add the unit selected event. One is for all units. ( You have to filter out the ones you do not want.)
One is for a specific unit. The one for the specific unit is for that unit so if you use a unit variable it will not work since variables are only pointers. It registers that specific unit. The registered unit must be created before the trigger register event is called.
JASS:
        call TriggerRegisterUnitEvent( t, specificUnit, EVENT_UNIT_SELECTED)
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SELECTED)
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
The player chat message above is for player 1 red, the true means exact match. the string Message is the string it looks for.

You can add multiple trigger events to one trigger but not in the manner you want.
Example i also want to add player 2 blue to that trigger i do this.
JASS:
call TriggerRegisterPlayerChatEvent( t, Player(0), "Message", true) // Player 1 red
call TriggerRegisterPlayerChatEvent( t, Player(1), "Message", true) // Player 2 blue
)[/code]


Thank you for answering my questions. I just fail to understand how the developer's original set up for commands worked, because I can't find TriggerRegisterPlayerChatEvent anywhere in this library.

JASS:
scope Commands initializer init

globals
    private player owner
    private integer length
    private string curS
    private integer val
endglobals

private function main takes nothing returns nothing
    //Var Setting
    set owner = GetTriggerPlayer()
    set curS = StringCase(GetEventPlayerChatString(), false)
    set length = StringLength(curS)

    //Roll
    if curS == "-roll" then
        call Roll_exec(owner)

    //Clear
    elseif curS == "-clear" or curS == "-cls" then
        if owner == GetLocalPlayer() then
            call ClearTextMessages()
        endif

    //Ping
    elseif curS == "-ping" then
        call Ping_exec(owner)
    
    elseif curS == "-hello" then
        call DisplayTimedTextToPlayer(owner, 0, 0, 20, "success!!")
    
    //Monsterinfo
    elseif SubString(curS, 0, 12) == "-monsterinfo" or SubString(curS, 0, 3) == "-mi" then
        call MonsterInfo_exec(owner, curS, length)
    
    //New & Repick
    elseif curS == "-new" or curS == "-repick" then
        call NewCommand_exec(owner)
    
    //Take Monster
    elseif SubString(curS, 0, 3) == "-tm" then
        set val = S2I(SubString(curS, 3, length)) - 1
        call Court_take(owner, val)
    
    //Remove Monster
    elseif SubString(curS, 0, 7) == "-remove" then
        set val = S2I(SubString(curS, 7, length)) - 1
        call Court_remove(owner, val)
    
    //Swap Slot
    elseif SubString(curS, 0, 5) == "-swap" then
        call Court_swap(owner, curS)
    
    //PvP
    elseif curS == "-pvp" then
        call PvPStart_exec(owner)
    
    //PvC
    elseif curS == "-pvc" then
        call PvCStart_exec(owner)
    
    //Save
    elseif curS == "-save" then
        call SaveSys_exec(owner)
    
    //Load
    elseif SubString(curS, 0, 5) == "-load" then
        call LoadSys_exec(owner, GetEventPlayerChatString())

    //Afk
    elseif curS == "-afk" then
        call AfkSys_exec(owner)

    //Tutorial
    elseif curS == "-tutorial" or curS == "-tut" then
        call Tutorial_exec(owner)

    endif
endfunction

private function init takes nothing returns nothing
    set gg_trg_Commands = CreateTrigger()
    call TriggerAddAction(gg_trg_Commands, function main)
endfunction

endscope
 

Cokemonkey11

Spell Reviewer
Level 30
Joined
May 9, 2006
Messages
3,537
Then what was the reasoning behind such an architecture?

It is necessary for GUI triggers to be able to split the script into three sections, events, conditions, and actions, so by association we have access to all three as well.

While seemingly trivial, there are a few important concepts going on here.

  • There are no such thing as threads in warcraft III because warcraft III is single-threaded.
  • There is still a synchronous nature required by boolexprs and an asynchronous nature available to codes. In modding we refer to these as "thread", but really we should be saying something like "pseudo-thread of execution".
  • code threads, started by ExecuteFunc() or TriggerAddAction() support the TriggerSleepAction() native, which causes the thread to wait some number of seconds before going to the next line.
  • Sleep actions in JASS are highly inaccurate because they're handled by the interpreter. They are NOT a replacement for timers like they can be in real programming languages. There are some rules associated with sleep.
    1. The minimum applicable wait time is ~100ms (0.1s).
    2. Wait values level of imprecision increases as the wait duration increases (IIRC, around +/- 10%)
    3. Wait durations are not "paused" during game pause events like opening a menu in single player. This means if you "wait 30 seconds" then open the menu in game for 30 seconds, then close the menu, the wait duration will immediately end.
  • By these rules, you can see that the value of TriggerSleepAction are few and far between, and therefore almost exclusively avoided in favor of timers.
  • Each pseudo-thread of execution is bound to an "op-limit". An op-limit is a value, hidden to us, which counts the number of operations performed by a thread and interrupts it (without error handling) if the value becomes too high - designed to be infinite loop safety. Each native requires different amount of "ops" for the "op-limit", and it's hard to put a definite value on them, but we do know the number is high, but countable.
  • Op-limits can be reset by waiting (TriggerSleepAction()) or using ExecuteFunc()

I hope this explanation was concise as it was complete. There are some things missing here, like the limitations on ExecuteFunc() and the affects of TriggerEvaluate and TriggerExecute, but I hope you see why using conditions instead of actions is almost always preferred.

@DIMF I don't think AddCondition works in vJASS, since Jasshelper threw an error at me when I transfered to JNGP while not using AddAction..

It works, you just have to make sure the condition function follows the pattern:

JASS:
function c takes nothing returns boolean

    return false // Necessary to make JassHelper happy, but not necessary in plain JASS
endfunction
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
A few things to add Coke. The op limit has been found along with each amount of bytecode used by each function call in GUI. Although it is mainly pointless it is out there i forget link to it. I believe it is on Wc3c.net but not entirely sure.

Another thing to note is waits.
While the things you said about it are mostly correct except one thing.
The accuracy and minimum timeout.
The accuracy is very bad with lower wait times. But gets better with higher timeouts. Tested by Apocalypse
Take a look at this. For full example.
http://www.hiveworkshop.com/forums/2284304-post9.html

The minimum wait time on Multiplayer is .225
Single player is about .075
But these values are almost constant not increase by percent.
A 5 second wait takes about 5.075 on single player which is not that bad.

Also +rep and thanks for correct my wording and providing a better explanation to the other stuff i could not.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
well, yes, wc3 is single threaded app, but that doesnt mean you cant have more threads, it just has to force the current running to pause and execute new one
Im not saying this is right, just a opinion, you know you can have application with 150 active threads yet your CPU only supports 4 concurent threads

also you said Sleep is handled by interpreter, but arent timers too?

and just to add to the callback to Condition/Filter functions, they must return boolean, but you can trick JassHelper by setting code to the function, which returns nothing, and call Condition(code), the return value is defaulted to false
 
Level 15
Joined
Aug 7, 2013
Messages
1,337
well, yes, wc3 is single threaded app, but that doesnt mean you cant have more threads, it just has to force the current running to pause and execute new one
Im not saying this is right, just a opinion, you know you can have application with 150 active threads yet your CPU only supports 4 concurent threads

also you said Sleep is handled by interpreter, but arent timers too?

and just to add to the callback to Condition/Filter functions, they must return boolean, but you can trick JassHelper by setting code to the function, which returns nothing, and call Condition(code), the return value is defaulted to false

Is that the standard approach these days?

Also, so it's not possible for a trigger to run without an event of some kind? Just to make sure, because I have yet to find the TriggerRegisterPlayerChatEvent for the previous developer's code.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Also, so it's not possible for a trigger to run without an event of some kind? Just to make sure, because I have yet to find the TriggerRegisterPlayerChatEvent for the previous developer's code.

A trigger without any events will not run this is correct.

Also a pc has a lot more than 4 threads currently running at a time. But i get your point about how it uses a queue and forces the next thread to run.
 

Cokemonkey11

Spell Reviewer
Level 30
Joined
May 9, 2006
Messages
3,537
well, yes, wc3 is single threaded app, but that doesnt mean you cant have more threads, it just has to force the current running to pause and execute new one

Im not saying this is right, just a opinion, you know you can have application with 150 active threads yet your CPU only supports 4 concurent threads

Yes, these are real threads being managed by a real operating system. The "pseudo-threads-of-execution" are just handled one at a time in arbitrary order by the interpreter.

also you said Sleep is handled by interpreter, but arent timers too?

Yes, but sleep is handled by the interpreter in what we suspect to be a similar fashion as a real binary program would handle sleep - literally "do nothing until the next cycle" - in this case though the cycles aren't CPU cycles so their behavior is erratic.

I'm not sure if timers are handled by the interpreter or not, actually.

and just to add to the callback to Condition/Filter functions, they must return boolean, but you can trick JassHelper by setting code to the function, which returns nothing, and call Condition(code), the return value is defaulted to false

Yes, but some versions of jasshelper will complain if you don't return false. That's what Wrathion was talking about.

Is that the standard approach these days?

Yes. Most, if not all, public vjass resources don't even use TriggerAddAction.

Also, so it's not possible for a trigger to run without an event of some kind? Just to make sure, because I have yet to find the TriggerRegisterPlayerChatEvent for the previous developer's code.

Not possible. Like I said, copy paste the scope into an empty map and you'll see it's never run.
 
Status
Not open for further replies.
Top