• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Spell] Help with JASS

Status
Not open for further replies.
Level 3
Joined
Sep 9, 2009
Messages
658
I'm trying to do a spell where the Hero gets 400% animation speed and attacks a target 50 times. The 10th strike will deal damage. I want him to attack with .1 intervals so a periodic trigger is needed, right? Anyway, I can do this in GUI but I decided to go with JASS just for a change of pace. I just want someone to tell me the things I'm doing wrong with this spell.

JASS:
function Aurora_Periodic takes nothing returns nothing
    local unit u = GetTriggerUnit()//I have to declare them again right? 
//Even though I already declared them in the bottom function?
//And since this is a periodic trigger, I'm declaring them more than once right?
//Is that a problem?
//And is there really no way for me to refer to the locals I've declared in Trig_Aurora_Strike_Actions?
    local unit u1 = GetSpellTargetUnit()
    local integer i = 0 //I have a feeling there's something wrong with this part.
    set i = i + 1//Am I doing this right?
    call SetUnitTimeScale(u,4)
    call SetUnitAnimation(u,"attack")
    call DestroyEffect(AddSpecialEffectTarget("IceNova.mdx",u1,"origin"))
    if i == 5 then
        call SetUnitTimeScale(u,1)
        call UnitDamageTarget(u,u1,1000,false,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_ENHANCED,WEAPON_TYPE_WHOKNOWS)
        call DestroyEffect(AddSpecialEffectTarget("IceNova.mdx",u1,"origin"))
        call PauseUnit(u,false)
        call PauseUnit(u1,true)
        set i = 0
    endif
//Biggest question I have is how do I tell the trigger to stop?
//And how do I destroy the timer if I declared its local in another function?
    set u = null
    set u1 = null
endfunction

function Trig_Aurora_Strike_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    local unit u = GetTriggerUnit()
    local unit u1 = GetSpellTargetUnit()
    call PauseUnit(u,true)
    call PauseUnit(u1,true)
    call SetUnitPosition(u,GetUnitX(u1)-75,GetUnitY(u1)-75)
    call TimerStart(t,.20,true,function Aurora_Periodic)
    set t = null
    set u = null
    set u1 = null
endfunction

function Trig_Aurora_Strike_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A000' then
        call Trig_Aurora_Strike_Actions() 
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Aurora_Strike takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Trig_Aurora_Strike_Conditions ) )
    set t = null
endfunction

Oh, and I need to use == when I'm using conditions right?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
yes == or != for conditions.

change this.
JASS:
call SetUnitPosition(u,GetUnitX(u1)-75,GetUnitY(u1)-75)
to this
JASS:
call SetUnitX( u, GetUnitX(u1)-75)
call SetUnitY( u, GetUnitY(u1)-75)

u should also not pause units as it stops a buff / debuff counter from running.

u have to use indexing / deindexing or use a hashtable to find out which timer has expired.

to destroy the timer use
JASS:
GetExpiredTimer()

not sure what u mean by this. plz elaborate.
Biggest question I have is how do I tell the trigger to stop?

no idea y ur doing this ?
JASS:
local integer i = 0 //I have a feeling there's something wrong with this part.
    set i = i + 1//Am I doing this right?

yes u have to redeclare them but the problem is that if another unit triggers this b4 the timer ends then it will stop working.
u have to use indexing / deindexing or save the unit into a hashtable based on the timers handle id. then when timer ends use
JASS:
local timer tmr = GetExpiredTimer()
then load the units and anything else u need.
just note that hashtables while they may be easier r also slower than indexed arrays.
JASS:
local unit u = GetTriggerUnit()//I have to declare them again right? 
//Even though I already declared them in the bottom function?
//And since this is a periodic trigger, I'm declaring them more than once right?
//Is that a problem?
//And is there really no way for me to refer to the locals I've declared in Trig_Aurora_Strike_Actions?
 
I'll give you a quick rundown on it. At the moment, this is your code:
JASS:
function Aurora_Periodic takes nothing returns nothing
    local unit u = GetTriggerUnit()//I have to declare them again right? 
//Even though I already declared them in the bottom function?
//And since this is a periodic trigger, I'm declaring them more than once right?
//Is that a problem?
//And is there really no way for me to refer to the locals I've declared in Trig_Aurora_Strike_Actions?
    local unit u1 = GetSpellTargetUnit()
    local integer i = 0 //I have a feeling there's something wrong with this part.
    set i = i + 1//Am I doing this right?
    call SetUnitTimeScale(u,4)
    call SetUnitAnimation(u,"attack")
    call DestroyEffect(AddSpecialEffectTarget("IceNova.mdx",u1,"origin"))
    if i == 5 then
        call SetUnitTimeScale(u,1)
        call UnitDamageTarget(u,u1,1000,false,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_ENHANCED,WEAPON_TYPE_WHOKNOWS)
        call DestroyEffect(AddSpecialEffectTarget("IceNova.mdx",u1,"origin"))
        call PauseUnit(u,false)
        call PauseUnit(u1,true)
        set i = 0
    endif
//Biggest question I have is how do I tell the trigger to stop?
//And how do I destroy the timer if I declared its local in another function?
    set u = null
    set u1 = null
endfunction

function Trig_Aurora_Strike_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    local unit u = GetTriggerUnit()
    local unit u1 = GetSpellTargetUnit()
    call PauseUnit(u,true)
    call PauseUnit(u1,true)
    call SetUnitPosition(u,GetUnitX(u1)-75,GetUnitY(u1)-75)
    call TimerStart(t,.20,true,function Aurora_Periodic)
    set t = null
    set u = null
    set u1 = null
endfunction

function Trig_Aurora_Strike_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A000' then
        call Trig_Aurora_Strike_Actions() 
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Aurora_Strike takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Trig_Aurora_Strike_Conditions ) )
    set t = null
endfunction

The issue with the code at the moment is in "Aurora_Periodic". You want to refer to GetTriggerUnit() and GetSpellTargetUnit(), but sadly it doesn't work. :( This is because when you use:
JASS:
call TimerStart(t, 0.2, true, function Aurora_Periodic)
It won't automatically transfer the values. GetTriggerUnit() and GetSpellTargetUnit() won't return the proper values (they may return null). To fix this, we have to store that data when the spell is cast, and then load it each time the timer ticks.

How do we do this? In regular JASS, the easiest and most convenient way is to use hashtables. They are storage structures (we'll get into how they work a little later). To create a hashtable, open the variable editor and create a variable. We'll name it AuroraHash. However, it has no value at first. You have to actually create it. To do this, we simply edit your initialization function:
JASS:
function InitTrig_Aurora_Strike takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Trig_Aurora_Strike_Conditions ) )
    set t = null

    set udg_AuroraHash = InitHashtable() // add this
endfunction
This will initialize the hashtable, so that AuroraHash now points to a hashtable.

Alrighty, next we have to store the actual information. When dealing with timers, you usually want to attach the data to the timer itself. Here is how:
JASS:
call Save<Type>( <hashtable>, <parent key>, <child key>, <data>)
This is how most of the "Save" functions look like for hashtables. An easy way to visualize it is to imagine the hashtable as a room. In the room, you have a bunch of filing cabinets. Those will be the parent keys. Inside those filing cabinets, you have a bunch of file folders (child keys). Inside the file folders, you have a particular data. So let's say you forgot your wife's anniversary, but you saved it somewhere under a file. If you know which "room" (hashtable) it is in, and you know the "filing cabinet" (parent key), as well as the "file folder" (child key), you know how to find the date of the anniversary.

In a similar way, we will attach data to the timer. The timer will sort of serve as the filing cabinet, and under it will store a bunch of little files (such as the caster, the target unit, etc.). Anything you want.

Let's take a look at an actual function:
JASS:
call SaveUnitHandle(hashtable ht, integer parentKey, integer childKey, unit data)
You simply fill in the blanks. However, you'll notice that they expect integers for the parent key and child key. Have no fear! GetHandleId() is here. Instead of storing it all under the timer itself, we'll store it under the ID of the timer.

The child key can be anything you want. Usually for spells, you just make the first data that you save have a child key of 0. The second data will have a child key of 1. The third will have a child key of 2, and so forth.

The <data> field would just be whatever you want to save. So let's try to put this together:
JASS:
function Trig_Aurora_Strike_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    local unit u = GetTriggerUnit()
    local unit u1 = GetSpellTargetUnit()

    local integer parentKey = GetHandleId(t) // this is our parent key

    call PauseUnit(u,true)
    call PauseUnit(u1,true)
    call SetUnitPosition(u,GetUnitX(u1)-75,GetUnitY(u1)-75)
    call TimerStart(t,.20,true,function Aurora_Periodic)

    call SaveUnitHandle(udg_AuroraHash, parentKey, 0, u) // save trigger unit
    call SaveUnitHandle(udg_AuroraHash, parentKey, 1, u1) // save target unit   

    set t = null
    set u = null
    set u1 = null
endfunction

Cool. Although, you might have other things you want to save. For example, how about the count? How many times has the unit hit the target? At the moment, it is 0. We'll save it as 0 first. Each time the timer ticks, we'll increase the count by 1 and save it again. So let's add that to our list of "saved" things:
JASS:
    call SaveUnitHandle(udg_AuroraHash, parentKey, 0, u) // save trigger unit
    call SaveUnitHandle(udg_AuroraHash, parentKey, 1, u1) // save target unit  
    call SaveInteger(udg_AuroraHash, parentKey, 2, 0) // save the count

Now we have to load it in our callback function. One thing to note is that you should change the timer tick to 0.1, since you want him to attack with 0.1 intervals:
JASS:
call TimerStart(t, 0.1, true, function Aurora_Periodic)

So how do we load something? Simple:
JASS:
call Load<Type>(hashtable ht, integer parentKey, integer childKey)

Here is an example:
JASS:
function Aurora_Periodic takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer parentKey = GetHandleId(t) // get the parent key

    local unit u = LoadUnitHandle(udg_AuroraHash, parentKey, 0)
    // this will load the caster unit that we saved under the "0" child key

    local unit u1 = LoadUnitHandle(udg_AuroraHash, parentKey, 1)
    // this will load the caster unit that we saved under the "1" child key

    local integer count = LoadInteger(udg_AuroraHash, parentKey, 2)
    // this will load the "count" that we saved under the "2" child key

    set count = count + 1 // one attack has happened
    call SetUnitTimeScale(u,4)
    call SetUnitAnimation(u,"attack")

    call SaveInteger(udg_AuroraHash, parentKey, 2) // let's save the new count

    call DestroyEffect(AddSpecialEffectTarget("IceNova.mdx",u1,"origin"))
    if count == 10 then
    // on the 10th attack, it will deal damage
        call SetUnitTimeScale(u, 1)
        call UnitDamageTarget(u, u1, 1000, false, false, ATTACK_TYPE_HERO, DAMAGE_TYPE_ENHANCED, null)
        // DAMAGE_TYPE_WHOKNOWS can just be null -> no sound
        call DestroyEffect(AddSpecialEffectTarget("IceNova.mdx",u1,"origin"))
        call PauseUnit(u,false)
        call PauseUnit(u1,true)
    endif

    if count == 50 then
    // we want it to stop on the 50th attack, right?
        call PauseTimer(t) // pause the timer
        call DestroyTimer(t) // destroy it
        call FlushChildHashtable(udg_AuroraHash, parentKey)
        // this will perform some clean up
        // it will flush the data stored under the timer
    endif

    set t = null
    set u = null
    set u1 = null

endfunction

That is something like how your spell will look like. Sorry, I have to leave for a little so I can't add more of an explanation at the moment. If you have any questions, just post or PM me. Of course, the above code might be a bit buggy/look weird. That is stuff you may have to work out with the spell itself (e.g. you might want to have higher intervals than 0.1 and reduce the number of total attacks).

Sorry if some of the code above doesn't compile. I could not check it myself. However, I hope you get the jist of it. See the tutorials section for some guides on how to make spells in JASS/using hashtables. :)
 
Level 3
Joined
Sep 9, 2009
Messages
658
@deathismyfriend
local integer i = 0 //I have a feeling there's something wrong with this part.
set i = i + 1//Am I doing this right?
What I mean by telling the trigger to stop is for it to stop running after the 50th strike.

And I'd love to try out the suggestions but due to perhaps the buggy nature of the code, the map won't load. WE just crashes once it tries loading the units. But then I had a campaign open at the time I saved it. Maybe that caused it to crash.

@PurgeandFire I noticed you just used set count = count + 1 right off the bat. You don't need to declare it first or anything?

I'd probably go with hashtables but indexing would be good to know as well. Does it go like using a global variable and using a local variable as the index? Like
set udg_caster[[i]] = GetTriggerUnit() or do I do it like this this?
JASS:
local integer i = 0
set i = i+1
local unit u[[i]] = GetTriggerUnit

Lastly, why get the timer's handle id? Because that's what I'm basing my spell on?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
if u are using i as the counter for stopping the trigger at 50 then yes its wrong.
a local is only seen in that specific instance of the function. It will always be 0 and always count to only 1. u need a global variable and set it to 0 in the InitTrig function.

for indexing u again need a global integer. otherwise it would be set to 0 again and again so ud be rewritting the same index over and over.
 
@PurgeandFire I noticed you just used set count = count + 1 right off the bat. You don't need to declare it first or anything?

I declared it here:
JASS:
local integer count = LoadInteger(udg_AuroraHash, parentKey, 2)
    // this will load the "count" that we saved under the "2" child key

set count = count + 1 // one attack has happened

In the function that fires when the spell is cast, I saved the count as "0". When you load it (like above), you increment the count once. Then you save it using:
JASS:
 call SaveInteger(udg_AuroraHash, parentKey, 2)

In a way, it is kind of like a variable in itself.

Lastly, why get the timer's handle id? Because that's what I'm basing my spell on?

You have 1 timer per spell cast. When you use TimerStart(), the only thing you can refer to backwards is GetExpiredTimer(). GetTriggerUnit()/GetSpellTargetUnit()... etc.. won't necessarily refer to the units you want it to. However, GetExpiredTimer() does. That is why we can attach it to the timer ID. We can refer to the timer when the spell is cast (to save the data), and per timer tick (to load the data).

EDIT: If you want to learn indexing, you may want to look into vJASS. It makes it a lot easier since you can make arrays very easily with global blocks:
JASS:
globals
    unit array units
endglobals
Versus opening the variable editor and making a unit array. You also don't need to add the "udg_" for globals that were declared in the block.
 
Level 3
Joined
Sep 9, 2009
Messages
658
So indexing is more of a vJASS thing? I guess I really will stick to hashtables then. I could never get vJASS to work even with JNGP and the latest version of JASShelper. I'm not at home right now so I'll work on the spell later and see if I can get it working.

Anyway, since the game slows down if you use too many hashtables, can you just use one hashtable for all your spells? You'll need to clear the data at the end of the script anyway, right? Or will that cause problems?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
indexing is not a vJass thing. look at my tutorial things a GUIer should know. the chapter on how to index deals with indexing and de-indexing. it is the same way to index in jass as it is in GUI

yes u can use one hashtable for ur spells but u have to be careful as u can easily overwrite the data from another spell if u do something wrong
 
Level 3
Joined
Sep 9, 2009
Messages
658
Alright, I'll give indexing a shot when I make another spell.

And there's a limit to how many integers a hashtable can have right?

And I forgot to ask you this earlier but why is this
JASS:
call SetUnitX( u, GetUnitX(u1)-75)
call SetUnitY( u, GetUnitY(u1)-75)
better than this?
JASS:
SetUnitPosition(u, GetUnitX(u1)-75, GetUnitY(u1)-75)
What's the difference?
 
Level 3
Joined
Sep 9, 2009
Messages
658
So what would be better? Having multiple hashtables each storing 4-10 handles, or just lumping all the handles into one hashtable?

By the way, I got the spell working with no problems. Thanks for the help.

And uh this is just an example but if I wanted to make polar projections...I do it like this??

JASS:
function Polar_Projection takes nothing returns nothing
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local real x1
    local real y1
    local integer loop = 1
    local real angle = 0
    loop
        exitwhen loop == 8
            set x1 = x + 500 * Cos(angle * bj_DEGTORAD)
            set y1 = y + 500 * Sin(angle * bj_DEGTORAD)
            call DestroyEffect(AddSpecialEffect("sfx.mdx",x1,y1))
            set angle = angle + 45
            set loop = loop + 1
    endloop
endfunction
 
Last edited:
Level 3
Joined
Sep 9, 2009
Messages
658
JASS:
function DistanceBetweenXYImpBomb takes real x1, real y1, real x2, real y2 returns real
    local real x = x2 - x1
    local real y = y2 - y1
    return SquareRoot(x*x + y*y)
endfunction

function GetAngleBetweenXYImpBomb takes real x1, real y1, real x2, real y2 returns real
    return Atan2( y1 - y2, x1 - x2)
endfunction

function PolarProjectionXImpBomb takes real x, real dist, real angle returns real // returns x
    return x + dist * Cos( angle)
endfunction

function PolarProjectionYImpBomb takes real y, real dist, real angle returns real // returns y
    return y + dist * Sin( angle)
endfunction

JASS:
set udg_distanceMXImpBomb[ m] = udg_targetSXImpBomb[ m] - PolarProjectionXImpBomb( udg_targetMXImpBomb[ m], udg_velocityImpBomb/2, angle)
set udg_distanceMYImpBomb[ m] = udg_targetSYImpBomb[ m] - PolarProjectionYImpBomb( udg_targetMYImpBomb[ m], udg_velocityImpBomb/2, angle)

You mean do it like this?

@Maker so instead of using 45 I could do it like 45*(3.14/180) = 0.785 and use 0.785 instead?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
JASS:
function DistanceBetweenXYImpBomb takes real x1, real y1, real x2, real y2 returns real
    local real x = x2 - x1
    local real y = y2 - y1
    return SquareRoot(x*x + y*y)
endfunction

function GetAngleBetweenXYImpBomb takes real x1, real y1, real x2, real y2 returns real
    return Atan2( y1 - y2, x1 - x2)
endfunction

function PolarProjectionXImpBomb takes real x, real dist, real angle returns real // returns x
    return x + dist * Cos( angle)
endfunction

function PolarProjectionYImpBomb takes real y, real dist, real angle returns real // returns y
    return y + dist * Sin( angle)
endfunction

JASS:
set udg_distanceMXImpBomb[ m] = udg_targetSXImpBomb[ m] - PolarProjectionXImpBomb( udg_targetMXImpBomb[ m], udg_velocityImpBomb/2, angle)
            set udg_distanceMYImpBomb[ m] = udg_targetSYImpBomb[ m] - PolarProjectionYImpBomb( udg_targetMYImpBomb[ m], udg_velocityImpBomb/2, angle)

You mean do it like this?

@Maker so instead of using 45 I could do it like 45*(3.14/180) = 0.785 and use 0.785 instead?

the way i use it in there is makers way. he was saying that when doing the math u would have to convert it but the way i do it u dont have to convert anything so use the way in the jass portion u copied.
yes maker meant to use the .785 instead. that is what mine does so u can just use mine. u can put those functions in ur map header so any of ur spells or systems can use them easily rather than recreating them over and over.
 
Level 3
Joined
Sep 9, 2009
Messages
658
Thanks. The functions I'm suppose to put in the header are the first ones above right?

And this.
JASS:
function DistanceBetweenXYImpBomb takes real x1, real y1, real x2, real y2 returns real
    local real x = x2 - x1
    local real y = y2 - y1
    return SquareRoot(x*x + y*y)
endfunction

It's for the hypotenuse right? Why does he calculate the hypotenuse?
 
And I forgot to ask you this earlier but why is this
JASS:
call SetUnitX( u, GetUnitX(u1)-75)
call SetUnitY( u, GetUnitY(u1)-75)
better than this?
JASS:
SetUnitPosition(u, GetUnitX(u1)-75, GetUnitY(u1)-75)
What's the difference?

Just to clarify, SetUnitPosition() performs pathing checks. In addition, the unit is ordered to "stop" afterward (thus, interrupting the current order).

SetUnitX and SetUnitY will force the unit to move to the point, even if it is occupied or even if it is off the map (can cause a crash). However, they are usually preferred because SetUnitPosition() is considered "slow" and because of the whole "stop" order thing. You should use a pathing check system if you use SetUnitX/Y, such as:
http://www.wc3c.net/showthread.php?t=103862

The functions I'm suppose to put in the header are the first ones above right?

Yes. Anything in the map header is placed above regular code. This means that you can use those functions in any trigger (In vJASS, you can achieve a similar functionality with the library blocks).

It's for the hypotenuse right? Why does he calculate the hypotenuse?

It is. :) But that particular formula is most often known as the distance formula, as deathismyfriend said. The distance formula is just a variant of the pythagorean theorem.
 
I tried downloading the attachment in the link you gave me. It just redirects me to a gif image. Do I have to be a member there or something?

Yes I have the same issue. wc3c had some database issues a while back so a lot of the attachments stopped working.

However, the library is still there in the code. See the part where it says "Code", and click the "+" to reveal the code. You can copy and paste that, assuming that you have Jass NewGen Pack.

Here is an example of its usage:
JASS:
local unit u = GetTriggerUnit()
local real x = GetUnitX(u) + 50 * 0.866
local real y = GetUnitY(u) + 50 * 0.5 
if IsTerrainWalkable(x, y) then // check if the point is walkable
    call SetUnitX(u, x) // if so, then move the unit
    call SetUnitY(u, y)
endif
set u = null
And there you go. :)

Those x and y are just random (for the purpose of the example). It would project the unit 50 units at an angle of 30 degrees.
 
Status
Not open for further replies.
Top