| 02-01-2006, 01:28 PM | #1 |
Triggers in JASS This tutorial is about triggers in JASS. I am expecting you to know JASS' syntax already, reading my previous JASS tutorial would be enough. You can call this Tutorual the sequel of the previously named tutorial. That was a great tutorial for understanding all of the theory behind JASS and, I accept it, programming. This tutorial is more about applying the knowledge you got in that tutorial. But you can learn from this tutorial as long as you understand JASS' syntax and don't know about the stuff I am gonna talk about here. What is this tutorial about? This section covers explanations about the objective of the tutorial. Because of blizzard's way of calling things the title "Triggers in JASS" might be ambiguous.
In few words this tutorial is about using the trigger object in JASS thus allowing you to actually do stuff in JASS instead of having to use GUI for everything. Because you edit the JASS script directly you have a lot more of freedom and less limitations than using GUI. Getting started What do you need? you would need some sort of text editor. I use notepad++, you could use others like textpad or the notepad included in windows. You can also try JASS shop pro. You need access to common.j and blizzard.j functions. I have them open in notepad++, you can use JASS shop pro to browse for them or the API browser at http://jass.sourceforge.net/doc/ . Let's exploit the editor So the world editor's triggers are sections of code that include a function that is automatically executed on map initialization. And also a global variable. We are going to create one of them, this is easy, go to the Trigger Editor then choose New then click Trigger. You will be able to set a name for it. Let us give it the name testtrig. It will show something like this: Trigger: testtrig![]() Events![]() Conditions![]() ActionsNow we have to go to Edit\Convert to custom text. Now we have something decent out there. From now on this is the process called "Create a World Editor trigger". It will JASS text like this: Code:
function Trig_testtrig_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_testtrig takes nothing returns nothing
set gg_trg_testtrig = CreateTrigger( )
call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions )
endfunction
I will begin to use the [jass] tag because it is easier to read, don't expect world editor to show the fancy syntax highlighting JASS:function Trig_testtrig_Actions takes nothing returns nothing endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) endfunction It is time to face the truth, World editor isn't robust and if you use JASS it may crash for just the tinyest syntax error sometimes. So it is time to get used to use a text editor everytime you want to change any JASS stuff inside your map. And save the scripts externally too. So you have to select all the text in the WE trigger and copy it to your favorite text editor. If you are lucky your text editor is ready to highlight JASS syntax in a way like the [jass] that is not required though. Being slow I've been? Well the introduction is the most important part. It is time to explain. I will start advancing some topics that are important before understanding the JASS script world editor just generated for us. I. The code type You certainlly understand the integer, boolean, string and real types already. There is another important type called code. code is an important part of JASS syntax. You would certainlly note it when checking the argument list of TriggerAddAction for example: JASS:native TriggerAddAction takes trigger whichTrigger, code actionFunc returns triggeraction code values The code type is used to store "pointers to functions" you would have to pass to some natives in order to make them know what function to call when they need to. A value of type code consists of the syntax word function followed by the function's name. JASS:call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) note the function Trig_testtrig_Actions . The reason to add the function keyword is to let whatever parses the script know that you are not talking about a variable but a function. JASS:call TriggerAddAction( gg_trg_testtrig, Trig_testtrig_Actions ) This would be refering to a variable called Trig_testtrig_Actions instead of a code value. Another allowed value for the code type is null. Note that the function must be declared before you try to use it as a value for 'code'. Most likelly the function must be Above in the script or in another script file that is loaded before your map's script, like a custom blizzard.j for example. You can actually use natives as code values but that's probably useless. And in order to be used as a value for code , the function must take NO ARGUMENTS. code variables - arguments - return values Just an informative place in case you didn't note that as magical as the code type is it can be used like any other types JASS://global variable: globals code barggaa = null code K = function thisfunction_is_somehow_above_this_declaration endglobals //local variable: local code name = value //Argument / return value function TheCodeFunction takes code A returns code local code B= function thisfunction_is_somehow_above_this_declaration if (GetRandomInt(0,1)==0) then return (A) endif return (B) endfunction II. Boolexprs If the code type is used to tell natives what function to call some time. Boolexprs are actually "pointers to functions that return boolean", Sometimes the function used by the native is used as a condition that the native evaluates. There are some issues with Boolexprs, they are ambiguous too. Cause for some reason there are 2 types that are derived from them, you would note in common.j: JASS:type boolexpr extends handle //... type conditionfunc extends boolexpr type filterfunc extends boolexpr What's the difference between conditionfunc, filterfunc and boolexpr? The answer is none. In fact I will focus on talking about boolexpr here. Because all the natives take boolexpr arguments. Although some natives return conditionfunc or filterfunc, you won't have any problem if you use boolexpr directly. Create a boolexpr JASS:native Condition takes code func returns conditionfunc So you would have to use Condition(function Some_function) or soemthing like that in order to create a boolexpr. Yes it returns conditionfunc but it can be used freely as boolexpr since conditionfunc is a type derived from boolexpr [/b]Destroy a boolexpr[/b] JASS:native DestroyBoolExpr takes boolexpr b returns nothing What? I was feeling that this theory was required before going into usage of triggers. Sorry if it was kind of boring or obvious. III. The trigger object We will get back to the script we got. JASS:function Trig_testtrig_Actions takes nothing returns nothing endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) endfunction As I said a WE trigger consists of a block of code that includes a function that is executed automatically on map initialization and a global variable. The names of the function and the global variable depend on the name of the World editor trigger, if you change that name it will just change the name of the global variable and the init function. I forgot to say that a World Editor trigger also has a checkbox that allows you to make world editor set the map script so it automatically executes the trigger pointed by the variable after all the init functions of all the triggers were called. This option is called "run on map initialization" In this case it is clear that the init function is InitTrig_testtrig and the global variable is gg_trg_testtrig. In other words the InitTrig_testtrig will automatically be execute when the map starts. The rest is easy to understand once you now about the trigger object. What is an event? An event is a condition you add to the trigger to tell the game to execute the trigger whenever that event happens. Events are optional. A trigger without events may exist and it would be used probably by TriggerExecute , ConditionalTriggerExecute or maybe the script would add events to the trigger later. A trigger may have many events and only one of them is required to trigger in order to make the trigger execute What is a condition? A condition is a function that is automatically called and evaluated by the game when an event triggers, if the function returns true then it will execute the trigger, otherwise it will not execute the trigger. Conditions are optional too, a trigger without conditions will simply execute automatically when an event triggers or if the trigger is executed. What is an action? An action is a function that is called by the trigger when the trigger is executed. Actions are optional too, because of natives like GetTriggerEvalCount or GetTriggerExecCount , you could have a trigger and use it as a counter to know how many times an event has happened. Create a trigger JASS:native CreateTrigger takes nothing returns trigger This is the native that creates a trigger for you and returns a reference (pointer) for you. You should assign this to a variable actually. In the case of our very own script code it is set gg_trg_testtrig = CreateTrigger() It is creating a trigger then making gg_trg_testtrig point to the new object. Action! JASS:native TriggerAddAction takes trigger whichTrigger, code actionFunc returns triggeraction JASS:call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) To the already created trigger we are adding the function Trig_testtrig_Actions as the function to be called by the trigger when the trigger is executed. What? We currently have a function that is called during map initialization that creates a trigger for us, a trigger that does absolutelly nothing and is never executed. What a waste of time, all the text I typed and the lesson has not even started yet. So let's just make this work. First of all remove the "Melee Initialization" trigger in case it is still there. Change the description of the map to trigger tests. And let's continue. Add Events to your trigger Triggers wouldn't be as useful if it wasn't because of events. JASS would be an extremely disabled scripting language if it wasn't because of triggers' events. The definition of event is somewhere above. But the only way to tell the game that you'd want to execute a trigger when XXXX thing happens is to use a function to register the event . I will have to say something, Long ago when I was just starting I knew about JASS and how to use the "Custom Script:" option in GUI also how to add functions to the custom script section. I was reluctant to start using JASS seriously and it was because event registering seemed like chinesse. If you want to use JASS seriously you have to get used into registering events . They are just functions, nothing more. But of course because there are so many player and unit events there are many variations. In order to make JASS work for you I will have to give some examples of events and specially tell a way to figure out what event to use. Enjoy. The press escape event
JASS:native TriggerRegisterPlayerEvent takes trigger whichTrigger, player whichPlayer, playerevent whichPlayerEvent returns event That's the first function you would get if you take a look in common.j and try to find functions that seem to be registering player events. Note that this function requires 3 arguments, first one if the trigger, second one the player, and 3rd one is a playerevent. JASS has many predefined types that were actually made just as helpers for function arguments, I like to call these 'constant handles' but the name is not relevant for this tutorial. We have to figure out what to put under whichPlayerEvent , lucky for us blizzard comments common.h JASS://=================================================== // For use with TriggerRegisterPlayerEvent //=================================================== constant playerevent EVENT_PLAYER_STATE_LIMIT = ConvertPlayerEvent(11) constant playerevent EVENT_PLAYER_ALLIANCE_CHANGED = ConvertPlayerEvent(12) constant playerevent EVENT_PLAYER_DEFEAT = ConvertPlayerEvent(13) constant playerevent EVENT_PLAYER_VICTORY = ConvertPlayerEvent(14) constant playerevent EVENT_PLAYER_LEAVE = ConvertPlayerEvent(15) constant playerevent EVENT_PLAYER_CHAT = ConvertPlayerEvent(16) constant playerevent EVENT_PLAYER_END_CINEMATIC = ConvertPlayerEvent(17) These are just some of the constants of the type playervent that are declared in common.j . So playerevent is just a type that holds the different kinds of player events you can use. After looking and looking the only one that seems to be what we are looking for is EVENT_PLAYER_END_CINEMATIC , because we know that ESC is used to skip cinematics, let's try it then. Let's use the trigger registering native. We only needed 3 things, the trigger (we already have one), the player (what?) and the playerevent (we now know that we have to use EVENT_PLAYER_END_CINEMATIC ) What to put for player? For the moment there is only one thing you need to know about players and it is that to get a good reference to a player you only need the function: JASS:constant native Player takes integer number returns player The number starts from 0, so Player(0) is Player 1, Player(1) is Player 2 and so and so. We will start simple, we want to display a message whenever Player 1 presses Escape. I am going to use the DisplayTextToPlayer function JASS:function Trig_testtrig_Actions takes nothing returns nothing call DisplayTextToPlayer(Player(0),0,0,"You just pressed ESC") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) call TriggerRegisterPlayerEvent(gg_trg_testtrig,Player(0), EVENT_PLAYER_END_CINEMATIC) endfunction Paste the code in your World Editor Trigger and press test map. Whenever you press ESC it will show the silly message. The "unit starts the effect of spell" event
JASS:
constant playerunitevent EVENT_PLAYER_UNIT_SPELL_CHANNEL = ConvertPlayerUnitEvent(272)
constant playerunitevent EVENT_PLAYER_UNIT_SPELL_CAST = ConvertPlayerUnitEvent(273)
constant playerunitevent EVENT_PLAYER_UNIT_SPELL_EFFECT = ConvertPlayerUnitEvent(274)
constant playerunitevent EVENT_PLAYER_UNIT_SPELL_FINISH = ConvertPlayerUnitEvent(275)
constant playerunitevent EVENT_PLAYER_UNIT_SPELL_ENDCAST = ConvertPlayerUnitEvent(276)
These are the playerunitevent that seem to have a relationship with spells being casted. Which of these ones to use? Common knowledge will say: EVENT_PLAYER_UNIT_SPELL_CHANNEL : useless, ignore it EVENT_PLAYER_UNIT_SPELL_CAST : To detect the moment before a unit spends mana/cooldown and is about to cast a spell EVENT_PLAYER_UNIT_SPELL_EFFECT : The exact moment a unit casts the spell and consumes mana/cooldown EVENT_PLAYER_UNIT_SPELL_FINISH : The unit has succesfully ended casting the spell EVENT_PLAYER_UNIT_SPELL_ENDCAST: The unit stopped casting the spell (either succesfully or not) So we would want EVENT_PLAYER_UNIT_SPELL_EFFECT . The conclusion: JASS:function Trig_testtrig_Actions takes nothing returns nothing call DisplayTextToPlayer(Player(0),0,0,"A unit is casting a spell!") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ( gg_trg_testtrig, EVENT_PLAYER_UNIT_SPELL_EFFECT) endfunction Now preplace a bunch of units with spells , make sure they can cast the spells and use test map. Make one of them cast a spell and read the message. End of Events You now know that you require functions to register events for triggers. Note that you won't always find an event to detect what you want, and sometimes the event names are not as precise as you would like. TriggerRegisterUnitEvent works the same as TriggerRegisterPlayerUnitEvent but it works only if the specified unit causes the event and also uses other events that start with EVENT_UNIT_ instead of EVENT_PLAYER_UNIT_ note that some EVENT_UNIT_ events don't have EVENT_PLAYER_UNIT_ equivalents and viceversa. Note that there are other kinds of events like gamestateevents and some that are of importance, timer events. Map initialization is not an event it is a flag you add to a trigger so world editor makes the script execute the trigger automatically after initialization has finished. Event Responses An event response is a function that returns something that has a relationship with the "triggering event" . Note that all the functions called by a trigger that was under the effect of an event can use these Event Responses. GetTriggerUnit When you are handling a unit event. You would have to know what unit triggered the event, right?. The general event response used by those events is GetTriggerUnit: JASS:// returns handle to unit which triggered the most recent event when called from // within a trigger action function...returns null handle when used incorrectly constant native GetTriggerUnit takes nothing returns unit Let's apply it, since we are using text messages, we would want to display the name of the unit that is casting the spell. Let's us use GetUnitName JASS:function Trig_testtrig_Actions takes nothing returns nothing call DisplayTextToPlayer(Player(0),0,0,GetUnitName(GetTriggerUnit())+" is casting a spell!") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ( gg_trg_testtrig, EVENT_PLAYER_UNIT_SPELL_EFFECT) endfunction Finally something that seems like a trigger. Trigger Conditions It is time to learn how to add a condition to a trigger. Let's browse common.j you would find this: JASS:native TriggerAddCondition takes trigger whichTrigger, boolexpr condition returns triggercondition So we need a trigger and a boolexpr, the boolexpr points to the function that will be evaluated before executing the trigger, that function should return true. Note that this native does create a triggercondition object. Horrible but true, if you plan destroying a trigger you will need to remove the condition too. As a matter of fact the desctruction or not of the boolexpr is something to worry about too. But that's something we will take care of later. We first need a boolexpr. The boolexpr needs a function that returns boolean. So we will make it. Let's say we want the trigger to only work if the name of the unit is bob. So pick the object editor and change the name of the archmage to bob , keep it lower case. Then place a bob unit in the map and make sure he has enough mana to cast spells. Also please any other unit that can cast spells, and make sure both are owned by Player 1. JASS:function TheNameCondition takes nothing returns boolean return (GetUnitName(GetTriggerUnit())=="bob") //simple boolean returning function endfunction function Trig_testtrig_Actions takes nothing returns nothing call DisplayTextToPlayer(Player(0),0,0,GetUnitName(GetTriggerUnit())+" is casting a spell!") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) //Note that We are directly making the boolexpr, saves us some time: call TriggerAddCondition( gg_trg_testtrig, Condition(function TheNameCondition)) call TriggerRegisterAnyUnitEventBJ( gg_trg_testtrig, EVENT_PLAYER_UNIT_SPELL_EFFECT) endfunction Threads and multi instances I accidentally said so before "Triggers also create new threads" this is going to be completelly crazy. But I think it is vital to understand threads in order to understand the rest. After testing this thing with units casting abilities you may have noted that many units can cast abilities and the trigger works everytime. It is not crazy like in starcraft where you had to add preserve trigger to every single trigger in order to stop it from self destructing. It is not that important until you start using waits in triggers. JASS:native TriggerSleepAction takes real delay returns nothing IS the function to make triggers wait some time before continuing, there is also PolledWait which makes sure to wait game-time seconds, but it internally uses TriggerSleepACtion Let's say a trigger has something called [i]thread[i]. I won't explain directly what a thread is, mostly because I don't know either. I think the word thread is a metaphore like most of the programming world's words. What TriggerSleepAction does is stop the trigger's thread, then look for other pending threads, in case one of the pending thread check if the thread's sleep time has ended continue on that thread's actions till the thread sleeps again , passes execution to another thread or ends. And so and so till all other threads are sleeping or finished and the trigger's thread's sleeping time has ended. For the moment we will try a little experiment with waits. Let's make a trigger that shows numbers from 1 to 50 each time a unit casts a spell. To make it more interesting it will not only show a number but also the unit's name. We will wait 0.1 seconds before showing a new message. JASS:function Trig_testtrig_Actions takes nothing returns nothing local integer i=1 loop exitwhen (i>50) call DisplayTextToPlayer(Player(0),0,0,GetUnitName(GetTriggerUnit())+" : "+I2S(i)) call TriggerSleepAction(0.1) set i=i+1 endloop endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ( gg_trg_testtrig, EVENT_PLAYER_UNIT_SPELL_EFFECT) endfunction Place 2 units of different unit types and able to cast spells in the map. Test the map and make one cast a spell then the other one should do so. The result is predictible it will show something like this: archmage 1 bloodmage 1 archmage 2 bloodmage 2 archmage 3 bloodmage 3 ... Have fun and try with 3 or more units. As you can see different instances of the same trigger execute simultaneously, GetTriggerUnit() behaves differently depending on the instance. Note that if you were using a global variable instead of that i local variable the result would have been much different. This time create an i variable with the variable editor so it is a global: JASS:function Trig_testtrig_Actions takes nothing returns nothing loop exitwhen (udg_i>50) call DisplayTextToPlayer(Player(0),0,0,GetUnitName(GetTriggerUnit())+" : "+I2S(udg_i)) call TriggerSleepAction(0.1) set udg_i=udg_i+1 endloop endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing set udg_i=1 set gg_trg_testtrig = CreateTrigger( ) call TriggerAddAction( gg_trg_testtrig, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ( gg_trg_testtrig, EVENT_PLAYER_UNIT_SPELL_EFFECT) endfunction The same variable will be used by all the instances of the trigger, so it will show the numbers from 1 to 50 but the units will be different and it will show each number once. It is a problematic topic cause how would you have values that work in different functions but are not that global and work well for each instance? That is a problem for a tutorial about handle variables or attacheable variables. TriggerExecute Events are not required by a trigger to be executed. You can use other triggers or timers or the main function to execute a trigger from it. The function we use to do so is TriggerExecute: JASS:native TriggerExecute takes trigger whichTrigger returns nothing We are going to join the strange world of Executing trigger. But first we need 2 triggers instead of just one. Let's make a very simple trigger that shows an "Another trigger" message. The original trigger has to be fixed too, we will use the global variable for the new trigger instead of the one we are using right now, because in order to execute a trigger you need to have a pointer to that trigger, which would most of the times be in the form of a variable. - Step 1: Make the first trigger use a local instead of the global variable. JASS:function Trig_testtrig_Actions takes nothing returns nothing //removed the old test's contents endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddAction(t, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set t=null endfunction Set to null? That is a needed step. To be short all local variables of types derived from handle have to set to null once their use in a function has ended, otherwise they leak memory somehow. Take a look to the top of common.j all the types that have extends handle in their declaration are handle derived types. (In this case it is not a needed step because it is little memory that leaks just once, but setting things to null is a habit I built after using JASS for a long time=) - Step 2: Add the new trigger, assign it to the global gg_trg_testtrig variable. JASS:function Trig_testtrig_Actions takes nothing returns nothing call TriggerExecute(gg_trg_testtrig) endfunction function ToExecute takes nothing returns nothing call DisplayTextToPlayer(Player(0),0,0,"another trigger") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddAction(t, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set t=null set gg_trg_testtrig=CreateTrigger() call TriggerAddAction(gg_trg_testtrig,function ToExecute) endfunction First thing to note: It doesn't matter that the ToExecute function is bellow the function that executes the trigger. Cause the function that adds the action to the trigger is bellow the function already. This lame example will just show "another trigger" each time a unit casts a spell. Note that although the t variable is local, the Trigger object persists once the function stops, this is because handle derived variables are always pointers (they hold references to objects instead of objects themselves) . Threads and TriggerExecute Every time a trigger is executed it creates a new thread for that instance. So TriggerExecute does create its own thread. The next example is a typical one I must have used 15 times already to explain how TriggerExecute creates a thread. I am going to use BJDebugMsg for the text. JASS:function Trig_testtrig_Actions takes nothing returns nothing call BJDebugMsg("A") call TriggerExecute(gg_trg_testtrig) call BJDebugMsg("B") call TriggerSleepAction(4) call BJDebugMsg("C") endfunction function ToExecute takes nothing returns nothing call BJDebugMsg("D") call TriggerSleepAction(2) call BJDebugMsg("E") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddAction(t, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set t=null set gg_trg_testtrig=CreateTrigger() call TriggerAddAction(gg_trg_testtrig,function ToExecute) endfunction Will it show A B E C D , A B E C D, A B D E C or A D B E C ? It shows A D B E C . - The first line that gets execute is BJDebugMsg("A") - TriggerExecute(gg_trg_testtrig) makes it go to the trigger object which will call the ToExecute function with another thread. TriggerExecute changes the thread to the other trigger's one until that thread sleeps or ends, then goes back to the thread that called it. - Because we are in the second trigger's thread now the next call is BJDebugMsg("D") - Now it calls TriggerSleepAction(2) , this means this thread will sleep for 2 seconds. Since second trigger's thread is sleeping warcraft III will go back to the previous thread, and continue moving on. - BJDebugMsg("B") is the line now, it will show B - TriggerSleepAction(4) - Make the thread sleep for 4 seconds. - warcraft III will now keep looking for threads that can be executed. Eventually - more than 2 seconds later depending on the number of threads that were pending - will get back to second trigger's thread and call BJDebugMsg("E") . - The thread will be finished after that and warcraft III will look for threads that could continue, 2 seconds later it wil find the first trigger's thread.. - call BJDebugMsg("C") Conditions and TriggerExecute First thing you would note after using TriggerExecute for a long time is that it ignores conditions. This is because conditions were meant for events. But you can make sure it evaluates conditions before executing the trigger. This is done by a native function TriggerEvaluate: JASS:native TriggerEvaluate takes trigger whichTrigger returns boolean This function calls the trigger's condition and returns the value the condition returned. (if no condition is present it returns true automatically). Of course it could be kind of redundant to always check the condition so you can use this bj function: JASS://=========================================================================== // Runs the trigger's actions if the trigger's conditions evaluate to true. // function ConditionalTriggerExecute takes trigger trig returns nothing if TriggerEvaluate(trig) then call TriggerExecute(trig) endif endfunction JASS:function Trig_testtrig_Actions takes nothing returns nothing call BJDebugMsg("First Trigger") call ConditionalTriggerExecute(gg_trg_testtrig) call TriggerExecute(gg_trg_testtrig) endfunction function ToExecute takes nothing returns nothing call BJDebugMsg("Second Trigger") endfunction function ToExecute_Condition takes nothing returns boolean return (GetRandomInt(0,1)==0) endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddAction(t, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set t=null set gg_trg_testtrig=CreateTrigger() call TriggerAddAction(gg_trg_testtrig,function ToExecute) call TriggerAddCondition(gg_trg_testtrig,Condition(function ToExecute_Condition)) ) endfunction This one will show "First Trigger" then "Second Trigger". It has a 50% chance of showing "Second Trigger" twice. The chance is done by using GetRandomInt(0,1) and comparing it to 0 . 1 time out of 2 GetRandomInt(0,1) will return 0. Trigger Execute and event responses When a trigger executes another trigger, it passes all its event responses to the other trigger so it can freely use them too. JASS:function Trig_testtrig_Actions takes nothing returns nothing call BJDebugMsg("First Trigger") call ConditionalTriggerExecute(gg_trg_testtrig) call TriggerExecute(gg_trg_testtrig) endfunction function ToExecute takes nothing returns nothing call BJDebugMsg("Second Trigger "+GetUnitName(GetTriggerUnit())) endfunction function ToExecute_Condition takes nothing returns boolean return (GetRandomInt(0,1)==0) endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddAction(t, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set t=null set gg_trg_testtrig=CreateTrigger() call TriggerAddAction(gg_trg_testtrig,function ToExecute) call TriggerAddCondition(gg_trg_testtrig,Condition(function ToExecute_Condition)) ) endfunction Slight variation of the previous sample, you would note that the name of the casting unit will appear after the words "Second Trigger ". Turn Off the beast Another important thing to know about triggers, is the ability to turn them off or on. When a trigger is turned off, it simply won't react to events. To turn a trigger off: JASS:native DisableTrigger takes trigger whichTrigger returns nothing To turn a trigger on (triggers are turned on by default): JASS:native EnableTrigger takes trigger whichTrigger returns nothing There are many important uses for this. You would like a trigger to work only under certain situations for example we will make a trigger that will work once then wait 30 seconds before working again: JASS:function Trig_testtrig_Actions takes nothing returns nothing call BJDebugMsg("Detected an ability") call DisableTrigger(gg_trg_testtrig) call PolledWait(30) call EnableTrigger(gg_trg_testtrig) call BJDebugMsg("Can detect another ability") endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddAction(t, function Trig_testtrig_Actions ) call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set gg_trg_testtrig=t set t=null endfunction Note that this time I am using the t variable to create the trigger and add actions to it, then I make gg_trg_testtrig to point to the trigger that is pointed by the t variable. DisableTrigger and TriggerExecute TriggerExecute doesn't check if a trigger is off before executing it. Turning triggers off is only for events. If you want DisableTrigger to stop TriggerExecute you can use this function: JASS:native IsTriggerEnabled takes trigger whichTrigger returns boolean So to execute a trigger and make sure it only executes it when it is on you can use this: JASS:if (IsTriggerEnabled(sometrigger)) then call TriggerExecute(sometrigger) endif Triggers without actions I said before that you can have an action-less trigger and that it might be useful some times. Mostly to count the number of times events/conditions were detected. It is important do use actionless triggers when you are dynamically creating triggers (see next section). That can save you the problem of removing triggeractions. JASS:constant native GetTriggerEvalCount takes trigger whichTrigger returns integer constant native GetTriggerExecCount takes trigger whichTrigger returns integer GetTriggerEvalCount returns the number of times the trigger's condition was evaluated. GetTriggerExecCount returns the number of times a trigger was executed. If you are not using things like TriggerExecute / ConditionalTriggerExecute or TriggerEvaluate on that trigger then GetTriggerEvalCount would return the number of times an event registered for the trigger has happened. And GetTriggerExecCount would return the number of times an event registered for the trigger has happened AND was validated by the trigger's condition. A sample would be counting the number of times units casted an ability. We would need a trigger that shows the number of times when you press escape and another trigger that has that event registered. JASS:function ToExecute takes nothing returns nothing call BJDebugMsg("Number of times spell effect was detected :" + I2S(GetTriggerExecCount(gg_trg_testtrig) ) ) endfunction //=========================================================================== function InitTrig_testtrig takes nothing returns nothing local trigger t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT) set gg_trg_testtrig=t set t=CreateTrigger() call TriggerAddAction(t,function ToExecute) call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC) set t=null endfunction Dynamic triggers You already know a lot about triggers. But since you are using JASS you should know how to use one of its greatest advantages over just using GUI to make your scripts. You can create / destroy triggers in game. But I will get into this topic later in another tutorial, because to make it work perfectly without leaks you better get into some gamecache+return bug usage. I think you can actually go on alone from now |
| 02-01-2006, 02:20 PM | #2 | |
Quote:
|
| 02-02-2006, 12:23 PM | #3 |
Excellent tutorial. Good job. |
| 02-03-2006, 12:01 PM | #4 |
Might want to fix the bold tags when explaining the press escape event and spell effect event. Regarding memory leak prevention on triggers you dynamically create and destroy: you use the TriggerRemoveAction(), but does that actually destroy the triggeraction object or just de-link it from a trigger? How about event objects, you don't destroy those at all, is there no function to do that? At least the boolexprs have a way of destroying them, but sadly they're the least used element of a trigger. |
| 02-03-2006, 12:07 PM | #5 |
It is dumb to use boolexprs when using a trigger that has to be destroyed later. Events get destroyed with DestroyTrigger. TriggerRemoveAction seems to stop the leaks. But if it doesn't then it is a leak you'll have to live with, it won't be worth it to stop using dynamic triggers just to stop a minor leak like that. |
| 02-03-2006, 12:17 PM | #6 |
Well, I did say boolexprs are the least used anyway. Thanks for the explanation, I can use dynamic systems without fear now :) |
| 08-06-2006, 08:50 AM | #7 | |||
I'm newbie to JASS and have some question Quote:
when InitTrig_testtrig is run on the map initialization you have create trigger t then set t = null that mean on the map initialization finish runing your t = null but when i use spell the message is display!! how it can display when t = null?? i try to do some experiment by use Quote:
and then Quote:
|
| 08-06-2006, 09:08 AM | #8 |
t is not the trigger - t is the pointer TO the trigger. Think of it as a name in a register. If you remove the name from the register (nullifying it), you save space in the register, but do you remove (destroy) the actual object (ie: the person which the name belongs to, or in this case the trigger)? DistroyTrigger(t) takes the trigger which the variable points to, and then destroys the object. This cannot be done if the pointer (the variable) has been nullified. |
| 08-06-2006, 09:32 AM | #9 |
Thanks for your lesson,Captain Griffen Now,am I understand correct? http://i64.photobucket.com/albums/h1...eiryuu/trg.jpg |
| 11-23-2006, 03:19 AM | #10 |
I m p r e s s i v e This tut just teach me alot! Thanks for making this. Especially the boolexpr part ;) |
| 05-24-2007, 05:31 PM | #11 |
Great tutorial! Just one question: What would TriggerEvaluate return if the conditions are containing event responses? |
| 05-24-2007, 05:33 PM | #12 |
whatever the condition function returns? |
| 05-24-2007, 07:19 PM | #13 |
But if you have for example an event EVENT_PLAYER_UNIT_SPELL_EFFECT, and you have a condition Casting unit equals to Blood Mage 000 <gen>, how will TriggerEvaluate work if the event didn't fire, so Casting unit doesn't exist? |
| 05-24-2007, 07:25 PM | #14 |
Well you shouldn't freely use Event responses in conditions if you plan calling the conditions for instances different to those events. Casting Unit might exist, if you call triggerEvaluate from another trigger with that event, else CastingUnit will be null |
| 05-24-2007, 08:09 PM | #15 |
Sorry, bad thread, delete this. Tabbed browsers are evil :P |
