• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[JASS] How to pass the Triggering Unit?

Status
Not open for further replies.
Level 4
Joined
Feb 2, 2009
Messages
71
I just learned that GetTriggerUnit() doesn't work when inside a function that is called by a Timer. Probably because the Triggering Unit is only saved for a small period of time.

This does not work:
JASS:
function CalledFunction takes nothing returns nothing
unit u = GetTriggerUnit()
endfunction
...
call TimerStart(Timer, 1.0, TRUE, function CalledFunction)

This doesn't work either:
JASS:
function CalledFunction takes unit u returns nothing
endfunction
...
call TimerStart(Timer, 1.0, TRUE, function CalledFunction(GetTriggeringUnit()))

...so how do I pass the Triggering unit to the function?
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,258
Store it somewhere else for the duriation of the timer and then recall it again once the timer expires. This is usually done by storing it "on the timer" so that you can fetch the value from the timer value.

This logically invloves H2I() no mater what you do.
Way 1 is to encode the handle value somehow onto the timer, which slightly alters its timing but in the nano seconds.
Way 2 is to use a gamecache system to attach the unit to the timer by storing the integer of the unit in a mission of the string of the integer of the timer with a certain key word and then fetching it with the string of the integer of the timer and then using the return bug to convert the integer back into a unit. This is slow and not recommended for large numbers of processes.
Way 3 is like way 2 but faster as it uses stacks of array and H2I to store into array avoiding string conversion and multiple functions. Downside is it may bug if your map has too many handles or leaks, which may still be avoided via additional coding but then it becomes basically Way 2.

On top of that, you also have unrecommended ways.
You store it into a table and loop throu the various items until you find the timer, in which case you find its corosponding unit. This is unrecommended as although it may be faster for low numbers, it ends up a lot slower as it is an indirect method of storing an object (meaning it has to look for an object instead of going straight to it).
 
Level 11
Joined
Apr 6, 2008
Messages
760
or you can use a struct with an indexer

JASS:
struct Data
unit u

//more stuff if needed

static integer array Ar //these 3 are the same as globals but they are "bounded" with the struct
static integer Total = 0
static timer Time = CreateTimer()

    static method create takes unit u returns nothing  //static methods are the same as functions
        local Data Dat = Data.allocate()
   
        set Dat.u = u

        if Dat.Total == 0 then //if there is no structs "running" we start the timer
            call TimerStart(//stuff) 
        endif

        set Dat.Ar[Dat.Total] = Dat
        set Dat.Total = Dat.Total + 1
        
        set u = null
    endmethod

    static method Loop takes nothing returns nothing
        local Data Dat
        local integer i = 0
       
        loop
            exitwhen i >= Dat.Total
            set Dat = Dat.Ar[i]
 
            // ur stuff

            if then //some reason to destroy it
                set Dat.Total = Dat.Total - 1
                set Dat.Ar[i] = Dat.Ar[Dat.Total]
                set i = i - 1
                call Dat.destroy()
            endif

            set i = i + 1
        endloop
     
        if Dat.Total == 0 then
            call PauseTimer(Dat.Time)
        endif
    endmethod
 
Level 4
Joined
Feb 2, 2009
Messages
71
Ok, I've been trying to learn about structs and methods and stuff, but I'm still confused about what's going on here.

Correct me if/where I'm wrong.

JASS:
struct Data
unit u

//more stuff if needed

static integer array Ar //array, separating simultanous instances
static integer Total = 0  //the total number of instances running
static timer Time = CreateTimer() //creates the timer

    static method create takes unit u returns nothing   // shouldn't this return Data?
        local Data Dat = Data.allocate()  // assigning the struct a unique id
        
        set Dat.u = u   // setting Dat.u to... probably GetTriggerUnit()

        if Dat.Total == 0 then //start the timer if it's not already in use
            call TimerStart(//stuff)
        endif

        set Dat.Ar[Dat.Total] = Dat   //assigning the struct a unique id (again?). I guess it's so you can easily loop through the instances
        set Dat.Total = Dat.Total + 1   //showing that there is one more instance running

        set u = null        //cleaning leak
    endmethod

    static method Loop takes nothing returns nothing
        local Data Dat     //Shouldn't I create the Data here? .create? If not, what is this?
        local integer i = 0   // an integer keeping track of which instance the loop is running

        loop
            exitwhen i >= Dat.Total   //exit when all instances are looped
            set Dat = Dat.Ar[i]     //setting which instance being run

            // ur stuff

            if then
                set Dat.Total = Dat.Total - 1   //reducing the number of instances because this one is finnished
                set Dat.Ar[i] = Dat.Ar[Dat.Total]  //what's this?
                set i = i - 1        //run previous instance (why's that?)
                call Dat.destroy()   //destroying the struct, because the instance is finnished
            endif

            set i = i + 1      // run next instance
        endloop

        if Dat.Total == 0 then    // if there's no instances running, pause the timer
            call PauseTimer(Dat.Time)
        endif
    endmethod
 
Level 11
Joined
Apr 6, 2008
Messages
760
JASS:
struct Data
unit u

//more stuff if needed

static integer array Ar //array, separating simultanous instances
static integer Total = 0 //the total number of instances running
static timer Time = CreateTimer() //creates the timer

    static method create takes unit u returns Data //yes must return the structs name
        local Data Dat = Data.allocate()

        set Dat.u = u // depends on what you need

        if Dat.Total == 0 then 
            call TimerStart(//stuff)
        endif

        set Dat.Ar[Dat.Total] = Dat //This is soo we can get the right struct in the loop
        set Dat.Total = Dat.Total + 1 //this "indicate" that how many structs that is running
        set u = null

        return Dat // return the struct
    endmethod

    static method Loop takes nothing returns nothing
        local Data Dat //you shouldn't create a struct here then u will create useless structs, this is so we can do set Dat = Dat.Ar[i] else this is a syntax erroe
        local integer i = 0

        loop
            exitwhen i >= Dat.Total
            set Dat = Dat.Ar[i]

            // ur stuff

            if then
                set Dat.Total = Dat.Total - 1
                set Dat.Ar[i] = Dat.Ar[Dat.Total] //setting this struct who is finished to the last in the stack which wont be runned in a loop, since we exit >= Dat.Total
                set i = i - 1 //tbh i don't know. Dynasti told me to do it
                call Dat.destroy()
            endif

            set i = i + 1
        endloop

        if Dat.Total == 0 then 
            call PauseTimer(Dat.Time)
        endif
    endmethod
endstruct
 
Level 14
Joined
Nov 18, 2007
Messages
816
JASS:
struct Data
    unit u

    //more stuff if needed

    static integer array Ar
    static integer Total = 0
    static timer Time = CreateTimer()
    
    static method Loop takes nothing returns nothing
        local Data Dat
        local integer i = 0
        loop
            exitwhen i >= Data.Total
            set Dat = Data.Ar[i]
            // your stuff
            if (Some_Condition_is_met) then
                set Data.Total = Data.Total - 1
                set Data.Ar[i] = Data.Ar[Data.Total]
                set i = i - 1 // this prevents skipping Data.Ar[Data.Total], now Data.Ar[i]
                call Dat.destroy()
            endif
            set i = i + 1
        endloop
        if Data.Total == 0 then
            call PauseTimer(Data.Time)
        endif
    endmethod

    static method create takes unit u returns Data
        local Data Dat = Data.allocate()

        set Dat.u = u

        if Data.Total == 0 then
            call TimerStart(Data.Time, TICK, true, function Data.Loop) // TICK is a constant of type real and should be defined in a globals block above.
        endif

        set Data.Ar[Data.Total] = Dat 
        set Data.Total = Data.Total + 1 
        return Dat
    endmethod
endstruct

This actually has a chance of passing syntax checking.
 
Level 4
Joined
Feb 2, 2009
Messages
71
I'm starting to get it now. Thank you both.

I have some errors I need help to correct though, see my notes "//":
JASS:
scope Formation initializer Init

struct formation
    unit caster
    integer spellLvl
    integer auraLvl
    boolean isAlive
    filterfunc filter
    group alliesInRange
    
    static integer array Ar
    static integer Total = 0
    static timer Tim = CreateTimer()
    static integer spellId = 'A002'
    static integer auraId = 'ForA'
    static real radius = 400.0
    
    method filterAlliesInRange takes nothing returns boolean
        local formation f
        local unit filteredUnit = GetFilterUnit()
        return (IsUnitAlly(filteredUnit, GetTriggerPlayer()) and IsUnitType(filteredUnit, UNIT_TYPE_HERO) and not (filteredUnit == f.caster))
    endmethod
    
    method getAuraLevel takes nothing returns integer
        local formation f
        local integer n = CountUnitsInGroup(f.alliesInRange)
        return (f.spellLvl + n)
    endmethod
    
    method tic takes nothing returns nothing
        local formation f
        local integer i = 0
        loop
            exitwhen i >= f.Total
            set f = f.Ar[i]
            set f.isAlive = (GetUnitState(f.caster, UNIT_STATE_LIFE) <= 0)
            call GroupEnumUnitsInRange(f.alliesInRange, GetUnitX(f.caster), GetUnitY(f.caster), f.radius, f.filter)
            
            if f.isAlive == TRUE then
                set f.spellLvl = GetUnitAbilityLevel(f.caster, f.spellId)
                set f.auraLvl = GetUnitAbilityLevel(f.caster, f.auraId)
                
                call SetUnitAbilityLevel(f.caster, f.auraId, f.getAuraLevel())
            endif
            set i = i + 1
        endloop
        if f.Total == 0 then
            call PauseTimer(f.Tim)
        endif
    endmethod
    
    static method create takes unit u returns formation
        local formation f = formation.allocate()
        
        set filter = Filter(function f.filterAlliesInRange()) //Syntax Error
        set f.caster = u
        set f.isAlive = TRUE
        
        if f.Total == 0 then
            call TimerStart(f.Tim, 0.1, TRUE, function f.tic) //f is not a struct name
        endif
        
        set f.Ar[f.Total] = f
        set f.Total = f.Total + 1
        return f
    endmethod
endstruct

///////////////////////////////////////////////////////////

function Conditions takes nothing returns boolean
    local formation f
    return  (GetUnitAbilityLevel(f.caster, f.spellId) == 1)
endfunction

function Actions takes nothing returns nothing
    local formation f
    call UnitAddAbility(f.caster, f.auraId)
    set f = formation.create(GetTriggerUnit())
endfunction

//===========================================================================
function Init takes nothing returns nothing
    local trigger trig = CreateTrigger( )
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_HERO_SKILL )
    call TriggerAddCondition( trig, Condition( function Conditions ) )
    call TriggerAddAction( trig, function Actions )
endfunction

endscope

You'll notive I never use [f]f.destroy()[/f]. That's because it's an aura, so the effect should never stop at all, except when the hero dies. I solved that problem by adding the [f]f.isAlive[/f] variable, and only running the effect of the aura if [f]f.isAlive = TRUE[/f]. The formation struct will never get "full" unless 8190 heroes choosing the ability, and that won't happen.

If you're wondering about what the spell does. Basically it is an aura wich armorbonus is depending on the number of nearby allied heroes.
The trigger adjusts the level of a non-hero aura on the caster.

I haven't tried the spell yet because of the syntax-errors so there might be major errors I haven't noticed yet.
 
Level 14
Joined
Nov 18, 2007
Messages
816
set filter = Filter(function formation.filterAlliesInRange) //Syntax Error // no more

JASS:
call TimerStart(f.Tim, 0.1, TRUE, function formation.tic) //f is not a struct name // f is of type integer (after being translated); use the name of your struct instead, since its a static method
 
Level 4
Joined
Feb 2, 2009
Messages
71
I also had to make the method I was calling static in order to get it to work. Else it just said like "the method is not static or does not take nothing".

I encountered another problem I can't solve...

JASS:
static method filterAlliesInRange takes nothing returns boolean
        local formation f
        local unit filteredUnit = GetFilterUnit()
        return (IsUnitAlly(filteredUnit, GetTriggerPlayer()) and IsUnitType(filteredUnit, UNIT_TYPE_HERO) and not (filteredUnit == f.caster))
    endmethod
...
JASS:
call GroupEnumUnitsInRange(f.alliesInRange, GetUnitX(f.caster), GetUnitY(f.caster), f.radius, f.filter)

The filter does not run, probably because the "filterAlliesInRange" method doesn't know what f I'm talking about. As the method can't take anything I'm pretty much back where I started...
How do I pass the "f" to the "filterAlliesInRange" method?

If you need to see the code in it's context, see my previous post. It's the same except I corrected f.filterAlliesInRange into formation.filterAlliesInRange, and made the methods static.
 
Status
Not open for further replies.
Top