wc3campaigns
WC3C Homepage - www.wc3c.netUser Control Panel (Requires Log-In)Engage in discussions with other users and join contests in the WC3C forums!Read one of our many tutorials, ranging in difficulty from beginner to advanced!Show off your artistic talents in the WC3C Gallery!Download quality models, textures, spells (vJASS/JASS), systems, and scripts!Download maps that have passed through our rigorous approval process!

Go Back   Wc3C.net > Tutorials > JASS/AI scripts tutorials
User Name
Password
Register Rules Get Hosted! Chat Pastebin FAQ and Rules Members List Calendar



Reply
 
Thread Tools Search this Thread
Old 02-01-2006, 01:28 PM   #1
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default Triggers in JASS

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.
  • What is a trigger?
    A trigger is a JASS object. Probably the most important JASS object cause it detects events, checks conditions and executes functions for your if necessary. Also creates a new thread. I will ellaborate about threads and their use later.
  • What is a World Editor Trigger?
    That's the problem, what world editor calls triggers are : A conjunction of a global variable and a script section which includes a function that is automatically executed on map initialization.
  • What is 'triggers' ?
    A lot of people think that "triggers" is a language that is different of JASS. You would most of the times find a typical question thread "What is better, JASS or triggers?" what they call triggers is actually a GUI that world editor provides for JASS. That tree thing that is based in clicks and allows you to add events/ conditions / actions and some control structures is a Graphical User Interface that creates JASS scripts for you.
  • Convert to custom text
    Is the process of telling world editor to hide the GUI and show the JASS code it would otherwise generate directly in the map script, So you can modiffy it to your will.


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
Actions

Now 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

Collapse 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:

Collapse JASS:
native TriggerAddAction     takes trigger whichTrigger, code actionFunc returns triggeraction
the actionFunc argument is of the type 'code'.

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.

Collapse 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.

Collapse 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
Collapse 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:



Collapse 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
Collapse 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]
Collapse 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.


Collapse 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
Collapse 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!
Collapse JASS:
native TriggerAddAction     takes trigger whichTrigger, code actionFunc returns triggeraction
This native sets a function to be the trigger's action. But it is not just that. It also creates a triggeraction object. It is a problem when you are no longer going to use the trigger, destroying the trigger ins't enough you have to destroy the triggeraction object too unless you want it to leak memory. That's something to take care later. The returned pointer is also useful when you want to change a trigger's action although the users for that may not be clear.

Collapse 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
  • What do we want?
    In this case we want to make a trigger execute when the player presses Escape. You would have noted that this is used by the campaign to skip cinematics for example.
  • So what kind of event is it?
    The player presses the key escape so it should be a player event
  • What function to use

Collapse 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

Collapse 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:
Collapse 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


Collapse 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
  • What do we want?
    In this case we want to make a trigger execute when any unit casts a spell.
  • So what kind of event is it?
    The unit casts a spell so it is a unit event.
  • What function to use

    Collapse JASS:
    native TriggerRegisterUnitEvent takes trigger whichTrigger, unit whichUnit, unitevent whichEvent returns event

    It is a native that registers a unit event. It requires a trigger, a unit and a unitevent. You would have noted that this is not what we want cause it would only work if that specific unit casts a spell, but we want every unit to do so. Let's go on:

    Collapse JASS:
    native TriggerRegisterPlayerUnitEvent takes trigger whichTrigger, player whichPlayer, playerunitevent whichPlayerUnitEvent, boolexpr filter returns event

    This one seems to require a player instead of unit, but uses playerunitevent instead of unitevent (the constants are different).

    It also requires a boolexpr filter argument? It is probably used as a condition to add to the event (this condition is independant of the trigger's condition )

    This doesn't seem to be what we want. But if you browse for the rest of common.j looking for register or event you wouldn't find much else. Maybe we have to browse blizzard.j?

    Collapse JASS:
    function TriggerRegisterPlayerUnitEventSimple takes trigger trig, player whichPlayer, playerunitevent whichEvent returns event
        return TriggerRegisterPlayerUnitEvent(trig, whichPlayer, whichEvent, null)
    endfunction
    


    Note that it calls the previous function and just uses null as filter argument. So there is no condition and it works too! . But still only for one player, we want it to run each time any unit casts a spell.

    The next function is interesting:
    Collapse JASS:
    function TriggerRegisterAnyUnitEventBJ takes trigger trig, playerunitevent whichEvent returns nothing
        local integer index
    
        set index = 0
        loop
            call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)
    
            set index = index + 1
            exitwhen index == bj_MAX_PLAYER_SLOTS
        endloop
    endfunction

    Now this blizzard function helps us. It just uses a loop to register the same event for every possible player in the game. We will use this one this time.
  • Anything else?
Yes, note that the function we are going to use requires a playerunitevent argument. Which is fairly similar to the playerevent type. After looking for constants of that type we have a bunch of options for what we want here:

Collapse 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:

Collapse 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:

Collapse 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

Collapse 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:
Collapse 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.


Collapse 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.

Collapse 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.

Collapse 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:
Collapse 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:

Collapse 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.

Collapse 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.



Collapse 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.

Collapse 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:
Collapse 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:

Collapse 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



Collapse 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.


Collapse 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:

Collapse JASS:
native DisableTrigger   takes trigger whichTrigger returns nothing

To turn a trigger on (triggers are turned on by default):

Collapse 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:

Collapse 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:

Collapse JASS:
native IsTriggerEnabled takes trigger whichTrigger returns boolean
It simply returns true as long as the trigger is on.

So to execute a trigger and make sure it only executes it when it is on you can use this:

Collapse 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.

Collapse 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.

Collapse 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
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Sponsored Links - Login to hide this ad!
Old 02-01-2006, 02:20 PM   #2
Jacek
Banned
 
Join Date: Dec 2004
Posts: 1,095

Submissions (1)

Jacek is a jewel in the rough (164)Jacek is a jewel in the rough (164)

Send a message via MSN to Jacek
Default

Quote:
Random Fact
I once disguised me with the name Conix to enter a spell contest at wc3sear.ch
That was Arcane Competition? It was strange for me that some guy, few posts and wants to make JASS spells.
Jacek is offline   Reply With Quote
Old 02-02-2006, 12:23 PM   #3
Blade.dk
.
 
Blade.dk's Avatar


Respected User
 
Join Date: May 2005
Posts: 1,990

Submissions (15)

Blade.dk is a glorious beacon of light (418)Blade.dk is a glorious beacon of light (418)Blade.dk is a glorious beacon of light (418)Blade.dk is a glorious beacon of light (418)Blade.dk is a glorious beacon of light (418)Blade.dk is a glorious beacon of light (418)

Approved Map: Azeroth's Arcane ArenaSpell session 01 winner

Send a message via MSN to Blade.dk
Default

Excellent tutorial. Good job.
__________________
Spell Making Course: Part 1: Making a simple stomp spell.
I wonder if I'll ever finish part 2.
Blade.dk is offline   Reply With Quote
Old 02-03-2006, 12:01 PM   #4
Anitarf
Procrastination Incarnate


Development Director
 
Join Date: Feb 2004
Posts: 8,071

Submissions (19)

Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)

2008 Spell olympics - Fire - SilverApproved Map: Old School Alliance TacticsHero Contest #2 - 3rd PlaceSpell making session 2 winner

Default

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.
__________________
Anitarf is offline   Reply With Quote
Old 02-03-2006, 12:07 PM   #5
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

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.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 02-03-2006, 12:17 PM   #6
Anitarf
Procrastination Incarnate


Development Director
 
Join Date: Feb 2004
Posts: 8,071

Submissions (19)

Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)Anitarf has a brilliant future (884)

2008 Spell olympics - Fire - SilverApproved Map: Old School Alliance TacticsHero Contest #2 - 3rd PlaceSpell making session 2 winner

Default

Well, I did say boolexprs are the least used anyway. Thanks for the explanation, I can use dynamic systems without fear now :)
__________________
Anitarf is offline   Reply With Quote
Old 08-06-2006, 08:50 AM   #7
StriderSeiryuu
User
 
Join Date: Aug 2006
Posts: 2

StriderSeiryuu has little to show at this moment (0)

Default

I'm newbie to JASS and have some question
Quote:
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

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:
call TriggerAddAction(t, function Trig_testtrig_Actions )
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call DestroyTrigger(t)
the message isn't display (i think because trigger t has been destroy)

and then
Quote:
call TriggerAddAction(t, function Trig_testtrig_Actions )
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
set t = null
call DestroyTrigger(t)
the message can display?? oh very confuse T_T

Last edited by StriderSeiryuu : 08-06-2006 at 08:52 AM.
StriderSeiryuu is offline   Reply With Quote
Old 08-06-2006, 09:08 AM   #8
Captain Griffen
Dread Lord of the Cookies
 
Captain Griffen's Avatar


Content Director
 
Join Date: Sep 2003
Posts: 5,368

Submissions (2)

Captain Griffen is a glorious beacon of light (497)Captain Griffen is a glorious beacon of light (497)Captain Griffen is a glorious beacon of light (497)Captain Griffen is a glorious beacon of light (497)Captain Griffen is a glorious beacon of light (497)

Approved Map: Warlords[Quicksilver #2] - 1st Place

Default

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.
__________________
Quote:
Originally Posted by Earth-Fury
Griffen is correct, you are not.
Quote:
[13:32] <Akolyt0r> hmm.. stil i want to have some unused women
Captain Griffen is offline   Reply With Quote
Old 08-06-2006, 09:32 AM   #9
StriderSeiryuu
User
 
Join Date: Aug 2006
Posts: 2

StriderSeiryuu has little to show at this moment (0)

Default

Thanks for your lesson,Captain Griffen

Now,am I understand correct?

http://i64.photobucket.com/albums/h1...eiryuu/trg.jpg

Last edited by StriderSeiryuu : 08-06-2006 at 09:35 AM.
StriderSeiryuu is offline   Reply With Quote
Old 11-23-2006, 03:19 AM   #10
Daxtreme
Wc3Helper
 
Daxtreme's Avatar
 
Join Date: May 2006
Posts: 199

Submissions (2)

Daxtreme will become famous soon enough (34)Daxtreme will become famous soon enough (34)

Default

I m p r e s s i v e

This tut just teach me alot! Thanks for making this.

Especially the boolexpr part ;)
Daxtreme is offline   Reply With Quote
Old 05-24-2007, 05:31 PM   #11
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

Great tutorial!

Just one question: What would TriggerEvaluate return if the conditions are containing event responses?
Silvenon is offline   Reply With Quote
Old 05-24-2007, 05:33 PM   #12
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

whatever the condition function returns?
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 05-24-2007, 07:19 PM   #13
Silvenon
User
 
Silvenon's Avatar
 
Join Date: May 2007
Posts: 492

Submissions (1)

Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)Silvenon will become famous soon enough (63)

Send a message via MSN to Silvenon
Default

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?

Last edited by Silvenon : 05-24-2007 at 07:20 PM.
Silvenon is offline   Reply With Quote
Old 05-24-2007, 07:25 PM   #14
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,905

Submissions (37)

Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)Vexorian has a reputation beyond repute (1060)

Hero Contest #3 - 2nd Place

Default

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
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 05-24-2007, 08:09 PM   #15
CommanderZ
Ayreonaut
 
CommanderZ's Avatar
 
Join Date: Feb 2007
Posts: 313

Submissions (1)

CommanderZ will become famous soon enough (48)CommanderZ will become famous soon enough (48)

Send a message via ICQ to CommanderZ
Default

Sorry, bad thread, delete this. Tabbed browsers are evil :P
__________________
My skills are my fantasy and creativity, nothing else

Last edited by CommanderZ : 05-24-2007 at 08:21 PM.
CommanderZ is offline   Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off


All times are GMT. The time now is 06:16 PM.


Donate

Affiliates
The Hubb http://bylur.com - Warcraft, StarCraft, Diablo and DotA Blog & Forums The JASS Vault Clan WEnW Campaign Creations Clan CBS GamesModding Flixreel Videos

Powered by vBulletin (Copyright ©2000 - 2014, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services