[JASS Tutorials]

General talk about editing, cheating, and deprotecting maps.

Moderator: Cheaters

User avatar
qweasd011
Forum Addict
Posts: 451
Joined: November 21st, 2008, 9:36 am
Title: Coleen!

[JASS Tutorials]

Post by qweasd011 »

Spoiler for Creating a Passive ability in Jass:
Thx to Romek for creating this guide credits for him
Introduction:
Welcome to my second tutorial. Today, you'll be learning how to make a simple passive spell in vJass. Specifically, we'll be making a spell which gives a chance to possess (Give the attacker permanent control over the target) the target.

As well as this, you'll learn how to make spells easily configurable, use scopes, and other Jass skills.

When creating passive spells with triggers, the actual spell will have to do nothing, and the chances will have to be triggered.

Creating the Spell:
Well, you obviously need a map and a spell. So open NewGen, make a new map, and go to the object editor.
Now you should note that when making passive abilities with triggers, the actual ability should have no effects whatsoever. It should just be a dummy spell which is used to check if the attacker has the spell.

I'll base my ability off of "Bash" (Human, Hero Ability)
I set the spell so that it has no chance to do no damage. And I removed the buffs. I also changed the tooltips and the icons :)

My spells Raw Code is 'Pwnt'. Yours will probably be different.
To check the raw code, go to the object editor and press CTRL + D. Everything will then be displayed in the Raw Codes.

Creating the Trigger:
Right. We have the spell, now all we need to do is make the triggers, the Hero, and the weaklings which will be possessed.

Open up the trigger editor (F4) and Create a new Trigger (CTRL + T).
Name the trigger whatever you want. It really doesn't matter.
Then go to Edit > Convert to Custom Text so that we can use Jass instead of GUI :).
Now delete everything that appears in the text box. It's rubbish.

Creating the Initializer and the Condition:
Now that we have a blank Jass trigger, we need to start the actual triggering.
We'll begin with creating a scope by using the syntax:

Code: Select all

scope NAME initializer Init

endscope
Scopes allow the use of Public and Private functions (Which prevent the functions being accessed from any other trigger). The Initializer is the name of the function which will be ran at Map Initialization.
Now, if our initializer is called "Init" we'll obviously need a function called Init. So lets create that now. We'll also create a local trigger to detect when a unit is attacked.

For this tutorial, my scope will be called "Possession"

Next, we'll register the events using Player Events. To do this, create a local integer and loop through it until you stop at 16 (All Players, including Neutral). Then register the event for the Player you're currently looping for.

Code: Select all

local integer i = 0
loop
    exitwhen i > 15
    call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, null)
    set i = i + 1
endloop
There! We now have a trigger with the events registered... But wait. We have a problem. When using "null" as an argument for a boolexpr, we get a leak. (For some weird reason). So we'll have to create a boolexpr to take care of that leak.
Make a new, private function called "True" that takes nothing, and returns true.

Code: Select all

private function True takes nothing returns boolean
    return true
endfunction
Now, use the function as an argument instead of null
Your complete trigger should now look like this:

Code: Select all

scope Possession initializer Init

    private function True takes nothing returns boolean
        return true
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            exitwhen i > 15
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, Filter(function True))
            set i = i + 1
        endloop
    endfunction

endscope
We're nearly finished with the preparations. We just need a condition and an action for our new trigger.

We'll start off with the condition. Create a new function called "Con" which takes nothing and returns a boolean. In this function, we'll be checking if the attacked unit is an ally of the attacker, as well as making the spell random.

We'll start off with checking if the owners of the attacker and the target are enemies. To do this, we'll use a function called "IsPlayerEnemy". This function takes 2 arguments, both of which are players, and returns true if they are enemies. Another function we will use is "GetUnitOwner" which returns the owner of the unit given to the function.
So to check if the Owner of the Target and the Owner of the Attacker are enemies, we need to do:

Code: Select all

IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker()))
Now we need to make sure that the spell is random. At the moment, we'll add a 2% chance of the spell happening.
To do this, we'll get a random integer between 0 and 100, and check if it is less than or equal to 2.
For this, we will use the function call:

Code: Select all

GetRandomInt(0, 100)
Now we will return the value of both of these functions. As we will need both of these to be true (Enemy, and Random), we will use the and keyword so that it will return true only if both of the conditions are true.

Our condition should now look like this:

Code: Select all

private function Cons takes nothing returns boolean
    return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= 2
endfunction
After that, we'll need to check if the target is a Hero or not. We don't want to possess heroes do we? :P
To do this, we use:

Code: Select all

GetTriggerUnit(), UNIT_TYPE_HERO) == false // Compared to false so it ISN\'T a hero =
Finally, we'll need to make sure the attacking unit has the ability, or every unit would be possessing units with no end :p

To do this we use:

Code: Select all

GetUnitAbilityLevel(GetAttacker(), 'Pwnt') > 0
Your final conditions should look like this:

Code: Select all

return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker
Now, we need to add the conditions to the trigger. To do this, we use the function

Code: Select all

call TriggerAddCondition(t, Filter(function Cons))
Making the Actions:
Time for the actions. This is where all the effects of the spell will happen.
In this function, we'll change the owner of the target, and create a special effect on it.

Create a new, private function called Possession which takes and returns nothing. In this function, create 2 local unit variables: t and u (For Target, and Attacker)

Code: Select all

private function Possession takes nothing returns nothing
    local unit u = GetAttacker()
    local unit t = GetTriggerUnit()
endfunction
These will make it easier to use these units later on in the trigger without having to keep calling the same functions.

Next, we'll create an effect on the target to show the players that it's been possessed. I'll use the model "Abilities\Spells\Items\AIvi\AIviTarget.mdl" because it looks cool for something like this :p

As we won't be needing the effect except for showing it once, we'll have to destroy it instantly. We can do this in 1 line by using

Code: Select all

DestroyEffect(AddSpecialEffect(...))
So I'll add my effect to the "chest" of the target.

Code: Select all

call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Items\\AIvi\\AIviTarget.mdl", t, "chest"))
Next, we're going to change the owner of the target.
To achieve this, we'll use the function called "SetUnitOwner"
We'll need to change the owner of t (target unit) to the Owner of u (Attacker) and change the colour of the unit.
For this, we'll use:

Code: Select all

call SetUnitOwner(t, GetOwningPlayer(u), true)
Now we'll need to null the 2 unit variables to prevent them from leaking.

Code: Select all

set u = null
set t = null
Finally, we'll need to add the actions to the trigger.
For this, we use the function:

Code: Select all

call TriggerAddAction(t, function Possession)
Your code should now look like this:

Code: Select all

scope Possession initializer Init

    private function True takes nothing returns boolean
        return true
    endfunction
    
    private function Cons takes nothing returns boolean
       return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= 2 and IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) and GetUnitAbilityLevel(GetAttacker(), 'Pwnt') > 0
    endfunction
    
    private function Possession takes nothing returns nothing
        local unit u = GetAttacker()
        local unit t = GetTriggerUnit()
        call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Items\\AIvi\\AIviTarget.mdl", t, "chest"))
        call SetUnitOwner(t, GetOwningPlayer(u), true)
        set u = null
        set t = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            exitwhen i > 15
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, Filter(function True))
            set i = i + 1
        endloop
        call TriggerAddCondition(t, Filter(function Cons))
        call TriggerAddAction(t, function Possession)
    endfunction

endscope
Creating the Configurables:
Now, although you have a fully working spell, it still isn't up to a good standard. It has a 2% chance of happening no matter what happens, and it'll be very difficult for newbies to change the effect created when the conditions are met.

We'll start off by creating another global block above everything except the first scope keyword. This global block will be used for the configurables.
In this block, create 2 private, constant strings. One for the effect, and one for the place it's attached to.

Code: Select all

private constant string EFFECT
private constant string EFFECT_POSITION
As these are constants, they have to be initialized (Have a value assigned to them immediately) So we'll assign them to what they are in the code, and replace the strings in the code with the variable names.

Your entire code should now look like this:

Code: Select all

scope Possession initializer Init

    globals
        private constant string EFFECT = "Abilities\\Spells\\Items\\AIvi\\AIviTarget.mdl"
        private constant string EFFECT_POSITION = "chest"
    endglobals

    private function True takes nothing returns boolean
        return true
    endfunction
    
    private function Cons takes nothing returns boolean
        return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= 2 and IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == false and GetUnitAbilityLevel(GetAttacker(), 'Pwnt') > 0
    endfunction
    
    private function Possession takes nothing returns nothing
        local unit u = GetAttacker()
        local unit t = GetTriggerUnit()
        call DestroyEffect(AddSpecialEffectTarget(EFFECT, t, EFFECT_POSITION))
        call SetUnitOwner(t, GetOwningPlayer(u), true)
        set u = null
        set t = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            exitwhen i > 15
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, Filter(function True))
            set i = i + 1
        endloop
        call TriggerAddCondition(t, Filter(function Cons))
        call TriggerAddAction(t, function Possession)
    endfunction

endscope
Secondly, we'll create a private, constant function which returns the chances of the unit being possessed based on the level of the ability.
This will also go at the top of the script for the user to change as they like :)
I'll make it so that for every level of the spell, a 1% chance is added.

Code: Select all

private constant function CHANCE takes integer level returns integer
    return level * 1
endfunction
We'll also have to change the Con function so that it checks the chance based on this function.

Code: Select all

private function Cons takes nothing returns boolean
    return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= CHANCE(GetUnitAbilityLevel(GetAttacker(), 'Pwnt')) and IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == false and GetUnitAbilityLevel(GetAttacker(), 'Pwnt') > 0
endfunction
Thirdly, what if the person using the spell made the Raw Code 'A000'. Then it could become difficult for them to configure the spell. So we create another constant integer called ID, which is the Raw Code of the spell. We'll also change the code so that it uses the constant.
Your code should now look like this:

Code: Select all

scope Possession initializer Init

    globals
        private constant string EFFECT = "Abilities\\Spells\\Items\\AIvi\\AIviTarget.mdl"
        private constant string EFFECT_POSITION = "chest"
        private constant integer ID = 'Pwnt'
    endglobals
    
    private constant function CHANCE takes integer level returns integer
        return level * 1
    endfunction

    private function True takes nothing returns boolean
        return true
    endfunction
    
    private function Cons takes nothing returns boolean
        return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= CHANCE(GetUnitAbilityLevel(GetAttacker(), ID)) and IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == false and GetUnitAbilityLevel(GetAttacker(), ID) > 0
    endfunction
    
    private function Possession takes nothing returns nothing
        local unit u = GetAttacker()
        local unit t = GetTriggerUnit()
        call DestroyEffect(AddSpecialEffectTarget(EFFECT, t, EFFECT_POSITION))
        call SetUnitOwner(t, GetOwningPlayer(u), true)
        set u = null
        set t = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            exitwhen i > 15
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, Filter(function True))
            set i = i + 1
        endloop
        call TriggerAddCondition(t, Filter(function Cons))
        call TriggerAddAction(t, function Possession)
    endfunction

endscope
Finishing Off:
Finally, we'll just need to add some comments to the code, and make a neat test map.

So, make a nice map header and place it above Everything (Or below the scope keyword if you want) and give a brief description of the spell, say who made it, and write how to import it.

Code: Select all

// +------------------------------------------------------------------+
// |     Possession - Created by Romek.  Requires a vJass preprocessor!           |
// +------------------------------------------------------------------------+
// | Gives a small chance to permanently change control of the target unit       |
// | to the owner of the attacker.                                                             |
// +------------------------------------------------------------------------+
// | How to Import:                                                                                 |
// |  - Create a new trigger                                                                      |
// |  - Convert it to Custom text (Edit > Convert to Custom Text)                   |
// |  - Replace everything there with this code                                            |
// |  - Change the constants to suit yourself                                               |
// |  - Enjoy!                                                                                         |
// +------------------------------------------------------------------------+
And then add some short, simple comments around the constants to explain what should be changed.

Your final code should look something like this:
[code]scope Possession initializer Init
// +------------------------------------------------------------------------+
// |     Possession - Created by Romek.  Requires a vJass preprocessor!           |
// +------------------------------------------------------------------------+
// | Gives a small chance to permanently change control of the target unit       |
// | to the owner of the attacker.                                                             |
// +------------------------------------------------------------------------+
// | How to Import:                                                                                 |
// |  - Create a new trigger                                                                      |
// |  - Convert it to Custom text (Edit > Convert to Custom Text)                   |
// |  - Replace everything there with this code                                            |
// |  - Change the constants to suit yourself                                               |
// |  - Enjoy!                                                                                         |
// +------------------------------------------------------------------------+

// |-------------|
// | Constants:  |
// |-------------|
    globals
        // The effect created on the target when it is being possessed:
        private constant string EFFECT = "Abilities\\Spells\\Items\\AIvi\\AIviTarget.mdl"
        // Which is attached to the targets:
        private constant string EFFECT_POSITION = "chest"
        
        // The Raw code of the ability
        private constant integer ID = 'Pwnt'
    endglobals
    
    private constant function CHANCE takes integer level returns integer
    // The chance of a unit being possessed. "level" is the level of the ability.
        return level * 1
    endfunction
    
// |------------------|
// | End of Constants |
// |------------------|

// DO NOT EDIT BELOW THIS LINE!

    private function True takes nothing returns boolean
        return true
    endfunction
    
    private function Cons takes nothing returns boolean
        return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= CHANCE(GetUnitAbilityLevel(GetAttacker(), ID)) and IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == false and GetUnitAbilityLevel(GetAttacker(), ID) > 0
    endfunction
    
    private function Possession takes nothing returns nothing
        local unit u = GetAttacker()
        local unit t = GetTriggerUnit()
        call DestroyEffect(AddSpecialEffectTarget(EFFECT, t, EFFECT_POSITION))
        call SetUnitOwner(t, GetOwningPlayer(u), true)
        set u = null
        set t = null
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        loop
            exitwhen i > 15
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, Filter(function True))
            set i = i + 1
        endloop
        call TriggerAddCondition(t, Filter(function Cons))
        call TriggerAddAction(t, function Possession)
    endfunction

endscope
Congratulations! You have just created a passive spell in vJass :D

Note: The DeathKnight in the Demo Map has superfast attack speed and low damage, so it's easier to notice the spells effect :)
Image
User avatar
qweasd011
Forum Addict
Posts: 451
Joined: November 21st, 2008, 9:36 am
Title: Coleen!

Re: [Tutorials]

Post by qweasd011 »

Spoiler for GetLocalPlayer():
Thx for PurgeandFire for making this tutorial credits for him!
Introduction:
What is GetLocalPlayer()?

Code: Select all

constant native GetLocalPlayer      takes nothing returns player
GetLocalPlayer is one of the most useful functions in the common.j, but it is also one of the most dangerous. GetLocalPlayer is a function, that gets the current player running the trigger in that instance. So, if used correctly, you can perform an action for only one player and it will be undone for other players.

GetLocalPlayer and Desyncs:

GetLocalPlayer is a function that we call a "Desyncy Function". This means that it can cause desyncs. Desyncs are errors where, when playing multiplayer, will cause all players (with exceptions to the local player etc.) to be disconnected from the map. In this tutorial, I will warn you from the hazards of GetLocalPlayer...

Players in JASS:

In JASS, Players range from 0-11 instead of 1-12.

(0 = 1, 1 = 2, 2 = 3 etc.)

So, you would do something like:

Code: Select all

    if GetLocalPlayer() == Player(7) then

endif
Player 7 is JASS for Player 8 in GUI.

Other common ones would be to use:

Code: Select all

GetTriggeringPlayer()
GetOwningPlayer(GetTriggerUnit())
The first is the Triggering Player and the second is the owner of the triggering unit.

GetLocalPlayer Basic Usage:

GetLocalPlayer can be used to perform an action for a player, as stated previously. Now, lets look at a basic example of a GetLocalPlayer block.

Code: Select all

function Test takes nothing returns nothing
    if GetLocalPlayer() == Player(0) then
        //actions
    endif
endfunction
Let's do something I call a break down! You will break the function into parts, and show the definitions. Then you stitch the definitions together to get a basic concept of the function.

function Test... = The function "Test" takes nothing and returns nothing.
if GetLocalPlayer() = If the player running this trigger
== Player(0) then = Is Equal to Player 1 (Red), then
//actions = do these actions
endif/endfunction = . (Period)

So, now lets bring together the function.

"The function "Test" takes nothing and returns nothing. If the player running this trigger is equal to Player 1 (Red), then do these actions.

So, this will perform the actions in that block if the player running the trigger is Player 1 (Red).

Disconnections of Handles:

First, let me show an example:

Code: Select all

function Test takes nothing returns nothing
    if GetLocalPlayer() == Player(0) then
        call AddSpecialEffect("none.mdl",0.,0.)
        // Will this desync?
    endif
endfunction
This will desync
sorry i don't have picture because it causes the picture showing only X

Code: Select all

native AddSpecialEffect             takes string modelName, real x, real y returns effect
This function returns effect.

Code: Select all

type effect             extends     handle
Effect extends handle.

So you are creating a handle. Creating handles for players are very dangerous and will most likely lead to a desync. Though, there are exceptions.

There is a super special function called "Typecasting - Handle to Integer". Handle to Integer will retrieve a certain handle's value. So, let's look at the function.

Code: Select all

function H2I takes handle h returns integer
    return h
    return 0
endfunction
This is a trick, the Jass parser only checks the last return value. So we trick it into taking a handle when it really is supposed to take an integer. This famous trick was exploited by SuperIKI, followed by Peppar.

Anyway, we can get the handle value using this function. If the value is greater than 0x100000, it will desync on creation in the block. If you test many handles, you might get something like 1048670 or 1048576... To test to retrieve the value, you can use something like this:

Code: Select all

function Wootage takes nothing returns nothing
    local texttag t = CreateTextTag()
    call BJDebugMsg(I2S(H2I(t)))
endfunction
That would probably return 99, unless there are more texttags. Some handles aren't allocated like regular handles. Why 0x100000? That is the number handles which are allocated normally begin at. (this is probably not worded properly)

Anywho, so if you were to create a unit (value of 1048576), it would cause a desync because 1048576 > 100000. (0x symbolizes a hexadecimal number).

Manipulating the Editor's System:

Say you want to create a special effect for a player. How would you do that?

Code: Select all

function Test takes nothing returns nothing
    if GetLocalPlayer() == Player(0) then
        call AddSpecialEffect("war3mapImported\\FX.mdl",0.,0.)
    endif
endfunction
WRONG! You cannot create a special effect because 1048576 (Its value) > 100000. So, how would you do that? In many cases, you would show/hide something for a player. Have no fear, because there is an alternative! Special effects don't have a hide/display function, so this is what we'll do!

Code: Select all

function Test takes nothing returns nothing
    local string s = ""
//So the path will be nothing, so it won\'t show at all
    if GetLocalPlayer() == Player(0) then
        set s = "war3mapImported\\FX.mdl"
//An actual path, so it <span style=\'font-style:italic;\'>will</span> have a path for that player but not for
//the other players
    endif
    call DestroyEffect(AddSpecialEffect(s,0.,0.))
endfunction
Congratulations, you made it correctly! But according to some recent thread viewing, I found out that this has a chance of making the string table unsynced, thus we should do this instead:

Code: Select all

function Test takes nothing returns nothing
    local string s = "war3mapImported\\FX.mdl"

    if GetLocalPlayer() != Player(0) then
        set s = ""
    endif
    call DestroyEffect(AddSpecialEffect(s,0.,0.))
endfunction
Basic Usage:

Say you have a multiboard, but you only want it for a player? Remember, you can't create it for a player, and they have no path values or whatever, so you can display/hide it whenever!
[/code]

Basic Usage:

Say you have a multiboard, but you only want it for a player? Remember, you can't create it for a player, and they have no path values or whatever, so you can display/hide it whenever!

Code: Select all

function Test takes multiboard mb returns nothing
    call MultiboardDisplay(mb,false)
    if GetLocalPlayer() == Player(0) then
        call MultiboardDisplay(mb,true)
    endif
And it will display it only for player 1!

Performing Functions For Forces

Performing functions for forces, also has a technique. Instead of using GetLocalPlayer() over and over again, you can use a function called "IsPlayerInForce()". An example:

Code: Select all

function DisplayTextToForce takes force toForce, string message returns nothing
    if (IsPlayerInForce(GetLocalPlayer(), toForce))
        call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, message)
    endif
endfunction
See this function? The "toForce" area is the force you will perform it for, and the message is the message to be displayed.

IsPlayerInForce checks if a player (argument 1) is in a specified force (argument 2). Since GetLocalPlayer() returns a player, (the player performing the trigger at that moment), you can check if that player is in that force, then it will perform that function to that player. This trigger will run for all players, and those in the force will receive a message, and those outside the force, will receive nothing.

This is a useful technique and can save a lot of time.

Conclusion:

Now you know how to show stuff for only the local player! In GUI, you can also just use custom scripts. I hope this helps! Feel free to comment, test or anything!

Credits:



• SuperIKI and Peppar for H2I
• Earth-Fury for the "technical" side of GetLocalPlayer()
• weaaddar for a small H2I definition



Enjoy!
Spoiler for Timers and how to pass data to them:
Timers and how to pass data to them

This little tutorial aims to teach you how to use timers and make MUI spells and systems with them.

You need to have a nice knowledge about structs and Jass in general. This tutorial contains lots of vJass stuff, so you must have NewGen and newest JassHelper


Basic Timer Knowledge


Timer is a really usefull handle, because it gives us a control over the time itself. We can delay our actions or call some function in really fast periods, which is needed for stuff like moving units smoothly around.

List of useful timer functions:

Code: Select all

native CreateTimer takes nothing returns timer

Code: Select all

native DestroyTimer takes timer whichTimer returns nothing
In these days, we dont destroy timers anymore. They are usually recycled.
More about this later.

Code: Select all

native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
This starts our timer. Function handlerFunction is the function which is called when timer expires. Boolean periodic determines whether timer is paused after it expires, or does it keep calling the handlerFunction.

Code: Select all

native PauseTimer takes timer whichTimer returns nothing

Code: Select all

native GetExpiredTimer takes nothing returns timer
You can use this in handlerFunction. It gives you the timer, which is calling the handlerFunction, so you can for example pause it using PauseTimer.

There is more timer related functions in Jass, and you can find them using NewGens Function List.

The MUI problem


The problem with timers is that the actions are called in handlerFunction, which prevents us from using local variables. To make our spells and systems MUI, we need to somehow pass all needed data to the handlerFunction. There is several ways of doing this.

Code: Select all

globals
   timer time=CreateTimer()
endglobals

function handler takes nothing returns nothing
     
     call KillUnit(u) // error, local unit u doesnt work here.
     // What should we do now?
     
endfunction

function start takes nothing returns nothing
    
    local unit u=GetTriggerUnit()
    call TimerStart(time,5.,false,function handler)

endfunction
There is dozens cool timer and struct attachment -systems around there, which can do this job for you.
The purpose of this tutorial is not to teach you use those systems, but to learn few techniques you can use to do it by yourself.

I mention TimerUtils here as an example of a timer system.

Periodic Timers


For fast periodic timers you can use one timer with struct arrays. This is pretty easy technique, and Im now going to tell how it works.

The idea is to have all spell instances in a struct array and then loop through that array and call actions for all instances no matter whether there is 1 or 100 of them.

You should already know something about structs and how they work.

Code: Select all

struct Data

 // stuff you need, like:
 
    unit caster
    real damage
    integer count=0
    
    method action takes nothing returns nothing
        // some stuff
    endmethod

endstruct
For this technique you need a timer, an integer and a Data array.

Code: Select all

globals
    private integer Total=0
    private Data array D
    private timer T=CreateTimer()
endglobals
Adding Struct to array:

When you have created a new spell instance ( Data ), you need to add it to array D, so timer can start calling its actions.

Integer Total tells us how many active instances there is currently. Our first action is to check if Total is 0. This means that we need to start our timer.

Code: Select all

        
        if Total==0 then
           call TimerStart(T,0.03,true,function TimerLoop)
        endif
You better not to forget adding your struct to array.

Code: Select all

set D[Total]=this
After this we incerease Total, because there is now a new instance created.

Code: Select all

set Total=Total+1

Code: Select all

private function Start takes nothing returns nothing
        local Data this=Data.create()
        // store all needed data to struct and do actions you need
        if Total==0 then
           call TimerStart(T,0.03,true,function TimerLoop)
        endif
        set D[Total]=this
        set Total=Total+1
endfunction
Periodic Actions:

Our handlerFunction is a function called TimerLoop. We need to loop through the array now.

Code: Select all

private function TimerLoop takes nothing returns nothing
     local integer i=0 // integer i is our loopindex
     loop
        exitwhen i>=Total // We stop when we have done actions for all instances

            // actions

        set i=i+1
     endloop

endfunction
You also need to do all the actions. If you are making a damage over time spell, you do your damage here. If you are making a knockback, this is where the units are moved.

One option is to have your actions in a method.

Code: Select all

private function TimerLoop takes nothing returns nothing
     local integer i=0
     loop
        exitwhen i>=Total

            call D[i].action() 

        set i=i+1
     endloop
endfunction
Removing instances from array:

When your spell is done, you need to remove that instance from array.
We also need to keep our array whole. When an instance is removed from array it leaves an empty spot. The best way to fix this spot, is to move our last array member to there..

Code: Select all

set D[i]=D[Total]
We also need to decerease Total, because we are just removing an instance.

Code: Select all

             call D[i].destroy() // dont forget to destroy your struct
             set Total=Total-1 // decerease total by one
             // If Total is greater than 0 we need to reorganize our array a bit 
             if Total > 0 then
                 set D[i]=D[Total] // Here we fill that empty spot.
                 set i=i-1 // Loop index must also be decereased
             else // Total is 0 so there is no active instances
                 call PauseTimer(T) // We can pause our timer
             endif
Complete TimerLoop function:

Code: Select all

private function TimerLoop takes nothing returns nothing
     local integer i=0
     loop
        exitwhen i>=Total
        if D[i].end then // end is a boolean which tells us when to stop calling actions
             call D[i].destroy()
             set Total=Total-1
             if Total > 0 then
                 set D[i]=D[Total]
                 set i=i-1
             else
                 call PauseTimer(T)
             endif
        else
            call D[i].action()
        endif
        set i=i+1
     endloop
endfunction
Using a struct array is a fast and safe method. It is an easy and good solution for periodic timers and you should use it whenever you can.
Attaching Data to Timers
There is still many situations where above mehod is not an option.
You might need a non periodic timer for waiting few seconds, or 0.0 timer for damage blocking.

In these situations, you need to attach your data to timer. There is many ways to do this, yet all of them are somehow based on famous H2I function.

Code: Select all

function H2I takes handle h returns integer
    return h
    return 0
endfunction
This function uses wc3´s return bug and allows us to use any handles unique handle index.

Code: Select all

      local location loc=Location(0.,0.)
      local integer handleID=H2I(loc)
The problem of hanlde indexes is that they are too big integers to be used as array indexes. LocArray[ H2I(loc) ] doesnt really work, but there is few solutions for that.
You can use some hashing algorithm or decerease handle id by wc3´s initial handle amount, which is 0x100000. ( Actutally BJ creates some extra handles too, but this should work )

You can search these timer systems from different wc3 forums and check what kind of methods they use for timer attaching.

Timer System


This Timer System does pretty much the same thing TimerUtils (Red) does. This part aims to explain how a system like it works.

Some Basic stuff:

Code: Select all

library TimerSystem
globals
//========================================================================
    // wc3´s initial handle count. If your map has lots of preplaced stuff,   
   //  which incereases handle count, you should incerease this.
    private constant integer MIN_HANDLE_COUNT = 0x100000
    // How many timers you are gonna need?
   // You should use one timer & struct array in most of cases, so you 
   // should not need too many timers
    private constant integer TIMER_COUNT = 100
    
//========================================================================   
   // These are needed for timer recycling.
    private timer array Timers
    private integer Index
endglobals

// H2I is one of the coolest functions ever.
private function H2I takes handle h returns integer
    return h
    return 0
endfunction

endlibrary
Recycling Timers:

In our initializer function, we fill our array with timers. Note that the amount of timers this system can support is limited. You need to know how many timers your need for your map.

Code: Select all

private function InitTrig takes nothing returns nothing
    local integer i=1
    loop
        exitwhen i > TIMER_COUNT // we stop when we have all the 100 timers have been created
        set Stack[i]=CreateTimer()
        set i = i + 1
    endloop
    set Index=TIMER_COUNT+1
endfunction
This system requires you to use NewTimer and ReleaseTimer functions.
These functions handle the recycling.

Code: Select all

function NewTimer takes nothing returns timer
    if Index==0 then  // if index is 0 , all our 100 timers are in use.
        call BJDebugMsg("no more timers left")
        return null
    endif
    set Index=Index-1 // We use a same kind of array handling here than we
    return Timers[Index]  // used with struct arrays
endfunction

function ReleaseTimer takes timer t returns nothing
    if t != null then
        call PauseTimer(t) // Pausing the timer is a good idea.
        set Timers[Index]=t // We return our timer back to array
        set Index=Index+1 // and incerease Index
    endif
endfunction
Attaching Data

Because all timers are created in map init, their handle id´s should be low enough to be converted to suitable array index´s. However, if you are creating a lot of other handles in map init, you might need a bigger number for MIN_HANDLE_COUNT. Suitable MIN_HANDLE_COUNT can be easily found by adding a simple debug msg to init function:

Code: Select all

    
    set Timers[0]=CreateTimer() // System doesnt use Timers[0], so we can use it to see how
    //  many handles are created before our Timers.
    debug call BJDebugMsg("MIN_HANDLE_COUNT: "+I2S(H2I(Timers[0])))

A function like this can be used to get timers index:

Code: Select all

function GetTimerId takes timer t returns integer
    return H2I(t)-MIN_HANDLE_COUNT
endfunction
You can use this index as an array index. An easy way to make spells MUI.

Code: Select all

globals
    private Data array D
endglobals

function Expire takes nothing returns nothing
    local timer t=GetExpiredTimer()
    local Data d = D[  GetTimerId(t)  ]
    
    //....
    call ReleaseTimer(t)
endfunction

function Start takes nothing returns nothing
    local Data d=Data.create()
    local timer t=NewTimer()
    //....
    set D[ GetTimerId(t) ] = d
    
endfunction
You can also have a one integer array for storing all struct types, and some nice GetTimerData and SetTimerData -functions.
Vexorian´s TimerUtils ( Red flavor ) uses this method. Studying that system is a good idea.

This kind of timer attaching system is really fast, because it only takes an array lookup, substraction and H2I call, but it is not very practical because of limited number of timers.
And still, it is really usefull when you cant use struct arrays for some reason.

Code: Select all

library TimerSystem initializer Init
globals
//========================================================================
    
    private constant integer MIN_HANDLE_COUNT = 0x100000
    private constant integer TIMER_COUNT = 100
    
//========================================================================   
    private timer array Timers
    private integer Index
endglobals

private function H2I takes handle h returns integer
    return h
    return 0
endfunction

function GetTimerId takes timer t returns integer
    return H2I(t)-MIN_HANDLE_COUNT
endfunction

function NewTimer takes nothing returns timer
    if Index==0 then
        call BJDebugMsg("no more timers left")
        return null
    endif
    set Index=Index-1
    return Timers[Index]
endfunction

function ReleaseTimer takes timer t returns nothing
    if t != null then
        call PauseTimer(t)
        set Timers[Index]=t
        set Index=Index+1
    endif
endfunction

private function Init takes nothing returns nothing
    local integer i=1
    set Timers[0]=CreateTimer()
    debug call BJDebugMsg("MIN_HANDLE_COUNT: "+I2S(H2I(Timers[0])))
    loop
        exitwhen i > TIMER_COUNT
        set Timers[i]=CreateTimer()
        set i = i + 1
    endloop
    set Index=TIMER_COUNT+1
endfunction

endlibrary
This is my first tutorial, and I know that it will need a lot of editing before its complete. Comments and criticism are welcome.

I hope this helps those guys who want to learn about the use of timers.
Image
User avatar
qweasd011
Forum Addict
Posts: 451
Joined: November 21st, 2008, 9:36 am
Title: Coleen!

Re: [JASS Tutorials]

Post by qweasd011 »

Spoiler for JASS Crash Course - A Basic JASS Tutorial:
Credits to Andrewgosu

The purpouse of this tutorial is to teach the very basics of JASS, which gives you a solid foundation from where to develop your JASS skills and knowledge.

REQUIREMENTS:
- JASS NewGen Package (used to help to declare globals)

RECOMMENDATIONS:
- JASSCraft 1.1.3 - A nice JASS Editor with function lists and templates.

TABLE OF CONTENTS

[INDENT]Chapter one – functions
Chapter two - variables
Chapter three - operators
Chapter four - conditionals, loops
Chapter five - conclusion and suggestions
[/INDENT]

CHAPTER ONE - FUNCTIONS

New concept – function
Function is a portion of code within a larger program, which performs a specific task.


[INDENT]Image[/INDENT]


Welcome to your very first function in JASS, called „HelloWorld“. As you've guessed already, the task of this function is to display a „Hello, world!“ text on the screen. Lets get started by taking a closer look at the example and study the syntax plus key elements of declaring a function.

A function in JASS must start with the keyword "function" and end with the keyword "endfunction". This tells Warcraft where the function starts and ends.

Once, you have started declaring the function you must add a name to it. It does not matter how you name your functions, but for the sake of better understanding it's wiser to give them explanatory names. I'll name my function "DisplayStatistics".
[INDENT]
NOTE #1: Two functions cannot have the same name!

TIP #1: Use explanatory function names, which relate to the function task.

TIP #2: Normally, JASS function naming convention says to capitalize the first letter of every new word. e.g MyTestFunction[/INDENT]

[INDENT]TIP #3: To make a comment, use the double forward slashs. "//".

Image[/INDENT]

New concept - argument
Argument is a reference or value that is passed to a function.

After naming your function, carry on by adding the argument list - the values or references needed by the function in order to complete its task. The argument list must start with the keyword "takes". The example function "HelloWorld" did not take any arguments, so it used "takes nothing". However, lets add a unit argument to our function.


[INDENT]Image[/INDENT]


The first keyword after "takes" denotes which type of parameter the function needs to perform its task, in our case "unit". The next word is the reference name of the argument, which is used inside the function. It does not matter how you name the references, "whichHero" could have been just "a". However, short and explanatory is the best way.


[INDENT]NOTE #2: Functions can take more than one argument. Multiple arguments must be separated with a comma!


Image[/INDENT]


As the function definition says, it's a piece of code with a specific task. After the task has been done, the function must return the outcome of it. For example, when you go to the store and don't click me something, the cashier's task is to charge (takes) you the money and return (returns) any change.

That means, once our done with the argument list, it's time to finish the function decleration altogether with a "returns" keyword and the variable type it returns.


[INDENT]Image[/INDENT]


To return a value or reference inside a function, you must use the "return" keyword. Do not mix that up with the keyword "returns", the latter is used in the function declaration process.


[INDENT]Image[/INDENT]



[INDENT]NOTE #3: A function ends its task, when it encounters a "return" keyword.[/INDENT]

New concept - function call
A function call is an expression containing a simple type name and a parenthesized argument list.

We're nearly done with learning the basic knowledge about JASS functions. The last vital information you need to know is how to make your function perform its task - how to call a function.


[INDENT]Image[/INDENT]


To make your function run its actions you must place the keyword "call", separated by a space, before your function name.

Next, you must add parathesis right after the function name and pass the arguments to the function (remember the "takes"?).

CHAPTER TWO - VARIABLES

New concept - variable
An object of a specified type whose value can be changed.

New concept - local variable
A variable that exists only inside a particular function, not affecting anything outside the function.


[INDENT]Image[/INDENT]


To declare a local variable, you must first add the "local" keyword, which is followed by the type of the variable. Lastly, you need to write a reference name to it.


[INDENT]Image[/INDENT]


Local variables can also be declared with a initial value, using the "=" sign. Note how the keyword "call" is missing, though, we are still running the function "GetRandomInt".


[INDENT]Image[/INDENT]


Lastly, if you don't need to declare a local variable with an initial value, but instead want to assign it later, you can use the "set" keyword.

[INDENT]NOTE #4: All local variables must be declared in the beginning of the function![/INDENT]


New concept - global variable
A variable that can be accessed by all parts of the code.


[INDENT]Image[/INDENT]


To declare global variables, you must first create a globals block, which starts with the keyword "globals" and ends with the keyword "endglobals".


[INDENT]Image[/INDENT]


Global variables do not need a keyword in front of them, unlike local variables.

However, as same as with local variables, you can declare them with an initial value and change the content later with the "set" keyword.

[INDENT]TIP #4: JASS global variable naming convention prefers block letter names, to distinguish them from local variables.[/INDENT]


New concept - variable array
A set of sequentially indexed variables.


[INDENT]Image[/INDENT]


To declare a local variable array, you have to use the keyword "array" after the type of the variable.


[INDENT]Image[/INDENT]


To assign a value or reference, you must choose the index which will hold it, using the "[]" sign.

The same applies to global arrayed variables declaration.

[INDENT]NOTE #5: Array index values in JASS have a limit of 8190, any larger values will not work.[/INDENT]

[INDENT]NOTE #6: Initial values are not possible when declaring arrayed variables.[/INDENT]


CHAPTER THREE - OPERATORS
New concept - operator
A symbol representing a operation.

New concept - assignment operator
An operator which assigns a value to a variable.

Marker - "="


New concept - arithmetic operators
Operators, which perform arithmetic operations.

Addition
Marker
- "+"

Subtraction
Marker -
"-"

Multiplication
Marker -
"*"

Division
Marker -
"/"


[INDENT]Image


NOTE #7: Multiplication and division operations have higher priority than addition and substraction.

NOTE #8: You can use parenthesis to give a group of operations a higher priority.
[/INDENT]

New concept - Relational and equality operators
Operators, which give a boolean result after operation.

Relational operators are needed, when you want to compare two (or more) variables to determine your further actions.

expression equal to expression
Marker - "=="

[INDENT]NOTE #9: Do not mix this up with assignment operator "="![/INDENT]

expression not equal to expression
Marker - "!="

value greater than value
Marker - ">"

value less than value
Marker - "<"

value greater than or equal to value
Marker - ">="

value less than or equal to value
Marker - "<="

To join a group of operations together, you can use the keywords "and" and "or".


[INDENT]Image[/INDENT]


The function returns true if both of the comparisons evaluate to true.


[INDENT]Image[/INDENT]


The function return true, if either of the comparisons evaluate to true.

CHAPTER FOUR - CONDITIONALS, LOOPS

Now, we've come this far that we know how to declare functions and variables and know how to do some basic operations. The next step would be learning some basic conditional statements and loops.

New concept - conditionals
A specific type of statement which performs something dependant upon the result of an expression.


[INDENT]Image[/INDENT]


The syntax for declaring an else-conditional is following: first comes the keyword "if", which is followed by the condition which needs evaluation. After that comes the keyword "then". All conditionals end with the keyword "endif". Between the keywords "then" and "endif" belong the actions which will be run depending on the evaluation.


[INDENT]Image[/INDENT]


The if-else-conditional is just an expanded if-conditional, with an added "else" keyword. The actions between "else" and "endif" will be run when the first condition evaluated differently than wanted.


[INDENT]Image[/INDENT]


The elseif-conditional is kind of like an expanded if-else. Instead of keyword "else" you must use "elseif" which is followed by a condition. After the condition, you must use "then" keyword, just like with the regural if-condition.


New concept - loop
A piece of code that performs a series of instructions repeatedly until some specified condition is satisfied.


[INDENT]Image[/INDENT]


Loops are useful, when you need a function to do it's task multiple times.

To declare a loop, you must use the keyword "loop" to start and the keyword "endloop" to end it. To escape the loop, you must use the keyword "exitwhen" together with a condition. When the condition is satisfied, the loop is ended.

In the example loop, we use an enumerator variable, which is increased every loop iteration. Once the variable is greater than 5, the loop ends.

[INDENT]NOTE #10: A loop which conditions are never satisfied will crash Warcraft, so be careful.[/INDENT]

CHAPTER FIVE - CONCLUSION AND SUGGESTIONS

This was the very basic of JASS. Without knowing the information this tutorial explained, it would be very difficult to learn more advanced things and develop your skills. If anything this tutorial explained is left unclear to you, take your time to read it again to graspe the idea.

If you feel like you're ready to move on, here's a list of little bit more advanced tutorials, which recap some of the things this tutorial explains and teach new knowledge.

Moving from GUI to JASS by Rheias
Writing Efficient Code by phyrex1an
JASS Introduction (a lenghty tutorial) by Vexorian
So You Want to Learn JASS by emjlr3

The key to learning any programming language is simple: practise! It might sound dull and boring, but eventually you will find yourself writing more and more complex scripts.

In addition, when learning JASS, use the "Convert to Custom Script" action in World Editor to turn your GUI triggers to JASS. Though it's a very unefficient way to learn JASS, it helps you to learn Blizzard functions, used by every script out there.

Anyway, this ends our little tutorial!


Happy mapping!

Andrewgosu, your favorite Pandaren
[/INDENT]
Spoiler for vJASS (Light):
Credits To: 1337D00D

This is a tutorial on how to use some of the advanced JASS functions provided by vJass.

Stop! If you are not using the JASS NewGen Pack, don't bother reading this tutorial!

This tutorial will cover how to use:
Libraries
Scopes
Text Macros
Structs
<More later>

__________


Globals

You are able to use global blocks almost anywhere now.

Code: Select all

globals
	integer val = 0
endglobals


__________



Libraries

Libraries are groups of functions that will be placed before any others in the map script. Useful if you have a function that is called by others.
Synthax

Code: Select all

library NAME [requires LIBRARY] [initializer FUNCTIONNAME]
...
endlibrary
EXAMPLE

Code: Select all

library mylib
	function libfunc takes nothing returns nothing
	endfunction
endlibrary
libfunc() will be loaded before any other functions.

Library Requirements

Sometimes functions within libraries will need to be placed before other libraries. In this case, you use the "requires" parameter. The library name given here will be placed before this library.

Example:

Code: Select all

library liba requires libb
	function libaf takes nothing returns nothing
		call libbf()
	endfunction
endlibrary

library libb
	function libbf takes nothing returns nothing
	endfunction
endlibrary
liba requires libb, so libb will be put before liba.
Note that you cannot have two libraries require each other.

Library Initializers

In cases where you need a function to be run before a certain library is loaded, use the "initializer" parameter. The function specified must take nothing.

Example:

Code: Select all

function init_liba takes nothing returns nothing
endfunction

library liba initializer init_liba
	function run takes nothing returns nothing
	endfunction
endlibrary
Library Private

Functions within libraries can be labeled as "private". Functions that are private are only used within their library. Functions outside the library can have the same name as the private one. Useful if there are multiple functions with similar names.

Only functions within the library are able to call to that function.

To make a function private, simply add "private" before the "function" declaration.
Example:

Code: Select all

library whee
	private function test takes nothing returns nothing
	endfunction
endlibrary

function test takes nothing returns nothing
endfunction
__________



Scopes

Scopes are similar to libraries, though they don't put the contained code at the top. Useful if you want to have private stuff.

Syntax:

Code: Select all

scope NAME
...
endscope
EXAMPLE:

Code: Select all

scope myscope
	function scopetest takes nothing returns nothing
	endfunction
endscope
Scope Private

Same as Library Private

__________




Text Macros

This is something for all you lazy people. Textmacros allow you to make similar copies of other functions.

Syntax:

Code: Select all

//! textmacro NAME [takes ANYTHING,ANYTHING,...]
	function...
	...
	endfunction
//! endtextmacro
//! runtextmacro NAME(PARAMETERS)
The text macro will replace $ANYTHINGS$ with whatever value you give it when you run it. It's kind of hard to understand, so look at the example.

Example:

Code: Select all

//! textmacro dofunc takes FUNC,INSTANCE
function do$INSTANCE$ takes nothing returns nothing
	call $FUNC$
endfunction
//! endtextmacro
//! runtextmacro dofunc("DoNothing()","1")
//! runtextmacro dofunc("BJDebugMsg("HI")","2")
This text macro will create two functions:

Code: Select all

function do1 takes nothing returns nothing
	call DoNothing()
endfunction
function do2 takes nothing returns nothing
	call BJDebugMsg("HI")
endfunction
The parameters this text macro takes are FUNC and INSTANCE. Whatever you put as INSTANCE will be appended to "do". This is required, for if you left this part out it would create two functions with the same name.

Here's another example:

Code: Select all

//! textmacro chat takes num
call TriggerRegisterPlayerChatEvent( gg_trg_Untitled_Trigger_001, Player($num$), "asdf", true )
//! endtextmacro
//! runtextmacro chat("0")
//! runtextmacro chat("1")
//! runtextmacro chat("2")
//! runtextmacro chat("3")
Here, you don't have to copy the whole line of code over and over again, just 0,1,2, and 3.

__________



Structs

Structs allow JASS to become more object-oriented. Kind of like Javascript or PHP class.

Example:

Code: Select all

struct somestruct
	string message = "Hello!"
endstruct

function showmsg takes nothing returns nothing
	local somestruct s=somestruct.create()
	call BJDebugMsg(s.message)
	call s.destroy()
endfunction
This would display "Hello!".

Synthax

Code: Select all

struct NAME
...
endstruct
Create a Struct:

Code: Select all

local STRUCTNAME VARIABLENAME=STRUCTNAME.create(ARGS)
Destroy a struct:

Code: Select all

call VARIABLENAME.destroy()
Note that you need to destroy structs when you're done with them. There's a limit of 8190 undestroyed structs at one time.

In order to get a variable within a struct:

Code: Select all

VARIABLENAME.VARIABLEINSIDEOFSTRUCT
Here's another example:

Code: Select all

struct test
	real one=1
	real two=2
	real three=3
	real sum
endstruct

function add takes nothing returns nothing
	local test blah=test.create()
	set blah.sum=blah.one+blah.two+blah.three
	call BJDebugMsg(R2S(blah.sum))
	call blah.destroy()
endfunction
The above function will display "6.000".

Note that you don't need to null struct variables.
Structs also cannot have arrays in them.
Structs also instance themselves, so you can have two of the same structs out at the same time.

Struct Methods

Methods are functions within structs. That's it.

Example:

Code: Select all

struct we
	string msg="Hello!"
	method sayit takes nothing returns nothing
		call BJDebugMsg(this.msg)
	endmethod
endstruct

function test takes nothing returns nothing
	local we s=we.create()
	call s.sayit()
	call s.destroy()
endfunction
To call a method:

Code: Select all

	call VARIABLENAME.METHOD(ARGS)
Note inside the "sayit" method, I used "this.msg". To get a variable inside the same struct, use "this.".

Don't use GetTriggeringTrigger() or waits inside methods.


You can use structs to almost completely replace local handle variables.

Code: Select all

struct spell
	real x
	real y
	unit target
endstruct

function Trig_Spell_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

function Trig_Spell_Timeout takes nothing returns nothing
	local timer t = GetExpiredTimer()
	local spell data = spell(GetHandleInt(t,"struct"))
	call SetUnitPosition(data.target,data.x,data.y)
	call data.destroy()
	call PauseTimer(t)
	call FlushHandleLocals(t)
	call DestroyTimer(t)
	set t=null
endfunction


function Trig_Spell_Actions takes nothing returns nothing
	local spell data = spell.create()
	local unit u = GetSpellAbilityUnit()
	local unit ta = GetSpellTargetUnit()
	local timer t = CreateTimer()
	set data.x = GetUnitX(t)
	set data.y = GetUnitY(t)
	set data.target = ta
	call SetHandleInt(t,"struct",data)
	call TimerStart(t,GetRandomReal(0,10),false,function Trig_Spell_Timeout)
	set u=null
	set ta=null
	set t=null
endfunction

//===========================================================================
function InitTrig_Spell takes nothing returns nothing
    set gg_trg_Spell = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Spell, EVENT_PLAYER_UNIT_SPELL_FINISH )
    call TriggerAddCondition( gg_trg_Spell, Condition( function Trig_Spell_Conditions ) )
    call TriggerAddAction( gg_trg_Spell, function Trig_Spell_Actions )
endfunction
This spell teleports the target unit back to its original position after a random number of seconds. This spell only uses local handle variables once, and is much faster because structs are faster than using GameCaches.
Image