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

[JASS] Prevent 1 hit kill with shield spell

Status
Not open for further replies.

TKF

TKF

Level 19
Joined
Nov 29, 2006
Messages
1,266
I created a shield system that works fine except the part of 1 hit kill. I have a problem to prevent 1 hit kill. Is there a way to prevent it. Lets say a unit with 300 max hp has 150 hp left. How do I make it so it have 150 hp left being hit?

JASS:
function Damage_Block takes nothing returns boolean
        local timer t = GetExpiredTimer()
        local real Damage = LoadReal(udg_hashdamage, GetHandleId(t), 1)
        local unit target = LoadUnitHandle(udg_hashdamage, GetHandleId(t), 2)
        local real CurrentLife = GetUnitState(target,UNIT_STATE_LIFE)
        local real MaxLife = GetUnitState(target,UNIT_STATE_MAX_LIFE)
        local integer shieldvalue = GetCustomIntValue(target)
        local real shieldvaluereal = I2R(GetCustomIntValue(target))
        call DestroyTimer(t)
	//Set to 3 - prevents unecesary calls
	if Damage > 3 then
                if UnitHasBuffBJ(target, 'B000') == true then
			if CurrentLife-Damage < MaxLife then
				if shieldvaluereal > Damage then
					call SetUnitState(target, UNIT_STATE_LIFE, CurrentLife+Damage)
                                        set shieldvaluereal = shieldvaluereal - Damage
                                        set shieldvalue = R2I(shieldvaluereal)
                                        call SetCustomIntValue(target, shieldvalue)
				else
					call SetUnitState(target, UNIT_STATE_LIFE, CurrentLife+shieldvaluereal)
                                        call SetCustomIntValue(target, 0)
                                        set shieldvaluereal = 0.00
                                        call UnitRemoveBuffBJ( 'B000', target )
                                        call GroupRemoveUnitSimple(target, udg_ShieldGroup)
				endif
                        else
			endif
                else
		endif
        else
	endif
        call FlushChildHashtable(udg_hashdamage, GetHandleId(t))
	set target = null
return false
endfunction


//This timer is for allowing full hp units able to block damage
function Trig_Damage_Detection_Conditions takes nothing returns boolean
    local unit tu = GetTriggerUnit()
    local real DamageMemory = GetEventDamage()
    local real CurrentLifeMem = GetUnitState(tu,UNIT_STATE_LIFE)
    local real MaxLifeMem = GetUnitState(tu,UNIT_STATE_MAX_LIFE)
    local timer t = CreateTimer()  
    call SaveReal(udg_hashdamage, GetHandleId(t), 1, DamageMemory)
    call SaveUnitHandle(udg_hashdamage, GetHandleId(t), 2, tu)  
    if CurrentLifeMem > DamageMemory then
        call TimerStart(t, 0.01, false, function Damage_Block)
    else
        call TimerStart(t, 0.00, false, function Damage_Block)
    endif
    set tu = null
return false
endfunction


//===========================================================================
function InitTrig_Damage_Detection takes nothing returns nothing
	set gg_trg_Damage_Detection = CreateTrigger(  )
	call DisableTrigger( gg_trg_Damage_Detection )
	call TriggerAddCondition( gg_trg_Damage_Detection, Condition( function Trig_Damage_Detection_Conditions ) )
endfunction
Is there a way of doing this only in triggers? Or do I have to add something?
 
The general process for blocking damage goes as follows:
(1) Add a life-gain ability to the unit so that their max-hp goes up by 1000000 or so.
(2) Store their current life in a variable. Set their hp to the max-hp. Start a timer that expires after 0 seconds, e.g.:
JASS:
call TimerStart(t, 0, false, function RemoveLifeGain)
(3) In the timer's call back, remove the life-gain ability from the unit. Set their life to the life that was stored in the variable.

Note that for it to be MUI, you'll have to use some sort of storage system (e.g. hashtables, or an array) to store the life in case two or more units are damaged at the same time. 0-second timers wait the minimum amount of time that a timer can run--it isn't really expiring after 0 seconds. It is just long enough for the damage to have been dealt (since the damage event fires before the damage has been dealt).

Also, that is just for blocking damage. Reassigning damage on the fly or turning it into healing is a bit more complicated. But I highly recommend that you look into it! If you make a nice system for yourself, you'll be able to make very advanced spell mechanics quite easily. For information on the process, you may want to look into something like Bribe's DDS:
http://www.hiveworkshop.com/forums/spells-569/gui-damage-engine-v2-2-1-0-a-201016/
And looking_for_help's DDS (I think his covers a bug that still exists in Bribe's system):
http://www.hiveworkshop.com/forums/spells-569/physical-damage-detection-gui-v1-2-0-1-a-231846/
Once you have a good DDS in your map, it'll be easy to make damage-absorbing heals, spells that turn healing into damage, triggered critical strikes/misses, invulnerability spells, temporary bonuses to attacks for a short period of time, and more. :D
 
Level 25
Joined
May 11, 2007
Messages
4,651
What happens if you use create a trigger:
Events: "A unit dies"
conditions: unit has buff asdsdf //try one time without the buff just to see if it triggers.
Actions:
set unit life of triggering unit to 1.

You can convert it to jass later.
 

TKF

TKF

Level 19
Joined
Nov 29, 2006
Messages
1,266
So if I understand you correctly, I need an ability that adds max hp to a unit and then remove it/change levels. So what I need is a 2nd timer if the damage surpass current hp and that 2nd timer will execute a function that remove the max hp ability? Doesn't sound to complex.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
The 0 timeout timer is needed because the damage event fires before the damage is removed from the units health.

The basic idea.
Event fires -> record damage -> reheal and up health -> timer 0 seconds -> restore health -> set health to appropriate amount

It is important to remember that although the timer is set to a 0 second timeout it is possible for multiple damage events against the same target to occur before it expires. Hence you need some system which can track all damage events during that time without starting a new timer and then when the timer expires it computes the appropriate amount.
 
  • Like
Reactions: TKF

TKF

TKF

Level 19
Joined
Nov 29, 2006
Messages
1,266
So a timer with 0, still add enough delay for the event damage to apply? So I can set the 0.01 to 0.00 instead? Right? So I basically don't need 2 timers then.

So how do I call a function directly without timer? I tried to use a call but somehow that crashes the wc3, unless I was doing it wrong.
I tried call Damage_Block() but it crashes.
 
@TKF: Oops, sorry I didn't look at your code. You only need one timer. You would do all the calculations in Trig_Damage_Detection_Conditions. You have to increase the hp of the unit before damage is dealt, otherwise the unit might die in the process.

As for calling a function, you had it right. Perhaps you were accidentally calling the function within Damage_Block (causing an infinite loop)? Or maybe since you still had functions relating to GetExpiredTimer(), perhaps it crashed. But that isn't important. Try to follow the guidelines I or DSG gave, and post back with the trigger if you're stuck.
 
  • Like
Reactions: TKF

TKF

TKF

Level 19
Joined
Nov 29, 2006
Messages
1,266
Thanks guys.

Zwiebelchen: I know there is other systems out there. I wanted to learn this myself.


I had to use a item ability which added insane amount of hp as you mentioned, the thing is that it still have the hp bonus so I had to make a negative one to remove it. Level 2 adds a lot of negative hp, level 3 adds same amount of hp to balance out negative hp.

JASS:
function Damage_Block takes nothing returns boolean
        local timer t = GetExpiredTimer()
        local real Damage = LoadReal(udg_hashdamage, GetHandleId(t), 1)
        local unit target = LoadUnitHandle(udg_hashdamage, GetHandleId(t), 2)
        local real CurrentLife = LoadReal(udg_hashdamage, GetHandleId(t), 3)
        local real MaxLife = GetUnitState(target,UNIT_STATE_MAX_LIFE)
        local integer shieldvalue = GetCustomIntValue(target)
        local real shieldvaluereal = I2R(GetCustomIntValue(target))
        local timer t2 = CreateTimer()
        call DestroyTimer(t)
        //Adds same hp same as the negative value to null out hp adjust
        if LoadBoolean(udg_hashdamage, GetHandleId(t), 4) == true then
            call UnitAddAbility(target, 'A001')
            call SetUnitAbilityLevel(target, 'A001', 3 )
            call UnitRemoveAbility(target, 'A001')
            if Damage < 3 then
                call SetUnitState(target, UNIT_STATE_LIFE, CurrentLife)
            else
            endif  
        else
        endif  
        //Set to 3 - prevents unecesary calls
	if Damage > 3 then
                if UnitHasBuffBJ(target, 'B000') == true then
				if shieldvaluereal > Damage then
					call SetUnitState(target, UNIT_STATE_LIFE, CurrentLife)
                                        set shieldvaluereal = shieldvaluereal - Damage
                                        set shieldvalue = R2I(shieldvaluereal)
                                        call SetCustomIntValue(target, shieldvalue)
				else
					call SetUnitState(target, UNIT_STATE_LIFE, CurrentLife+shieldvaluereal-Damage)
                                        call SetCustomIntValue(target, 0)
                                        set shieldvaluereal = 0.00
                                        call UnitRemoveBuffBJ( 'B000', target )
                                        call GroupRemoveUnit(udg_ShieldGroup, target)
				endif
                else
		endif
        else
	endif
        call FlushChildHashtable(udg_hashdamage, GetHandleId(t))
	set target = null
return false
endfunction


//This timer is important, otherwise full hp units will be taken damage
function Trig_Damage_Detection_Conditions_Copy takes nothing returns boolean
    local unit tu = GetTriggerUnit()
    local real DamageMemory = GetEventDamage()
    local real CurrentLifeMem = GetUnitState(tu,UNIT_STATE_LIFE)
    local real MaxLifeMem = GetUnitState(tu,UNIT_STATE_MAX_LIFE)
    local real shieldvaluerealmem = I2R(GetCustomIntValue(tu))
    local timer t = CreateTimer()  
    call SaveReal(udg_hashdamage, GetHandleId(t), 1, DamageMemory)
    call SaveUnitHandle(udg_hashdamage, GetHandleId(t), 2, tu)
    call SaveReal(udg_hashdamage, GetHandleId(t), 3, CurrentLifeMem)
    if UnitHasBuffBJ(tu, 'B000') == true then
        if DamageMemory > CurrentLifeMem then
            call SaveBoolean(udg_hashdamage, GetHandleId(t), 4, true)
            call UnitAddAbility(tu, 'A001')
            //negative hp first, somehow I had to do this first for some odd reason
            call SetUnitAbilityLevel( tu, 'A001', 2 )
            call UnitRemoveAbility(tu, 'A001')          
        else
        endif
        call TimerStart(t, 0.00, false, function Damage_Block)
    else
        call FlushChildHashtable(udg_hashdamage, GetHandleId(t))
        set t = null
    endif  
    set tu = null
return false
endfunction


//===========================================================================
function InitTrig_Damage_Detection_Copy takes nothing returns nothing
	set gg_trg_Damage_Detection_Copy = CreateTrigger(  )
	call DisableTrigger( gg_trg_Damage_Detection_Copy )
	call TriggerAddCondition( gg_trg_Damage_Detection_Copy, Condition( function Trig_Damage_Detection_Conditions_Copy ) )
endfunction

Thanks for help, rep given to those of you who gave me help.

~Solved
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
I had to use a item ability which added insane amount of hp as you mentioned, the thing is that it still have the hp bonus so I had to make a negative one to remove it. Level 2 adds a lot of negative hp, level 3 adds same amount of hp to balance out negative hp.
Should not work? Item abilities that add health are bugged. I see you using the bug yet it is not required. You are meant to allow the unit to keep the health ability until the 0 second timer expires after which you remove it (no need to change levels). Each time the unit is damaged during that time you then alter what the resulting HP will be after the timer expires.

Remove the item ability that adds health and then restore the unit's health to the computed amount after the 0 second timer expires.

To break it down better...

Code:
Unit takes damage
Use damage and current life to compute resulting life
If resulting life is less than or equal to 0.504 then stop here and clean-up, the unit is going to die anyway so no need to block
Add dummy health ability
Set life to 100% (maximum life)
Start timer with 0 second timeout

Unit takes damage (between timer started and before timer expires)
Use damage and last computed resulting life to compute new resulting life
If resulting life is less than or equal to 0.504 then set life to 0.505 and clean-up (remove health ability as well) since the unit needs to die on that hit (kill correctly credited) and then end
Otherwise set life to 100% (maximum life)

Timer with 0 second timeout expires
Set life to 100% (maximum life)
Remove dummy health ability
Set life to last computed resulting life

The two damage handler functions could possibly be combined since you cannot add abilities twice and the same resulting life logic can be used with the first response using current life instead of last computed.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
At some point HP bugs on another thing.
When you give a unit a health bonus ability with insane amounts, the unit's actual health (after you removed that ability again) is not the same as before.
Giving him 500,000 hp should be more than the possible damage that a unit could take.
 
Level 12
Joined
May 22, 2015
Messages
1,051
What happens if you use create a trigger:
Events: "A unit dies"
conditions: unit has buff asdsdf //try one time without the buff just to see if it triggers.
Actions:
set unit life of triggering unit to 1.

You can convert it to jass later.

Setting a unit's life after they die doesn't do anything. Their life will be set, but they will still be dead.

I was thinking of trying to code something like this, myself, so it is good to see all these tips.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
But how do I stop damage if I do not use life add
You add life by giving the unit the item ability that raises maximum life. You only remove it after the damage has all been dealt. Damage comes in the form of one or more damage event responses. It is dealt only when the 0 timeout timer expires (before then there can still be outstanding damage to deal). It is important to factor in that the timer despite having 0 second timeout can still fire after several damage events are run for the same unit.

The reason behind this is probably the game engine being in a damage deal stage so all units that deal damage at the same time get to deal their damage. Timers are then run in a separate part of the game engine after damage processing is done.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
@DSG
It is more like this:
JASS:
function damage
    call Event(A_UNIT_TAKES_DAMAGE, target)
    call ApplyDamage(source, target, amount)
endfunction

Your actions like starting a timer, giving the health bonus ability, changing stuff, making unit invulnerable, etc happen during the "call Event()".
When that is done, the damage is applied. (this will not be filtered again for invulnerable units etc because that is done before)

The timers continue to run during the sleep of the main thread of the game.
This means that a timer can NEVER expire during other code's execution.
So the timer will continue to run and expire immediately once the current frame is done. (after the damage)
So at that moment, you can start your other code again which will then remove the health ability and reset the health.

What the big problem is is using GetWidgetLife() or GetUnitState() while that health ability is still on the unit.

Try using the function UnitDamageTarget() and then BJDebugMsg() to show the GetWidgetLife().
That will result in the 500.000 health that the target has because the timer didnt expire yet however the hp ability is still on the unit.

So after all.
Other actions can be run while the health ability is still on the unit but never before the damage is applied. (as long as you run it outside of the OnDamage event)
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
@DSG
It is more like this:
I know what happens, I have written such systems before.

Timers do not "run". Instead I am pretty sure they are implemented by polling (the game engine enters a timer poll stage where it then advances all timers and runs when appropriate). These are software timers so have nothing to do with the actual timer hardware in your processor next to there being some very loose synchronization if real-time system requirements are met (everything is schedulable).

The lack of information about WC3 timers is annoying. I really need to run some experiments some day.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
So....

"It is dealt only when the 0 timeout timer expires"
False.
"The reason behind this is probably the game engine being in a damage deal stage so all units that deal damage at the same time get to deal their damage."
False.

So I thought... you know... yea.

How timers work though... I have no idea.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
"It is dealt only when the 0 timeout timer expires"
False.
Do not misquote me...
It is dealt only when the 0 timeout timer expires (before then there can still be outstanding damage to deal).
Only when the timer expires can you be sure that all damage has been dealt so the health can be lowered. There might still be outstanding damage to deal to the unit before then. With each damage event you know that the damage from the previous damage event has been dealt but that is of no help since you still have the current damage event and its damage to deal with so you need to wait for the timer to expire anyway. Other events might also work (periodic trigger) but the timer is the classic and most common one used.
"The reason behind this is probably the game engine being in a damage deal stage so all units that deal damage at the same time get to deal their damage."
False.
Have you tested it? Even with a 0 timeout timer you can still get multiple damage event responses firing before a 0 timeout timer expires if you start the timer on the first damage event response. Hence it is in a damage dealing stage dealing all the damages at that point in time.

If you want to be more technical then actually it is likely in the attack damage dealing stage where all units landing attack damage at once do their attack damage without allowing timers to run between. Other damage stages do exist (eg trigger damage creates one for every call).

This makes shielding units a lot more difficult since you need to be able to cope with damage being dealt during the period after a damage event has occurred but before the 0 timeout timer that correctly sets the unit's health in response to the first damage. You also need to be able to support allowing the unit to die during that time and crediting the correct killer (the one which did the fatal damage).
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I dont misquote you.
Indeed there can be other damages that have been applied, but the damage is not applied when the timer expires which is what you said.

A 0s timer is the fastest way to remove it... unless you can make a real ThreadSleep of 0 seconds but afaik that is impossible without hacks.

On the second one.
The game is not in a state where it deals all the damages.
The game runs through the code where a unit is doing the on-hit of his basic attacks, or a function ran the UnitDamageTarget() function.

On the other hand... it doesnt make any difference though.

However, to leave at least something usefull in this thread :D
Here is what I call, the "Vanilla Damage Detection":
It runs the onDamage event and has a boolean which can be set to true to remove the original damage.
Ofcourse, I tried to make it as efficient as possible and added a few systems to make it better.
This is kind of the base for a Damage Detection System which you will place inside the VDD_OnDamage() function. (From that one, you call your own system.)
JASS:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  
//  Damage Detection
//  
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//requires TimerIndex (+TimerUtils), Unit Indexer

function VDD_OnDamage takes unit source, unit target, real amount returns nothing
    
    call BJDebugMsg(GetUnitName(target) + " has been damaged by " + GetUnitName(source) + " for " + R2S(amount) + " damage.")
    
endfunction





globals
    
    real                udg_VDD_HPThreshold                     = 0.5
    integer             udg_VDD_ABILITY_CHEAT_DEATH             = 'A000'    //map dependent
    integer             udg_VDD_RESET_COUNT                     = 15        //user preference
    trigger             udg_VDD_TRIGGER_ON_DAMAGE               = null
    rect                udg_VDD_WORLD_BOUNDS                  /*= GetWorldBounds()*///cannot be done on map loading
    
    integer             udg_VDD_DeindexCount                    = 0
    boolean             udg_VDD_RemoveOriginalDamage            = false
    timer array         udg_VDD_TimerArray
    unit array          udg_VDD_UnitArray
    real array          udg_VDD_HealthArray
    
endglobals

library vddSystem uses timerIndex
    
    function GetUnitLife takes unit whichUnit returns real
        local integer unitId = GetUnitUserData(whichUnit)
        local integer timerId
        
        if udg_VDD_TimerArray[unitId] != null then
            set timerId = GetTimerData(udg_VDD_TimerArray[unitId])
            call ReleaseIndexedTimer(udg_VDD_TimerArray[unitId])
            set udg_VDD_TimerArray[unitId] = null
            
            call UnitRemoveAbility(udg_VDD_UnitArray[timerId], udg_VDD_ABILITY_CHEAT_DEATH)
            call SetWidgetLife(udg_VDD_UnitArray[timerId], udg_VDD_HealthArray[timerId])
            
            set udg_VDD_UnitArray[timerId] = null
            set udg_VDD_HealthArray[timerId] = 0.
        endif
        
        return GetWidgetLife(whichUnit)
    endfunction
    
endlibrary

function VDD_RemoveStandardDamage takes nothing returns nothing
    local integer timerId = GetTimerData(GetExpiredTimer())
    local integer unitId = GetUnitUserData(udg_VDD_UnitArray[timerId])
    call ReleaseIndexedTimer(udg_VDD_TimerArray[unitId])
    set udg_VDD_TimerArray[unitId] = null
    
    call UnitRemoveAbility(udg_VDD_UnitArray[timerId], udg_VDD_ABILITY_CHEAT_DEATH)
    call SetWidgetLife(udg_VDD_UnitArray[timerId], udg_VDD_HealthArray[timerId])
    
    set udg_VDD_UnitArray[timerId] = null
    set udg_VDD_HealthArray[timerId] = 0.
endfunction

function VDD_TakingDamage takes nothing returns boolean
    local unit target
    local integer newIndex
    local real amount
    local integer id
    local integer targetId
    local timer t
    
    set amount = GetEventDamage()
    if amount == 0. then
        return false
    endif
    set target = GetTriggerUnit()
    set targetId = GetUnitUserData(target)
    
    if udg_VDD_TimerArray[targetId] != null then
        set t = udg_VDD_TimerArray[targetId]
        set udg_VDD_TimerArray[targetId] = null
        set id = GetTimerData(t)
        call UnitRemoveAbility(target, udg_VDD_ABILITY_CHEAT_DEATH)
        call SetWidgetLife(target, udg_VDD_HealthArray[id])
    else
        set t = NewIndexedTimer()
        set id = GetTimerData(t)
    endif
    
    /////////////////////////////////////////////////////////
    call VDD_OnDamage(GetEventDamageSource(), target, amount)
    /////////////////////////////////////////////////////////
    
    if udg_VDD_RemoveOriginalDamage then
        call TimerStart(t, 0, false, function VDD_RemoveStandardDamage)
        set udg_VDD_UnitArray[id] = target
        set udg_VDD_HealthArray[id] = GetWidgetLife(target)
        
        call UnitAddAbility(target, udg_VDD_ABILITY_CHEAT_DEATH)
        call SetWidgetLife(target, GetWidgetLife(target) + amount)
        
        set udg_VDD_TimerArray[targetId] = t
        set t = null
    else
        call ReleaseIndexedTimer(t)
        set t = null
        set udg_VDD_TimerArray[targetId] = null
        set udg_VDD_UnitArray[id] = null
        set udg_VDD_HealthArray[id] = 0.
    endif
    
    set target = null
    return false
endfunction

function VDD_InitDamageSystem takes nothing returns nothing
    local group g = CreateGroup()
    local unit FoG
    
    call DestroyTrigger(udg_VDD_TRIGGER_ON_DAMAGE)
    set udg_VDD_TRIGGER_ON_DAMAGE = CreateTrigger()
    
    call GroupEnumUnitsInRect(g, udg_VDD_WORLD_BOUNDS, null)
    
    loop
        set FoG = FirstOfGroup(g)
        exitwhen FoG == null
        call GroupRemoveUnit(g, FoG)
        
        call TriggerRegisterUnitEvent(udg_VDD_TRIGGER_ON_DAMAGE, FoG, EVENT_UNIT_DAMAGED)
    endloop
    call DestroyGroup(g)
    set g = null
    
    call TriggerAddCondition(udg_VDD_TRIGGER_ON_DAMAGE, Filter(function VDD_TakingDamage))
endfunction

function VDD_AddUnit takes nothing returns boolean
    call TriggerRegisterUnitEvent(udg_VDD_TRIGGER_ON_DAMAGE, udg_UDexUnits[udg_UDex], EVENT_UNIT_DAMAGED)
    
    set udg_VDD_DeindexCount = udg_VDD_DeindexCount +1
    if udg_VDD_DeindexCount == udg_VDD_RESET_COUNT then
        set udg_VDD_DeindexCount = 0
        call VDD_InitDamageSystem()
    endif
    return false
endfunction

function VDD_Initialize takes nothing returns nothing
    local trigger t
    set udg_VDD_WORLD_BOUNDS = GetWorldBounds()
    
    call VDD_InitDamageSystem()
    
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.)
    call TriggerAddCondition(t, Filter(function VDD_AddUnit))
    
    set t = null
    call DestroyTimer(GetExpiredTimer())
endfunction

//===========================================================================
function InitTrig_VDD_System takes nothing returns nothing
    
    call TimerStart(CreateTimer(), 0, false, function VDD_Initialize)
    
endfunction
You need TimerIndex (Wietlol), TimerUtils (Vexorian) and Unit Indexer (Bribe).
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
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
but the damage is not applied when the timer expires which is what you said.
But I never said it was. I said by the time the timer expires it is all done and that is because before then you have no certainty that the unit has received all the damage it will be getting.

It is dealt only when the 0 timeout timer expires (before then there can still be outstanding damage to deal).
Only when the timer expires can you be sure that all damage has been dealt so the health can be lowered.
 
In the case of multiple damage being dealt to the same target within the 0 second timeout, all you need to store is the resulting damage after modifications.

So let's say you have a spell that reduces damage taken by 20 for 2 hits. The unit gets hit by 40 then 50. So you apply the reduction and store 20 and 30 damage. In the timer callback, simply reduce the life by 20 and then by 30: SetWidgetLife(u, GetWidgetLife(u) - 20) (and then the same for 30).

That way, you shouldn't run into issues. For storage, you would just have a unit array and a real array that are stored in parallel (unit to be damaged, and amount of damage to be dealt).
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
That way, you shouldn't run into issues. For storage, you would just have a unit array and a real array that are stored in parallel (unit to be damaged, and amount of damage to be dealt).
That works for emulating SC2 armor pretty well and painlessly. The problem comes when you use some form of damage shield system since then you need to get how much shield you have left and if the resulting damage will be fatal or not.

You need to save the resulting life in that case and determine if a hit will be fatal or not. This is especially the case if you are using a life modification to stop "one hit death" abilities that deal more damage than unit life.
 

TKF

TKF

Level 19
Joined
Nov 29, 2006
Messages
1,266
One more question. I thought timer 0.00 was pretty instant and nothing there was no way 2 attacks could happen in between.

When I set local timer and starts the timer, wouldn't it be a unique instance for each timer started?
 
The lack of information about WC3 timers is annoying. I really need to run some experiments some day.
Yeah, regarding timers, there's one thing I was always curious about (but never remembered actually testing it):

JASS:
TimerStart(CreateTimer(), 1, true,  function test)
TimerStart(CreateTimer(), 5, false,  function test)

//vs:

TimerStart(CreateTimer(), 5, false, function test)
TimerStart(CreateTimer(), 1, true,  function test)
Will the periodic timer (at the 5 second mark) execute before or after the one-shot timer? Or is it pretty much random what executes first?

Does the order in which I issue the Timer commands matter (top vs. bottom example)?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
@Zwiebelchen
When you create a timer, and another timer, and run them both with the same duration, then the timer that is started first is executed first, no matter the periodic, handlerfunc, timer existence, or any other stuff.

That said, the 1s timer will run 5 times and after that, the 5s timer will run.
In the second example,
the 1s timer will run 4 times, then the 5s timer one time and then the 1s timer one time again.

@TKF
Yes, it will create new timers.
What you can do on the other hand is create a unit array and a real array and use a single timer.
This will never create more than one timer and will not fail either.
I will try to run some speedtests on what is actually better to use.
 
@Zwiebelchen
When you create a timer, and another timer, and run them both with the same duration, then the timer that is started first is executed first, no matter the periodic, handlerfunc, timer existence, or any other stuff.

That said, the 1s timer will run 5 times and after that, the 5s timer will run.
In the second example,
the 1s timer will run 4 times, then the 5s timer one time and then the 1s timer one time again.
Have you tested that or are you assuming things here?

Obviously, two five second one-shot timers will run in the order they are initialized, but especially with the periodic one, I'm not sure because technically it will be restarted every second, so it should fire after the one-shot no matter the initialization order.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I have tested it:
call TimerStart(CreateTimer(), 1, true, function test1)
call TimerStart(CreateTimer(), 5, false, function test2)
=
1: test1
2: test1
3: test1
4: test1
5: test1
5: test2


call TimerStart(CreateTimer(), 5, false, function test2)
call TimerStart(CreateTimer(), 1, true, function test1)
=
1: test1
2: test1
3: test1
4: test1
5: test2
5: test1
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
It is worth noting that floating point error might cause fixed ordering in the case of two timers which timeout share a common factor.

The example shown does not have such a problem because 1 and 5 are accurately representable and so 1+1+1+1+1 = 5.

However in the case of...
JASS:
call TimerStart(CreateTimer(), 0.1, false, function test2)
call TimerStart(CreateTimer(), 0.01, true, function test1)
// and
call TimerStart(CreateTimer(), 0.01, true, function test1)
call TimerStart(CreateTimer(), 0.1, false, function test2)
This might or might not be the case (depending on internal implementation).
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
That is indeed an interesting thing.
In that case, it would be annoying but understandable...

However, this made me wonder another thing...
Try this:
JASS:
function test1 takes nothing returns nothing
    call BJDebugMsg("test1")
endfunction
function test2 takes nothing returns nothing
    call BJDebugMsg("test2")
endfunction

//===========================================================================
function InitTrig_Test takes nothing returns nothing
    call TimerStart(CreateTimer(), 0.0002, false, function test2)
    call TimerStart(CreateTimer(), 0.0001, false, function test1)
endfunction
and then try it again but then adding another zero in the durations.
0.00002 and 0.00001
At that point, there is something that goes wrong.
It is either that, the timers are so close to each other that the game doesnt regocnize any difference between them and it runs the 0.00002 before 0.00001 OR
The reals are rounded to 4 decimals resulting in 0.0000 in both meaning that both will be the same.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
they dont round up, but the game takes more time to run one logical frame than 0.0002 seconds(that would be 5000 logical frames, I dont think you can achieve that) so he just elides them and runs them in order in next logical frame, because in that frame, the time delta is bigger than 0.0002, so it just runs it because it considers enough time passed
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,207
There is no limit to the number of times a timer can fire per frame. When it processes timers it must advance them iteratively until enough time passes. This is different from SC2 which forces anything time related to run at most once per frame.

It is either that, the timers are so close to each other that the game doesnt regocnize any difference between them and it runs the 0.00002 before 0.00001 OR
The reals are rounded to 4 decimals resulting in 0.0000 in both meaning that both will be the same.
Most likely rounding is occurring. Try computing the value with 1.0/10000.0.

It is also possible that you will eventually hit a limit whereby the timeout is so small that it gets lost in the error of the current time.

It is also possible that timers internally use fixed point and hence have different limits. This is how timers should be implemented as it is faster and translates better to computer time. If this is the case then you will eventually reach a float so small it is represented as 0.
 
Status
Not open for further replies.
Top