JASS - Creating a Passive Ability in Jass

Tutorial By Romek

Creating a Passive Ability in Jass
By Romek


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:
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.
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.
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:
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:
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:
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:
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:
IsUnitType(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:
GetUnitAbilityLevel(GetAttacker(), 'Pwnt') > 0


Your final conditions should look like this:
return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetAttacker())) and GetRandomInt(0, 100) <= 2 and IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO) == false and GetUnitAbilityLevel(GetAttacker(), 'Pwnt') > 0


Now, we need to add the conditions to the trigger. To do this, we use the function
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)
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
DestroyEffect(AddSpecialEffect(...))


So I'll add my effect to the "chest" of the target.
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:
call SetUnitOwner(t, GetOwningPlayer(u), true)


Now we'll need to null the 2 unit variables to prevent them from leaking.
set u = null
set t = null


Finally, we'll need to add the actions to the trigger.
For this, we use the function:
call TriggerAddAction(t, function Possession)



Your code should now look like this:
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.
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:
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.
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.
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:
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.
// +------------------------------------------------------------------------+
// |     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:
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 :)

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.