Starting our spell
Well, to start I am going to make a SIMPLE spell using everything I tough you behind. I have in mind an AOE spell. I am a fan of AOE spells because in ladder they are essential to kill armies, which will grant you the final bonus. My AOE spell will have a different effect depending on the number of units in the AOE. I intend to do the following:
- If there are more allies than enemies, we heal all allies by X
- If there are more enemies, we damage the enemies for Y
- If the number of allies is equal to the number of enemies, we damage the enemies and heal the allies
So, the spell will be JESP and will use vJASS. To start I decided to use Silence. I want the spell to be instant so I will set its duration to 0.01. I will make this ability as dummy as possible, so I will also change the “Attacks prevented” field so they don’t prevent any attack at all. Instead of using silence you could use the "Channel" ability. To quote HINDYhat:
Channel is the ultimate dummy ability. You can change the order id so if you have two spells based on Channel on the same caster, they won't conflict. Within the Channel spell definition, you can make it a Unit target spell, a Point target, an AoE or a Unit-Point target spell. You can also make it a unique cast... anyway you can do pretty much anything with it. You'd better check it out yourself. It's at default a neutral hostile hero spell.
So, this ability allows you to create any kind of ability for your heroes with just a few twists. The channel ability is quite important for a coder's life so, although I don't use it on my spell, I think it is important enough for me to mention it. I also added the channel ability to my test map, right next to Instant Justice spell. You can have fun trying to find out its multiple uses.
With these fields set it is time to move to the coding.
First I press “CTRL + T” to create a new “trigger” (this is not an actual trigger but a trigger window, triggers are more complex things but for now that really doesn’t matter), and I will give it the name of “InstantJustice” (I think this is a cool name).
Now we convert it to custom text as usual and we delete everything inside it.
First let’s create our scope and our initializer. Inside our Init function we create our local trigger and set the actions and conditions. Why do we create a local trigger and why don’t we just use the global one?
Globals trigger:
JASS:
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
set gg_trg_Untitled_Trigger_001 = CreateTrigger( )
call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction
I create a local trigger because it is more portable. Vexorian himself suggested so. Local triggers in this case (making spells) are easier to deal with.
So, after creating the function and the trigger, we add the condition and the function to it.
After creating our Init function we create our empty functions “Actions and Conditions”. Note that everything will be inside our scope “InstantJustice”.
So, now we have something like this:
JASS:
scope InstantJustice initializer Init
private function Conditions takes nothing returns boolean
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
//preloading effects
endfunction
endscope
Note that the name of the scope MUST much EXACTLY the name of the trigger, else it won’t work!
Also, you should see in my Init function I added two comments in the end. These comments will define a small area, which we will use soon.
Now, this may seem hard so far, stop, re-read, if you don’t understand make a post, I am here to help. Try to understand the logic, if need, review the previous chapters. The notions of scope and private are very important here and soon the notion of free global declaration will come in handy.
Now, let us start making the easy part – completing the Conditions function. In this function we check if our spell is the one being cast. Because we also want to make a JESP spell, it is the perfect time to make our SETUP section. This setup section will be the first thing to appear and it will have what we want to make customizable. I have in mind some things I would like the user to change:
1 - Rawcode of the spell
2 - Range of the spell
3 - Damage of the spell
4 - Healing of the spell
The 1st point is the rawcode of the spell. This rawcode will NEVER change during the game so it is a good idea to save it into a constant global. I want all other fields to change according to the level of the spell, so I need to use functions because globals can’t take arguments. Note that all functions and globals of our spell will be private.
So, now I am going to help, you should have something like this:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
//preloading effects
endfunction
endscope
Note I have delimited my SETUP section using comments. It is good to do this because this way our user will know what he needs to change and therefore he won’t go the spell’s core. I also took the freedom of choosing the return values and to complete our Conditions function. Now we he want to damage, heal, or pick units we will call these functions and give them level as a parameter.
When importing the spell, the only the user will have to change, will be the rawcode and the functions if he wants to.
So, I will give you an idea of the function Actions:
- Select all units in the target AOE
- Save those units into a group
- loop through the group counting allied units and enemy units
- enter an if-then-else statement that will do the actions
- end spell
However before selecting the units we must get the spell location. Blizzard screwed up here, there is no way to get spellTargetX and spelltargetY, we must first get the location and then extract the information from it. We must all nullify the location so it won’t leak.
It would also be a good idea to save the level into an integer local, and the caster into a local unit variable. Remember we will nullify caster in the end.
Here is the code so far:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
//preloading effects
endfunction
endscope
Function Actions is not getting complicated so far. We will loop through a group so we are going to need a variable “f” to save the current unit (I use the FirstOfGroup way of doing things). We will also need a counter for the allies and the enemies, and this counters must start with zero. Let’s give a look to function Actions again:
JASS:
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
Now things will complicate. This is the hard part of the spell, I hope you can understand the way I will explain things. If not, remember to make posts!
So as I said before we will need a group to save all units in the AOE. However, we have two problems:
- We don’t want to add all units to or group, per example, buildings and dead units are not a good choice
- We first need to loop thourgh the group to count the number of allies and enemies. Because we are using the FirstOfGroup method of doing things, this means that to move on we are forced to call “GroupRemoveUnit”. This way, in the end of the countdown, our group would have no units at all, which would be bad. Thus we conclude, we need an auxiliary group that will be a copy of our first group.
First things first. Lets create our group, and call it “all”. Many people just do “local group groupName = CreateGroup()” however every time someone casts a spell we will be creating a group. Let’s enter a world of magic where our spell becomes super famous. Imagine we have it on a footies map, and each footman has this ability. Imagine also that the map has 12 players, each one with 30 footmans. If all units cast the spell at the same time we would be forced to create and destroy 360 groups in a matter of milliseconds. This will obviously cause huge and massive lag, and we don’t want that. We can instead
use 1 single global group. This group will be used in the core, and we have no intention to make it accessible to the user, so it will be in the core of our spell. This has a simple translation though – after your SETUP section end, create another globals block with a private group called “all”. It’s truth; a spell can have several global blocks. Now, as good practice, we do NOT initialize our group in the block. Thing is some natives can and will crash the game if settled automatically. CreateGroup is one of those evil natives. But don’t worry, we will initialize this global in our Init function. There is no problem because the game has already started.
Now, the code for you to understand it:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
endglobals
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
//preloading effects
endfunction
endscope
You see now in the Init function our Group is being set to “CreateGroup”. Now our spell will be more efficient and lighter. Some of you may think, is this spell still MUI? Well, yes it is. Why? There is an answer for this question which is quite complicated. As far as I understand Warcraft3 runs in threads, in a very low level. In this case two threads can’t run at the same time, so this spell is MUI enough to be playable.
Now that we have our global group created, we need a Boolean expression, so we can call “GroupEnumUnitsInRange”.
Again some people create and destroy boolexpr for no reason. According to my example in the magical world, we would have to create and destroy 360 local Boolean expressions. This is even more stupid, having in mind a Boolean expression is quite of a constant which will NEVER change in game, we can and should make of if a global as well. We use the same procedure. Inside the globals block we created earlier we add another variable of type “boolexpr” and name it “b”. We initialize this variable in the Init function as we did with the group. Now a boolean expression needs a function, a condition to call. I decided to create one called “Pick” and placed it above the function “Conditions”. For now function “Pick” only returns true, we shall change that value soon.
With our boolexpr created and our main group created as well, we can now call the function “GroupEnumUnitsInRange”.
So far the code is getting more complex. We have this:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private boolexpr b
endglobals
//===========================================================================
private function Pick takes nothing returns boolean
return true
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set b = Condition(function Pick)
//preloading effects
endfunction
endscope
Note how we set our boolexpr in the Init function. This is very handy, having a function that can initialize every global. In function Actions we added the call “GroupEnumUnitsInRange”. Note how I define the Range of the spell, by passing level as an argument to the SETUP function “Range”. This is how we make spells easy to edit. It isn’t that hard is it? However the hard part only begins now. Note that so far, we are picking ALL units inside the AOE, and as I said before, I want to make a selection. I think it is always a good idea to allow the user to select which units he wants the spell to affect. To fix this we could place function Pick inside the SETUP section, however by doing that we would force the user to deal with “GetFilterUnit()”. So, I decided to make the life easier for the user. Function “Pick” will call another function inside the SETUP section. This function will be called Targets and will take as an argument a unit “target” which is the filtered unit. If it seems complicated don’t worry, it is actually quite simple and I will show It now:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return true
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private boolexpr b
endglobals
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set b = Condition(function Pick)
//preloading effects
endfunction
endscope
Note that now function Pick only makes a simple call. It will be in function Targets that we will actually pick our targets. However if you look right you see function Targets returns true, which means we still pick all units. Well, we don’t want that do we? So it’s time to choose which units we want to affect, or maybe easier, which units we do NOT want to affect. The negation is many times easier to deal with and in the case more simple.
So a quick list of units we don’t want to affect:
- Dead units
- Buildings
- Magic Immune units
- Mechanical Units
And I think this is enough, all other units will be affected.
Another thing I saw when teaching my students is that many of them use “GetUnitState(unit, state)” to know the life of a unit. Truth is that there is an easier and faster way, the “GetWidgetLife(widget)” function. A widget can be many things like a destructible or, in this case a unit. So we will use this function to check the life of a unit by typing “return (GetWidgetLife(target) > 0.405)”. Another common mistake is to think that dead units have <= 0 hitpoints. In Warcraft a unit actually dies when its life becomes < 0.405 and not 0. This is quite important and I am sure it will aid you in the rest of your coding life in wc3. Now the buildings, the magic immune units and the mechanical units are quite easy to deal with, and I am not going to explain them in detail.
Therefore I present you the final code of the function Targets:
JASS:
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
So, now that we pick the units we want to, it is time to go back to the function “Actions” where things will now get a little bit more complicated.
If you remember our second problem, you will know we need to make a copy of the group all. Well, for this effect we need to create an auxiliary group, also a global group, and name it “copy”. The initialization will be done in function Init as well. Now I present you with the code we have so far:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private group copy
private boolexpr b
endglobals
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
endfunction
endscope
Now, to make the copy, I am going to use a function made by a well known warcraft coder called Blade.dk. This function is called CopyGroup and is available in one of his tutorials, which belongs to a session he never finished. The link to his tutorial is in the bibliography. The function is the following:
JASS:
function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
So what does this function do? Well, this function receives a group as a parameter. Than it uses a bj global variable (BJ variables are NOT evil as many people think, they are actually faster. Only some BJ
functions are evil) called “bj_groupAddGroupDest” which will become a group. This group will then receive all units the group passed as an argument has due the ForGroup. To finish Blade returns the bj global, now an exact copy of the group we passed as an argument.
Some people don’t like it, but truth is that we can and should use other people work if we find it good in order to make our OWN work better. However, remember an important thing, always give credits, else you are stealing. I will add this nice function to our code but I will comment that it was not made by me, but by Blade.dk and a place so people can know where to find more information about it.
Finally, remember we want this function to be renamed when we compile, we also don’t want other functions from outside the scope “InstantJustice” to access this function we are importing. So, we must make the function private.
Here is the code so far:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private group copy
private boolexpr b
endglobals
//===========================================================================
//Function made by Blade.dk; Search for [url=http://www.wc3campaigns.com]wc3campaigns.com[/url] for more info
private function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
endfunction
endscope
Now, that we have everything we need, I say let’s finish the damn spell. The tutorial is already 29 pages long and I am getting tired of writing this much =P (actually I am not tired but meh lol).
So, remember we have a group ready to be used, the “copy” group. So before we do the effect to the units we have to count them, and this is why we need the auxiliary group.
All this information results in the following:
1 –Copy group “all” to group “copy”
2 - Enter a “FirstOfGroup” loop where we make the counting
3 – For each allied unit we increment the allies’ integer, and for each enemy unit we increment the enemies integer.
4 - We exit the loop and we make the effect
So, this pseudo code now transforms into the following:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private group copy
private boolexpr b
endglobals
//===========================================================================
//Function made by Blade.dk; Search for [url=http://www.wc3campaigns.com]wc3campaigns.com[/url] for more info
private function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
set copy = CopyGroup(all)
loop
set f = FirstOfGroup(copy)
exitwhen(f == null)
call GroupRemoveUnit(copy, f)
if IsUnitAlly(f, GetOwningPlayer(caster)) then
set allies = allies + 1
else
set enemies = enemies + 1
endif
endloop
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
endfunction
endscope
It is very important for you to understand how the loop works. I assume that as a JASSER you know this basic trick, if you don’t remember to post for help. Note that when we exit the loop the unit variable “f” is null, so we never need to nullify it, and even better we can reuse it.
Now that we made the counting, we can do the effects, heal and damage. For this effect I decided to use an if statement with several loops inside it. It is quite simple:
JASS:
if allies > enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
endloop
elseif enemies > allies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//damage enemies
endloop
elseif allies == enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies and damage enemies
endloop
endif
Now the only thing we need to do is to simulate the healing and the damage. I will start by the healing.
Again, many people use “SetUnitState” do simulate healing. Although it is not wrong, it is not the best way. I use “SetWidgetLife”because it is easier to understand and it is faster. So, our healing line will be the following:
JASS:
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
You see I use GetWidgetLife here. I also use the function in the SETUP “Heal” and pass it level as a parameter so it is easy to change.
Now moving to the next if, we want to damage the enemies. Some of you may think “Hey we can use SetWIdgetLife” again. Answer is NO, you should never use “SetWidgetLife” to cause damage. Why? Well, there is a reason for that. If a unit dies due the damage “SetWidgetLife” caused, the computer will not know the killer. This is especially bad when you need a multiboard to count the number of kills per example, in such a case, the counter wouldn’t increment unless you wanted to add the multiboards code to all your spells, which would be a very bad idea. To simulate damage the best function I always use is the “UnitDamageTarget”. This function follows me since GUI and it is very useful in most cases. This way, our damaging line:
JASS:
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
Finally and moving to the last case, we heal and damage using this same lines.
Now, do not forget we need to use another if statement inside the loops, to verify if we are healing allies and damaging enemies. So, with no further delay, I show you all how my if statement goes:
JASS:
if allies > enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
endif
endloop
elseif enemies > allies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//damage enemies
if IsUnitEnemy(f, GetOwningPlayer(caster)) then
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endif
endloop
elseif allies == enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
//if an unit is not an ally than it is an enemy
else
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endif
endloop
endif
It may look very complicated at first, but note it is quite easy. In every case we always clean the “all” group and f always stays null at the end of the iteration.
Now the heart of our spell is done. You can test it right away if you want. However you should know that most users are newbs and so they don’t care if a spell is good coded, they don’t even know how to see that. They care only about how “cool” the spell is. A way of making our spells cool is by adding effects. For the sake of learning, let0s add some effects!
I have an idea of what I want to use and when:
- When casting the AOE spell, I want to use the Wisp detonate model
- When healing the allies think the holly bolt model is nice
- When damaging the enemies I think a thunder effect will fit
Now, because this is a JESP spell we want the user to have the ability to choose the effects. How to do that? Well, we save the path of the effects in a string. This string will never change inside game, so it can be (and should be) a private constant string. We will place this string in our SETUP section because we want the user to change it.
So now the global block from our SETUP section looks like this:
JASS:
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
private constant string AOE_EFFECT = "Units\\NightElf\\Wisp\\WispExplode.mdl"
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
private constant string DAMAGE_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
endglobals
Now we need to go to function Actions and add the call to create the effects.
For a matter of efficiency, I will create and destroy the effects in one line:
JASS:
call DestroyEffect(AddSpecialEffect(AOE_EFFECT, spellX, spellY))
This will create and destroy the special effect for the AOE in one line.
For the healing and damaging effect I will use something similar, but not equal:
JASS:
call DestroyEffect(AddSpecialEffectTarget(effectName, f, “origin”))
Now we just have to add the lines to the correct places and “voalá” our spell becomes cool =P
With no further delay, here is the code I have done so far:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
private constant string AOE_EFFECT = "Units\\NightElf\\Wisp\\WispExplode.mdl"
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
private constant string DAMAGE_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private group copy
private boolexpr b
endglobals
//===========================================================================
//Function made by Blade.dk; Search for [url=http://www.wc3campaigns.com]wc3campaigns.com[/url] for more info
private function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
//create the AOE effect
call DestroyEffect(AddSpecialEffect(AOE_EFFECT, spellX, spellY))
//counting the units
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
set copy = CopyGroup(all)
loop
set f = FirstOfGroup(copy)
exitwhen(f == null)
call GroupRemoveUnit(copy, f)
if IsUnitAlly(f, GetOwningPlayer(caster)) then
set allies = allies + 1
else
set enemies = enemies + 1
endif
endloop
//making the effect of the spell
if allies > enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, f, "origin")) //the healing effect
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
endif
endloop
elseif enemies > allies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//damage enemies
if IsUnitEnemy(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, f, "origin")) //the damaging effect
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endif
endloop
elseif allies == enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, f, "origin"))
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
//if an unit is not an ally than it is an enemy
else
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, f, "origin"))
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endif
endloop
endif
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
endfunction
endscope
Now our spell is cool but, it will lag the first time we cast it. To solve this we must preload everything! We must preload:
1 – the effects
2 – the ability
Preloading the effects will be quite easy. The difficult part of the job is done. To preload an effect we just need to type:
In the Init function.
So, preloading our effect will look like this:
JASS:
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
call Preload(AOE_EFFECT)
call Preload(HEAL_EFFECT)
call Preload(DAMAGE_EFFECT)
endfunction
Now we need to preload the ability. Preloading an ability is quite complex. You can do that by using an auxiliary system such as “xe” (see links in Bibliography) or doing it manually, as I will do. To preload an ability we must add it to an unit in the Init function. So, this means we need a dummy unit. We can use anything as a dummy unit, I will create one by using a peon and removing all abilities he has. The dummy will also have the ability locust. The use of a dummy unit also implies that we must place its rawcode in the SETUP function, so it is easy to change.
The code will be this:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
private constant integer DUMMY_ID = 'h000' //rw of the dummy unit
private constant string AOE_EFFECT = "Units\\NightElf\\Wisp\\WispExplode.mdl"
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
private constant string DAMAGE_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private group copy
private boolexpr b
endglobals
//===========================================================================
//Function made by Blade.dk; Search for [url=http://www.wc3campaigns.com]wc3campaigns.com[/url] for more info
private function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
//create the AOE effect
call DestroyEffect(AddSpecialEffect(AOE_EFFECT, spellX, spellY))
//counting the units
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
set copy = CopyGroup(all)
loop
set f = FirstOfGroup(copy)
exitwhen(f == null)
call GroupRemoveUnit(copy, f)
if IsUnitAlly(f, GetOwningPlayer(caster)) then
set allies = allies + 1
else
set enemies = enemies + 1
endif
endloop
//making the effect of the spell
if allies > enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, f, "origin")) //the healing effect
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
endif
endloop
elseif enemies > allies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//damage enemies
if IsUnitEnemy(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, f, "origin")) //the damaging effect
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endif
endloop
elseif allies == enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, f, "origin"))
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
//if an unit is not an ally than it is an enemy
else
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, f, "origin"))
call UnitDamageTarget(caster, f, Damage(level), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null)
endif
endloop
endif
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
call Preload(AOE_EFFECT)
call Preload(HEAL_EFFECT)
call Preload(DAMAGE_EFFECT)
//preloading the ability
set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, 0, 0, 0)
call UnitAddAbility(bj_lastCreatedUnit, SPELL_ID)
call KillUnit(bj_lastCreatedUnit)
endfunction
endscope
Now the important thing to note is that the globals block in the SETUP section now has another variable “DUMMY_ID” and that inside the Init function I’ve added a preloading ability area. Note that I am using a bj variable that many of us know, the bj_lastCreateUnit. Preloading an ability is as simple as that:
- Create a unit
- Add the ability to the unit
- Kill the unit
Now our spell is DONE!!!! (Finally, the tutorial is 43 pages long so far).
Now we have a cool efficient spell.
There are more things we can change though, per example some people like to choose the damage type or the attack type the spell will cause. We can easily allow this by using globals in the SETUP section:
JASS:
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
private constant integer DUMMY_ID = 'h000' //rw of the dummy unit
private constant string AOE_EFFECT = "Units\\NightElf\\Wisp\\WispExplode.mdl"
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
private constant string DAMAGE_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
private constant damagetype D_TYPE = DAMAGE_TYPE_NORMAL
private constant attacktype A_TYPE = ATTACK_TYPE_MAGIC
endglobals
Magic of vJASS; you can0t have this variables in the variable editor of WE =D
Now the only thing we have to change is the “UnitDamageTarget” function to use these variables.
Here is the code:
JASS:
scope InstantJustice initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer SPELL_ID = 'ANsi' //the rawcode of the spell
private constant integer DUMMY_ID = 'h000' //rw of the dummy unit
private constant string AOE_EFFECT = "Units\\NightElf\\Wisp\\WispExplode.mdl" //effect that will be created when we cast the spell on the AOE
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl" //effect that will be created when we heal units
private constant string DAMAGE_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //effect that will be created when we damage units
private constant damagetype D_TYPE = DAMAGE_TYPE_NORMAL //the attack type of the spell
private constant attacktype A_TYPE = ATTACK_TYPE_MAGIC //the damage type of the spell
endglobals
private function Range takes integer level returns real
//returns the range the spell will affect
return level * 150.
endfunction
private function Heal takes integer level returns real
//returns the heal allies will take
return level * 75.
endfunction
private function Damage takes integer level returns real
//returns the damage enemies will take
return level * 50.
endfunction
private function Targets takes unit target returns boolean
//the units the spell will affect
return (GetWidgetLife(target) > 0.405) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false)
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
private group all
private group copy
private boolexpr b
endglobals
//===========================================================================
//Function made by Blade.dk; Search for [url=http://www.wc3campaigns.com]wc3campaigns.com[/url] for more info
private function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
//===========================================================================
private function Pick takes nothing returns boolean
return Targets(GetFilterUnit())
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local location spellLoc = GetSpellTargetLoc()
local real spellX = GetLocationX(spellLoc)
local real spellY = GetLocationY(spellLoc)
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, SPELL_ID)
local unit f
local integer allies = 0
local integer enemies = 0
//create the AOE effect
call DestroyEffect(AddSpecialEffect(AOE_EFFECT, spellX, spellY))
//counting the units
call GroupEnumUnitsInRange(all, spellX, spellY, Range(level), b)
set copy = CopyGroup(all)
loop
set f = FirstOfGroup(copy)
exitwhen(f == null)
call GroupRemoveUnit(copy, f)
if IsUnitAlly(f, GetOwningPlayer(caster)) then
set allies = allies + 1
else
set enemies = enemies + 1
endif
endloop
//making the effect of the spell
if allies > enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, f, "origin")) //the healing effect
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
endif
endloop
elseif enemies > allies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//damage enemies
if IsUnitEnemy(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, f, "origin")) //the damaging effect
call UnitDamageTarget(caster, f, Damage(level), true, false, A_TYPE, D_TYPE, null)
endif
endloop
elseif allies == enemies then
loop
set f = FirstOfGroup(all)
exitwhen (f == null)
call GroupRemoveUnit(all, f)
//heal allies
if IsUnitAlly(f, GetOwningPlayer(caster)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, f, "origin"))
call SetWidgetLife(f, GetWidgetLife(f) + Heal(level))
//if an unit is not an ally than it is an enemy
else
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, f, "origin"))
call UnitDamageTarget(caster, f, Damage(level), true, false, A_TYPE, D_TYPE, null)
endif
endloop
endif
call RemoveLocation(spellLoc)
set spellLoc = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger InstantJusticeTrg = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(InstantJusticeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(InstantJusticeTrg, Condition( function Conditions ) )
call TriggerAddAction( InstantJusticeTrg, function Actions )
//setting globals
set all = CreateGroup()
set copy = CreateGroup()
set b = Condition(function Pick)
//preloading effects
call Preload(AOE_EFFECT)
call Preload(HEAL_EFFECT)
call Preload(DAMAGE_EFFECT)
//preloading the ability
set bj_lastCreatedUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, 0, 0, 0)
call UnitAddAbility(bj_lastCreatedUnit, SPELL_ID)
call KillUnit(bj_lastCreatedUnit)
endfunction
endscope
Now, at last but not least, the most important thing in a spell to prevent stealing: a header.
Many people don’t use headers, this is a mistake. Although we cannot prevent other people to delete them, it is always good to have them, because we can place there important information about our spells. If people try to delete it and steal the spell, they will lose the important information. Non less it always good to know because it also makes it easier to get credits for the work done. I decided to use this stylish header I made:
JASS:
//===========================================================================
//A vJASS and JESP spell that will have an effetc depending on the number
//of units in the AOE. If there are more allies than enemies, we heal allies
//if there are more enemies that allies we damage enemies, if the number of
//allies equals the number of enemies we heal the allies and damage the enemies
//
//@author Flame_Phoenix
//
//@credits
//- Blade.dk, for the CopyCroup function
//
//@version 1.0
//===========================================================================