• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[General] Making DoT MUI Spell

Status
Not open for further replies.
Level 4
Joined
May 21, 2015
Messages
70
qi have a question in making a MUI Damage over time ability....
1. do I have to store the caster, target unit, to an array variable?
2. what about the damage? do I need to store it too?
3. also, do I need indexing?
 
1. do I have to store the caster, target unit, to an array variable?
2. what about the damage? do I need to store it too?
Yes
3. also, do I need indexing?
Depends, if you use unit indexer or Hashtable or Table, you'll less likely to index data, but rather save and reload data. Else it's yes.
 
Indexing is easy if you understand it. But it is definitely hard to grasp when you're starting out. I tried my best to explain one particular indexing method in this tutorial: http://www.hiveworkshop.com/forums/...orials-279/visualize-dynamic-indexing-241896/

It is geared to help beginners form a picture inside their heads of what is going on. If you feel confused about it, please let me know. I'd be happy to clarify points, and it'll help me improve the tutorial as a whole. :) Fortunately, the example spell covered in that tutorial is a DoT as well. See if it helps!
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Not really...
If you do not care about the inaccuracy of timed waits, then you can do this without problems.

First of all, the GUI action "Wait <real> seconds" is really bad because it uses real life time (still inaccurate) so pauses or game speed dont care about it.
The GUI action "Wait <real> seconds of gametime" leaks a timer handle.
So what you have to do is place the following script in the header of your map.
(The header file is at the top of the trigger list, named the same as your map and has a blue WE icon.)
JASS:
    function WaitGameTime takes real duration returns nothing
        local timer t
        local real  timeRemaining
        
        if duration > 0 then
            set t = CreateTimer()
            call TimerStart(t, duration, false, null)
            loop
                set timeRemaining = TimerGetRemaining(t)
                exitwhen timeRemaining <= 0
                
                if timeRemaining > bj_POLLED_WAIT_SKIP_THRESHOLD then
                    call TriggerSleepAction(0.1 * timeRemaining)
                else
                    call TriggerSleepAction(bj_POLLED_WAIT_INTERVAL)
                endif
            endloop
            call DestroyTimer(t)
            set t = null
        endif
    endfunction

Once you have done that, you are ready to go.

This trigger will deal damage to the target on each interval.
  • Damage Over Time
    • Events
    • Conditions
    • Actions
      • Custom script: local unit udg_DOT_Source
      • Custom script: local unit udg_DOT_Target
      • Custom script: local real udg_DOT_Damage
      • Custom script: local attacktype udg_DOT_AttackType
      • Custom script: local damagetype udg_DOT_DamageType
      • Custom script: local real udg_DOT_Interval
      • Custom script: local integer udg_DOT_Number
      • -------- - --------
      • -------- - --------
      • -------- - --------
      • Set DOT_Source = TempUnit[0]
      • Set DOT_Target = TempUnit[1]
      • Set DOT_Damage = TempReal[0]
      • Set DOT_AttackType = TempAttackType[0]
      • Set DOT_DamageType = TempDamageType[0]
      • Set DOT_Interval = TempReal[1]
      • Set DOT_Number = TempInteger[0]
      • -------- - --------
      • For each (Integer I) from 1 to DOT_Number, do (Actions)
        • Loop - Actions
          • Custom script: call WaitGameTime(udg_DOT_Interval)
          • Unit - Cause DOT_Source to damage DOT_Target, dealing DOT_Damage damage of attack type DOT_AttackType and damage type DOT_DamageType
      • -------- - --------
      • -------- - --------
      • -------- - --------
      • Set DOT_Source = No unit
      • Set DOT_Target = No unit
      • Custom script: set udg_DOT_AttackType = null
      • Custom script: set udg_DOT_DamageType = null
This trigger will create local variables of the global versions (which can then be used by normal GUI actions) so every separate thread running this trigger will have its own variables.
(Because of this, handles have to be nulles to prevent leaks.)

The TempVariables are just one way to do it but I think it is the best way to store the variables that you temporarily need.

This script will deal 100 damage every 0.5 seconds for 6 times (3 seconds).
  • Actions
    • Set TempUnit[0] = (Triggering unit)
    • Set TempUnit[1] = (Target unit of ability being cast)
    • Set TempReal[0] = 100.00
    • Set TempAttackType[0] = Normal
    • Set TempDamageType[0] = Normal
    • Set TempReal[1] = 0.50
    • Set TempInteger[0] = 6
    • Trigger - Run Damage Over Time <gen> (ignoring conditions)
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
But as far as I go, that will require JASS.
Or you can do some library to make it work, but still... It doesnt sound that he is familiar with JASS.

But if you really want to do that then...
This can be helpfull:
JASS:
library damageOverTime uses timerIndex
    globals
        unit array          dot_Source
        unit array          dot_Target
        real array          dot_Damage
        attacktype array    dot_AttackType
        damagetype array    dot_DamageType
        integer array       dot_Number
    endglobals
    
    function DOT_Callback takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer id = GetTimerData(t)
        
        if dot_Number[id] > 0 then
            set dot_Number[id] = dot_Number[id] -1
            call UnitDamageTarget(dot_Source[id], dot_Target[id], dot_Damage[id], true, false, dot_AttackType[id], dot_DamageType[id], WEAPON_TYPE_WHOKNOWS)
        else
            set dot_Source[id] = null
            set dot_Target[id] = null
            set dot_Damage[id] = 0.
            set dot_AttackType[id] = null
            set dot_DamageType[id] = null
            set dot_Number[id] = 0
            call ReleaseIndexedTimer(t)
        endif
        
        set t = null
    endfunction
    function ApplyDamageOverTime takes unit source, unit target, real damage, attacktype at, damagetype dt, real interval, integer number returns nothing
        local timer t = NewIndexedTimer()
        local integer id = GetTimerData(t)
        set dot_Source[id] = source
        set dot_Target[id] = target
        set dot_Damage[id] = damage
        set dot_AttackType[id] = at
        set dot_DamageType[id] = dt
        set dot_Number[id] = number
        
        call TimerStart(t, interval, true, function DOT_Callback)
        set t = null
    endfunction
endlibrary

Using the GUI custom text action of
  • Custom script: call ApplyDamageOverTime(GetTriggerUnit(), GetSpellTargetUnit(), 100, ATTACK_TYPE_MELEE, DAMAGE_TYPE_NORMAL, 0.5, 6)
will deal the damage.
I hope you are familiar with how you can use variables in custom scripts.

JASS:
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
JASS:
library timerIndex uses TimerUtils
//*********************************************************************
//* TimerIndex 1.0
//* ----------
//*
//*  This library creates a unique index for the TimerData of timerUtils.
//*  A timer that is created with an index must also be released as an indexed timer.
//*
    
    globals
        integer udg_NextTimerIndex = 0
        boolean array udg_TimerIndex_Occupied
    endglobals
    
    function ReleaseIndexedTimer takes timer t returns nothing
        set udg_TimerIndex_Occupied[GetTimerData(t)] = false
        call ReleaseTimer(t)
    endfunction
    function NewIndexedTimer takes nothing returns timer
        loop
            set udg_NextTimerIndex = udg_NextTimerIndex + 1
            if udg_NextTimerIndex > 8191 then
                set udg_NextTimerIndex = 1
            endif
            
            exitwhen not udg_TimerIndex_Occupied[udg_NextTimerIndex]
        endloop
        
        set udg_TimerIndex_Occupied[udg_NextTimerIndex] = true
        return NewTimerEx(udg_NextTimerIndex)
    endfunction
    
endlibrary
 
@Wietlol
You don't need Jass to use indexing and get high accuracy, else we lack approved GUI stuffs.
It's not hard to get it work. Though I prefer Unit Indexer for some reason to make complex spells (maybe due to habit I developed when making my first spell with loops).

I wonder why you don't just recommend EoT to him. That resources should do the job without needs of JNGP, just GUI in configuration part and more or less you're done with it.
 
Status
Not open for further replies.
Top