JASS - Creating a Passive Ability in Jass

Romek

Super Moderator
Reaction score
963
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:
JASS:
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.
JASS:
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.
JASS:
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:
JASS:
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:


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


Your final conditions should look like this:
JASS:


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

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


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


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



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

Attachments

  • [Tutorial] Passive Spells.w3x
    28.5 KB · Views: 562

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
Can you also add in more customization methods, like how to filter out heroes from being charmed.
 

Romek

Super Moderator
Reaction score
963
Can you also add in more customization methods, like how to filter out heroes from being charmed.
Good point.

I knew I forgot something.
 

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
Can you also add in a level factor as a global too? People could custmoize and make it like Hero Level/2 or something.
 

Romek

Super Moderator
Reaction score
963
Can you also add in a level factor as a global too? People could custmoize and make it like Hero Level/2 or something.
Nahh. I don't think that is needed.

Whoever's following the tutorial could find out how to put that in themselves :)
 

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
Ye, just a simple if comparison and such.
 

Romek

Super Moderator
Reaction score
963
Anyway, any comments on the actual tutorial?
 

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
It's a pretty good tutorial, hope to see more. :D
 

trb92

Throwing science at the wall to see what sticks
Reaction score
142
You do not add Possession as an action. You also do not add Cons as a condition. If your making an example, it should probably work. This will do nothing.
 

Romek

Super Moderator
Reaction score
963
You do not add Possession as an action. You also do not add Cons as a condition. If your making an example, it should probably work. This will do nothing.

Can't believe I missed that :p
Thanks.

Maybe add how you can see your spell's Raw Code? Not everyone knows how to.

Added.
 

Nina

New Member
Reaction score
8
Thanks for making this! I don't need something like this right now but it's well explained and I learned now how to use scopes and such and I know now why people have functions like True which returns true and I never knew units leaked o_O. Damn learning owns.
 

Romek

Super Moderator
Reaction score
963
Thanks for making this! I don't need something like this right now but it's well explained and I learned now how to use scopes and such and I know now why people have functions like True which returns true and I never knew units leaked o_O. Damn learning owns.
Thanks a lot :)
It's nice to know my tutorial helped someone :p

Also, all Handles leak. Units, Groups, items, triggers, etc.
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Fairly decent tutorial I guess, though very basic (everyone who coded in GUI should probably be able to create this once switching to JASS). A few things:

1) A unit is attacked - event sucks.

2) What if you do not want a passive ability that registers when you attack? Maybe you would want an ability that passively increases your armor by 2 when you're damaged with a cap of X bonus armor. Each +2 armor gained from this passive skill is removed after Y seconds.

Now that would be something awesome, and could be made to give attack damage or attack speed when attacking, giving stats when attacked.
 

Romek

Super Moderator
Reaction score
963
Fairly decent tutorial I guess, though very basic (everyone who coded in GUI should probably be able to create this once switching to JASS). A few things:

1) A unit is attacked - event sucks.

2) What if you do not want a passive ability that registers when you attack? Maybe you would want an ability that passively increases your armor by 2 when you're damaged with a cap of X bonus armor. Each +2 armor gained from this passive skill is removed after Y seconds.

Now that would be something awesome, and could be made to give attack damage or attack speed when attacking, giving stats when attacked.
This is meant to be basic. It's obviously not a very advanced tutorial :p

Why does the event suck?
Although it registers before the unit takes damage, it is much simpler.
Using unit takes damage will register spell damage as well as normal damage.

Well, if you want that, you can make it.
This tutorial should show you the basics about passive spells, as well as other stuff while making a possession spell. :)
 
Reaction score
456
> This tutorial should show you the basics about passive spells
And such a passive spell in WC3 needs unit takes damage event.
 

Romek

Super Moderator
Reaction score
963
> This tutorial should show you the basics about passive spells
And such a passive spell in WC3 needs unit takes damage event.

The only downside about "Unit is Attacked" is that it can take effect before the missile hits.
The downside with Unit is Damaged is that it takes spell damage into consideration. Which is unwanted. Especially for periodic damage spells.
(Namely ShadowStrike, and knockback systems which do ~2 damage every ~0.01 seconds)

Now you need to add how you get out of raw code mode. :p

Common sense can tell people :p
 

Romek

Super Moderator
Reaction score
963
Just one question.

Why you use this:
JASS:
        loop
            exitwhen i > 12
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, Filter(function True))
            set i = i + 1
        endloop


instead of this:



?

I mean its only called once, so it doesnt hurt even if it is BJ right?
Flare already asked me this.

I just prefer to use the Non-BJ way.

I agree that this BJ isn't really bad. It's also not exactly great. It shortens code by only 5 lines, and it leaks anyway. :p

Also, I find it funny how when BJ's are used, people complain, and when they aren't used, people also complain.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top