• 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.

[Spell] GUI to JASS Merge and Optimize Help

Status
Not open for further replies.
Level 5
Joined
Feb 22, 2013
Messages
161
Here are two separate triggers I made in GUI and converted to JASS, I can get rid of BJs easily, but merging and optimizing the trigger as one I don't fully understand, can I get some help with that?

Renew Casted:
JASS:
function Trig_Renew_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'RE00' ) ) then
        return false
    endif
    return true
endfunction

function Trig_Renew_Func003C takes nothing returns boolean
    if ( not ( udg_NewIndex >= 1 ) ) then
        return false
    endif
    return true
endfunction

function Trig_Renew_Actions takes nothing returns nothing
    set udg_NewIndex = ( udg_NewIndex + 1 )
    set udg_TempTarget[udg_NewIndex] = GetSpellTargetUnit()
    if ( Trig_Renew_Func003C() ) then
        call TimerStart(udg_RenewTimer, 1.00, true, null )
    else
    endif
endfunction

//===========================================================================
function InitTrig_Renew takes nothing returns nothing
    set gg_trg_Renew = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Renew, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Renew, Condition( function Trig_Renew_Conditions ) )
    call TriggerAddAction( gg_trg_Renew, function Trig_Renew_Actions )
endfunction

Renew Loop:
JASS:
function Trig_Renew_Loop_Func002Func003C takes nothing returns boolean
    if ( not ( udg_RenewCount[udg_CurrentIndex] == 10 ) ) then
        return false
    endif
    return true
endfunction

function Trig_Renew_Loop_Actions takes nothing returns nothing
    set udg_CurrentIndex = 1
    loop
        exitwhen udg_CurrentIndex > udg_NewIndex
        set udg_RenewCount[udg_CurrentIndex] = ( udg_RenewCount[udg_CurrentIndex] + 1 )
        call SetUnitLifeBJ( udg_TempTarget[udg_CurrentIndex], ( GetUnitStateSwap(UNIT_STATE_LIFE, udg_TempTarget[udg_CurrentIndex]) + 20.00 ) )
        if ( Trig_Renew_Loop_Func002Func003C() ) then
            set udg_TempTarget[udg_CurrentIndex] = null
            set udg_CurrentIndex = ( udg_CurrentIndex - 1 )
        else
        endif
        set udg_CurrentIndex = udg_CurrentIndex + 1
    endloop
endfunction

//===========================================================================
function InitTrig_Renew_Loop takes nothing returns nothing
    set gg_trg_Renew_Loop = CreateTrigger(  )
    call TriggerRegisterTimerExpireEventBJ( gg_trg_Renew_Loop, udg_RenewTimer )
    call TriggerAddAction( gg_trg_Renew_Loop, function Trig_Renew_Loop_Actions )
endfunction
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
JASS:
call TimerStart(udg_RenewTimer,
1.00, true, function <your timer func>)

Move the 2 funcs in the timer expired trig to the top of the spell trig.

Get rid of the brackets in the conditions. You can optimize your code like this.

JASS:
function Trig_Renew_Loop_
Func002Func003C takes nothing returns boolean
return udg_RenewCount[udg_
CurrentIndex] == 10
endfunction

function Trig_Renew_Conditions takes nothing returns boolean
return GetSpellAbilityId() ==
'RE00'
endfunction

function Trig_Renew_Func003C takes nothing returns boolean
return udg_NewIndex >= 1
endfunction

Here is a good and efficient JASS code. Take a look :)

http://www.hiveworkshop.com/forums/triggers-scripts-269/what-would-most-efficient-way-doing-235880/
 
Level 5
Joined
Feb 22, 2013
Messages
161
JASS:
//=========================================================
//GLOBALS:
//=========================================================

globals
    integer NEW_INDEX      
    integer CURRENT_INDEX    
    integer array RENEW_COUNT
    unit array TEMP_TARGET
endglobals

//=========================================================
//CONSTANTS:
//=========================================================

function Spell_ID takes nothing returns integer
    return 'RE00'
endfunction

//=========================================================
//CONFIGS:
//=========================================================

function HoT_Amount takes integer level, integer intel returns integer
    return (level * 5) + intel
endfunction

//=========================================================
//ACTIONS:
//=========================================================

function Renew_Loop takes nothing returns nothing
    local trigger Renew = GetTriggeringTrigger()
    local integer RenewID = GetHandleId(Renew)
    local integer HoT
    
    set CURRENT_INDEX = 1
    loop
        exitwhen CURRENT_INDEX > NEW_INDEX
        set HoT = LoadInteger(udg_RE00_Hashtable, RenewID, 2)
        set RENEW_COUNT[CURRENT_INDEX] = RENEW_COUNT[CURRENT_INDEX] + 1
        call SetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE, GetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE) + HoT)
        set CURRENT_INDEX = CURRENT_INDEX + 1
    endloop
endfunction

function Renew_Conditions takes nothing returns boolean
    local trigger Renew
    local integer RenewID
    local unit caster
    local integer HoT
    
    if GetSpellAbilityId() == Spell_ID() then
        set NEW_INDEX = NEW_INDEX + 1
        set caster = GetSpellAbilityUnit()
        set TEMP_TARGET[NEW_INDEX] = GetSpellTargetUnit()
        set HoT = HoT_Amount(GetUnitAbilityLevel(caster, Spell_ID()), GetHeroInt(caster, true))
        set Renew = CreateTrigger()
        set RenewID = GetHandleId(Renew)
        call TriggerRegisterTimerEvent(Renew, 1.00, true)
        call SaveTriggerActionHandle(udg_RE00_Hashtable, RenewID, 0, TriggerAddAction(Renew, function Renew_Loop))
        call SaveUnitHandle(udg_RE00_Hashtable, RenewID, 1, TEMP_TARGET[NEW_INDEX])
        call SaveInteger(udg_RE00_Hashtable, RenewID, 2, HoT)
        set Renew = null
        set caster = null
        
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Renew takes nothing returns nothing
    local trigger RE = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( RE, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( RE, Condition( function Renew_Conditions ) )
    set RE = null
endfunction

This is as far as I've gotten on my spell so far, but the healing won't work, I don't know why...
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
HoT has to be converted to real first to be used in the SetUnitState

JASS:
call SetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE, GetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE) + I2R (HoT))

edit
You also forgot to initialize the hashtable. Add this line in the InitTrig_Renew func set udg_RE00_Hashtable = InitHashtable ()
 
Level 5
Joined
Feb 22, 2013
Messages
161
HoT has to be converted to real first to be used in the SetUnitState

JASS:
call SetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE, GetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE) + I2R (HoT))

edit
You also forgot to initialize the hashtable. Add this line in the InitTrig_Renew func set udg_RE00_Hashtable = InitHashtable ()

So I did what you said, and I added the hashtable initializer in the InitRenew function. I also tried a conversion of HoT to real, I don't know if I did it wrong or not, but it still wouldn't work.

JASS:
//=========================================================
//GLOBALS:
//=========================================================

globals
    integer NEW_INDEX      
    integer CURRENT_INDEX    
    integer array RENEW_COUNT
    unit array TEMP_TARGET
endglobals

//=========================================================
//CONSTANTS:
//=========================================================

function Spell_ID takes nothing returns integer
    return 'RE00'
endfunction

//=========================================================
//CONFIGS:
//=========================================================

function HoT_Amount takes integer level, integer intel returns integer
    return (level * 5) + intel
endfunction

//=========================================================
//ACTIONS:
//=========================================================

function Renew_Loop takes nothing returns nothing
    local trigger Renew = GetTriggeringTrigger()
    local integer RenewID = GetHandleId(Renew)
    local integer HoT
    
    set CURRENT_INDEX = 1
    loop
        exitwhen CURRENT_INDEX > NEW_INDEX
        set HoT = LoadInteger(udg_RE00_Hashtable, RenewID, 2)
        set RENEW_COUNT[CURRENT_INDEX] = RENEW_COUNT[CURRENT_INDEX] + 1
        call SetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE, GetUnitState(TEMP_TARGET[CURRENT_INDEX], UNIT_STATE_LIFE) + I2R(HoT))
        set CURRENT_INDEX = CURRENT_INDEX + 1
    endloop
endfunction

function Renew_Conditions takes nothing returns boolean
    local trigger Renew
    local integer RenewID
    local unit caster
    local integer HoT
    
    if GetSpellAbilityId() == Spell_ID() then
        set NEW_INDEX = NEW_INDEX + 1
        set caster = GetSpellAbilityUnit()
        set TEMP_TARGET[NEW_INDEX] = GetSpellTargetUnit()
        set HoT = HoT_Amount(GetUnitAbilityLevel(caster, Spell_ID()), GetHeroInt(caster, true))
        set Renew = CreateTrigger()
        set RenewID = GetHandleId(Renew)
        call TriggerRegisterTimerEvent(Renew, 1.00, true)
        call SaveTriggerActionHandle(udg_RE00_Hashtable, RenewID, 0, TriggerAddAction(Renew, function Renew_Loop))
        call SaveUnitHandle(udg_RE00_Hashtable, RenewID, 1, TEMP_TARGET[NEW_INDEX])
        call SaveInteger(udg_RE00_Hashtable, RenewID, 2, HoT)
        set Renew = null
        set caster = null
        
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Renew takes nothing returns nothing
    local trigger RE = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( RE, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( RE, Condition( function Renew_Conditions ) )
    set udg_RE00_Hashtable = InitHashtable()
    set RE = null
endfunction
 
Level 8
Joined
Feb 3, 2013
Messages
277
I don't think your hp renewal thing is ever being called to run lol.
You save the action handle for the trigger but there's no event calling the trigger.
You could do something like TimerStart(lv_timer, 1, true, function Renew_Loop)

dude, but this is meant to be some kind of looping healing spell right?

you dont even need to use trigger action, just save information via struct and attach the struct to a timer and have it run
The instances will stack properly and you dont need this action trigger stuff.

edit: The index globals are confusing me lol
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Ha ha lol I see now. You have to set New Index = 0 first. Without that, in your loop Current index will be 1 and new index is 0 -> immediate loop end. Btw your method is really strange. You should either go with Hashtable or indexing, not both like that.

And also, GetTriggerUnit () seems to be a faster alternative to GetSpellAbilityUnit ()

Since you are picking vJASS, learn structs as msongyboi said.

@ msongyboi: periodic triggers automatically run. There is no need to call anything.
 
Level 5
Joined
Feb 22, 2013
Messages
161
So I started the same spell using vJass and came up with this:

JASS:
scope Renew initializer InitTrig_Renew

//=====================================================
//SETUP START:
//=====================================================

    struct Renew_Info
        unit array TempTarget[1]
        integer HoT
        
    endstruct

    globals
        constant integer ABILITY_ID     = 'RE00'
        constant real DURATION          = 10.00
        constant string HEAL_EFFECT     = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl"
        Renew_Info array Ar
        integer Renew_Current           = 0
        integer Renew_Max               = 0
        timer Tim                       = CreateTimer()
        
    endglobals

    private function HealAmount takes integer lvl, integer intel returns real
        return I2R((lvl * 5) + intel)
    endfunction
    
//=====================================================
//SETUP END:
//=====================================================        
    
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
    endfunction
    
    private function RenewLoop takes nothing returns nothing
        local Renew_Info ri
        local integer array i
        
        set i[Renew_Current] = 0
        set Renew_Current = 1
        loop
            exitwhen Renew_Current > Renew_Max
            set ri = Ar[i[Renew_Current]]
            set i[Renew_Current] = i[Renew_Current] + 1
            call DisplayTextToPlayer(GetOwningPlayer(ri.TempTarget[Renew_Current]), 0, 0, "Looping...")
            call SetUnitState(ri.TempTarget[Renew_Current], UNIT_STATE_LIFE, GetUnitState(ri.TempTarget[Renew_Current], UNIT_STATE_LIFE) + I2R(ri.HoT))
            if ( i[Renew_Current] == R2I(DURATION)  ) then
                call PauseTimer(Tim)
                set Ar[i[Renew_Current]] = Ar[Renew_Current - 1]
                set i[Renew_Current] = 0
                set ri.TempTarget[Renew_Current] = null
                set Renew_Current = Renew_Current - 1
                call ri.destroy()
            endif
            set Renew_Current = Renew_Current + 1
        endloop
    endfunction
    
    private function Renew takes nothing returns nothing
        local Renew_Info ri = Renew_Info.create()
        local unit caster = GetTriggerUnit()
        
        set Renew_Max = Renew_Max + 1
        set Ar[Renew_Max - 1] = ri
        set ri.TempTarget[Renew_Max] = GetSpellTargetUnit()
        set ri.HoT = R2I(HealAmount(GetUnitAbilityLevel(caster, ABILITY_ID), GetHeroInt(caster, true)))
        call DisplayTextToPlayer(GetOwningPlayer(caster), 0, 0, "Renew Casted.")
        
        if Renew_Max >= 1 then
            call TimerStart(Tim, 1.00, true, function RenewLoop)
            call DisplayTextToPlayer(GetOwningPlayer(ri.TempTarget[Renew_Max]), 0, 0, "Renew Timer Started.")
        endif
    endfunction    
    
    private function InitTrig_Renew takes nothing returns nothing
        local trigger RE = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( RE, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( RE, Condition( function Conditions ) )
        call TriggerAddAction(RE, function Renew)
        set RE = null
    endfunction
endscope

Now I get the healing to work... But now I've run into a new problem, the healing loops forever and doesn't listen to the ITE condition in the loop.
 
Level 5
Joined
Feb 22, 2013
Messages
161
Show me the code.

JASS:
scope Renew initializer InitTrig_Renew

//=====================================================
//SETUP START:
//=====================================================

    struct Renew_Info
        unit array TempTarget[1]
        integer HoT
        
    endstruct

    globals
        constant integer ABILITY_ID     = 'RE00'
        constant real DURATION          = 10.00
        constant string HEAL_EFFECT     = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl"
        Renew_Info array Ar
        integer Renew_Current           = 0
        integer Renew_Max               = 0
        timer Tim                       = CreateTimer()
        
    endglobals

    private function HealAmount takes integer lvl, integer intel returns real
        return I2R((lvl * 5) + intel)
    endfunction
    
//=====================================================
//SETUP END:
//=====================================================        
    
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
    endfunction
    
    private function RenewLoop takes nothing returns nothing
        local Renew_Info ri
        local integer array i
        
        set i[Renew_Current] = 0
        set Renew_Current = 1
        loop
            exitwhen Renew_Current > Renew_Max
            set ri = Ar[i[Renew_Current]]
            set i[Renew_Current] = i[Renew_Current] + 1
            call DisplayTextToPlayer(GetOwningPlayer(ri.TempTarget[Renew_Current]), 0, 0, "Looping...")
            call SetUnitState(ri.TempTarget[Renew_Current], UNIT_STATE_LIFE, GetUnitState(ri.TempTarget[Renew_Current], UNIT_STATE_LIFE) + I2R(ri.HoT))
            if ( I2R(i[Renew_Current]) >= (DURATION)  ) then
                call PauseTimer(Tim)
                set Ar[i[Renew_Current]] = Ar[Renew_Current - 1]
                set i[Renew_Current] = 0
                set ri.TempTarget[Renew_Current] = null
                set Renew_Current = Renew_Current - 1
                call ri.destroy()
            endif
            set Renew_Current = Renew_Current + 1
        endloop
    endfunction
    
    private function Renew takes nothing returns nothing
        local Renew_Info ri = Renew_Info.create()
        local unit caster = GetTriggerUnit()
        
        set Renew_Max = Renew_Max + 1
        set Ar[Renew_Max - 1] = ri
        set ri.TempTarget[Renew_Max] = GetSpellTargetUnit()
        set ri.HoT = R2I(HealAmount(GetUnitAbilityLevel(caster, ABILITY_ID), GetHeroInt(caster, true)))
        call DisplayTextToPlayer(GetOwningPlayer(caster), 0, 0, "Renew Casted.")
        
        if Renew_Max >= 1 then
            call TimerStart(Tim, 1.00, true, function RenewLoop)
            call DisplayTextToPlayer(GetOwningPlayer(ri.TempTarget[Renew_Max]), 0, 0, "Renew Timer Started.")
        endif
    endfunction    
    
    private function InitTrig_Renew takes nothing returns nothing
        local trigger RE = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( RE, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( RE, Condition( function Conditions ) )
        call TriggerAddAction(RE, function Renew)
        set RE = null
    endfunction
endscope
 
Level 5
Joined
Feb 22, 2013
Messages
161
ah I see the periodic event now;;

OT: rupture can you tell me what exactly your spell should be doing?

It's just a basic HoT spell, nothin special...

I've run into another problem unfortunately, and I apologize for confronting the Hive to fix this so many times over, I'm not particularly good at spotting errors on my own yet...

JASS:
scope Renew initializer InitTrig_Renew

//=====================================================
//SETUP START:
//=====================================================

    struct Renew_Info
        unit array TempTarget[1]
        integer HoT
        
    endstruct

    globals
        constant integer ABILITY_ID     = 'RE00'
        constant real DURATION          = 10.00
        constant string HEAL_EFFECT     = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl"
        Renew_Info array Ar
        integer Renew_Current           = 0
        integer Renew_Max               = 0
        timer Tim                       = CreateTimer()
        integer array Renew_Count
        
    endglobals

    private function HealAmount takes integer lvl, integer intel returns real
        return I2R((lvl * 5) + intel)
    endfunction
    
//=====================================================
//SETUP END:
//=====================================================        
    
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
    endfunction
    
    private function RenewLoop takes nothing returns nothing
        local Renew_Info ri
        
        set Renew_Current = 1
        loop
            exitwhen Renew_Current > Renew_Max
            set ri = Ar[Renew_Count[Renew_Current]]
            set Renew_Count[Renew_Current] = Renew_Count[Renew_Current] + 1
            call DisplayTextToPlayer(GetOwningPlayer(ri.TempTarget[Renew_Current]), 0, 0, "Looping...")
            call SetUnitState(ri.TempTarget[Renew_Current], UNIT_STATE_LIFE, GetUnitState(ri.TempTarget[Renew_Current], UNIT_STATE_LIFE) + I2R(ri.HoT))
            if ( I2R(Renew_Count[Renew_Current]) >= (DURATION)  ) then
                call PauseTimer(Tim)
                set Ar[Renew_Count[Renew_Current]] = Ar[Renew_Current - 1]
                set Renew_Count[Renew_Current] = 0
                set ri.TempTarget[Renew_Current] = null
                set Renew_Current = Renew_Current - 1
                call ri.destroy()
            endif
            set Renew_Current = Renew_Current + 1
        endloop
    endfunction
    
    private function Renew takes nothing returns nothing
        local Renew_Info ri = Renew_Info.create()
        local unit caster = GetTriggerUnit()
        
        set Renew_Max = Renew_Max + 1
        set Ar[Renew_Max - 1] = ri
        set ri.TempTarget[Renew_Max] = GetSpellTargetUnit()
        set ri.HoT = R2I(HealAmount(GetUnitAbilityLevel(caster, ABILITY_ID), GetHeroInt(caster, true)))
        call DisplayTextToPlayer(GetOwningPlayer(caster), 0, 0, "Renew Casted.")
        
        if Renew_Max >= 1 then
            call TimerStart(Tim, 1.00, true, function RenewLoop)
            call DisplayTextToPlayer(GetOwningPlayer(ri.TempTarget[Renew_Max]), 0, 0, "Renew Timer Started.")
        endif
    endfunction    
    
    private function InitTrig_Renew takes nothing returns nothing
        local trigger RE = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( RE, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( RE, Condition( function Conditions ) )
        call TriggerAddAction(RE, function Renew)
        set RE = null
        
    endfunction
endscope

The looping ends after 10 seconds as it should, however, it only heals the target once and not a full 10 times per second.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
It is alright. We have all been here once. What matters is whether you have the will to get to the next level.

This has moved to vJASS, which is outside of my scope. Yet it seems that your vJASS coding style is weird (using indexing). IIRC, there are allocate and deallocate method for structs. Guess someone proficient in vJASS will help you then :)
 
Level 8
Joined
Feb 3, 2013
Messages
277
aite rupture ill step in and help you out a bit

a little information about structs.... they are like mini hashtables - each time you use a struct - it saves a new bundle of information - and this is KEY in making shit MUI and multiple timed events work properly

JASS:
scope Heal initializer init 

    globals
        hashtable HASH = InitHashtable()
//There are many ways to attach structs to timers, but at the core of each system is a procedure involving saving a struct as an integer in a hashtable...
    endglobals
    
    private struct data //We're gonna save into each instance of spell - a unit and we can work the rest out from there.
        unit target
        real amount //Healing amount - this can be set here, or in the create method, vv same with the count variable
        integer count = 0 //this is like the counter, after it reaches a number, we can make it quit.
        effect array fx[11] //This is to save the effect handles and destroy them later.
        
        static method create takes unit u returns thistype
            local thistype this = thistype.allocate()
            set this.target = u //Create takes unit u (which you should alwyas put as the target) - then match the info in the struct
            set this.amount = 5 //Like i said here, or up there
            return this 
        endmethod
//Just like a function, but inside structs they're called method. The create method for a struct can be changed a little bit, so we can customize each struct to start off differently. they keyword 'this' refers to the struct the method is working for and thistype is a TYPE of 'this' struct.
//The create has to ALWAYS  be static, can take as much as parameters as it wants, and ALWAYS return thistype
//Everytime a struct is order'd to be created, it will do what its in the create method.
        method destroy takes nothing returns nothing
            local integer i = 0
            set this.target = null
            loop
                exitwhen i == 11
                call DestroyEffect(this.fx[i]) //This is to clear all the fx handles - the models I'm using don't have a death animation so I have to remove them after they've been played
                set this.fx[i] = null
                set i = i + 1
            endloop
            call this.deallocate()
        endmethod
//The destroy has to ALWAYS be non static and must take and return nothing   
//So basically everytime a struct is order'd to be destroyed, it will do what is in the destroy method
    endstruct

//Each time a struct is created, it's unique so we don't have to worry about stuff not being MUI
    
    private function H_Heal takes nothing returns nothing
        local timer lv_timer = GetExpiredTimer()
        local data d = LoadInteger(HASH, GetHandleId(lv_timer), 0) //Structs are integers :)
        
        if d.count == 10 then //Our heal buff will last 5 seconds, the timer expires every .5. 10/2 5 seconds rite :P
            call d.destroy()
            call FlushChildHashtable(HASH, GetHandleId(lv_timer))//Clean up info in hash
            call PauseTimer(lv_timer) //Pause the timer when the count is up
            call DestroyTimer(lv_timer) //Destroy :)
            call BJDebugMsg("done!") //A msg that will display when its done
            set lv_timer = null
            return
        endif
        
        call SetWidgetLife(d.target, GetUnitState(d.target, UNIT_STATE_LIFE) + d.amount) //heal
        set d.fx[d.count] = AddSpecialEffectTarget("Abilities\\Spells\\Human\\Heal\\HealTarget.mdl", d.target, "origin") //Save the fx handle
        set d.count = d.count + 1 //Increase the count
        set lv_timer = null //remove memory leak
    endfunction
    
    private function H_Cond takes nothing returns boolean
        local unit lv_target = GetSpellTargetUnit()
        local timer lv_timer = CreateTimer()
        
        if GetSpellAbilityId() == 'A000' then
            call SaveInteger(HASH, GetHandleId(lv_timer), 0, data.create(lv_target)) 
//we create a struct and attach it to the handle timer as an integer everytime the spell is cast
            call TimerStart(lv_timer, .5, true, function H_Heal)
//the timer runs every .5 seconds
        endif
        
        set lv_target = null
        set lv_timer = null
        return false
    endfunction
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, function H_Cond)
        
        set t = null
    endfunction
    
endscope

Note that I didn't have to use structs for something this simple, I could have just attached the count and the unit to a timer and made the amount some kind of constant variable or function. But later on, you will use structs for everything along with one of the many excellent struct timer attachment systems - because they are SO simple. And you will never have to make your own hashtables.

Ofc.. there are many ways to attach structs to timers
my favorite is Cohdar's TimerTicker
but there's also Vex's TimerUtils
and a lil' some somethn about structs
 

Attachments

  • Heal.w3x
    19.6 KB · Views: 44
Last edited:
Level 5
Joined
Feb 22, 2013
Messages
161
aite rupture ill step in and help you out a bit

a little information about structs.... they are like mini hashtables - each time you use a struct - it saves a new bundle of information - and this is KEY in making shit MUI and multiple timed events work properly

JASS:
scope Heal initializer init 

    globals
        hashtable HASH = InitHashtable()
//There are many ways to attach structs to timers, but at the core of each system is a procedure involving saving a struct as an integer in a hashtable...
    endglobals
    
    private struct data //We're gonna save into each instance of spell - a unit and we can work the rest out from there.
        unit target
        real amount //Healing amount - this can be set here, or in the create method, vv same with the count variable
        integer count = 0 //this is like the counter, after it reaches a number, we can make it quit.
        effect array fx[11] //This is to save the effect handles and destroy them later.
        
        static method create takes unit u returns thistype
            local thistype this = thistype.allocate()
            set this.target = u //Create takes unit u (which you should alwyas put as the target) - then match the info in the struct
            set this.amount = 5 //Like i said here, or up there
            return this 
        endmethod
//Just like a function, but inside structs they're called method. The create method for a struct can be changed a little bit, so we can customize each struct to start off differently. they keyword 'this' refers to the struct the method is working for and thistype is a TYPE of 'this' struct.
//The create has to ALWAYS  be static, can take as much as parameters as it wants, and ALWAYS return thistype
//Everytime a struct is order'd to be created, it will do what its in the create method.
        method destroy takes nothing returns nothing
            local integer i = 0
            set this.target = null
            loop
                exitwhen i == 11
                call DestroyEffect(this.fx[i]) //This is to clear all the fx handles - the models I'm using don't have a death animation so I have to remove them after they've been played
                set this.fx[i] = null
                set i = i + 1
            endloop
            call this.deallocate()
        endmethod
//The destroy has to ALWAYS be non static and must take and return nothing   
//So basically everytime a struct is order'd to be destroyed, it will do what is in the destroy method
    endstruct

//Each time a struct is created, it's unique so we don't have to worry about stuff not being MUI
    
    private function H_Heal takes nothing returns nothing
        local timer lv_timer = GetExpiredTimer()
        local data d = LoadInteger(HASH, GetHandleId(lv_timer), 0) //Structs are integers :)
        
        if d.count == 10 then //Our heal buff will last 5 seconds, the timer expires every .5. 10/2 5 seconds rite :P
            call d.destroy()
            call FlushChildHashtable(HASH, GetHandleId(lv_timer))//Clean up info in hash
            call PauseTimer(lv_timer) //Pause the timer when the count is up
            call DestroyTimer(lv_timer) //Destroy :)
            call BJDebugMsg("done!") //A msg that will display when its done
            set lv_timer = null
            return
        endif
        
        call SetWidgetLife(d.target, GetUnitState(d.target, UNIT_STATE_LIFE) + d.amount) //heal
        set d.fx[d.count] = AddSpecialEffectTarget("Abilities\\Spells\\Human\\Heal\\HealTarget.mdl", d.target, "origin") //Save the fx handle
        set d.count = d.count + 1 //Increase the count
        set lv_timer = null //remove memory leak
    endfunction
    
    private function H_Cond takes nothing returns boolean
        local unit lv_target = GetSpellTargetUnit()
        local timer lv_timer = CreateTimer()
        
        if GetSpellAbilityId() == 'A000' then
            call SaveInteger(HASH, GetHandleId(lv_timer), 0, data.create(lv_target)) 
//we create a struct and attach it to the handle timer as an integer everytime the spell is cast
            call TimerStart(lv_timer, .5, true, function H_Heal)
//the timer runs every .5 seconds
        endif
        
        set lv_target = null
        set lv_timer = null
        return false
    endfunction
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, function H_Cond)
        
        set t = null
    endfunction
    
endscope

Note that I didn't have to use structs for something this simple, I could have just attached the count and the unit to a timer and made the amount some kind of constant variable or function. But later on, you will use structs for everything along with one of the many excellent struct timer attachment systems - because they are SO simple. And you will never have to make your own hashtables.

Ofc.. there are many ways to attach structs to timers
my favorite is Cohdar's TimerTicker
but there's also Vex's TimerUtils
and a lil' some somethn about structs

So I was able to make a similar trigger to yours, here's what I have

JASS:
scope renew initializer InitTrig_Renew

//================================================
//SETUP START:
//================================================

	globals
		hashtable RE00_Hash = InitHashtable()
	endglobals
	
	private struct RE_Data
		unit target
		real HoT
		integer count = 0
		effect array sfx[11]
		
		static method create takes unit u, real r returns thistype
			local thistype this = thistype.allocate()
			set this.target = u
			set this.HoT = r
			return this
		endmethod
		
		method destroy takes nothing returns nothing
			local integer i = 0
			set this.target = null
			loop
				exitwhen i == 11
				call DestroyEffect(this.sfx[i])
				set this.sfx[i] = null
				set i = i + 1
			endloop
			call this.deallocate()
		endmethod
	endstruct
	
//================================================
//SETUP END:
//================================================
	
	private function Renew_Ex takes nothing returns nothing
		local timer Tim = GetExpiredTimer()
		local RE_Data d = LoadInteger(RE00_Hash, GetHandleId(Tim), 0)
		
		if d.count == 12 then
			call d.destroy()
			call FlushChildHashtable(RE00_Hash, GetHandleId(Tim))
			call PauseTimer(Tim)
			call DestroyTimer(Tim)
			set Tim = null
			return
		endif
		
        call DisplayTextToPlayer(GetOwningPlayer(d.target), 0, 0, "Loop")
		call SetWidgetLife(d.target, GetUnitState(d.target, UNIT_STATE_LIFE) + d.HoT)
		set d.sfx[d.count] = AddSpecialEffectTarget("Abilities\\Spells\\Human\\Heal\\HealTarget.mdl", d.target, "origin")
		set d.count = d.count + 3
		set Tim = null
	endfunction
	
	private function Renew_Start takes nothing returns boolean
		local unit Tar = GetSpellTargetUnit()
		local unit caster = GetSpellAbilityUnit()
		local timer Tim = CreateTimer()
		local real HoT = 10 * GetUnitAbilityLevel(caster, 'RE00') + 2 * GetHeroInt(caster, true)
		
		if GetSpellAbilityId() == 'RE00' then
			call SaveInteger(RE00_Hash, GetHandleId(Tim), 0, RE_Data.create(Tar, HoT))
			call TimerStart(Tim, 3.00, true, function Renew_Ex)
			call DisplayTextToPlayer(GetOwningPlayer(Tar), 0, 0, "Target Registered")
			call DisplayTextToPlayer(GetOwningPlayer(caster), 0, 0, "Caster Registered")
			call DisplayTextToPlayer(Player(0), 0, 0, "Spell Registered")
		endif
		
		set Tar = null
		set Tim = null
		return false
	endfunction
	
	private function InitTrig_Renew takes nothing returns nothing
		local trigger t = CreateTrigger()
		
		call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
		call TriggerAddCondition(t, function Renew_Start)
		
		set t = null
	endfunction
	
endscope

It works like a charm, MUI and everything, but the thing I am having a difficult time understanding is exactly how this trigger is Multi-Instanceable even knowing it somehow is :xxd: , care to explain?
 
Sure thing. First you have to understand structs a bit. They are kind of like boxes, boxes that hold a bunch of variables. When you "create" a struct, such as through:
RE_Data.create(Tar, HoT)
You are basically getting a unique box to hold the data for that one spell cast.

Going back to the box scenario, let's say you want to hold one fruit and one vegetable. When you create a "box" struct, you can assign the variables to that specific box. So let's say we have a struct that holds one fruit and one vegetable:
JASS:
struct Box
    fruit myfruit
    vegetable myvegetable
endstruct
Let's say you "create" one box and make the fruit -> apple, and the vegetable -> carrot. If you make another box, will it also contain an apple and carrot? Not at first. Only the first box holds an apple and a carrot. The second box might carry a banana and some celery. A third box might carry buddha's hand and a romanesco (those are legit fruit and veggies).

So going back to the spell, you are creating a unique instance of the struct RE_Data, and it will hold the data for that spell cast (the target, amount, effects, and count). :)

That is all nice, but what about the hashtable attachment? Well, there are still problems with timers. How do you know which RE_Data to use? In the box scenario, it is kind of similar. Imagine a blind man wanted to store those boxes (which happen to be sealed tight, so he can't touch them). How would he know which box goes where? Imagine if he put the box with the apple and the carrot on the exotic fruits/veggies shelf! Nightmare!

That is where the hashtable comes in. You are saving the instance (which is represented by an integer, hence SaveInteger). But what do you save it to? Well, when you call TimerStart to a function, you can refer to the timer that expired through GetExpiredTimer()! This means you can simply "attach" the RE_Data instance to the timer, by using the "Key of <timer>" as the index in the hashtable, which is GetHandleId(). This will return a unique integer representation of the timer (basically the timer has its own number, kind of like how we all have our own phone numbers). You just save that RE_Data instance under the ID of the timer, then you load it under the ID of the timer. That is a classic example of achieving MUI. :)
 
Level 5
Joined
Feb 22, 2013
Messages
161
Sorry for semi-necroposting but I'm noticing something in the trigger... shouldn't the size of the array for the fx in the array be 12 because there would be 4 instances of the struct being created after 12 seconds therefore leaving one effect not being destroyed?
 
When you declare an array in a struct, such as:
JASS:
struct X 
    real array var[11]
endstruct
The size of the real is 11, but it only can take the slots 0 to 10. It cannot take 11, 12, and so on.

So in your case, if you want 12 effects then you would have the size as 12, and fill up the slots 0 to 11. Then in the loop, you would exitwhen i == 12.
 
Level 5
Joined
Feb 22, 2013
Messages
161
JASS:
scope renew initializer InitTrig_Renew

//================================================
//SETUP START:
//================================================

	globals
		hashtable RE_Hash = InitHashtable()
	endglobals
	
	struct RE_Data
		unit RE_abilityTarget
		real RE_healAmount
		real RE_Duration
		real Amount
		string RE_sfxString
		string RE_sfxPlacement
		effect array RE_sfx[12]
		integer count
			static method create takes unit U, real R, real D, string whatEffect, string whereAt returns thistype
				local thistype this = thistype.allocate()
				
				set this.RE_abilityTarget = U
				set this.RE_healAmount = R
				set this.RE_Duration = D
				set this.Amount = R/D
				set this.RE_sfxString = whatEffect
				set this.RE_sfxPlacement = whereAt
				
				return this
			endmethod
			
			method destroy takes nothing returns nothing
				local integer i = 0
				
				set this.RE_abilityTarget = null
				loop
					exitwhen i == udg_RE_Duration
					call DestroyEffect(this.RE_sfx[i])
					set this.RE_sfx[i] = null
					set i = i + 1
				endloop
				call this.deallocate()
			endmethod
	endstruct
				
	
//================================================
//SETUP END:
//================================================
	
	private function Renew_Loop takes nothing returns nothing
		local timer tim = GetExpiredTimer()
		local RE_Data d = LoadInteger(RE_Hash, GetHandleId(tim), 0)
		
		if d.count == udg_RE_Duration then
			call d.destroy()
			call FlushChildHashtable(RE_Hash, GetHandleId(tim))
			call PauseTimer(tim)
			call DestroyTimer(tim)
			call BJDebugMsg("Done")
			set tim = null
			return
		endif
		
        call BJDebugMsg("looping...")
        
		call SetWidgetLife(d.RE_abilityTarget, GetUnitState(d.RE_abilityTarget, UNIT_STATE_LIFE) + d.Amount)
		set d.RE_sfx[d.count] = AddSpecialEffectTarget(d.RE_sfxString, d.RE_abilityTarget, d.RE_sfxPlacement)
		set d.count = d.count + 1
		set tim = null
	endfunction
	
	private function RE_CondStart takes nothing returns boolean
		local unit t = GetSpellTargetUnit()
		local timer tim = CreateTimer()
		
        call BJDebugMsg("Trigger registered")
		if GetSpellAbilityId() == udg_RE_Ability then
            call BJDebugMsg("Spell registered")
			call SaveInteger(RE_Hash, GetHandleId(tim), 0, RE_Data.create(t, udg_RE_healAmount, udg_RE_Duration, udg_RE_sfxString, udg_RE_sfxPlacement))
			call TimerStart(tim, 1.00, true, function Renew_Loop)
        else
            call BJDebugMsg("Spell not registered")
		endif
        
		set t = null
		set tim = null
		return false
	endfunction
	
	private function InitTrig_Renew takes nothing returns nothing
		local trigger t = CreateTrigger()
        
		call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
		call TriggerAddCondition(t, function RE_CondStart)
		
		set t = null
	endfunction
	
endscope


So I re-did the Renew trigger to make it configurable for a GUI user, and it works, but only once, after the spell casts once and it finishes when I cast again it doesn't loop but the spell registers fine.... Only while the spell is currently in a loop will it be able to cast again and work fine.

EDIT: Never mind I figured it out... xD!
 
Last edited:
Level 5
Joined
Feb 22, 2013
Messages
161
A question about the spell... Since I can't change the variable array size in the struct to another variable how am I supposed to make the RE_Duration configurable variable work without having to go and change the array size of the RE_sfx as well?
 
Status
Not open for further replies.
Top