Spell Making School: A Simple Dash Spell
Introduction:
The purpose of this tutorial is to teach people how to make a simple dash spell in JASS. This not only enables it to be very efficient, but also MUI, where in GUI this could be neither of the above.
That is, however, not the only thing you will learn. The dash spell is mainly used as an example, the tutorial should teach you some general things about JASS and spell making, which is more important.
This tutorial is NOT an introduction or starting course to JASS. It is highly recommended (more accurately, required) that you know the basics of JASS before you start on this. There are plenty of them around here, just take a look, and come back when you feel you are ready.
This is the first part of a series of tutorials covering different areas in spell-making that I plan to make - A so-called spell making school I am starting, per say.
All you need to follow this tutorial, is the WE some basic JASS knowledge, JASS Craft and the CScache system found and easily implemented from Vexorian’s caster system, found here . I use this, and recommend that you do the same. We could go through and make our own here, but why bother? When there is one available for us to work from.
Even though code can be restored, I don't recommend coding spells in the WE, because a small error is enough to make the program crash on saving, and nobody enjoys that.
{Parts taken and edited from Blade.dk’s tutorial here .}
Creating Our Spell:
Let us start by first creating a trigger in the GUI. This is a trick I often do to save myself time, as it takes me about 10 seconds to click a few buttons and convert to custom script, where as it would take me a few minutes to look up my rawcodes, and type it all up from scratch:
Here we use starts the effect of an ability because it is more accurate. This event makes the trigger fire when the spell is cast, cool down starts and mana is taken. Therefore it is the ideal event in this case. A suggest not using “Begins Casting An Ability” because that runs before these things happens, and can be exploited. It can however be used to check for a special condition for an ability, and only let the ability cast if that condition is met, but that is neither here nor there. Ok now convert your GUI trigger to JASS:
Lets start by noting a few things about this. The ‘Auan’ denotes the ability being cast. Each ability in each map has a unique rawcode for it. The simplest way to find a spell's rawcode, is to select it in the object editor and press CTRL+D. The first four letters of the spell's name is the rawcode (case sensitive, must be put within single quotes) the next four letters (only appears on custom spells) is the rawcode of the spell it was based on.
The first thing you want to do with all your ability triggers is to simplify the initial condition. It seems a bit goofy does it not? If you read is carefully, basically what it is doing is checking if the ability is not equal to the one we want, and returning false, else returning true. Honestly, what is the point? So we change it:
Now, all we are doing is returning the Boolean of our equation, whether the spell cast, GetSpellAbilityId(), is equal to the ability we want, ‘Auan’. If it returns true, our actions are ran, if it returns false, nothing happens.
We only plan to use one special effect model for the spell for now: The Blood Elf Spell Thief blood model.
The path of that model is: Objects\Spawnmodels\Human\HumanBlood\BloodElfSpellThiefBlood.mdl
When you want to use a path in JASS, you must first put it between quotes, as it is a string. You'll also have to replace every single backslash (\) with two backslashes instead (\\). Another trick to reduce first cast lag is to preload the effects that are going to be used in the spell. So in our init trig we add PreLoad native for the effect we are going to use, which is ran during the map init. It adds to load time, but reduces lag in game:
Alright now lets us beging the actual coding ! We first go to the actions function and store the basic locals we will be needing for the ability:
We store the caster and the location of the target of the ability cast. I then like to get the x and y for that location, as well as that for the caster, since they will used used often in a bit, and use those, because I personally like using reals better then locations, as they are easier for use in natives and do not require any clean up. Next is the angle. In order to move a unit from its position to another position in a straight line, the angle between this points is required. This we use take a look at the function AngleBetweenPoints(). We take what this returns, which uses reals, and use that instead of a bj function, so we eliminate a function, and optimize our trigger.
The next step is to store the distance between the caster and the target. I like to do this because I like the unit to move to where he cast, instead of a fixed distance. This is better for in game strategy as well as realness. What we are going to do with this is to compare the casters position from where he started after every periodic dash movement to that of the total distance. Once they are within a range, then the movement is over. So just like with the angle, we take a look at the DistanceBetweenPoints() function and use what is in that by plugging in our stored reals for the caster position and target position.
We then create a trigger to do the damage with. Why another trigger you ask? Can’t we just damage units around the caster as he moves? Yes you can, however that is far less efficient, and can lead to targets being damaged many times on a single pass.
We next need to create a local for the string to get the attachment of our trigger, since we will be storing locals onto it, and use GetAttTable() to get it. If you are unfamiliar with this process, please refer to Daelin’s Advanaced JASS tutorial.
A timer is then created, and the same process used to set a string equal to the attachment table of the timer.
Now that our local are set, we can continue. The next step is to set up our trigger that will damage the units who come close to the caster as he dashes.
There are three parts to a basic trigger, the event, the conditions, and the actions. We will set them up in order. First the event, that of a unit coming within a range of our caster, so any units who come within 150 of the caster during the duration of the spell will have the conditions ran for them. The next is the condition function, which adds our condition function to the trigger. The next part is adding the triggeraction. I use this method to remove the triggeraction leak, by storing the trigger action to the strig string and creating it in 1 line, we can remove it at a later date, and save and extra line of code. The last part is that of storing the caster in the trigger, since there is no way to get the unit which the triggering unit came in range of ( that I know of).
Our next step will be that of creating our condition and actions function for the local damage trigger we created. A rule of thumb is that any function that is referred to in another function must be above the function it was referred to in. Therefore our condition and actions functions we added to our local trigger must be above the Trig_Bloody_Dash_Actions function in our trigger or we will get errors. So let is create them:
This is why we need the stored caster to this trigger, to find out if the triggering unit(the one that came in range) is an enemy of our caster. We get the attached table of our trigger, and return whether the filter unit is an enemy of the owner of our stored caster, u. I also check if the filter unit is alive with GetUnitState, since we do not want this to fire for dead units. Next is the actions:
Again we start here by declaring our locals as any information we need. I also set the heroes agility*5 to a local real variable since that is the damage I will be doing. My function then proceeds to damage the target based on the dam real I stored, and add 25% of it back to our casters hp, as well as creating our preloaded effect on the target. Again I use all native functions to keep the code as optimized as possible. I set my local units u and v to null at the end to remove the unit leak.
Now our damage trigger is complete, so any enemy unit that is alive who comes in 150 range of our caster will take 5*the agility of the caster in damage and get an effect on them. The caster will also regain 25% of the damage done back to him.
Now we go back to our actions function, Trig_ Dash_Actions, to set up our periodic timer for the casters movement.
As we did with the trigger earlier, we must store all needed values to our timer as well. We will need the target location reals, our angle, distance, and the caster. Two extra ones we store are the trigger we created, so we can remove its leaks when the movement is done, as well as a real named “moved”, which we will use as the total distance moved for the caster. This is what we will compare to the distance we found earlier to decide when our dash will be complete. I usually use .03 as my periodic timer speed, since it is still fast and will look smooth in game, but to slow to cause any lag problems.
Before we go onto our timer function, let us finish this one first:
Three extra things I do before wrapping this function up is setting its timescapepercent, which is how fast the unit’s animations are. We want it to look like it is dashing, so why not make it look like it is running, by using a faster animation speed. I then set the caster to have no collision, and lastly so it’s animation index to that of its walk animation. This is a trick used to force units to play an animation even when they are being moved around. To check a units animation indexs, play them in game the same way, and use BJDebugMsg() to display what integer is being played, and you can then use the integer for the walk animation, or whatever other animation you need.
The last part of any function is to remove leaks. You should know enough about leaks and basic JASS functions to know what leaks are and how to remove them. I do that here, and so should you in all of your functions.
The last part we need to do is set up our periodic timer function to move our caster:
This is pretty self explanatory. If you do not understand what is going on here, either re-re-read a handle vars tutorial or the readme that came with the CScache system for a brush up. Two of the locals I created I did not set so anything. These will eventually refer to our local trigger created earlier, however there is no use setting them unless we are actually going to use them, as you will see it a bit.
The next part is probably the trickiest of the entire trigger, which is that of the actual movement. Many people are not really sure how to move units in a straight line, this is how:
We are setting our units position to a polar projection of where it originally was. Take a look at the polar projection bj in JASS Craft:
If you take your units location, and set it to a positive distance at the angle we found earlier, it will move along a straight line from where it started towards the target, at the distance increment you choose.
We also set our units animation again to keep it going, and set our real moved to itself plus the distance we moved our caster, so that we keep track of however it has moved. The next step is a fork in the road, on way, the timer will keep going and the caster moved again, the other, everything will stop since the caster will be close to the destination:
Our local real moved, which has stored how far we have moved the caster, is then compared to the total distance we found earlier, if it is still less then it, we store our new move distance, and the timer continues, however is the total move distance is more then our distance found, we must stop the timer, set everything back to normal, and remove our leaks. I also check to make sure our caster is alive, because it would look silly to dash a dead unit, no?
So if we are at our destination, it is time to shut everything down. Now we set our trigger and strig string locals to the trigger we created and its attached table. Remove the action to take care of that leak, clear the table to take care of that one, and destroy our trigger to save some memory and take care of that leak. Here we also set trig=null, since it only needs to be set to null if it was set to something in the first place.
We then reset our unit to normal animation speed, give it back its pathing, and set its animation to stand, so it doesn’t keep running but just stand there ( ). The last part is removing our leaks with the timer, by clearing its table, pausing it and destroying it. It is important to pause the timer before you destroy it because it can sometimes bug if you do. Again we clean up our leaks at the end by setting our local unit variable to null, and this function is complete.
Another little thing to note, I never set timers I attach things to to null, it can cause bugs. Yes it leaks, but its better to have it always work, and have that small leak, then sometimes bug on you in a game.
Let us take a look at our final trigger:
V 1.02(Fixed up a good bit recently)
That is all for now folks. The spell you have created is MUI and leak free. Congratulations on finishing the tutorial, and good luck JASSing.
Introduction:
The purpose of this tutorial is to teach people how to make a simple dash spell in JASS. This not only enables it to be very efficient, but also MUI, where in GUI this could be neither of the above.
That is, however, not the only thing you will learn. The dash spell is mainly used as an example, the tutorial should teach you some general things about JASS and spell making, which is more important.
This tutorial is NOT an introduction or starting course to JASS. It is highly recommended (more accurately, required) that you know the basics of JASS before you start on this. There are plenty of them around here, just take a look, and come back when you feel you are ready.
This is the first part of a series of tutorials covering different areas in spell-making that I plan to make - A so-called spell making school I am starting, per say.
All you need to follow this tutorial, is the WE some basic JASS knowledge, JASS Craft and the CScache system found and easily implemented from Vexorian’s caster system, found here . I use this, and recommend that you do the same. We could go through and make our own here, but why bother? When there is one available for us to work from.
Even though code can be restored, I don't recommend coding spells in the WE, because a small error is enough to make the program crash on saving, and nobody enjoys that.
{Parts taken and edited from Blade.dk’s tutorial here .}
Creating Our Spell:
Let us start by first creating a trigger in the GUI. This is a trick I often do to save myself time, as it takes me about 10 seconds to click a few buttons and convert to custom script, where as it would take me a few minutes to look up my rawcodes, and type it all up from scratch:
Code:
Dash
Events
Unit - A unit Starts the effect of an ability
Conditions
(Ability being cast) Equal to Dash
Actions
Here we use starts the effect of an ability because it is more accurate. This event makes the trigger fire when the spell is cast, cool down starts and mana is taken. Therefore it is the ideal event in this case. A suggest not using “Begins Casting An Ability” because that runs before these things happens, and can be exploited. It can however be used to check for a special condition for an ability, and only let the ability cast if that condition is met, but that is neither here nor there. Ok now convert your GUI trigger to JASS:
Code:
function Trig_Dash_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'AUan' ) ) then
return false
endif
return true
endfunction
function Trig_Dash_Actions takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Dash takes nothing returns nothing
set gg_trg_Dash = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Dash, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Dash, Condition( function Trig_Dash_Conditions ) )
call TriggerAddAction( gg_trg_Dash, function Trig_Dash_Actions )
endfunction
Lets start by noting a few things about this. The ‘Auan’ denotes the ability being cast. Each ability in each map has a unique rawcode for it. The simplest way to find a spell's rawcode, is to select it in the object editor and press CTRL+D. The first four letters of the spell's name is the rawcode (case sensitive, must be put within single quotes) the next four letters (only appears on custom spells) is the rawcode of the spell it was based on.
The first thing you want to do with all your ability triggers is to simplify the initial condition. It seems a bit goofy does it not? If you read is carefully, basically what it is doing is checking if the ability is not equal to the one we want, and returning false, else returning true. Honestly, what is the point? So we change it:
Code:
function Trig_Dash_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'AUan'
endfunction
Now, all we are doing is returning the Boolean of our equation, whether the spell cast, GetSpellAbilityId(), is equal to the ability we want, ‘Auan’. If it returns true, our actions are ran, if it returns false, nothing happens.
We only plan to use one special effect model for the spell for now: The Blood Elf Spell Thief blood model.
The path of that model is: Objects\Spawnmodels\Human\HumanBlood\BloodElfSpellThiefBlood.mdl
When you want to use a path in JASS, you must first put it between quotes, as it is a string. You'll also have to replace every single backslash (\) with two backslashes instead (\\). Another trick to reduce first cast lag is to preload the effects that are going to be used in the spell. So in our init trig we add PreLoad native for the effect we are going to use, which is ran during the map init. It adds to load time, but reduces lag in game:
Code:
//===========================================================================
function InitTrig_Dash takes nothing returns nothing
set gg_trg_Dash = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Dash, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Dash, Condition( function Trig_Dash_Conditions ) )
call TriggerAddAction( gg_trg_Dash, function Trig_Dash_Actions )
call Preload("Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl")
endfunction
Alright now lets us beging the actual coding ! We first go to the actions function and store the basic locals we will be needing for the ability:
Code:
function Trig_Dash_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local location l = GetSpellTargetLoc()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xtarg = GetLocationX(l)
local real ytarg = GetLocationY(l)
local real ang = bj_RADTODEG * Atan2(ytarg – y, xtarg – x)
local real dx = xtarg - x
local real dy = ytarg - y
local real dist = SquareRoot(dx * dx + dy * dy)
local trigger trig = CreateTrigger()
local string strig = GetAttachmentTable(trig)
local timer t = CreateTimer()
local string s = GetAttachmentTable(t)
We store the caster and the location of the target of the ability cast. I then like to get the x and y for that location, as well as that for the caster, since they will used used often in a bit, and use those, because I personally like using reals better then locations, as they are easier for use in natives and do not require any clean up. Next is the angle. In order to move a unit from its position to another position in a straight line, the angle between this points is required. This we use take a look at the function AngleBetweenPoints(). We take what this returns, which uses reals, and use that instead of a bj function, so we eliminate a function, and optimize our trigger.
The next step is to store the distance between the caster and the target. I like to do this because I like the unit to move to where he cast, instead of a fixed distance. This is better for in game strategy as well as realness. What we are going to do with this is to compare the casters position from where he started after every periodic dash movement to that of the total distance. Once they are within a range, then the movement is over. So just like with the angle, we take a look at the DistanceBetweenPoints() function and use what is in that by plugging in our stored reals for the caster position and target position.
We then create a trigger to do the damage with. Why another trigger you ask? Can’t we just damage units around the caster as he moves? Yes you can, however that is far less efficient, and can lead to targets being damaged many times on a single pass.
We next need to create a local for the string to get the attachment of our trigger, since we will be storing locals onto it, and use GetAttTable() to get it. If you are unfamiliar with this process, please refer to Daelin’s Advanaced JASS tutorial.
A timer is then created, and the same process used to set a string equal to the attachment table of the timer.
Now that our local are set, we can continue. The next step is to set up our trigger that will damage the units who come close to the caster as he dashes.
Code:
function Trig_ Dash_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local location l = GetSpellTargetLoc()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xtarg = GetLocationX(l)
local real ytarg = GetLocationY(l)
local real ang = bj_RADTODEG * Atan2(ytarg - y, xtarg - x)
local real dx = xtarg - x
local real dy = ytarg - y
local real dist = SquareRoot(dx * dx + dy * dy)
local trigger trig = CreateTrigger()
local string strig = GetAttachmentTable(trig)
local timer t = CreateTimer()
local string s = GetAttachmentTable(t)
call TriggerRegisterUnitInRange(trig,u,150,null)
call TriggerAddCondition( trig, Condition( function Dash_Dam_Cond ) )
call SetTableObject(strig,”ta”, TriggerAddAction( trig, function Dash_Dam ))
call SetTableObject(strig,"u",u)
There are three parts to a basic trigger, the event, the conditions, and the actions. We will set them up in order. First the event, that of a unit coming within a range of our caster, so any units who come within 150 of the caster during the duration of the spell will have the conditions ran for them. The next is the condition function, which adds our condition function to the trigger. The next part is adding the triggeraction. I use this method to remove the triggeraction leak, by storing the trigger action to the strig string and creating it in 1 line, we can remove it at a later date, and save and extra line of code. The last part is that of storing the caster in the trigger, since there is no way to get the unit which the triggering unit came in range of ( that I know of).
Our next step will be that of creating our condition and actions function for the local damage trigger we created. A rule of thumb is that any function that is referred to in another function must be above the function it was referred to in. Therefore our condition and actions functions we added to our local trigger must be above the Trig_Bloody_Dash_Actions function in our trigger or we will get errors. So let is create them:
Code:
function Dash_Dam_Cond takes nothing returns boolean
local string s = GetAttachmentTable(GetTriggeringTrigger())
return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(GetTableUnit(s,"u")))==true and GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE)>0
endfunction
This is why we need the stored caster to this trigger, to find out if the triggering unit(the one that came in range) is an enemy of our caster. We get the attached table of our trigger, and return whether the filter unit is an enemy of the owner of our stored caster, u. I also check if the filter unit is alive with GetUnitState, since we do not want this to fire for dead units. Next is the actions:
Code:
function Dash_Dam takes nothing returns nothing
local string s = GetAttachmentTable(GetTriggeringTrigger())
local unit u = GetTableUnit(s,"u")
local unit v = GetTriggerUnit()
local real dam = 5*GetHeroAgi(u,true)
call UnitDamageTarget(u,v,dam,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null)
call SetUnitState(u,UNIT_STATE_LIFE,GetUnitState(u,UNIT_STATE_LIFE)+(.25*dam))
call DestroyEffect(AddSpecialEffectTarget("Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl",v,"overhead"))
set u = null
set v = null
endfunction
Again we start here by declaring our locals as any information we need. I also set the heroes agility*5 to a local real variable since that is the damage I will be doing. My function then proceeds to damage the target based on the dam real I stored, and add 25% of it back to our casters hp, as well as creating our preloaded effect on the target. Again I use all native functions to keep the code as optimized as possible. I set my local units u and v to null at the end to remove the unit leak.
Now our damage trigger is complete, so any enemy unit that is alive who comes in 150 range of our caster will take 5*the agility of the caster in damage and get an effect on them. The caster will also regain 25% of the damage done back to him.
Now we go back to our actions function, Trig_ Dash_Actions, to set up our periodic timer for the casters movement.
Code:
function Trig_ Dash_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local location l = GetSpellTargetLoc()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xtarg = GetLocationX(l)
local real ytarg = GetLocationY(l)
local real ang = bj_RADTODEG * Atan2(ytarg - y, xtarg - x)
local real dx = xtarg - x
local real dy = ytarg - y
local real dist = SquareRoot(dx * dx + dy * dy)
local trigger trig = CreateTrigger()
local string strig = GetAttachmentTable(trig)
local timer t = CreateTimer()
local string s = GetAttachmentTable(t)
call TriggerRegisterUnitInRange(trig,u,150,null)
call TriggerAddCondition( trig, Condition( function Dash_Dam_Cond ) )
call SetTableObject(strig,”ta”, TriggerAddAction( trig, function Dash_Dam ))
call SetTableObject(strig,"u",u)
call SetTableReal(s,"ang",ang)
call SetTableReal(s,"dist",dist)
call SetTableReal(s,"moved",0)
call SetTableObject(s,"u",u)
call SetTableObject(s,"trig",trig)
call TimerStart(t,.03,true,function Dash_Effects)
As we did with the trigger earlier, we must store all needed values to our timer as well. We will need the target location reals, our angle, distance, and the caster. Two extra ones we store are the trigger we created, so we can remove its leaks when the movement is done, as well as a real named “moved”, which we will use as the total distance moved for the caster. This is what we will compare to the distance we found earlier to decide when our dash will be complete. I usually use .03 as my periodic timer speed, since it is still fast and will look smooth in game, but to slow to cause any lag problems.
Before we go onto our timer function, let us finish this one first:
Code:
function Trig_ Dash_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local location l = GetSpellTargetLoc()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xtarg = GetLocationX(l)
local real ytarg = GetLocationY(l)
local real ang = bj_RADTODEG * Atan2(ytarg - y, xtarg - x)
local real dx = xtarg - x
local real dy = ytarg - y
local real dist = SquareRoot(dx * dx + dy * dy)
local trigger trig = CreateTrigger()
local string strig = GetAttachmentTable(trig)
local timer t = CreateTimer()
local string s = GetAttachmentTable(t)
call TriggerRegisterUnitInRange(trig,u,150,null)
call TriggerAddCondition( trig, Condition( function Dash_Dam_Cond ) )
call SetTableObject(strig,”ta”, TriggerAddAction( trig, function Dash_Dam ))
call SetTableObject(strig,"u",u)
call SetTableReal(s,"ang",ang)
call SetTableReal(s,"dist",dist)
call SetTableReal(s,"moved",0)
call SetTableObject(s,"u",u)
call SetTableObject(s,"trig",trig)
call TimerStart(t,.03,true,function Dash_Effects)
call SetUnitTimeScalePercent(u, 400)
call SetUnitPathing(u,false)
call SetUnitAnimationByIndex(u,1)
set u = null
call RemoveLocation(l)
set l = null
set trig = null
endfunction
Three extra things I do before wrapping this function up is setting its timescapepercent, which is how fast the unit’s animations are. We want it to look like it is dashing, so why not make it look like it is running, by using a faster animation speed. I then set the caster to have no collision, and lastly so it’s animation index to that of its walk animation. This is a trick used to force units to play an animation even when they are being moved around. To check a units animation indexs, play them in game the same way, and use BJDebugMsg() to display what integer is being played, and you can then use the integer for the walk animation, or whatever other animation you need.
The last part of any function is to remove leaks. You should know enough about leaks and basic JASS functions to know what leaks are and how to remove them. I do that here, and so should you in all of your functions.
The last part we need to do is set up our periodic timer function to move our caster:
Code:
function Dash_Effects takes nothing returns nothing
local timer t = GetExpiredTimer()
local string s = GetAttachmentTable(t)
local trigger trig
local string strig
local unit u = GetTableUnit(s,"u")
local real ang = GetTableReal(s,"ang")
local real dist = GetTableReal(s,"dist")
local real moved = GetTableReal(s,"moved")
This is pretty self explanatory. If you do not understand what is going on here, either re-re-read a handle vars tutorial or the readme that came with the CScache system for a brush up. Two of the locals I created I did not set so anything. These will eventually refer to our local trigger created earlier, however there is no use setting them unless we are actually going to use them, as you will see it a bit.
The next part is probably the trickiest of the entire trigger, which is that of the actual movement. Many people are not really sure how to move units in a straight line, this is how:
Code:
function Dash_Effects takes nothing returns nothing
local timer t = GetExpiredTimer()
local string s = GetAttachmentTable(t)
local trigger trig
local string strig
local unit u = GetTableUnit(s,"u")
local real ang = GetTableReal(s,"ang")
local real dist = GetTableReal(s,"dist")
local real moved = GetTableReal(s,"moved")
call SetUnitPosition(u,GetUnitX(u) + 30 * Cos(ang * bj_DEGTORAD),GetUnitY(u) + 30 * Sin(ang * bj_DEGTORAD))
call SetUnitAnimationByIndex(u,1)
set moved = moved+30
We are setting our units position to a polar projection of where it originally was. Take a look at the polar projection bj in JASS Craft:
Code:
function PolarProjectionBJ takes location source, real dist, real angle returns location
local real x = GetLocationX(source) + dist * Cos(angle * bj_DEGTORAD)
local real y = GetLocationY(source) + dist * Sin(angle * bj_DEGTORAD)
return Location(x, y)
endfunction
If you take your units location, and set it to a positive distance at the angle we found earlier, it will move along a straight line from where it started towards the target, at the distance increment you choose.
We also set our units animation again to keep it going, and set our real moved to itself plus the distance we moved our caster, so that we keep track of however it has moved. The next step is a fork in the road, on way, the timer will keep going and the caster moved again, the other, everything will stop since the caster will be close to the destination:
Code:
function Dash_Effects takes nothing returns nothing
local timer t = GetExpiredTimer()
local string s = GetAttachmentTable(t)
local trigger trig
local string strig
local unit u = GetTableUnit(s,"u")
local real ang = GetTableReal(s,"ang")
local real dist = GetTableReal(s,"dist")
local real moved = GetTableReal(s,"moved")
call SetUnitPosition(u,GetUnitX(u) + 30 * Cos(ang * bj_DEGTORAD),GetUnitY(u) + 30 * Sin(ang * bj_DEGTORAD))
call SetUnitAnimationByIndex(u,1)
set moved = moved+30
if moved>=dist or GetUnitState(u,UNIT_STATE_LIFE)<=0 then
set trig = GetTableTrigger(s,"trig")
set strig = GetAttTable(trig)
call TriggerRemoveAction(trig,GetTableTriggerAction(strig,"ta"))
call ClearTable(strig)
call DestroyTrigger(trig)
set trig = null
call SetUnitTimeScalePercent(u, 100)
call SetUnitPathing(u,true)
call SetUnitAnimationByIndex(u,0)
call ClearTable(s)
call PauseTimer(t)
call DestroyTimer(t)
else
call SetTableReal(s,"moved",moved)
endif
set u = null
endfunction
Our local real moved, which has stored how far we have moved the caster, is then compared to the total distance we found earlier, if it is still less then it, we store our new move distance, and the timer continues, however is the total move distance is more then our distance found, we must stop the timer, set everything back to normal, and remove our leaks. I also check to make sure our caster is alive, because it would look silly to dash a dead unit, no?
So if we are at our destination, it is time to shut everything down. Now we set our trigger and strig string locals to the trigger we created and its attached table. Remove the action to take care of that leak, clear the table to take care of that one, and destroy our trigger to save some memory and take care of that leak. Here we also set trig=null, since it only needs to be set to null if it was set to something in the first place.
We then reset our unit to normal animation speed, give it back its pathing, and set its animation to stand, so it doesn’t keep running but just stand there ( ). The last part is removing our leaks with the timer, by clearing its table, pausing it and destroying it. It is important to pause the timer before you destroy it because it can sometimes bug if you do. Again we clean up our leaks at the end by setting our local unit variable to null, and this function is complete.
Another little thing to note, I never set timers I attach things to to null, it can cause bugs. Yes it leaks, but its better to have it always work, and have that small leak, then sometimes bug on you in a game.
Let us take a look at our final trigger:
Code:
function Trig_Dash_Conditions takes nothing returns boolean
return GetSpellAbilityId() == 'A05M'
endfunction
function Dash_Dam_Cond takes nothing returns boolean
local string s = GetAttachmentTable(GetTriggeringTrigger())
return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(GetTableUnit(s,"u")))==true and GetUnitState(GetFilterUnit(),UNIT_STATE_LIFE)>0
endfunction
function Dash_Dam takes nothing returns nothing
local string s = GetAttachmentTable(GetTriggeringTrigger())
local unit u = GetTableUnit(s,"u")
local unit v = GetTriggerUnit()
local real agil = GetHeroAgi(u,true)
local real dam = 5*agil
call UnitDamageTarget(u,v,dam,false,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null)
call SetUnitState(u,UNIT_STATE_LIFE,GetUnitState(u,UNIT_STATE_LIFE)+(.25*dam))
call DestroyEffect(AddSpecialEffectTarget("Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl",v,"overhead"))
set u = null
set v = null
endfunction
function Dash_Effects takes nothing returns nothing
local timer t = GetExpiredTimer()
local string s = GetAttachmentTable(t)
local trigger trig
local string strig
local unit u = GetTableUnit(s,"u")
local real ang = GetTableReal(s,"ang")
local real dist = GetTableReal(s,"dist")
local real moved = GetTableReal(s,"moved")
call SetUnitPosition(u,GetUnitX(u) + 30 * Cos(ang * bj_DEGTORAD),GetUnitY(u) + 30 * Sin(ang * bj_DEGTORAD))
call SetUnitAnimationByIndex(u,1)
set moved = moved+30
if moved>=dist or GetUnitState(u,UNIT_STATE_LIFE)<=0 then
set trig = GetTableTrigger(s,"trig")
set strig = GetAttTable(trig)
call TriggerRemoveAction(trig,GetTableTriggerAction(strig,"ta"))
call ClearTable(strig)
call DestroyTrigger(trig)
set trig = null
call SetUnitTimeScalePercent(u, 100)
call SetUnitPathing(u,true)
call SetUnitAnimationByIndex(u,0)
call ClearTable(s)
call PauseTimer(t)
call DestroyTimer(t)
else
call SetTableReal(s,"moved",moved)
endif
set u = null
endfunction
function Trig_Dash_Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local location l = GetSpellTargetLoc()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xtarg = GetLocationX(l)
local real ytarg = GetLocationY(l)
local real ang = bj_RADTODEG * Atan2(ytarg - y, xtarg - x)
local real dx = xtarg - x
local real dy = ytarg - y
local real dist = SquareRoot(dx * dx + dy * dy)
local trigger trig = CreateTrigger()
local string strig = GetAttachmentTable(trig)
local timer t = CreateTimer()
local string s = GetAttachmentTable(t)
call TriggerRegisterUnitInRange(trig,u,150,null)
call TriggerAddCondition( trig, Condition( function Dash_Dam_Cond ) )
call SetTableObject(strig,"ta", TriggerAddAction( trig, function Dash_Dam ))
call SetTableObject(strig,"u",u)
call SetTableReal(s,"ang",ang)
call SetTableReal(s,"dist",dist)
call SetTableReal(s,"moved",0)
call SetTableObject(s,"u",u)
call SetTableObject(s,"trig",trig)
call TimerStart(t,.03,true,function Dash_Effects)
call SetUnitTimeScalePercent(u, 400)
call SetUnitPathing(u,false)
call SetUnitAnimationByIndex(u,1)
set u = null
call RemoveLocation(l)
set l = null
set trig = null
endfunction
//===========================================================================
function InitTrig_Dash takes nothing returns nothing
set gg_trg_Dash = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Dash, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Dash, Condition( function Trig_Dash_Conditions ) )
call TriggerAddAction( gg_trg_Dash, function Trig_Dash_Actions )
Preload("Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl")
endfunction
V 1.02(Fixed up a good bit recently)
That is all for now folks. The spell you have created is MUI and leak free. Congratulations on finishing the tutorial, and good luck JASSing.