• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] Local Loop Iterators

Status
Not open for further replies.
Level 7
Joined
Mar 6, 2006
Messages
282
So I'm learning JASS, and I don't understand how to do something.

With bj_forLoopAIndex as a loop iterator, you can call a function from your loop and use bj_forLoopAIndex in the called function. But people say not to use global loop iterators. So how can you use a local iterator and use it in a function that the loop calls?

For example, this would be an MUI spell for an ability called Punch. The called function is Knockback, and it needs to know who is punching.

JASS:
function Knockback takes nothing returns nothing
    UnitDamageTarget( Hero[bj_forLoopAIndex], target, 999 dmg)
endfunction

function Punch takes nothing returns nothing
    set bj_forLoopAIndex = 0

    loop
         exitwhen bj_forLoopAIndex > 11

         if udg_HeroIsPunching[bj_forLoopAIndex] then
                set mygroup = GetPunchableUnits()
                ForGroup(mygroup, Knockback() )
         endif

         set bj_forLoopAIndex = bj_forLoopAIndex + 1
    endloop
endfunction


(this is kind of a 2nd question)

If GUI uses bj_forLoopAIndex for loops, then how can you have 2 loops going at the same time, and they don't change each other's bj_forLoopAIndex ??
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
Use a local integer.
JASS:
function Punch takes nothing returns nothing
    local integer i = 0

    loop
         exitwhen i > 11

         if udg_HeroIsPunching[i] then
                set mygroup = GetPunchableUnits()
                ForGroup(mygroup, Knockback() )
         endif

         set i = i + 1
    endloop
endfunction
 
OK, now how can I get the value of "i" in my KnockBack function?

You can use a global variable, although I think this would be a better option.

JASS:
function Punch takes nothing returns nothing
    local integer i = 0
    local unit u
    loop
         exitwhen i > 11
         if udg_HeroIsPunching[i] then
                set mygroup = GetPunchableUnits()
                set u = FirstOfGroup(mygroup)
                loop
                     exitwhen u == null
                     call UnitDamageTarget(udg_Hero[i], u, 999)
                     call GroupRemoveUnit(mygroup, u)
                     set u = FirstOfGroup(mygroup)
                endloop
         endif
         set i = i + 1
    endloop
endfunction

Excuse the errors (if any), I wrote it in the quick reply box :p
 
Level 7
Joined
Mar 6, 2006
Messages
282
You can use a global variable, although I think this would be a better option.

JASS:
jass

Excuse the errors (if any), I wrote it in the quick reply box :p

If I was to use global iterators, does each player need their own iterator? Would bj_forLoopAIndex ever be written to more than once by accident?

Also, you're suggesting that I get rid of any custom functions I call in an MUI spell, and write the code inside the main spell? I guess that works for my example where you eliminated "ForGroup" by expanding it (thanks for showing me that, I'll have use for it).

If I do use local iterators, then does that mean all MUI spells only have one function?! I guess my main question is still: How do you use local iterators in another function?
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
JASS:
function Knockback takes nothing returns nothing
    local integer i=TempInt
    UnitDamageTarget( Hero[i], target, 999 dmg)
endfunction

function Punch takes nothing returns nothing
    local integer i = 0

    loop
         exitwhen i > 11

         if udg_HeroIsPunching[i] then
                set TempInt = i
                set mygroup = GetPunchableUnits()
                ForGroup(mygroup, Knockback() )
         endif

         set i = i + 1
    endloop
endfunction
 
Level 7
Joined
Mar 6, 2006
Messages
282
JASS:
function Knockback takes nothing returns nothing
    local integer i=TempInt
    UnitDamageTarget( Hero[i], target, 999 dmg)
endfunction

function Punch takes nothing returns nothing
    local integer i = 0

    loop
         exitwhen i > 11

         if udg_HeroIsPunching[i] then
                set TempInt = i
                set mygroup = GetPunchableUnits()
                ForGroup(mygroup, Knockback() )
         endif

         set i = i + 1
    endloop
endfunction

I can't tell if that's supposed to be a joke or not.
 
DysfunctionaI said:
If I was to use global iterators, does each player need their own iterator? Would bj_forLoopAIndex ever be written to more than once by accident?

It won't be overwritten unless one of your actions triggers an event to another trigger which utilizesbj_forLoopAIndexand possibly if you use TriggerSleepAction.

DysfunctionaI said:
Also, you're suggesting that I get rid of any custom functions I call in an MUI spell, and write the code inside the main spell? I guess that works for my example where you eliminated "ForGroup" by expanding it (thanks for showing me that, I'll have use for it).

It's just easier (and probably faster) to do things in the same function.

DysfunctionaI said:
If I do use local iterators, then does that mean all MUI spells only have one function?!

Well, if your spell doesn't involve a timer or something then sure, it can all be in one function.

DysfunctionaI said:
I guess my main question is still: How do you use local iterators in another function?

You don't. That's what global variables are for. If you're using JNGP (or cli JassHelper), then you can define globals wherever you want like this:
JASS:
globals
    unit array units
endglobals
If not, you can create them in the variable editor and they will require the udg_ prefix.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
I can't tell if that's supposed to be a joke or not.

Look closer to what he does. He uses a global to pass the integer to the other function.

This is better to do though. If using Chobibos example.

JASS:
function Knockback takes integer i returns nothing
    UnitDamageTarget( Hero[i], target, 999 dmg)
endfunction

function Punch takes nothing returns nothing
    local integer i = 0

    loop
         exitwhen i > 11

         if udg_HeroIsPunching[i] then
                set mygroup = GetPunchableUnits()
                ForGroup(mygroup, Knockback( i) )
         endif

         set i = i + 1
    endloop
endfunction
 
Level 7
Joined
Mar 6, 2006
Messages
282
Look closer to what he does. He uses a global to pass the integer to the other function.

This is better to do though. If using Chobibos example.

JASS:
function Knockback takes integer i returns nothing
    UnitDamageTarget( Hero[i], target, 999 dmg)
endfunction

function Punch takes nothing returns nothing
    local integer i = 0

    loop
         exitwhen i > 11

         if udg_HeroIsPunching[i] then
                set mygroup = GetPunchableUnits()
                ForGroup(mygroup, Knockback( i) )
         endif

         set i = i + 1
    endloop
endfunction

If I used chobibo's solution, I could just eliminate the local iterator altogether. And you can't pass arguments to a function in ForGroup, that's kind of why I made this thread.

Edit: Omfg, I just randomly read this in some contest thread:
Don't use (Integer A) or (Integer B) because they are globals made to be used for all loops in your map, which is a terrible idea by Blizzard, because if the loops are nested, you can actually break a couple of things in the map. Use a custom global integer for each of your loops ^_^
 
Last edited:
Level 7
Joined
Mar 6, 2006
Messages
282
Ok guys, I think I have all the info I need to make some MUI spells. This is my first full JASS MUI spell and it seems to work correctly. If you guys don't see any problems, I'm going to continue to write them like this.

To anyone who wants to take a look at it and give me feedback, I greatly appreciate it.

It's a Timer, started by an event trigger, when the spell 'TW' is cast.

TW is like a shockwave, but it pushes enemies down the length of the wave, damaging them per tick. The art is handled in another trigger, but the current position of the shockwave is handled by Skill_TW_Dummy.

JASS:
function Trig_TW_Timer_Actions takes nothing returns nothing
    local real x
    local real y
    local real u_x
    local real u_y
    local unit dummy
    local group tempgroup = CreateGroup()
    local group targetgroup = CreateGroup()
    // player loop iterator starts at 3, because 1 and 2 are computers
    local integer i = 3
    local unit u
    // dmg = lvl * x
    local real dmgpertick = 10
    
    // Skill_TW_Count is the number of players using this spell
    // Disable timer if no one is using it
    if udg_Skill_TW_Count < 1 then
        call DisableTrigger( GetTriggeringTrigger() )
    else
    
        loop
            // Run through all the players (MUI)
            exitwhen i > 12
            
            // a dummy is created in the cast event trigger
            // To save a var, just check if the dummy exists to see if the player is using the spell
            if ( udg_Skill_TW_Dummy[i] != null) then
                
                set dmgpertick = I2R(GetUnitAbilityLevel(udg_PlayerBoat[i], 'A010')) * dmgpertick
                
                // Skill_TW_Length is the iteration number of the spell
                if ( udg_Skill_TW_Length[i] > 38 ) then
                    set udg_Skill_TW_Count = udg_Skill_TW_Count - 1
                    call RemoveUnit(udg_Skill_TW_Dummy[i])
                    set udg_Skill_TW_Dummy[i] = null
                    set dummy = null
                    exitwhen true
                endif
                
                // move the dummy down a line, like shockwave
                set dummy = udg_Skill_TW_Dummy[i]
                set x = GetUnitX( dummy ) + ( Cos(udg_Skill_TW_Angle[i] * bj_DEGTORAD) * 22 )
                set y = GetUnitY( dummy ) + ( Sin(udg_Skill_TW_Angle[i] * bj_DEGTORAD) * 22 )
                call SetUnitX( dummy, x)
                call SetUnitY( dummy, y)
                set udg_Skill_TW_Length[i] = udg_Skill_TW_Length[i] + 1
                
                // I use "if not" here because IsTerrainPathable seems to return backwards results
                if not IsTerrainPathable( x, y, PATHING_TYPE_FLOATABILITY) then
                    
                    call GroupEnumUnitsInRange(tempgroup, x, y, 270, null)
                    
                    // Conditions for the knockback check
                    set u = FirstOfGroup(tempgroup)
                    loop
                        exitwhen u == null
                        
                        if IsUnitEnemy( u, Player(i-1) ) then
                            if GetUnitState( u, UNIT_STATE_LIFE) > 0 then
                                if not IsUnitType( u, UNIT_TYPE_STRUCTURE) then
                                    call GroupAddUnit(targetgroup, u)
                                endif
                            endif
                        endif
                        
                        call GroupRemoveUnit(tempgroup, u)
                        set u = FirstOfGroup(tempgroup)
                    endloop
                    
                    call DestroyGroup(tempgroup)

                    // units that meet the knockback requirements are passed to 'targetgroup'
                    // Do knockback
                    set u = FirstOfGroup(targetgroup)
                    loop
                        exitwhen u == null
                        
                        set u_x = GetUnitX(u) + ( Cos( udg_Skill_TW_Angle[i] * bj_DEGTORAD ) * 25 )
                        set u_y = GetUnitY(u) + ( Sin( udg_Skill_TW_Angle[i] * bj_DEGTORAD ) * 25 )

                        call UnitDamageTarget(udg_PlayerBoat[i], u, dmgpertick, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )

                        // stop knockback if they hit a wall, again IsTerrainPathable returing backward result
                        if not IsTerrainPathable( u_x, u_y, PATHING_TYPE_FLOATABILITY) then
                            call SetUnitX(u, u_x )
                            call SetUnitY(u, u_y )
                            call DestroyEffect( AddSpecialEffectLoc( "Abilities\\Spells\\Orc\\Devour\\DevourEffectArt.mdl", Location(u_x, u_y) ) )
                        else
                            // unit could not be knockbacked (wall)
                            // make a boom effect or sound here later.
                            //call PlaySoundAtPointBJ(gg_snd_TW_End, 100, Location(u_x, u_y), 0)
                        endif

                        call GroupRemoveUnit(targetgroup, u)
                        set u = FirstOfGroup(targetgroup)
                    endloop
                    
                    call DestroyGroup(targetgroup)
                    // end Knockback
                    
                endif
                
            endif
            
            set i = i + 1
        endloop
        
    endif
endfunction

function InitTrig_TW_Timer takes nothing returns nothing
    set gg_trg_TW_Timer = CreateTrigger(  )
    call DisableTrigger( gg_trg_TW_Timer )
    call TriggerRegisterTimerEventPeriodic( gg_trg_TW_Timer, .03 )
    call TriggerAddAction( gg_trg_TW_Timer, function Trig_TW_Timer_Actions )
endfunction
 
I don't have much time but here you go.


JASS:
function InitTrig_TW_Timer takes nothing returns nothing
    set gg_trg_TW_Timer = CreateTrigger(  )
    call DisableTrigger( gg_trg_TW_Timer )
    call TriggerRegisterTimerEventPeriodic( gg_trg_TW_Timer, .03 )
    call TriggerAddAction( gg_trg_TW_Timer, function Trig_TW_Timer_Actions )
endfunction

->

JASS:
function InitTrig_TW_Timer takes nothing returns nothing
    call TimerStart(CreateTimer(), 0.03, true, function Actions
endfunction

GetUnitState( u, UNIT_STATE_LIFE) > 0 -> GetWidgetLife(u) > 0.405

The way you handle the spell is really weird, disabling the trigger and such. You should be using the event Unit - Starts the effect of an ability. I would explain more but I gtg, check how other people do spells.
 
Level 7
Joined
Mar 6, 2006
Messages
282
Damn, JASS is fckin awesome. I had no idea you could use countdown timers like that. I saw some people refer to timers in GUI spells as PeriodicEvents. It worked too, but the countdown timer seems more efficient.

And yeah, I am using "starts the effect of an ability". I have that in another trigger, which tells my Periodic trigger that someone is using the spell, so it enables it. I then disable the Periodic trigger when no one is using it, because it seems like a waste of memory. So if I use that new TimerStart thing, I could just do PauseTimer instead of disabling the trigger!

Do other people keep their timers running, or do they pause them until someone is using it? That's the only reason I have the trigger disable (cuz I was using PeriodicEvent).


Edit: OK, I've been messing with the timers now and I think I understand it. I'm going to post my revised code in a bit, just in case you were planning to school me on timers lol.

=========================

"GetWidgetLife(u) > 0.405"

Thanks for the tip. I love these little bits of information. Is 0.405 the absolute minimum health a unit can have before dying?

Edit: Just tested that. That's insane... a unit can have 0.4051 life but 0.405 means that it's dead, LOL. So wtf, the BJ's like IsUnitAlive aren't even accurate?! And it's hilarious that "GetWidgetLife > GetUnitState > IsUnitDead > IsUnitAlive". I should have started JASS a long time ago.
 
Last edited:
Level 7
Joined
Mar 6, 2006
Messages
282
I've completely derailed my own thread, but I didn't want to start a new one just in case my questions were really simple. Obviously, I've learned a lot between posts, so I'm hoping that I'm reaching the final stages of making good JASS MUI spells.

Thanks a bunch TriggerHappy for the info so far.

So... is THIS HOW YOU DO IT? (lol!)

- The trigger registers the cast
- The actions initialize the spell, then starts a personal global timer
- The timer runs until the spell is finished, then destroys itself

InitTrig_TidalWave = initialize trigger
Trig_TidalWave_Cast_Conditions = conditions for the casting spell
Trig_TidalWave_Cast = initialize timer actions
Trig_TidalWave = the timer actions


JASS:
function Trig_TidalWave_Cast_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A010' ) ) then
        return false
    endif
    return true
endfunction


function Trig_TidalWave takes nothing returns nothing
    local real x
    local real y
    local real u_x
    local real u_y
    local unit dummy
    local group tempgroup = CreateGroup()
    local group targetgroup = CreateGroup()
    local integer i = 3
    local unit u
    // dmg = lvl * x
    local real dmgpertick = 10
    
    loop
        exitwhen i > 12
        
        if ( udg_Skill_TW_Dummy[i] != null) then
            
            set dmgpertick = I2R(GetUnitAbilityLevel(udg_PlayerBoat[i], 'A010')) * dmgpertick
            
            // Max distance reached
            if ( udg_Skill_TW_Length[i] > 38 ) then
                call DestroyTimer(udg_Skill_TW_Timer[i])
                call RemoveUnit(udg_Skill_TW_Dummy[i])
                set udg_Skill_TW_Dummy[i] = null
                set dummy = null
                exitwhen true
            endif
            
            set dummy = udg_Skill_TW_Dummy[i]
            set x = GetUnitX( dummy ) + ( Cos(udg_Skill_TW_Angle[i] * bj_DEGTORAD) * 22 )
            set y = GetUnitY( dummy ) + ( Sin(udg_Skill_TW_Angle[i] * bj_DEGTORAD) * 22 )
            call SetUnitX( dummy, x)
            call SetUnitY( dummy, y)
            set udg_Skill_TW_Length[i] = udg_Skill_TW_Length[i] + 1
            
            if not IsTerrainPathable( x, y, PATHING_TYPE_FLOATABILITY) then
                
                call GroupEnumUnitsInRange(tempgroup, x, y, 270, null)
                
                // Conditions for GroupEnumUnits
                set u = FirstOfGroup(tempgroup)
                loop
                    exitwhen u == null
                    
                    if IsUnitEnemy( u, Player(i-1) ) then
                        if GetWidgetLife(u) > .405 then
                            if not IsUnitType( u, UNIT_TYPE_STRUCTURE) then
                                call GroupAddUnit(targetgroup, u)
                            endif
                        endif
                    endif
                    
                    call GroupRemoveUnit(tempgroup, u)
                    set u = FirstOfGroup(tempgroup)
                endloop
                
                call DestroyGroup(tempgroup)
                
                // Knockback here so I don't have to pass a global iterator
                set u = FirstOfGroup(targetgroup)
                loop
                    exitwhen u == null
                    
                    set u_x = GetUnitX(u) + ( Cos( udg_Skill_TW_Angle[i] * bj_DEGTORAD ) * 25 )
                    set u_y = GetUnitY(u) + ( Sin( udg_Skill_TW_Angle[i] * bj_DEGTORAD ) * 25 )

                    call UnitDamageTarget(udg_PlayerBoat[i], u, dmgpertick, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS )

                    if not IsTerrainPathable( u_x, u_y, PATHING_TYPE_FLOATABILITY) then
                        call SetUnitX(u, u_x )
                        call SetUnitY(u, u_y )
                        call DestroyEffect( AddSpecialEffectLoc( "Abilities\\Spells\\Orc\\Devour\\DevourEffectArt.mdl", Location(u_x, u_y) ) )
                    else
                        // unit could not be knockbacked (wall)
                        //call PlaySoundAtPointBJ(gg_snd_TW_End, 100, Location(u_x, u_y), 0)
                    endif

                    call GroupRemoveUnit(targetgroup, u)
                    set u = FirstOfGroup(targetgroup)
                endloop
                
                call DestroyGroup(targetgroup)
                // end Knockback
                
            endif
            
        endif
        
        set i = i + 1
    endloop
        
endfunction

function Trig_TidalWave_Cast takes nothing returns nothing
    local integer i = GetPlayerId(GetOwningPlayer(GetSpellAbilityUnit()))+1
    local location casterloc = GetUnitLoc(GetSpellAbilityUnit())
    local location targetloc = GetSpellTargetLoc()
    local unit u
    
    // Check if player is already using the spell
    if udg_Skill_TW_Dummy[i] != null then
        return
    endif
    
    set udg_Skill_TW_Length[i] = 0
    
    // create dummy and shockwave other dummy
    set u = CreateUnitAtLoc( Player(PLAYER_NEUTRAL_PASSIVE), 'e007', targetloc, 0)
    set udg_Skill_TW_Dummy[i] = CreateUnitAtLoc( Player(PLAYER_NEUTRAL_PASSIVE), 'e002', casterloc, 0)
    call IssueTargetOrder(udg_Skill_TW_Dummy[i], "shockwave", u )
    
    set udg_Skill_TW_Angle[i] = AngleBetweenPoints(casterloc, targetloc)
    
    if GetRandomInt(0, 1) == 1 then
        call PlaySoundAtPointBJ( gg_snd_TW_Cast1, 100, casterloc, 0 )
    else
        call PlaySoundAtPointBJ( gg_snd_TW_Cast2, 100, casterloc, 0 )
    endif
    
    call RemoveLocation(targetloc)
    call RemoveLocation(casterloc)
    call RemoveUnit(u)
    set u = null
    set udg_Skill_TW_Timer[i] = CreateTimer()
    call TimerStart(udg_Skill_TW_Timer[i], .03, true, function Trig_TidalWave)
endfunction

function InitTrig_TidalWave takes nothing returns nothing
    local integer i = 2
    set gg_trg_TidalWave = CreateTrigger()
    
    loop
        exitwhen i > 11
        
        call TriggerRegisterPlayerUnitEvent(gg_trg_TidalWave, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)

        set i = i + 1
    endloop
    
    call TriggerAddCondition( gg_trg_TidalWave, Condition( function Trig_TidalWave_Cast_Conditions ) )
    call TriggerAddAction( gg_trg_TidalWave, function Trig_TidalWave_Cast )
endfunction
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
If I used chobibo's solution, I could just eliminate the local iterator altogether. And you can't pass arguments to a function in ForGroup, that's kind of why I made this thread.

Edit: Omfg, I just randomly read this in some contest thread:
Don't use (Integer A) or (Integer B) because they are globals made to be used for all loops in your map, which is a terrible idea by Blizzard, because if the loops are nested, you can actually break a couple of things in the map. Use a custom global integer for each of your loops ^_^

Ya you are right sorry i forgot about ForGroup problems. You could eliminate the forgroup function call though and simply put the actions in the loop itself.

Also yes you should not use integer A or B.

Please do not double post either.

Look at my tutorial for Converting GUI to Efficient Jass. Your script as it is is very inefficient and that tutorial will help you.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
If I used chobibo's solution, I could just eliminate the local iterator altogether. And you can't pass arguments to a function in ForGroup, that's kind of why I made this thread.
The reason I passed it back to a local is to lock the data between passes to avoid overwritting, by doing that, I can safely use the local one without fear of it being overwritten by an interrupt from another thread, consider this:

JASS:
// TempInt is 10
call IssueImmediateOrder(Unit[TempInt],parameters)
// If a trigger with an EVENT_UNIT_ISSUED_ORDER is existing
// this would interrupt this thread's execution.
// Now that thread uses TempInt too, but for another
// purpose, so it gets overwritten to 101
call UnitApplyTimedLife(Unit[TempInt],...)
// Using a local integer, I'd lose that risk, even though the TempInt
// variable was overwritten, the value I needed was locked to a
// local intger.

I don't understand why you took my solution for a joke; In days long past, we used this
solution a lot to pass array indices, I learned this from observing cohadar's periodic timer system.

EDIT:
JASS:
function Trig_TidalWave_Cast takes nothing returns nothing
    local integer i = GetPlayerId(GetOwningPlayer(GetSpellAbilityUnit()))+1
    local location casterloc = GetUnitLoc(GetSpellAbilityUnit())
    local location targetloc = GetSpellTargetLoc()
    local unit u
   
    // Check if player is already using the spell
    if udg_Skill_TW_Dummy[i] != null then
        return
    endif
   
    set udg_Skill_TW_Length[i] = 0
   
    // create dummy and shockwave other dummy
    set u = CreateUnitAtLoc( Player(PLAYER_NEUTRAL_PASSIVE), 'e007', targetloc, 0)
    set udg_Skill_TW_Dummy[i] = CreateUnitAtLoc( Player(PLAYER_NEUTRAL_PASSIVE), 'e002', casterloc, 0)
    call IssueTargetOrder(udg_Skill_TW_Dummy[i], "shockwave", u )
   
    set udg_Skill_TW_Angle[i] = AngleBetweenPoints(casterloc, targetloc)
   
    if GetRandomInt(0, 1) == 1 then
        call PlaySoundAtPointBJ( gg_snd_TW_Cast1, 100, casterloc, 0 )
    else
        call PlaySoundAtPointBJ( gg_snd_TW_Cast2, 100, casterloc, 0 )
    endif
   
    call RemoveLocation(targetloc)
    call RemoveLocation(casterloc)
    call RemoveUnit(u)
    set u = null
    set udg_Skill_TW_Timer[i] = CreateTimer()
    call TimerStart(udg_Skill_TW_Timer[i], .03, true, function Trig_TidalWave)
endfunction

You forgot to null the location variables, also, try switching to coordinates (X/Y), it's much cleaner and requires less memory to use.
 
Last edited:
Level 7
Joined
Mar 6, 2006
Messages
282
That tutorial looks pretty good, I'll have a look at it tomorrow when I get on. +rep

The reason I passed it back to a local is to lock the data between passes to avoid overwritting, by doing that, I can safely use the local one without fear of it being overwritten by an interrupt from another thread, consider this:

JASS:
// TempInt is 10
call IssueImmediateOrder(Unit[TempInt],parameters)
// If a trigger with an EVENT_UNIT_ISSUED_ORDER is existing
// this would interrupt this thread's execution.
// Now that thread uses TempInt too, but for another
// purpose, so it gets overwritten to 101
call UnitApplyTimedLife(Unit[TempInt],...)
// Using a local integer, I'd lose that risk, even though the TempInt
// variable was overwritten, the value I needed was locked to a
// local intger.

I don't understand why you took my solution for a joke; In days long past, we used this
solution a lot to pass array indices, I learned this from observing cohadar's periodic timer system.

But who's to say that another event couldn't interrupt while TempInt is being applied to the local variable? Isn't that solution just lowering the risk of TempInt being overwritten?

And sorry; I didn't understand it the first time I looked at it.

EDIT:
JASS:
function Trig_TidalWave_Cast takes nothing returns nothing
    local integer i = GetPlayerId(GetOwningPlayer(GetSpellAbilityUnit()))+1
    local location casterloc = GetUnitLoc(GetSpellAbilityUnit())
    local location targetloc = GetSpellTargetLoc()
    local unit u
   
    // Check if player is already using the spell
    if udg_Skill_TW_Dummy[i] != null then
        return
    endif
   
    set udg_Skill_TW_Length[i] = 0
   
    // create dummy and shockwave other dummy
    set u = CreateUnitAtLoc( Player(PLAYER_NEUTRAL_PASSIVE), 'e007', targetloc, 0)
    set udg_Skill_TW_Dummy[i] = CreateUnitAtLoc( Player(PLAYER_NEUTRAL_PASSIVE), 'e002', casterloc, 0)
    call IssueTargetOrder(udg_Skill_TW_Dummy[i], "shockwave", u )
   
    set udg_Skill_TW_Angle[i] = AngleBetweenPoints(casterloc, targetloc)
   
    if GetRandomInt(0, 1) == 1 then
        call PlaySoundAtPointBJ( gg_snd_TW_Cast1, 100, casterloc, 0 )
    else
        call PlaySoundAtPointBJ( gg_snd_TW_Cast2, 100, casterloc, 0 )
    endif
   
    call RemoveLocation(targetloc)
    call RemoveLocation(casterloc)
    call RemoveUnit(u)
    set u = null
    set udg_Skill_TW_Timer[i] = CreateTimer()
    call TimerStart(udg_Skill_TW_Timer[i], .03, true, function Trig_TidalWave)
endfunction

You forgot to null the location variables, also, try switching to coordinates (X/Y), it's much cleaner and requires less memory to use.

Whoa, you have to null location variables and THEN remove them? I just recently saw that you have to null unit variables so I started doing that.

And yeah, I was planning on asking at some point if using coords is more efficient than locations, ty for the tip!

Quick question about that: there is a function that gets a location from coords, I'm not in front of an editor atm so I can't remember the name, but in GUI, it looks like Point(0.000,0.000) and you would pass that to a function requesting a location. My question is, does that leak?
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
But who's to say that another event couldn't interrupt while TempInt is being applied to the local variable? Isn't that solution just lowering the risk of TempInt being overwritten?

Local variables are local and available only on the function block they are declared (created/allocated, whichever you want to call it). No matter how hard you try, other functions can't touch it, and there's no pointer references(that I know of) in jass, so no.

Because you pass the value on a local variable, any interruptions can no longer affect the value since it's already a local variable. It's not lowering the risk, it removes the risk. Unless the declaration or set operations local integer i=TempInt causes a trigger to fire, which I don't think is possible. My example on the last post clearly explains why that fails.
=================================
Quick question about that: there is a function that gets a location from coords, I'm not in front of an editor atm so I can't remember the name, but in GUI, it looks like Point(0.000,0.000) and you would pass that to a function requesting a location. My question is, does that leak?

I didn't quite get the question. You don't get location data from coordinates, you use the coordinates to make a location set TempLoc=Location(real x, real y), it doesn't leak if you don't forget to remove it, and since you want to pass it to another function, you should remove it on the function that passed it.

JASS:
...
 local location l=Location(x,y)
 call PassLoc(l)
 call RemoveLocation(l)
 set l=null
...
 
Level 7
Joined
Mar 6, 2006
Messages
282
Because you pass the value on a local variable, any interruptions can no longer affect the value since it's already a local variable.

I understand that nothing can change it after it's been passed to a local. But what I'm saying is, what if something interrupts it BEFORE it's passed to the local, making the local equal to something else.

Example:

JASS:
function Main takes nothing returns nothing
        set udg_TempInt = 5
        //(another trigger with a short timer executes, changing TempInt to 1)
        call OtherFunction()
endfunction

function OtherFunction takes nothing returns nothing
         local integer i = udg_TempInt
         BJDebugMsg("Your saying that " + I2S(udg_TempInt) + " should be equal to 5, right?")
endfunction

Could that happen?


=================================


I didn't quite get the question. You don't get location data from coordinates, you use the coordinates to make a location set TempLoc=Location(real x, real y), it doesn't leak if you don't forget to remove it, and since you want to pass it to another function, you should remove it on the function that passed it.

Oh yeah, that's what I meant, Location(x, y)
So I have to remove it, kk. That makes more sense in JASS, cuz it looked different in GUI and I didn't know if it leaked.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Only one thread runs at any given time. The thread runs then it goes to the next thread in the thread queue.

That is why every spell can have tempInt without fear of the tempInt getting overwritten.

Easy way to see this. Make 2 threads that execute every so often. (at the same time) like every 1 second.
use game messages or debug messages and display which trigger is running multiple times in a loop ( like 10 to 100 times) Then look at the log and see what i mean. It will completely finish one before starting the other.

Oh yeah, that's what I meant, Location(x, y)
So I have to remove it, kk. That makes more sense in JASS, cuz it looked different in GUI and I didn't know if it leaked.

Locations leak if not removed. Also in jass you should never use locations (unless you are trying to get a z value). You should use real values instead. They don't require removing as they don't leak. Also they are a lot faster. ( You will also see that in my tutorial)
 
deathismyfriend said:
That is why every spell can have tempInt without fear of the tempInt getting overwritten.

This isn't always true, like I said in a previous post this is possible:

  • Untitled Trigger 002
    • Events
      • Unit - A unit Dies
    • Conditions
    • Actions
      • Set Integer = 10
  • Untitled Trigger 003
    • Events
      • Player - Player 1 (Red) skips a cinematic sequence
    • Conditions
    • Actions
      • Set Integer = 2
      • Unit - Kill Mountain King 0000 <gen>
      • Game - Display to (All players) the text: (String(Integer))
Displays 10
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
This isn't always true, like I said in a previous post this is possible:

  • Untitled Trigger 002
    • Events
      • Unit - A unit Dies
    • Conditions
    • Actions
      • Set Integer = 10
  • Untitled Trigger 003
    • Events
      • Player - Player 1 (Red) skips a cinematic sequence
    • Conditions
    • Actions
      • Set Integer = 2
      • Unit - Kill Mountain King 0000 <gen>
      • Game - Display to (All players) the text: (String(Integer))
Displays 10

That is interesting that does display 10. Yet another thing blizzard screwed up lol.
 
Level 7
Joined
Mar 6, 2006
Messages
282
Thanks a lot guys, I knew that was going to be hard to ask/get an answer for, and now I finally understand it.

I don't know how I feel about that though... it means I need to check all my triggers to make sure they aren't conflicting, every time I pass something with a global, lol.

It's OK though, because I'll use locals as much as I can and try to keep things in one function, like you guys showed me.

I feel much more confident now, thanks everyone! Thread is solved.

(until I make a new thread for another question!) >:)
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
That won't happen dude, as threads don't get interrupted by timers.
Yet another thing blizzard screwed up lol.
I don't think they messed that one up, since the events should really work like that (it's seems logical), they however fucked up GUI because you can't use locals.
 
Thanks a lot guys, I knew that was going to be hard to ask/get an answer for, and now I finally understand it.

I don't know how I feel about that though... it means I need to check all my triggers to make sure they aren't conflicting, every time I pass something with a global, lol.

It's OK though, because I'll use locals as much as I can and try to keep things in one function, like you guys showed me.

I feel much more confident now, thanks everyone! Thread is solved.

(until I make a new thread for another question!) >:)

This is why we use vJass, I highly recommend http://www.hiveworkshop.com/forums/tools-560/jassnewgenpack-5d-227445/

It includes many tools, one of which parses vJASS and introduces theprivatekeyword to avoid conflicts.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
The threads are supposed to run after another. They screwed it up because it doesn't do what they said it does.

Where'd you get that info? I never saw any Blizzard documents stating that as fact. Could you please provide a link to the said document (or an excerpt, that would be better! :D)?

AFAIR, the only documentation they provided was the comments on common.j and blizzard.j. I don't remember blizzard giving details on how jass threads worked.
JASS:
//===================================================
// Game, Player and Unit Events
//
// When an event causes a trigger to fire these
// values allow the action code to determine which
// event was dispatched and therefore which set of
// native functions should be used to get information
// about the event.
//
// Do NOT change the order or value of these constants
// without insuring that the JASS_GAME_EVENTS_WAR3 enum
// is changed to match.
//
//===================================================
Anyway, no point in arguing about this since the thread's problem was solved, but if you want to share the info, it wouldn't be bad right? :D
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
The threads are supposed to run after another. They screwed it up because it doesn't do what they said it does.

Not really, in a "real" programming langague, several threads can run at the same time and the execution of them are "random", you can't guess which one will end before the others (assuming the code within them is similar).

Now in jass it's one at a time, if a new thread is opened, the previous one is "halted" until an other one is opened or the current one is finished or if a TriggerSleepAction is used (or any native function which will have a "wait" part like the sync functions).
So here everything is logical, if "2" was displayed, that would simply mean that killing an unit is not something instant but delayed.

I suppose they don't act like "real" threads, simply because this way the code is much easier to write.
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Now in jass it's one at a time, if a new thread is opened, the previous one is "halted" until an other one is opened or the current one is finished or if a TriggerSleepAction is used.
So here everything is logical, if "2" was displayed, that would simply mean that killing an unit is not something instant but delayed.

That is the point i was getting at.

Not really, in a "real" programming langague, several threads can run at the same time and the execution of them are "random", you can't guess which one will end before the others (assuming the code within them is similar).

I already know about other programming languages allowing for multiple threads to run at a time. I have already dealt with multi-threading since i have switched over to C++ and C#
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
I suppose they don't act like "real" threads, simply because this way the code is much easier to write.
Why was it called thread anyway, it functions more like interrupts rather than threads.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
One time, someone quite reliable, said to me they worked like "real" threads years ago (or maybe only under some specials defined cases).
But i wasn't playing and even less modding wc3, so i don't know if it's true, probably not, it could even be some sort of joke.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
First time I learned about threads was this one time when cohadar was arguing with the mods about his system, he mentioned "pre-emptive" which provoked me to search for stuff I know I won't need to learn.

I'd rather call them interrupts, since they really act like one. I used to get punk'd by infinite recursions when I tried stopping a unit with triggers. Turns out that stop order invokes the event of that same trigger.
 
Level 7
Joined
Mar 6, 2006
Messages
282
I used to write scripts for Diablo II bots, and there were numerous threads going on at once. I remember having to keep that in mind all the time when dealing with variables and other things.

I think that's where I got concerned with JASS when I thought I couldn't trust a global to be passed safely.

=====================

Well, since you guys are still talking, I might as well ask another question. :)

So as it turns out, what I thought was MUI, was like half-MUI because it only worked under strict specific conditions in my map. It would have been fine, but I'd rather do it the real way since it's more efficient.

Anywho, I read some tutorials and made my spell MUI, but I just have one question:

For the actions of the spell, should I use Periodic Events or a countdown timer?

TriggerRegisterTimerEvent OR TimerStart

Every guide on MUI spells used a Periodic Event and turned it off when the spell index was empty, but at the start of this thread, TriggerHappy said I should use countdown timers.
 
Level 7
Joined
Mar 6, 2006
Messages
282
Why do you say that?

He really should be using timers not periodic events.

Every tutorial I've read so far, including various MUI spells made by users from here, have used periodic events.

Would you care to explain why countdown timers are better? Are they faster and/or less memory intensive?

Also, a really quick pseudo code example would be nice. Here's my guess, let me know if it's wrong:

JASS:
function init takes nothing returns nothing
t = create timer
timerstart( t, actions )

function cast takes nothing returns nothing
resumetimer( t )

function actions takes nothing returns nothing
if timer is not in use then
pausetimer
 
DysfunctionaI said:
Every tutorial I've read so far, including various MUI spells made by users from here, have used periodic events.

Are those spells GUI? lol

DysfunctionaI said:
Would you care to explain why countdown timers are better? Are they faster and/or less memory intensive?

Periodic Event: 3 handles
Timer: 1 handle

Logically you could assume that every time the period expires much more work is being done when using periodic events.
The game is going to have to check the conditions then execute the trigger's action(s) which is presumably slower than a timer callback.

DysfunctionaI said:
Also, a really quick pseudo code example would be nice.

JASS:
function Callback takes nothing returns nothing
endfunction

function Init takes nothing returns nothing
    call TimerStart(CreateTimer(), 0.03, true, function Callback)
endfunction
 
Level 7
Joined
Mar 6, 2006
Messages
282
Are those spells GUI? lol

Yes, they were. They were good tutorials though.

APeriodic Event: 3 handles
Timer: 1 handle

Logically you could assume that every time the period expires much more work is being done when using periodic events.
The game is going to have to check the conditions then execute the trigger's action(s) which is presumably slower than a timer callback.

Sounds good to me!

JASS:
function Callback takes nothing returns nothing
endfunction

function Init takes nothing returns nothing
    call TimerStart(CreateTimer(), 0.03, true, function Callback)
endfunction

Hmm, but this means that the timer is running all the time. Don't we only want it to run when someone is using it? In which case, it would need to pause. ( I had a problem resuming it when I tested, maybe I did it wrong, I could try again ).
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
Take a look at some of my spells they use timers like suggested above.

I said periodic timers but i meant like the one above. Since we are talking jass and not gui. I always call them periodic event as it runs the same just more efficient. Also you shouldn't use TriggerAddAction as said in my tutorial.
 
Last edited:
Also you shouldn't use TriggerAddCondition as said in my tutorial.

You should actually only use TriggerAddCondition because it's considerably faster than actions and requires less processing for the game.

JASS:
function Actions takes nothing returns boolean
    if (GetSpellAbilityId() == 'rawc') then
        //actions
    endif
    return false
endfunction

function Init takes nothing returns nothing
    local trigger t  = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Filter(function Actions))
endfunction

DysfunctionaI said:
Hmm, but this means that the timer is running all the time. Don't we only want it to run when someone is using it? In which case, it would need to pause. ( I had a problem resuming it when I tested, maybe I did it wrong, I could try again ).
JASS:
function Callback takes nothing returns nothing
    if (SpellIsDone) then
        set udg_SpellCount = SpellCount - 1
        if (udg_SpellCount == 0) then
            call PauseTimer(timer)
        endif
    endif
endfunction

function Actions takes nothing returns boolean
    if (GetSpellAbilityId() == 'rawc') then
        set udg_SpellCount = SpellCount + 1
        if (udg_SpellCount == 1) then
            call TimerStart(timer, 0.03, true, function Callback)
        endif
    endif
    return false
endfunction

function Init takes nothing returns nothing
    local trigger t  = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Filter(function Actions))
endfunction
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
You should actually only use TriggerAddCondition because it's considerably faster than actions and requires less processing for the game.

JASS:
function Actions takes nothing returns boolean
    if (GetSpellAbilityId == 'rawc') then
        //actions
    endif
    return false
endfunction

function Init takes nothing returns nothing
    local trigger t  = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Filter(function Actions))
endfunction


JASS:
function Callback takes nothing returns nothing
    if (SpellIsDone) then
        set udg_SpellCount = SpellCount - 1
        if (udg_SpellCount == 1) then
            call PauseTimer(timer)
        endif
    endif
endfunction

function Actions takes nothing returns boolean
    if (GetSpellAbilityId == 'rawc') then
        set udg_SpellCount = SpellCount + 1
        if (udg_SpellCount == 1) then
            call TimerStart(timer, 0.03, true, function Callback)
        endif
    endif
    return false
endfunction

function Init takes nothing returns nothing
    local trigger t  = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Filter(function Actions))
endfunction

Sorry i typed it wrong lol i meant to say you should not use TriggerAddAction. use TriggerAddCondition instead. My tutorial is correct though i just mistyped it on here.
 
Status
Not open for further replies.
Top