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

[Solved] Question about Continuing Spell

Status
Not open for further replies.
Level 8
Joined
May 12, 2018
Messages
106
Hello there, I'm studying basic tutorial Jass. I managed to learn and use the simple spell that has no projectile and occurs effects immediately, like Shaman's Purge(+Trigger).
But I have difficult studying struct and continuing spells.

I'm going to make a simple spell first. When a Shaman uses Purge, I want to make Dummy casts Purge on target every 0.3 seconds for 7 seconds.

This is my script being written.
JASS:
scope ImprovedPurge initializer InitTrig_ImprPrg
  private struct ImprPrgStruct
    timer prgtimer
    unit caster
    unit target
    real x
    real y
    integer i
    player p
  
    public static method create takes unit cast, unit targ returns ImprPrgStruct
      local ImprPrgStruct data = ImprPrgStruct.allocate()
      set data.caster = cast
      set data.target = targ
      return data
  
    endmethod
  endstruct

  private function Loop takes nothing returns nothing

  endfunction

  private function ImprPrgAct takes nothing returns nothing
//    call BJDebugMsg( "OK" )
    local timer prgtimer = NewTimer()
    call SetTimerData(prgtimer, data)

  endfunction

  private function ImprPrgCond takes nothing returns boolean
     return GetSpellAbilityId() == 'Aprg'
  endfunction

  private function InitTrig_ImprPrg takes nothing returns nothing
    local trigger trg = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( trg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( trg, Condition( function ImprPrgCond ) )
    call TriggerAddAction( trg, function ImprPrgAct)
    set trg = null
  endfunction
endscope

I searched Timers but the threads about that was very old, they explained scripts with NewTimer() and SetTimerData, but my reforged latest ver editor cannot find that func. So I can't proceed further with writing the script.

I need your help!
 
To use those functions, you need TimerUtils.
It kind of hard to find where to get it "officially" since wc3c is dead, so here is the version I have in my map:
JASS:
//TESH.scrollpos=21
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//*  To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//*  To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass)   More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//*  * Attaching
//*  * Recycling (with double-free protection)
//*
//* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x)   : Get a timer (alternative to CreateTimer), call
//*                            Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2)     : Attach value 2 to timer
//* GetTimerData(t)       : Get the timer's value.
//*                         You can assume a timer's value is 0
//*                         after NewTimer.
//*
//* Multi-flavor:
//*    Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************

//================================================================
    globals
        //How to tweak timer utils:
        // USE_HASH_TABLE = true  (new blue)
        //  * SAFEST
        //  * SLOWEST (though hash tables are kind of fast)
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true  (orange)
        //  * kinda safe (except there is a limit in the number of timers)
        //  * ALMOST FAST
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
        //  * THE FASTEST (though is only  faster than the previous method
        //                  after using the optimizer on the map)
        //  * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
        //                     work)
        //
        private constant boolean USE_HASH_TABLE      = true
        private constant boolean USE_FLEXIBLE_OFFSET = false

        private constant integer OFFSET     = 0x100000
        private          integer VOFFSET    = OFFSET
             
        //Timers to preload at map init:
        private constant integer QUANTITY   = 256
       
        //Changing this  to something big will allow you to keep recycling
        // timers even when there are already AN INCREDIBLE AMOUNT of timers in
        // the stack. But it will make things far slower so that's probably a bad idea...
        private constant integer ARRAY_SIZE = 8190

    endglobals

    //==================================================================================================
    globals
        private integer array data[ARRAY_SIZE]
        private hashtable     ht
    endglobals
   
   

    //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
    function SetTimerData takes timer t, integer value returns nothing
        static if(USE_HASH_TABLE) then
            // new blue
            call SaveInteger(ht,0,GetHandleId(t), value)
           
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-VOFFSET]=value
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-OFFSET]=value
        endif        
    endfunction

    function GetTimerData takes timer t returns integer
        static if(USE_HASH_TABLE) then
            // new blue
            return LoadInteger(ht,0,GetHandleId(t) )
           
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-VOFFSET]
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-OFFSET]
        endif        
    endfunction

    //==========================================================================================
    globals
        private timer array tT[ARRAY_SIZE]
        private integer tN = 0
        private constant integer HELD=0x28829022
        //use a totally random number here, the more improbable someone uses it, the better.
       
        private boolean       didinit = false
    endglobals
    private keyword init

    //==========================================================================================
    // I needed to decide between duplicating code ignoring the "Once and only once" rule
    // and using the ugly textmacros. I guess textmacros won.
    //
    //! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
    // On second thought, no.
    //! endtextmacro

    function NewTimerEx takes integer value returns timer
        if (tN==0) then
            if (not didinit) then
                //This extra if shouldn't represent a major performance drawback
                //because QUANTITY rule is not supposed to be broken every day.
                call init.evaluate()
                set tN = tN - 1
            else
                //If this happens then the QUANTITY rule has already been broken, try to fix the
                // issue, else fail.
                debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
                set tT[0]=CreateTimer()
                static if( not USE_HASH_TABLE) then
                    debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
                    static if( USE_FLEXIBLE_OFFSET) then
                        if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    else
                        if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    endif
                endif
            endif
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],value)
     return tT[tN]
    endfunction
   
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction


    //==========================================================================================
    function ReleaseTimer takes timer t returns nothing
        if(t==null) then
            debug call BJDebugMsg("Warning: attempt to release a null timer")
            return
        endif
        if (tN==ARRAY_SIZE) then
            debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

            //stack is full, the map already has much more troubles than the chance of bug
            call DestroyTimer(t)
        else
            call PauseTimer(t)
            if(GetTimerData(t)==HELD) then
                debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                return
            endif
            call SetTimerData(t,HELD)
            set tT[tN]=t
            set tN=tN+1
        endif    
    endfunction

    private function init takes nothing returns nothing
     local integer i=0
     local integer o=-1
     local boolean oops = false
        if ( didinit ) then
            return
        else
            set didinit = true
        endif
     
        static if( USE_HASH_TABLE ) then
            set ht = InitHashtable()
            loop
                exitwhen(i==QUANTITY)
                set tT[i]=CreateTimer()
                call SetTimerData(tT[i], HELD)
                set i=i+1
            endloop
            set tN = QUANTITY
        else
            loop
                set i=0
                loop
                    exitwhen (i==QUANTITY)
                    set tT[i] = CreateTimer()
                    if(i==0) then
                        set VOFFSET = GetHandleId(tT[i])
                        static if(USE_FLEXIBLE_OFFSET) then
                            set o=VOFFSET
                        else
                            set o=OFFSET
                        endif
                    endif
                    if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
                        exitwhen true
                    endif
                    if (GetHandleId(tT[i])-o>=0)  then
                        set i=i+1
                    endif
                endloop
                set tN = i
                exitwhen(tN == QUANTITY)
                set oops = true
                exitwhen not USE_FLEXIBLE_OFFSET
                debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")              
            endloop
           
            if(oops) then
                static if ( USE_FLEXIBLE_OFFSET) then
                    debug call BJDebugMsg("The problem has been fixed.")
                    //If this message doesn't appear then there is so much
                    //handle id fragmentation that it was impossible to preload
                    //so many timers and the thread crashed! Therefore this
                    //debug message is useful.
                elseif(DEBUG_MODE) then
                    call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
                    call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
                    call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
                    call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
                endif
            endif
        endif

    endfunction

endlibrary

Copy-paste this into a new trigger and you'll be able to use NewTimer(), NewTimerEx(<data>), ReleaseTimer(), etc.
It's really, really useful so I'd recommend doing it.
 
Level 8
Joined
May 12, 2018
Messages
106
To use those functions, you need TimerUtils.
It kind of hard to find where to get it "officially" since wc3c is dead, so here is the version I have in my map:
JASS:
//TESH.scrollpos=21
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//*  To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//*  To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass)   More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//*  * Attaching
//*  * Recycling (with double-free protection)
//*
//* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x)   : Get a timer (alternative to CreateTimer), call
//*                            Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2)     : Attach value 2 to timer
//* GetTimerData(t)       : Get the timer's value.
//*                         You can assume a timer's value is 0
//*                         after NewTimer.
//*
//* Multi-flavor:
//*    Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************

//================================================================
    globals
        //How to tweak timer utils:
        // USE_HASH_TABLE = true  (new blue)
        //  * SAFEST
        //  * SLOWEST (though hash tables are kind of fast)
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true  (orange)
        //  * kinda safe (except there is a limit in the number of timers)
        //  * ALMOST FAST
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
        //  * THE FASTEST (though is only  faster than the previous method
        //                  after using the optimizer on the map)
        //  * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
        //                     work)
        //
        private constant boolean USE_HASH_TABLE      = true
        private constant boolean USE_FLEXIBLE_OFFSET = false

        private constant integer OFFSET     = 0x100000
        private          integer VOFFSET    = OFFSET
            
        //Timers to preload at map init:
        private constant integer QUANTITY   = 256
      
        //Changing this  to something big will allow you to keep recycling
        // timers even when there are already AN INCREDIBLE AMOUNT of timers in
        // the stack. But it will make things far slower so that's probably a bad idea...
        private constant integer ARRAY_SIZE = 8190

    endglobals

    //==================================================================================================
    globals
        private integer array data[ARRAY_SIZE]
        private hashtable     ht
    endglobals
  
  

    //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
    function SetTimerData takes timer t, integer value returns nothing
        static if(USE_HASH_TABLE) then
            // new blue
            call SaveInteger(ht,0,GetHandleId(t), value)
          
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-VOFFSET]=value
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-OFFSET]=value
        endif       
    endfunction

    function GetTimerData takes timer t returns integer
        static if(USE_HASH_TABLE) then
            // new blue
            return LoadInteger(ht,0,GetHandleId(t) )
          
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-VOFFSET]
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-OFFSET]
        endif       
    endfunction

    //==========================================================================================
    globals
        private timer array tT[ARRAY_SIZE]
        private integer tN = 0
        private constant integer HELD=0x28829022
        //use a totally random number here, the more improbable someone uses it, the better.
      
        private boolean       didinit = false
    endglobals
    private keyword init

    //==========================================================================================
    // I needed to decide between duplicating code ignoring the "Once and only once" rule
    // and using the ugly textmacros. I guess textmacros won.
    //
    //! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
    // On second thought, no.
    //! endtextmacro

    function NewTimerEx takes integer value returns timer
        if (tN==0) then
            if (not didinit) then
                //This extra if shouldn't represent a major performance drawback
                //because QUANTITY rule is not supposed to be broken every day.
                call init.evaluate()
                set tN = tN - 1
            else
                //If this happens then the QUANTITY rule has already been broken, try to fix the
                // issue, else fail.
                debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
                set tT[0]=CreateTimer()
                static if( not USE_HASH_TABLE) then
                    debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
                    static if( USE_FLEXIBLE_OFFSET) then
                        if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    else
                        if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    endif
                endif
            endif
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],value)
     return tT[tN]
    endfunction
  
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction


    //==========================================================================================
    function ReleaseTimer takes timer t returns nothing
        if(t==null) then
            debug call BJDebugMsg("Warning: attempt to release a null timer")
            return
        endif
        if (tN==ARRAY_SIZE) then
            debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

            //stack is full, the map already has much more troubles than the chance of bug
            call DestroyTimer(t)
        else
            call PauseTimer(t)
            if(GetTimerData(t)==HELD) then
                debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                return
            endif
            call SetTimerData(t,HELD)
            set tT[tN]=t
            set tN=tN+1
        endif   
    endfunction

    private function init takes nothing returns nothing
     local integer i=0
     local integer o=-1
     local boolean oops = false
        if ( didinit ) then
            return
        else
            set didinit = true
        endif
    
        static if( USE_HASH_TABLE ) then
            set ht = InitHashtable()
            loop
                exitwhen(i==QUANTITY)
                set tT[i]=CreateTimer()
                call SetTimerData(tT[i], HELD)
                set i=i+1
            endloop
            set tN = QUANTITY
        else
            loop
                set i=0
                loop
                    exitwhen (i==QUANTITY)
                    set tT[i] = CreateTimer()
                    if(i==0) then
                        set VOFFSET = GetHandleId(tT[i])
                        static if(USE_FLEXIBLE_OFFSET) then
                            set o=VOFFSET
                        else
                            set o=OFFSET
                        endif
                    endif
                    if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
                        exitwhen true
                    endif
                    if (GetHandleId(tT[i])-o>=0)  then
                        set i=i+1
                    endif
                endloop
                set tN = i
                exitwhen(tN == QUANTITY)
                set oops = true
                exitwhen not USE_FLEXIBLE_OFFSET
                debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")             
            endloop
          
            if(oops) then
                static if ( USE_FLEXIBLE_OFFSET) then
                    debug call BJDebugMsg("The problem has been fixed.")
                    //If this message doesn't appear then there is so much
                    //handle id fragmentation that it was impossible to preload
                    //so many timers and the thread crashed! Therefore this
                    //debug message is useful.
                elseif(DEBUG_MODE) then
                    call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
                    call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
                    call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
                    call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
                endif
            endif
        endif

    endfunction

endlibrary

Copy-paste this into a new trigger and you'll be able to use NewTimer(), NewTimerEx(<data>), ReleaseTimer(), etc.
It's really, really useful so I'd recommend doing it.
Thank you so much! I'm going to test it.
 
Level 8
Joined
May 12, 2018
Messages
106
I've used TimerUitls and it works well, but I don't know how to handle it well.
If Purge is cast again for other units before the existing effects over, the existing Purge effects is gone. Is it better to use the Hashtable directly?
 
TimerUtils is a tool that is only good for "binding" a value to a timer.
The "value" is often an id to either a hashtable or a vjass struct.
In your example, you'd use the vjass struct "id" to bind it to the timer.

Can you show you current code?
Or is the initial post what you currently got?

You are fairly close to having it working imo, although a few crucial pieces are missing in the first post (that I didn't read too closely first time)

EDIT:
Below you can find the outline of doing something every 0.3 seconds for 7 seconds.
I use 2 timers for this. It's ofc possible to keep an integer "tickCounter" that counts up by 1 each time and if it has "ticked" more than "X" times, call destroy.
Edit2: Fixed copy-paste miss by me

JASS:
struct OverTimeUnitAction
    unit target
    unit source
    timer t
    timer tEnd
    method destroy takes nothing returns nothing
        call ReleaseTimer(t)
        call ReleaseTimer(tEnd)
        set t = null
        set tEnd = null
        set target = null
        set source = null
        call .deallocate()
    endmethod
    static method end takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        call .destroy()
    endmethod
    static method loopTick takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        //Do stuff. You have access to source, target and other variables found in this struct.
    endmethod
 
    static method setup takes unit source, unit target returns nothing
        local thistype this = thistype.allocate()
        set .t = NewTimerEx(this)
        set .tEnd = NewTimerEx(this)
        set .target = target
        set .source = source
        call TimerStart(t, 0.3, true, function thistype.loopTick)
        call TimerStart(t, 7.0, false, function thistype.end)
    endmethod
endstruct
 
Last edited:
Level 8
Joined
May 12, 2018
Messages
106
JASS:
scope RabidBite
 
globals
    private constant integer SpellID = 'Aprg' //Spell Rawcode.
    private constant integer BuffID = 'Bprg' //Buff Rawcode
    private constant real    dt      = 0.1 //timer period
endglobals
 
private constant function Damage takes integer level returns real
    return 10. + 7. * (level - 0)
endfunction
 
// the duration function is not needed anymore now that this spell is not stackable...
 
private struct Data
    static group IsBitten //This group is used to store all the unit affected by the spell...
    static integer index = 0 //This integer is used to keep a track of the size of the struct array.
 
    unit caster
    unit target
    boolean hasbuff = false
    // The counter is not needed anymore because we don't want to do stackable this spell
   
    private static method onInit takes nothing returns nothing
        set Data.IsBitten = CreateGroup() //Used to set the variable at map init
    endmethod
 
    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate()
        set D.caster = c
        set D.target = t
        call GroupAddUnit(Data.IsBitten, t) //Adds the unit to the affected units...
        if integer(D) > Data.index then //updates the array size
            set Data.index = integer(D)
        endif
        return D
    endmethod
   
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit(Data.IsBitten, .target) // Remove from the group the target unit, it's not affected anymore by the buff.
        set .hasbuff = false
        if integer(this) == Data.index then // Adjust the index size, so it doesn't search in inactive structs
            set Data.index = Data.index - 1
        endif
    endmethod
   
    static method SetCaster takes unit caster, unit target returns nothing
        // this method will search in all the active structs which of them has the  target unit, so it can update the caster properly...
        local integer i = 0
        local Data D
        loop
            exitwhen i > Data.index
            set D = Data(i)
            if D.target == target and D.hasbuff then
                set D.caster = caster
                return
            endif
            set i = i + 1
        endloop
    endmethod
endstruct
 
private function Loop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Data D = Data(GetTimerData(t))
    local real Dam = Damage(GetUnitAbilityLevel(D.caster, SpellID))
    if not D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
        set D.hasbuff = true
    endif
    if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
        call UnitDamageTarget(D.caster, D.target, Dam * dt, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
    endif
    if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) < 1 then
        call D.destroy()
        call ReleaseTimer(t)
    endif
    set t = null
endfunction
 
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction
 
private function Actions takes nothing returns nothing
    local Data D
    local timer t
    if not IsUnitInGroup(GetSpellTargetUnit(), Data.IsBitten) then
        //if the target unit doesn't have the effect, then it will start a new effect...
        set D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
        set t = NewTimer()
        call SetTimerData(t, integer(D))
        call TimerStart(t, dt, true, function Loop)
    else
        // Otherwise, it will update the current effect with the new caster...
        call Data.SetCaster(GetTriggerUnit(), GetSpellTargetUnit())
    endif
    set t = null
endfunction
 
//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction
 
endscope

i rewrote the existing scripts into new tutorial example. I must have made a mistake. There is no problem when I rewrite it. I performed the example by referring to the following tutorial.

and, May I ask if there is a way for me to use the script you wrote? I don't know how to run the 'method setup' by connecting it to the TriggerAddAction function.
 
JASS:
scope RabidBite
 
globals
    private constant integer SpellID = 'Aprg' //Spell Rawcode.
    private constant integer BuffID = 'Bprg' //Buff Rawcode
    private constant real    dt      = 0.1 //timer period
endglobals
 
private constant function Damage takes integer level returns real
    return 10. + 7. * (level - 0)
endfunction
 
// the duration function is not needed anymore now that this spell is not stackable...
 
private struct Data
    static group IsBitten //This group is used to store all the unit affected by the spell...
    static integer index = 0 //This integer is used to keep a track of the size of the struct array.
 
    unit caster
    unit target
    boolean hasbuff = false
    // The counter is not needed anymore because we don't want to do stackable this spell
  
    private static method onInit takes nothing returns nothing
        set Data.IsBitten = CreateGroup() //Used to set the variable at map init
    endmethod
 
    static method create takes unit c, unit t returns Data
        local Data D = Data.allocate()
        set D.caster = c
        set D.target = t
        call GroupAddUnit(Data.IsBitten, t) //Adds the unit to the affected units...
        if integer(D) > Data.index then //updates the array size
            set Data.index = integer(D)
        endif
        return D
    endmethod
  
    method onDestroy takes nothing returns nothing
        call GroupRemoveUnit(Data.IsBitten, .target) // Remove from the group the target unit, it's not affected anymore by the buff.
        set .hasbuff = false
        if integer(this) == Data.index then // Adjust the index size, so it doesn't search in inactive structs
            set Data.index = Data.index - 1
        endif
    endmethod
  
    static method SetCaster takes unit caster, unit target returns nothing
        // this method will search in all the active structs which of them has the  target unit, so it can update the caster properly...
        local integer i = 0
        local Data D
        loop
            exitwhen i > Data.index
            set D = Data(i)
            if D.target == target and D.hasbuff then
                set D.caster = caster
                return
            endif
            set i = i + 1
        endloop
    endmethod
endstruct
 
private function Loop takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Data D = Data(GetTimerData(t))
    local real Dam = Damage(GetUnitAbilityLevel(D.caster, SpellID))
    if not D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
        set D.hasbuff = true
    endif
    if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) > 0 then
        call UnitDamageTarget(D.caster, D.target, Dam * dt, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
    endif
    if D.hasbuff and GetUnitAbilityLevel(D.target, BuffID) < 1 then
        call D.destroy()
        call ReleaseTimer(t)
    endif
    set t = null
endfunction
 
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SpellID
endfunction
 
private function Actions takes nothing returns nothing
    local Data D
    local timer t
    if not IsUnitInGroup(GetSpellTargetUnit(), Data.IsBitten) then
        //if the target unit doesn't have the effect, then it will start a new effect...
        set D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
        set t = NewTimer()
        call SetTimerData(t, integer(D))
        call TimerStart(t, dt, true, function Loop)
    else
        // Otherwise, it will update the current effect with the new caster...
        call Data.SetCaster(GetTriggerUnit(), GetSpellTargetUnit())
    endif
    set t = null
endfunction
 
//===========================================================================
function InitTrig_Rabid_Bite takes nothing returns nothing
    set gg_trg_Rabid_Bite = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Rabid_Bite, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Rabid_Bite, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Rabid_Bite, function Actions )
endfunction
 
endscope

i rewrote the existing scripts into new tutorial example. I must have made a mistake. There is no problem when I rewrite it. I performed the example by referring to the following tutorial.

and, May I ask if there is a way for me to use the script you wrote? I don't know how to run the 'method setup' by connecting it to the TriggerAddAction function.

Similar to the code from the tutorial, you'd need a "layer between".
basically, a function that calls the setup like this:
JASS:
function CastOverTimeSpell takes nothing returns nothing
    call OverTimeUnitAction.setup(GetTriggerUnit(), GetSpellTargetUnit())
endfunction

Then the "TriggerAddAction" can use "function CastOverTimeSpell" (similar to your "Actions" method). Of course my names are just examples and could be anything.
 
Status
Not open for further replies.
Top