• 🏆 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!

[JASS] Timer (Utils) Issues.

Status
Not open for further replies.
Level 13
Joined
Jul 26, 2008
Messages
1,009
Hi.

I'm currently having an issue with my map and timer utils. (Or so I think it's Timer Utils, could just be Timers in general.)

It may be that I'm setting them up wrong, or it could be a script or system in my map. It may just be I don't have a necessary mechanic set up proper. I'm not sure at this point. I have the same systems in another map and have not had issues. The systems are: AutoIndex, Knockback by RisingDusk, GroupUtils, TerrainPathability, Table, BoundSentinel, LastOrder, XE 8.0, UnitProperties (Came after problem). Previously had CS16.0 but removed it.

There are a few of my spells that just bug up immediately and then it's all downhill from there. Sometimes it's random if the timer will work or not.

Anyone know why Timers or TimerUtils isn't working? I mean flat out not working, the Timer doesn't process past the create method. I figure there are people on this bored who know a lot more about TimerUtil bugs than I do.

I'll provide one of the spells that bugs often, and my Respawn system that bugs all the time.

I'll also attach my map for anyone who wishes to look through the systems to see if there's one that would conflict.

Help is always appreciated in these matters, thank you :) Seems the problem is beyond me, as nothing I do fixes it.

JASS:
scope Auspice initializer Init

globals
    private constant integer SPELLID = 'refl'
endglobals

private struct Data

    unit c
    integer lvl

    static method onLoop takes nothing returns nothing
     local timer tim = GetExpiredTimer()
     local thistype this = GetTimerData(tim)
        call UnitModifyDamage(.c, -(2+R2I(0.5*.lvl)))
        call ReleaseTimer(tim)
     set .c = null
     call this.destroy()
    endmethod

    static method create takes unit caster, integer level returns thistype
     local thistype this = thistype.allocate()
     local real tick = 2+1*level
     local real dmg = 1.5+0.5*level
     local timer tim = NewTimer()
        set .c = caster
        set .lvl = level
        call UnitModifyDamage(.c, dmg)
        call SetTimerData(tim, this)
        call TimerStart(tim, tick, false, function thistype.onLoop)
     return this
    endmethod

endstruct

private function Conditions takes nothing returns boolean
    if GetUnitAbilityLevel(GetAttacker(), SPELLID) > 0 then
        call Data.create(GetAttacker(), GetUnitAbilityLevel(GetAttacker(), SPELLID))
    endif
 return false
endfunction

//===========================================================================
public function Init takes nothing returns nothing
 local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ATTACKED )
    call TriggerAddCondition( t, Condition( function Conditions ) )
endfunction

endscope
JASS:
scope DyingHeroWindowTimer initializer Init

globals
    private real array X1              
    private real array Y1
    private real array X2
    private real array Y2
    private constant string Title       = "Respawn"
    private constant integer NumbersRed          = 255  //The Red amount in the Timer's Countdown numbers
    private constant integer NumbersGreen        = 0    //The Green
    private constant integer NumbersBlue         = 0    //The Blue
    private constant integer NumbersTransparency = 255  //The Transparency (0 for invisible, 255 for solid)
    
    private constant integer TitleRed            = 150 //The Red amount in the Timer's Title
    private constant integer TitleGreen          = 75  //The Green
    private constant integer TitleBlue           = 25  //The Blue
    private constant integer TitleTransparency   = 200 //The Transparency
    
endglobals

private struct Data

    unit d
    timer tim
    timerdialog timdia

    static method Timer takes nothing returns nothing
     local thistype this = GetTimerData(GetExpiredTimer())
     local string lol
     local integer i = GetRandomInt(1,5)
     local integer i2 = 0
     local unit Bat
     local real X
     local real Y
        if IsUnitInGroup(.d, udg_BloodPack) then
            set X = X1[i]
            set Y = Y1[i]
        elseif IsUnitInGroup(.d, udg_FangSquad) then
            set X = X2[i]
            set Y = Y2[i]
        else
            set X = -11102
            set Y = -7637
        endif
        if lives[GetPlayerId(GetOwningPlayer(.d))] <= 0 then
            call DisplayTextToPlayer(GetOwningPlayer(.d), 0, 0, "Your last life has been wasted, and your vampire soul damned to hell for all eternity.  However, feel free to stay and watch the game.")
            call TimerDialogDisplay(.timdia, false)
            set Bat = CreateUnit(GetOwningPlayer(.d), 'nshf', GetUnitX(.d), GetUnitY(.d), 270)
            call SetUnitInvulnerable(Bat, true)
            loop
            exitwhen i2 == 13
                if Player(i2) != GetOwningPlayer(.d) then
                    call UnitShareVision(Bat, Player(i2), false)
                endif
                set i2 = i2 + 1
            endloop
        else
            call ReviveHero(.d, X, Y, false)
            if GetUnitTypeId(.d) == 'GBaF' or GetUnitTypeId(.d) == 'OC91' then
                if GetUnitAbilityLevel(.d, 'fbfl') > 0 then
                    call UnitAddAbility(.d, 'hunt')
                    call SetUnitAbilityLevel(.d, 'hunt', GetUnitAbilityLevel(.d, 'fbfl'))
                endif
                if GetUnitAbilityLevel(.d, 'frdr') > 0 then
                    call UnitAddAbility(.d, 'rend')
                    call SetUnitAbilityLevel(.d, 'rend', GetUnitAbilityLevel(.d, 'frdr'))
                endif
            endif
            if (GetLocalPlayer() == GetOwningPlayer(.d)) then
                call PanCameraTo( X, Y)
                call TimerDialogDisplay(.timdia, false)
                set lol = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
                call ClearSelection()
                call SelectUnit(.d, true)
            else
                set lol = "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl"
            endif
            call DestroyEffect(AddSpecialEffectTarget(lol, .d, "origin"))
        endif
        call DestroyTimerDialog(.timdia)
        set lol = null
        set Bat = null
        call ReleaseTimer(.tim)
        call .destroy()
    endmethod

    static method create takes unit died returns thistype
     local real curLevel = GetHeroLevel(died)
     local real dur = 3.8 + 1.2*curLevel
     local integer i = 0
     local string team
     local thistype this = thistype.allocate()
        set .tim = NewTimer()
        set .d = died
        set .timdia = CreateTimerDialog(.tim)
        call TimerDialogSetTimeColor(.timdia, NumbersRed, NumbersGreen, NumbersBlue, NumbersTransparency)
        call TimerDialogSetTitle(.timdia, Title)
        call TimerDialogSetTitleColor(.timdia, TitleRed, TitleGreen, TitleBlue, TitleTransparency)
        set lives[GetPlayerId(GetOwningPlayer(.d))] = lives[GetPlayerId(GetOwningPlayer(.d))] - 1
        if IsUnitInGroup(.d, udg_BloodPack) then
            set team = "Blood Pack"
        elseif IsUnitInGroup(.d, udg_FangSquad) then
            set team = "Fang Squad"
        else
            set team = "Rogue Vampires"
        endif
        loop
         exitwhen i == 11
            call DisplayTextToPlayer( Player(i), 0, 0, GetPlayerName(GetOwningPlayer(.d)) + " of the " + team + " has perished. It has " + I2S(lives[GetPlayerId(GetOwningPlayer(.d))]) + " lives left." )
            set i = i + 1
        endloop
        if (GetLocalPlayer() == GetOwningPlayer(.d)) then
            call TimerDialogDisplay(.timdia, true)
        endif
        call SetTimerData(.tim,this)
        call TimerStart(.tim, dur, false, function thistype.Timer)
        set team = null
     return this
    endmethod
    
    static method Conditions takes nothing returns nothing
        if IsUnitType(GetDyingUnit(), UNIT_TYPE_HERO) == true and IsUnitType(GetDyingUnit(), UNIT_TYPE_UNDEAD) == true and GetPlayerController(GetOwningPlayer(GetDyingUnit())) == MAP_CONTROL_USER and GetUnitTypeId(GetDyingUnit()) != 'UC63' then
            call thistype.create(GetDyingUnit())
        endif
    endmethod

endstruct

//===========================================================================
public function Init takes nothing returns nothing
 local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddAction(t, function Data.Conditions )
    set X1[1] = -11072.
    set X1[2] = -4500.
    set X1[3] = -11114.
    set X1[4] = -5350.
    set X1[5] = -7950.
    set Y1[1] = 9200.
    set Y1[2] = 10500.
    set Y1[3] = -975.
    set Y1[4] = 2200.
    set Y1[5] = -1150.
    set X2[1] = -3856.
    set Y2[1] = -11561.
    set X2[2] = 0.
    set Y2[2] = -11400.
    set X2[3] = 8675.
    set Y2[3] = -8750.
    set X2[4] = -2750.
    set Y2[4] = -7350.
    set X2[5] = -11111.
    set Y2[5] = -10000.
 set t = null
endfunction

endscope
JASS:
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//*  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)
//* 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 = true

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

    //==========================================================================================
    function NewTimer takes nothing returns timer
        if (tN==0) then
            //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")
            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")
                set tT[0]=CreateTimer()
                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
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],0)
     return tT[tN]
    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
     
        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

Map wont' attach because it's already attached to this thread.
 
Last edited:
Level 13
Joined
May 11, 2008
Messages
1,198
i'm not really an experton knowing exactly how timers work...but i know enough to copy code from a working trigger when making a new one.

there was an invoker map posted i think at thehelper.net and those spells used timerutils...so i copied those things over.

for one thing i think you only have 2 variable names in your struct. i think you need more.

take a look at a spell that is a fancy version of a shadow strike spell

Code:
scope SithDebilitate initializer I
globals
private constant real PERIOD = 5.50
private constant integer ORBCOUNT = 3
endglobals
private struct Data
unit cast
unit targ
unit dum
integer lvl
integer orb
integer str
integer int
real sum
timer tim
boolean boo
static method create takes unit cast, unit targ returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.targ = targ
set d.lvl = GetUnitAbilityLevel(d.cast, SithApprentice_SithDebilitate)
set d.str = GetHeroStr(d.targ,false)
set d.int = GetHeroInt(d.cast,false)
set d.sum = (d.str * (0.0 + (0.25*d.lvl)))
if d.str < d.int then
set d.boo = true
call SetHeroStr(d.targ, GetHeroStr(d.targ,false) - R2I(d.sum), true)
else
set d.boo = false
endif
set d.orb = 0
set d.tim = NewTimer()
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private function Callback takes nothing returns boolean
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
call SetUnitMoveSpeed( d.targ, ( GetUnitMoveSpeed(d.targ) - 45.00 ) )
set d.orb = d.orb + 1
if d.orb >= ORBCOUNT then 
if d.boo==true then
call SetHeroStr(d.targ, GetHeroStr(d.targ,false) + R2I(d.sum), true)
endif
call SetUnitMoveSpeed( d.targ, ( GetUnitMoveSpeed(d.targ) + 135.00 ) )
call d.destroy()
endif
set tim = null
return false
endfunction
private function f takes nothing returns boolean
local Data d = Data.create(GetTriggerUnit(),GetSpellTargetUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
return false
endfunction
private function I takes nothing returns nothing
call GT_AddStartsEffectAction(function f, SithApprentice_SithDebilitate)
call XE_PreloadAbility(SithApprentice_SithDebilitate)
endfunction
endscope
there's about ten variables in the struct...sometimes you'll need alot. among those is a timer variable, i think that's usually one of the more important ones...
here's a couple of other examples, too, maybe it'll help...
Code:
scope DeathBlink initializer I
globals
private constant real PERIOD = 0.34
endglobals
private struct Data
unit cast
unit dum
integer walk
integer blink
integer cloud
timer tim
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.walk = GetUnitAbilityLevel(d.cast, DeathNinja_DeathWalk)
set d.blink = GetUnitAbilityLevel(d.cast, DeathNinja_DeathBlink)
set d.cloud = GetUnitAbilityLevel(d.cast, DeathNinja_DeathCloud)
set d.tim = NewTimer()
call TAF_UDT(d.cast,d.cast,95.00,-15.00*I2R(d.blink),PureMageAttack)//80,65,50,35
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private function Callback takes nothing returns boolean
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
set d.dum = CreateUnit(GetOwningPlayer(d.cast), DeathNinja_DeathOrb, GetUnitX(d.cast), GetUnitY(d.cast), 270.)
if d.walk > 0 then
call UnitAddAbility(d.dum, DeathNinja_DeathSlow)
call SetUnitAbilityLevel(d.dum, DeathNinja_DeathSlow, d.walk)
endif
if d.cloud > 0 then
call UnitAddAbility(d.dum, DeathNinja_DeathCloudBlinkCast)
call SetUnitAbilityLevel(d.dum, DeathNinja_DeathCloudBlinkCast, d.cloud)
call IssuePointOrder(d.dum, "cloudoffog", GetUnitX(d.cast), GetUnitY(d.cast))
endif
call UnitApplyTimedLife(d.dum, 'BTLF', 2.0 + (1.0 * I2R(d.blink)))
call d.destroy()
set tim = null
return false
endfunction
private function Actions takes nothing returns boolean
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
return false
endfunction
public function I takes nothing returns nothing
call GT_AddStartsEffectAction(function Actions, DeathNinja_DeathBlink)
call XE_PreloadAbility(DeathNinja_DeathBlink)
endfunction
endscope
Code:
scope DeathWalk initializer I
globals
private constant real PERIOD = 1.34
private constant integer ORBCOUNT = 3
endglobals
private struct Data
unit cast
unit dum
integer walk
integer orb
timer tim
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.walk = GetUnitAbilityLevel(d.cast, DeathNinja_DeathWalk)
set d.orb = 0
set d.tim = NewTimer()
call TAF_UDT(d.cast,d.cast, -15.00*I2R(GetUnitAbilityLevel(d.cast,DeathNinja_DeathBlink)),50.00+25.00* I2R(d.walk),PureMageAttack)
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private function Callback takes nothing returns boolean
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
set d.dum = CreateUnit(GetOwningPlayer(d.cast), DeathNinja_DeathOrb, GetUnitX(d.cast), GetUnitY(d.cast), 270.)
call UnitAddAbility(d.dum, DeathNinja_DeathSlow)
call SetUnitAbilityLevel(d.dum, DeathNinja_DeathSlow, d.walk)
call UnitApplyTimedLife(d.dum, 'BTLF', 2.0 + (1.0 * I2R(d.walk)))
set d.orb = d.orb + 1
if d.orb >= ORBCOUNT then 
call d.destroy()
endif
set tim = null
return false
endfunction
private function Actions takes nothing returns boolean
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
return false
endfunction
public function I takes nothing returns nothing
call GT_AddStartsEffectAction(function Actions, DeathNinja_DeathWalk)
call XE_PreloadAbility(DeathNinja_DeathWalk)
call XE_PreloadAbility(DeathNinja_DeathSlow)
endfunction
endscope
 
Level 8
Joined
Aug 2, 2008
Messages
193
The reason, why your respawn system may not work is:

JASS:
call TriggerSleepAction(dur)

TriggerSleepAction is always a very, very, very, very bad choice, if the duration is greater than 0.00 (which is often used in Damage Detection Systems).

Try to use the code for your respawn system i recoded below, using timer without TriggerSleepAction, there may be the one or other syntax error thus i wrote it on Notepad++, but you should be able to correct them ;)


JASS:
scope DyingHeroWindowTimer initializer Init

globals
    private real array X1              
    private real array Y1
    private real array X2
    private real array Y2
endglobals

private struct Data

	timer ticker = null
	unit u = null
	player owner = null
	
	//gets called when this.destroy is called and releases the timer
	private method onDestroy takes nothing returns nothing
		call ReleaseTimer(this.ticker)
	endmethod

	private static method onExpire takes nothing returns nothing
		local thistype this = thistype(GetTimerData(GetExpiredTimer())) //Get the struct of the expired timer
		local integer i = GetRandomInt(1, 5)
		local unit Bat = null
		local real X = 0.00
		local real Y = 0.00
		local string fxpath = "" //I would recommend using clear variable names ;)
		
        if IsUnitInGroup(this.u, udg_BloodPack) then
            set X = X1[i]
            set Y = Y1[i]
        elseif IsUnitInGroup(this.u, udg_FangSquad) then
            set X = X2[i]
            set Y = Y2[i]
        else
            set X = -11102
            set Y = -7637
        endif
		
        if lives[GetPlayerId(this.owner)] <= 0 then
            call DisplayTextToPlayer(this.owner, 0, 0, "Your last life has been wasted, and your vampire soul damned to hell for all eternity.  However, feel free to stay and watch the game.")
            set Bat = CreateUnit(this.owner, 'nshf', GetUnitX(this.u), GetUnitY(this.u), 270)
            call SetUnitInvulnerable(Bat, true)
			set i = 0 //no need for another variable for a loop, thus i isn't required anymore
            loop
            exitwhen i == 13
                if Player(i) != this.owner then
                    call UnitShareVision(Bat, Player(i), false)
                endif
                set i = i + 1
            endloop
        else
            call ReviveHero(this.u, X, Y, false)
            if GetUnitTypeId(this.u) == 'GBaF' or GetUnitTypeId(this.u) == 'OC91' then
                if GetUnitAbilityLevel(this.u, 'fbfl') > 0 then
                    call UnitAddAbility(this.u, 'hunt')
                    call SetUnitAbilityLevel(this.u, 'hunt', GetUnitAbilityLevel(this.u, 'fbfl'))
                endif
                if GetUnitAbilityLevel(this.u, 'frdr') > 0 then
                    call UnitAddAbility(this.u, 'rend')
                    call SetUnitAbilityLevel(this.u, 'rend', GetUnitAbilityLevel(this.u, 'frdr'))
                endif
            endif
            if (GetLocalPlayer() == this.owner) then
                call PanCameraTo(X, Y)
                set fxpath = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
                call ClearSelection()
                call SelectUnit(this.u, true)
            else
                set fxpath = "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl"
            endif
            call DestroyEffect(AddSpecialEffectTarget(fxpath, this.u, "origin"))
        endif
		
		//cleanup struct data
		call this.destroy()
		
		set Bat = null
	endmethod
		
    static method Actions takes unit died returns nothing
		local real dur = 2 + 0.2 * GetHeroLevel(died) /No need to create a new variable, that is only used one time, here it makes sense, thus it may looks ugly inside the TimerStart function call
		local thistype this = thistype.allocate()
		 
		set this.ticker = NewTimer() //creates a new timer
		set this.u = died //Store the unit in the struct
		set this.owner = GetOwningPlayer(died) //Just store the owner within the struct
		
		set lives[GetPlayerId(this.owner)] = lives[GetPlayerId(this.owner)] - 1
			
		//Its not needed to declare a string variable for the team, just display the message directly inside the if, which also doesn't require to loop through all player, using GetLocalPlayer, which sends the message to all players
		if IsUnitInGroup(died, udg_BloodPack) then
			call DisplayTextToPlayer(GetLocalPlayer(), 0.00, 0.00, GetPlayerName(this.owner) + " of the Blood Pack has perished. It has " + I2S(lives[GetPlayerId(owner)]) + " lives left." )
		elseif IsUnitInGroup(died, udg_FangSquad) then
			call DisplayTextToPlayer(GetLocalPlayer(), 0.00, 0.00, GetPlayerName(this.owner) + " of the Fang Squad has perished. It has " + I2S(lives[GetPlayerId(owner)]) + " lives left." )
		else
			call DisplayTextToPlayer(GetLocalPlayer(), 0.00, 0.00, GetPlayerName(this.owner) + " of the Rogue Vampires has perished. It has " + I2S(lives[GetPlayerId(owner)]) + " lives left." )
		endif
		

		call SetTimerData(this.ticker, this) //attaches the struct to the timer
		call TimerStart(this.ticker, dur, false, function thistype.onExpire) //The timer callback function
	endmethod
    
    static method Conditions takes nothing returns nothing
        if IsUnitType(GetDyingUnit(), UNIT_TYPE_HERO) == true and IsUnitType(GetDyingUnit(), UNIT_TYPE_UNDEAD) == true and GetPlayerController(GetOwningPlayer(GetDyingUnit())) == MAP_CONTROL_USER and GetUnitTypeId(GetDyingUnit()) != 'UC63' then
            call thistype.Actions(GetDyingUnit())
        endif
    endmethod

endstruct

//===========================================================================
public function Init takes nothing returns nothing
 local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddAction(t, function Data.Conditions )
    set X1[1] = -11072.
    set X1[2] = -4500.
    set X1[3] = -11114.
    set X1[4] = -5350.
    set X1[5] = -7950.
    set Y1[1] = 9200.
    set Y1[2] = 10500.
    set Y1[3] = -975.
    set Y1[4] = 2200.
    set Y1[5] = -1150.
    set X2[1] = -3856.
    set Y2[1] = -11561.
    set X2[2] = 0.
    set Y2[2] = -11400.
    set X2[3] = 8675.
    set Y2[3] = -8750.
    set X2[4] = -2750.
    set Y2[4] = -7350.
    set X2[5] = -11111.
    set Y2[5] = -10000.
 set t = null
endfunction

endscope
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
Thanks. I'll look into storing my variables in the struct to see if that doesn't remedy some of the problems.

Also I posted the wrong DeathWindow timer. :\ That Timer I posted was my work-around so I could at least respawn properly during multiplayer games. I've updated it.

That said I forgot to mention the problem is particularly bad in Multiplayer. It does bug in single player but when you're playing multiplayer the problems with timers are much more apparent.
 
Level 13
Joined
May 11, 2008
Messages
1,198
to be honest i dislike every other respawn timer out there. you want to check out mine?

btw...are you using random respawn locations in random regions for the heroes? it kinda looks like it...just from plain tesh coloring and your indents.

anyway, if you will explain exactly what you want your respawn system to do, i can probably explain how to do it.

well...i've updated my development thread, the ancient frontier today. i recommend you check out my respawn timers. see if you like them. if you got questions about anything about those, go ahead and pm me. oh and...i think the timer call itself is in the rather large summonhero trigger...if i remember right. and just so you know...the timers are in the multiboard!

anyway...i dont think you even used timer utils in your respawn trigger here...and i myself do not know how to make a proper timer window stuff easily but did it only in gui...maybe you need to do the same...i only made one timer window between my two main maps and it's just the time limit for the demon hunt tournament...pretty simple and basic and hard to screw up for just a gui trigger. when i made my timer window, it was based on older version of map, master of haosis...which wasn't locked at the time. now you have to ask permission for unlocked version. you can also just look at my tdht map for my one timer window.

oh yeah....one more thing: you complained about waits not working in init functions...

i hope you're doing something like this?:

Code:
    set gg_trg_tmr = CreateTrigger(  )
    call DisableTrigger( gg_trg_tmr )
    call TriggerRegisterTimerEventSingle( gg_trg_tmr, 55.00 )
    call TriggerAddAction( gg_trg_tmr, function Trig_tmr_Actions )
then you can enable the trigger when you need it...of course this is an optional time limit in my map.

it's just an example...but it does give you a preview of the timer trigger.

the main line we need to focus on is this one:
call TriggerRegisterTimerEventSingle( gg_trg_tmr, 55.00 )
it is same thing as wait from start of game(0 seconds elapsed), but more accurate, that is, waiting for 55 seconds of game time to go by from beginning of game.

you can obviously change it to whatever. i'm not sure if this was basic function you forgot about, or not. as a learner of jass, it's not a bad idea to convert gui to jass sometimes to see how functions get renamed.

in fact i have one init function in that map that looks like this:
Code:
private function I takes nothing returns nothing
local trigger t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 150.00,false )
call TriggerAddCondition( t,Condition( function m6 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 120.00 ,false)
call TriggerAddCondition( t,Condition( function m4 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 25.00 ,false)
call TriggerAddCondition( t,Condition( function m3 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 10.00 ,false)
call TriggerAddCondition( t,Condition( function m2 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 3.00 ,false)
call TriggerAddCondition( t,Condition( function m1 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 180.00 ,false)
call TriggerAddCondition( t,Condition( function m7 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 240.00 ,false)
call TriggerAddCondition( t,Condition( function m8 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 540.00 ,false)
call TriggerAddCondition( t,Condition( function m9 ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 53.00 ,false)
call TriggerAddCondition( t,Condition( function pickchasertime ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 15.00 ,false)
call TriggerAddCondition( t,Condition( function pickrunnertime ))
set t= CreateTrigger()
call TriggerRegisterTimerEvent( t, 1.0 ,false)
call TriggerAddCondition( t,Condition( function mbtime ))

as you can see(by viewing the rest of the trigger, which is not in this post), most of those functions are calling messages to be displayed...and other things are done as well...was this the sort of thing you were trying to do with some of your waits?
 
Last edited:
Level 13
Joined
Jul 26, 2008
Messages
1,009
Waits always struck me as difficult because they lack a certain MUI to them. Also, they cause major issues in loops. That's why I switched to TimerUtils.

But they can work just fine in certain cases. For instance in a Trigger that is used by the game's mechanics and not a specific unit.

The problem was Inits are not triggers, and thus a "TriggerSleepAction()" can stop all Inits after it from loading because there's not Trigger to sleep. I didn't try a PolledWait() though. I just reverted to using a simple TimerStart( CreateTimer(), 120, false, function Actions) setup.

Now, you're right. I didn't use TimerUtils in the old RespawnTimer I had listed. In the new one I updated on the first post just now, it's used along with a timer dialog. It has SetTimerData() and NewTimer(). That was my fault for copying the wrong trigger.

If you take a look up the updated Timer, which I'll post in this thread as well, you can see I wanted to create a Timer with the respawn time determined by level and a timerdialog.

A few messages are displayed, a value assigned to lives depletes, and if the unit is fully dead then he recieves a bat but no hero respawn. Respawn location is determined by Affiliation of team, and is a specific X, Y coordinate on the map. It is random which X, Y, Coordinate you'll be placed at (The Coordinates represent bases.)

JASS:
scope DyingHeroWindowTimer initializer Init

globals
    private real array X1              
    private real array Y1
    private real array X2
    private real array Y2
    private constant string Title       = "Respawn"
    private constant integer NumbersRed          = 255  //The Red amount in the Timer's Countdown numbers
    private constant integer NumbersGreen        = 0    //The Green
    private constant integer NumbersBlue         = 0    //The Blue
    private constant integer NumbersTransparency = 255  //The Transparency (0 for invisible, 255 for solid)
    
    private constant integer TitleRed            = 150 //The Red amount in the Timer's Title
    private constant integer TitleGreen          = 75  //The Green
    private constant integer TitleBlue           = 25  //The Blue
    private constant integer TitleTransparency   = 200 //The Transparency
    
endglobals

private struct Data

    unit d
    timer tim
    timerdialog timdia

    static method Timer takes nothing returns nothing
     local thistype this = GetTimerData(GetExpiredTimer())
     local string lol
     local integer i = GetRandomInt(1,5)
     local integer i2 = 0
     local unit Bat
     local real X
     local real Y
        if IsUnitInGroup(.d, udg_BloodPack) then
            set X = X1[i]
            set Y = Y1[i]
        elseif IsUnitInGroup(.d, udg_FangSquad) then
            set X = X2[i]
            set Y = Y2[i]
        else
            set X = -11102
            set Y = -7637
        endif
        if lives[GetPlayerId(GetOwningPlayer(.d))] <= 0 then
            call DisplayTextToPlayer(GetOwningPlayer(.d), 0, 0, "Your last life has been wasted, and your vampire soul damned to hell for all eternity.  However, feel free to stay and watch the game.")
            call TimerDialogDisplay(.timdia, false)
            set Bat = CreateUnit(GetOwningPlayer(.d), 'nshf', GetUnitX(.d), GetUnitY(.d), 270)
            call SetUnitInvulnerable(Bat, true)
            loop
            exitwhen i2 == 13
                if Player(i2) != GetOwningPlayer(.d) then
                    call UnitShareVision(Bat, Player(i2), false)
                endif
                set i2 = i2 + 1
            endloop
        else
            call ReviveHero(.d, X, Y, false)
            if GetUnitTypeId(.d) == 'GBaF' or GetUnitTypeId(.d) == 'OC91' then
                if GetUnitAbilityLevel(.d, 'fbfl') > 0 then
                    call UnitAddAbility(.d, 'hunt')
                    call SetUnitAbilityLevel(.d, 'hunt', GetUnitAbilityLevel(.d, 'fbfl'))
                endif
                if GetUnitAbilityLevel(.d, 'frdr') > 0 then
                    call UnitAddAbility(.d, 'rend')
                    call SetUnitAbilityLevel(.d, 'rend', GetUnitAbilityLevel(.d, 'frdr'))
                endif
            endif
            if (GetLocalPlayer() == GetOwningPlayer(.d)) then
                call PanCameraTo( X, Y)
                call TimerDialogDisplay(.timdia, false)
                set lol = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
                call ClearSelection()
                call SelectUnit(.d, true)
            else
                set lol = "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl"
            endif
            call DestroyEffect(AddSpecialEffectTarget(lol, .d, "origin"))
        endif
        call DestroyTimerDialog(.timdia)
        set lol = null
        set Bat = null
        call ReleaseTimer(.tim)
        call .destroy()
    endmethod

    static method create takes unit died returns thistype
     local real curLevel = GetHeroLevel(died)
     local real dur = 3.8 + 1.2*curLevel
     local integer i = 0
     local string team
     local thistype this = thistype.allocate()
        set .tim = NewTimer()
        set .d = died
        set .timdia = CreateTimerDialog(.tim)
        call TimerDialogSetTimeColor(.timdia, NumbersRed, NumbersGreen, NumbersBlue, NumbersTransparency)
        call TimerDialogSetTitle(.timdia, Title)
        call TimerDialogSetTitleColor(.timdia, TitleRed, TitleGreen, TitleBlue, TitleTransparency)
        set lives[GetPlayerId(GetOwningPlayer(.d))] = lives[GetPlayerId(GetOwningPlayer(.d))] - 1
        if IsUnitInGroup(.d, udg_BloodPack) then
            set team = "Blood Pack"
        elseif IsUnitInGroup(.d, udg_FangSquad) then
            set team = "Fang Squad"
        else
            set team = "Rogue Vampires"
        endif
        loop
         exitwhen i == 11
            call DisplayTextToPlayer( Player(i), 0, 0, GetPlayerName(GetOwningPlayer(.d)) + " of the " + team + " has perished. It has " + I2S(lives[GetPlayerId(GetOwningPlayer(.d))]) + " lives left." )
            set i = i + 1
        endloop
        if (GetLocalPlayer() == GetOwningPlayer(.d)) then
            call TimerDialogDisplay(.timdia, true)
        endif
        call SetTimerData(.tim,this)
        call TimerStart(.tim, dur, false, function thistype.Timer)
        set team = null
     return this
    endmethod

    static method Conditions takes nothing returns nothing
        if IsUnitType(GetDyingUnit(), UNIT_TYPE_HERO) == true and IsUnitType(GetDyingUnit(), UNIT_TYPE_UNDEAD) == true and GetPlayerController(GetOwningPlayer(GetDyingUnit())) == MAP_CONTROL_USER and GetUnitTypeId(GetDyingUnit()) != 'UC63' then
            call thistype.create(GetDyingUnit())
        endif
    endmethod

endstruct

//===========================================================================
public function Init takes nothing returns nothing
 local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddAction(t, function Data.Conditions )
    set X1[1] = -11072.
    set X1[2] = -4500.
    set X1[3] = -11114.
    set X1[4] = -5350.
    set X1[5] = -7950.
    set Y1[1] = 9200.
    set Y1[2] = 10500.
    set Y1[3] = -975.
    set Y1[4] = 2200.
    set Y1[5] = -1150.
    set X2[1] = -3856.
    set Y2[1] = -11561.
    set X2[2] = 0.
    set Y2[2] = -11400.
    set X2[3] = 8675.
    set Y2[3] = -8750.
    set X2[4] = -2750.
    set Y2[4] = -7350.
    set X2[5] = -11111.
    set Y2[5] = -10000.
 set t = null
endfunction

endscope
As far as I can tell, there's nothing wrong with this set up.

EDIT: I threw on debug for TimerUtils to see if that could tell me what the problem was. I'm getting this message on my spell, Hellfire:

Double free of type: Hellfire___Data

No clue what the means though.

After some time, the error becomes:

NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly.

Though I am pretty sure the Timer is getting recycled correctly. What do you guys think? here is the spell:

JASS:
scope HellFire initializer InitTrig_Hellfire

globals
    private constant integer SPELLID        = 'HeFi'
endglobals

private struct Data

    unit c
    integer lvl
    real x
    real y
    real x1
    real y1

    static method Star takes nothing returns nothing
     local xedamage d=xedamage.create()
     local timer tim = GetExpiredTimer()
     local Data D = Data(GetTimerData(tim))
     local integer lvl = GetUnitAbilityLevel(D.c, SPELLID)
     local real dam = lvl*8+10+GetHeroInt(D.c, true)*(0.4+0.1*lvl)
        call d.factor( UNIT_TYPE_STRUCTURE, 0.2 )
        set d.atype= ATTACK_TYPE_NORMAL
        set d.dtype= DAMAGE_TYPE_MAGIC
        call d.damageAOE(D.c, D.x1, D.y1, 60, dam)
        call ReleaseTimer(tim)
        call d.destroy()
        call D.destroy()
    endmethod

    static method Timer takes nothing returns nothing
     local timer tim = GetExpiredTimer()
     local timer tim1 = NewTimer()
     local Data D = Data(GetTimerData(tim))
     local real ang = GetRandomReal(0,2*bj_PI)
     set D.x1 = D.x+GetRandomReal(50,400)*Cos(ang)
     set D.y1 = D.y+GetRandomReal(50,400)*Sin(ang)
        if GetUnitCurrentOrder(D.c) == OrderId("starfall") then
            call DestroyEffect(AddSpecialEffect(GetAbilityEffectById(SPELLID, EFFECT_TYPE_MISSILE, 0), D.x1, D.y1))
            call SetTimerData(tim1, D)
            call TimerStart(tim1, 0.3, false, function Data.Star)
        else
            set D.c = null
            call D.destroy()
            call ReleaseTimer(tim)
        endif
    endmethod

    static method create takes unit c, real x, real y, integer i returns Data
     local Data D = Data.allocate()
     local timer tim = NewTimer()
        set D.c = c
        set D.x = x
        set D.y = y
        call SetTimerData(tim,D)
        call TimerStart(tim, 0.3*i+GetRandomReal(0.1,-0.1), true, function Data.Timer)
     return D
    endmethod

endstruct

private function Conditions takes nothing returns boolean
 local integer i = 1
 local integer lvl = GetUnitAbilityLevel(GetTriggerUnit(), SPELLID)
 local real x = GetUnitX(GetTriggerUnit())
 local real y = GetUnitY(GetTriggerUnit())
    if GetSpellAbilityId() == SPELLID then
        loop
            call Data.create(GetTriggerUnit(), x, y, i)
        exitwhen i > lvl*10+8
            set i = i + 1
        endloop
    endif
 return false
endfunction

//===========================================================================
public function InitTrig_Hellfire takes nothing returns nothing
 local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
    call TriggerAddCondition( t, function Conditions )
    call XE_PreloadAbility(SPELLID)
 set t = null
endfunction

endscope
This may be the exact reason why my TimerUtils are bugging.
 
Last edited:
Level 13
Joined
May 11, 2008
Messages
1,198
in that spell you don't have the timer as a struct variable...also you are creating new timers i think when you should be using timerutils functions. timerutils is supposed to make the timers for you.

look at this bit of code from a previous post:

Code:
private struct Data
unit cast
unit dum
integer walk
integer orb
[COLOR="DarkOrange"]timer tim[/COLOR]
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.walk = GetUnitAbilityLevel(d.cast, DeathNinja_DeathWalk)
set d.orb = 0
[COLOR="darkorange"]set d.tim = NewTimer()[/COLOR]
you're not calling NewTimer(). you should be. you can't really use timerutils unless you do.

i recommend you to take one of those(one using timerutils that works properly) triggers, eliminate irrelevant lines and replace them with relevant lines, and add in any further necessary lines for xedamage or whatever...and you should be good to go. after testing to make sure it works, delete the old trigger. to me, those triggers are junk filled with a lot of gibberish. i can't make sense of it.

when i was trying to use timerutils for first time i wrote nonsense code too since i didn't know what i was doing. i recommend you copy and paste and replace irrelevant information with relevant information. starting from scratch is too much of a doozy. until you get the hang of it...and even i haven't gotten the hang of it...just copy over existing working spells using timerutils and replace irrelevant info with relevant over and over again, until you got all your timerutils spells done.

i guess i can try to do one spell for you and we can take it from there.

hellfire might be a good candidate, i'll try that one.

i looked over your trigger and i don't really understand why you have two methods, and you create about 3 timers or so. how many do you really need? and why don't you use timerutils for every call? now editing trigger...

hmm...near the bottom...why do you have a loop to create a timer? are you trying to create the timer many times?

well...i think i'm beginning to see where you are taking this...do you not know that you need a callback? that's the usual way of things.

hmm...well i've never used xe.damage before, so not sure if it works, but it took me a while to do it, and lately i've made less mistakes once something compiles properly.

it looks ok but i haven't tested it thoroughly...of course i didn't test it thoroughly to begin with. (i never checked to see if units would get damaged)

if it works, i guess you taught me something new! after all i never used xedamage before and i never used multiple dots in a line like that before. i won't guarantee it, but my guess is, it's fine. you might look at a good example use of xedamage and compare, but my guess is most of the fuss is over the timerutils...i'm pretty sure i did that right.

Code:
scope HellFire initializer I
globals
private constant integer SPELLID        = 'HeFi'
private constant real PERIOD = 0.3
endglobals
private struct Data
    real rx
    real ry
unit cast
unit dum
integer walk
integer orb
integer orbcount
timer tim
real dmg
xedamage xd
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.xd=xedamage.create()
set d.cast = cast
set d.walk = GetUnitAbilityLevel(d.cast, SPELLID)
set d.rx=GetUnitX(d.cast)
set d.ry=GetUnitY(d.cast)
set d.orbcount=d.walk*10+8
set d.dmg=d.walk*8+10+GetHeroInt(d.cast, true)*(0.4+0.1*d.walk)
set d.orb = 0
set d.tim = NewTimer()
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private function Callback takes nothing returns boolean
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local real ang = GetRandomReal(0,2*bj_PI)
call d.xd.factor( UNIT_TYPE_STRUCTURE, 0.2 )
set d.xd.atype= ATTACK_TYPE_NORMAL
set d.xd.dtype= DAMAGE_TYPE_MAGIC
if GetUnitCurrentOrder(d.cast) == OrderId("starfall") then
call DestroyEffect(AddSpecialEffect(GetAbilityEffectById(SPELLID, EFFECT_TYPE_MISSILE, 0), d.rx+GetRandomReal(50,400)*Cos(ang), d.ry+GetRandomReal(50,400)*Sin(ang)))
call d.xd.damageAOE(d.cast, d.rx, d.ry, 60, d.dmg)
else
call d.destroy()
set tim = null
return false
endif
set d.orb = d.orb + 1
if d.orb >= d.orbcount then 
call d.destroy()
endif
set tim = null
return false
endfunction
private function Actions takes nothing returns boolean
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
return false
endfunction
public function I takes nothing returns nothing
local trigger t = CreateTrigger(  )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
call TriggerAddCondition( t, function Actions )
call XE_PreloadAbility(SPELLID)
endfunction
endscope

my guess is that whatever odd struct setup you were using works with a different type of timer system...that or something like it, anyway. if not, then i guess you were just writing code from scratch.
 
Last edited:
Level 13
Joined
Jul 26, 2008
Messages
1,009
Thanks! Your trigger seemed to have fixed a lot of the debug calls I was having. Also I noticed in my own trigger I was destroying the wrong timer.

I'm using yours now and the only issue I'm having with it is when you create xd.damage and then continously assign factors and new values to it, it has issues and doesn't do damage.

Also the effect doesn't do damage on impact of the effect (It's Rain of Fire effect and works with like effects) but rather on it's creation. This is the reason I had two timers, but just looking at the code I wouldn't expect you to have caught that.

But yeah, you guessed it. I was doing my code form scratch. :ogre_hurrhurr:
 
Level 13
Joined
May 11, 2008
Messages
1,198
Thanks! Your trigger seemed to have fixed a lot of the debug calls I was having. Also I noticed in my own trigger I was destroying the wrong timer.

I'm using yours now and the only issue I'm having with it is when you create xd.damage and then continously assign factors and new values to it, it has issues and doesn't do damage.

Also the effect doesn't do damage on impact of the effect (It's Rain of Fire effect and works with like effects) but rather on it's creation. This is the reason I had two timers, but just looking at the code I wouldn't expect you to have caught that.

But yeah, you guessed it. I was doing my code form scratch. :ogre_hurrhurr:

actually i did guess it, was just too lame to do any research on xedamage. haha. the thread title is for fixing issues with timerutils. going off-topic is a bad habit, i guess.

anyways...i made my own unit damage point function for use with my spells, so i don't really use the xedamage library for damage.

as for the spell damaging at point of impact of the fire...at this point i'm not sure how to cleanly accomplish that. i was trying to do a spell called force lightning that also needed me to have multiple timers but i think i could not pull it off.

a thought occured to me a bit ago, though. i remember i was slacking off and not implementing a certain feature of a spell i wanted.

the problem was i had taken a spell someone else made, altered it somewhat...and then i wanted to alter it some more. so i had changed the look of the dummy units and now it looks much better than before...but there was the issue of wanting to keep the target unit in the middle of the dummy units. the spell used KT, a timer system i don't know how to use, and judging by the spell, it being complicated, not knowing if KT itself is complicated and not knowing where is KT and where is the spell.

so eventually i'm like, ok i need to do this...and correctly found the periodic function and placed an if then sequence to put teleporting units back into the cage, so to speak. but before i did that...i pondered, what if it doesn't work? what will i do then?

i thought, well, i could run a timerutils timer deal of my own. just make another scope that responds to the spell. it can't be a terrible idea if you're stuck to do such a thing, right?

so i propose we do this with your spell.

take out all the xe.damage stuff in the spell, and move it to a new scope. and give it all it's own stuff.

so...i guess the idea is we are separating effect and damage into 2 different scopes. because the timing is different and we're only using one timer per scope. i'm not sure if it's the better way to do things, but for now, it might be the safer way to do things.

i'll come back later and try to figure it out. need a break for now. will edit this post if someone else doesn't post.

maybe the real issue is just not knowing how to use xe.damage. so if after moving it over to another scope, we can get it to work without errors, then it would be easier to put it back into the original spell's scope. it's just making it easier to work with, having less clutter in the scope to deal with, imo.
 
Level 13
Joined
Jul 26, 2008
Messages
1,009
Thanks for the encouragement and ideas. I'll give what you said a try, maybe something useful can be made to turn your idea into a working solution.

I realized in my function that I wasn't removing the right timer on the second timer. This was causing TimerUtil's stack to overflow and no longer create any more timers. Big problem.

I also realized the Double Free issue was coming up because I had my trigger on Periodic when it should have been a single shot timer.

These issue were brought to my attention thanks to Anitarf. But I am curious in the idea you have in mind, and will try to attempt it as well.
 
Level 13
Joined
May 11, 2008
Messages
1,198
well, i think i just noticed why the code i sent wouldn't do any damage. i chose to damage the aoe at the caster, instead of around the caster, like where the effects are.

anyway...i looked at another spell, which does use xedamage!
so, we can do damage with it.

it doesn't damage exactly how your spell damages, though...so your spell may require further editing...i did what i could for now.

here's the updated code, which now deals damage, with some comments added
Code:
scope FireFall initializer I
globals
 private xedamage xed
private constant integer SPELLID        = 'HeFi'
private constant real PERIOD = 0.3
// private string EFFECTSTRING =GetAbilityEffectById(SPELLID, EFFECT_TYPE_MISSILE, 0)
// commented out since it didn't work out
endglobals

    private constant function Damage takes integer level, integer herint returns real
  return 500.00      
// commentedyour damage out just for testing purposes.
// you can comment out 500.00 and put your damage back in, if you want.
  //return level*8+10+herint*(0.4+0.1*level)
// The damage dealt tot he target when it is hit.
    endfunction
    

    
    private function DamageOptions takes xedamage spellDamage returns nothing
        // Useful read: http://www.wc3campaigns.net/showpost.php?p=1030046&postcount=19
        set spellDamage.dtype=DAMAGE_TYPE_MAGIC//
        set spellDamage.atype=ATTACK_TYPE_NORMAL
       call spellDamage.factor( UNIT_TYPE_STRUCTURE, 0.2 )
//call spellDamage.useSpecialEffect(EFFECTSTRING,"origin")
  //the xedamage i don't think is compatible with the GetAbilityEffectById() function.
    // it's probablyan easy thing to fix...but i'm not sure.  i'm ill-inclined to bother with it.
//so i thought we can actually do an effect with xedamage...but i did not see an example trigger
//that already does that.  there are other ways to do the effect, of course. including your way.
    endfunction

private struct Data
    real rx
    real ry
unit cast
integer walk
integer orb
integer hi
integer orbcount
timer tim
static method create takes unit cast returns Data
local Data d = Data.allocate()
set d.cast = cast
set d.walk = GetUnitAbilityLevel(d.cast, SPELLID)
set d.rx=GetUnitX(d.cast)
set d.ry=GetUnitY(d.cast)
set d.hi=  GetHeroInt(d.cast,true)
set d.orbcount=d.walk*10+8
set d.orb = 0
set d.tim = NewTimer()
call SetTimerData(d.tim, d)
return d
endmethod
method onDestroy takes nothing returns nothing
call ReleaseTimer(.tim)
endmethod
endstruct
private function Callback takes nothing returns boolean
local timer tim = GetExpiredTimer()
local Data d = GetTimerData(tim)
local real ang = GetRandomReal(0,2*bj_PI)

if GetUnitCurrentOrder(d.cast) == OrderId("starfall") then

call DestroyEffect(AddSpecialEffect(GetAbilityEffectById(SPELLID, EFFECT_TYPE_MISSILE, 0), d.rx+GetRandomReal(50,400)*Cos(ang), d.ry+GetRandomReal(50,400)*Sin(ang)))

//call xed.damageTarget(.caster, .target, Damage(d.walk)*tx)
// commented out since it didn't work out...btw...i don't see any reason for this *tx part, just
// doesn't make any sense.
call xed.damageAOE(d.cast, d.rx+GetRandomReal(50,400)*Cos(ang), d.ry+GetRandomReal(50,400)*Sin(ang), 160, Damage(d.walk,d.hi))

else

call d.destroy()
set tim = null
return false
endif
set d.orb = d.orb + 1
if d.orb >= d.orbcount then 
call d.destroy()
endif
set tim = null
return false
endfunction
private function Actions takes nothing returns boolean
local Data d = Data.create(GetTriggerUnit())
call TimerStart(d.tim, PERIOD, true, function Callback)
return false
endfunction
public function I takes nothing returns nothing
local trigger t = CreateTrigger(  )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
call TriggerAddCondition( t, function Actions )
        // Initialize xedamage.
        set xed=xedamage.create()
        call DamageOptions(xed)
call XE_PreloadAbility(SPELLID)
endfunction
endscope
 
Last edited:
Status
Not open for further replies.
Top