• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[System] Guardian (StructuredDD Extension)

Guardian

Preface:

Guardian is an extremely simple system which prevents a unit from taking lethal damage. If a unit is logged in the system, it will propagate a chain of callback effects assigned by the user. This system is made purely for preventing units from dying, and exists as a separate script for modularity purposes.

Design Explanation:

  1. User adds a Guardian instance in the form of a (unit,code) pair.
  2. Whenever a unit in the system takes damage that would kill it, the damage is prevented and all callback functions are called.
  3. Callback functions can use Guardian.mitigated and Guardian.who to see how much damage was prevented and act further on the unit.

Limitations:

  • Guardian requires StructuredDD and thus will not work with other Damage Detection Systems.
  • Guardian requires the original Table by Vexorian and thus will not work with other versions that have different API's.
  • Guardian is written in vJass and therefore requires Jass Newgen Pack (or JassHelper externally) to preprocess.

API:


  • Guardian.LIFE_SHIM- Set this to the rawcode of the "bonus life" ability (see test map). If you don't configure this, the unit will not properly avoid damage whose magnitude is greater than their max hit points.



  • Guardian.mitigated- In the scope of a callback function, Guardian.mitigated can be used to check how much damage was avoided using Guardian.
  • Guardian.who- In the scope of a callback function, Guardian.who can be used to see which unit avoided death.



  • Guardian.exists(unit)- Method used for checking if unit is indexed by Guardian (therefore it is unkillable).
  • Guardian.add(unit,string)- Method for adding a callback function to a unit's avoidance of death. u is indexed and a function with the name s will be called when u would otherwise die.
  • Guardian.remove(unit,string)- Method for removing a callback node from a unit in the system. Returns a warning in debug mode if the node doesn't exist.


The script:

Requires http://www.hiveworkshop.com/forums/...uctureddd-structured-damage-detection-216968/

JASS:
//*     API:
//* static real mitigated:  In the scope of a callback function, Guardian.mitigated
//*                         can be used to check how much damage was avoided using
//*                         Guardian.
//* static unit who:        In the scope of a callback function, Guardian.who can
//*                         be used to see which unit avoided death.
//* static method exists:   Method used for checking if a unit is indexed by
//*                         Guardian (therefore it is unkillable).
//* static method add:      Method for adding a callback function to a unit's
//*                         avoidance of death. Arguments are of the form (unit
//*                         'u',string 's') where u is indexed and a function with
//*                         the name s will be called when u would otherwise die.
//* static method remove:   Method for removing a callback node from a unit in
//*                         the system. Returns a warning in debug mode if the
//*                         node doesn't exist.
library Guardian requires StructuredDD, Table
    //* Contains data for callback functions. Each instance is a node in a linked
    //* list for an indexed unit.
    private struct callback
        string cbf
        thistype prev=-1
        thistype next=-1
    endstruct
    
    //* Shim structure for assigning a unique integer for a unit temporarily.
    private struct unitShim
        unit who
    endstruct
    
    //* The struct shim which gives the API its sexy style that everyone loves
    //* (Guardian is never instanciated)
    struct Guardian extends array
    
        //<< BEGIN CUSTOMIZE SECTION
        
        //* The rawcode of the "bonus life" ability. If it's not configured, the
        //* unit will die if it's attacked with damage greater than its max hit
        //* points.
        private static constant integer LIFE_SHIM='A000'
        
        //>> END CUSTOMIZE SECTION
        
        private static constant string WARNING="|cffffcc00[Guardian] [Guardian.remove] Warning: Attempted to remove an instance that doesn't exist."
        private static HandleTable ht
        private static HandleTable fromTimers
        
        //* In the scope of a callback function, used to see how much damage was
        //* avoided using Guardian
        readonly static real mitigated=-1.
        
        //* In the scope of a callback function, used to see which unit prevented
        //* death by means of Guardian
        readonly static unit who
        
        //* Used to check if a unit is indexed in Guardian.
        public static method exists takes unit u returns boolean
            return ht.exists(u)
        endmethod
        
        //* Adds a callback function to a unit when preventing death.
        public static method add takes unit u, string cb returns nothing
            local callback newDat=callback.create()
            local callback otherDat
            set newDat.cbf=cb
            if ht.exists(u) then
                set otherDat=ht[u]
                set newDat.next=otherDat
                set otherDat.prev=newDat
            endif
            set ht[u]=newDat
        endmethod
        
        //* Removes a callback function from a unit. If it's the last node left
        //* in the linked list, the unit will no longer avoid death.
        public static method remove takes unit u, string cb returns nothing
            local callback temp
            local callback otherDat
            if ht.exists(u) then
                set temp=ht[u]
                loop
                    exitwhen temp==-1
                    if temp.cbf==cb then
                        if temp.prev==-1 and temp.next!=-1 then
                            set otherDat=temp.next
                            set otherDat.prev=-1
                            set ht[u]=otherDat
                        elseif temp.prev!=-1 and temp.next!=-1 then
                            set otherDat=temp.next
                            set otherDat.prev=temp.prev
                            set otherDat=temp.prev
                            set otherDat.next=temp.next
                        elseif temp.prev==-1 and temp.next==-1 then
                            call ht.flush(u)
                        else
                            set otherDat=temp.prev
                            set otherDat.next=-1
                        endif
                        call temp.destroy()
                        return
                    endif
                    set temp=temp.next
                endloop
            endif
            debug call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,thistype.WARNING)
        endmethod
        
        //* Auxillary method used by timers to set a units life after 0 seconds.
        private static method delay takes nothing returns nothing
            local timer time=GetExpiredTimer()
            local unitShim tempUS=fromTimers[time]
            call DestroyTimer(time)
            call fromTimers.flush(time)
            set time=null
            call UnitRemoveAbility(tempUS.who,thistype.LIFE_SHIM)
            call SetWidgetLife(tempUS.who,.406)
            call tempUS.destroy()
        endmethod
        
        //* StructuredDD handler used to look for units about to die.
        private static method h takes nothing returns nothing
            local unit tU=GetTriggerUnit()
            local real damage=GetEventDamage()
            local real life=GetWidgetLife(tU)
            local timer time
            local unitShim tempUS
            local callback temp
            if ht.exists(tU) and damage>life-.405 then
                set thistype.mitigated=damage+.406-life
                set thistype.who=tU
                if damage<GetUnitState(tU,UNIT_STATE_MAX_LIFE) then
                    call SetWidgetLife(tU,damage+.406)
                else
                    call UnitAddAbility(tU,thistype.LIFE_SHIM)
                    call SetWidgetLife(tU,damage+.406)
                    set time=CreateTimer()
                    set tempUS=unitShim.create()
                    set tempUS.who=tU
                    set fromTimers[time]=tempUS
                    call TimerStart(time,0.,false,function thistype.delay)
                    set time=null
                endif
                set temp=ht[tU]
                loop
                    exitwhen temp==-1
                    call ExecuteFunc(temp.cbf)
                    set temp=temp.next
                endloop
            endif
            set tU=null
        endmethod
        
        //* Initialization method
        private static method onInit takes nothing returns nothing
            set ht=HandleTable.create()
            set fromTimers=HandleTable.create()
            call StructuredDD.addHandler(function thistype.h)
        endmethod
    endstruct
endlibrary

Example Test Scope:


JASS:
scope test initializer i
    globals
        private unit u
    endglobals
    
    private function cbf takes nothing returns nothing
        call DisplayTextToPlayer(GetLocalPlayer(),0.,0.,"OMG!")
    endfunction
    
    private function c takes nothing returns boolean
        call Guardian.remove(u,SCOPE_PRIVATE+"cbf")
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t,Condition(function c))
        set u=CreateUnit(Player(0),'hfoo',0.,0.,0.)
        call SetWidgetLife(u,1.)
        call Guardian.add(u,SCOPE_PRIVATE+"cbf")
    endfunction
endscope

(Yes, it's that simple)


Change Log:


2013.05.27 - Initial submission to Hive: Jass Resources: Submissions


Special Thanks:

  • Magtheridon96 for trying to standardize warning/error formatting
  • -Kobas- for creating a map submission template, which turned out useful for system submissions as well.
  • Vexorian for developing JassHelper. Without vJass, I almost certainly would not still be scripting for wc3.
  • The various developers of JNGP including PitzerMike and MindworX. Integrating JassHelper and TESH is a godsend.
 

Attachments

  • Guardian001.w3x
    32.6 KB · Views: 164
Last edited:
Level 10
Joined
Sep 19, 2011
Messages
527
Try this to callback:

JASS:
struct test extends array
    private static thistype array instances
    private static integer count = 0
    
    method destroy takes nothing returns nothing
        set instances[this] = instances[count]
        set count = count - 1
    endmethod
    
    static method create takes real r returns thistype
        local thistype this
        
        set count = count + 1
        set this = count
        set instances[count] = this
        
        // your code
        
        return this
    endmethod
endstruct

To iterate each instance:

JASS:
local integer i = 0
local thistype this

loop
    set i = i + 1
    set this = instances[i]
    
    // your code
    
    exitwhen i == count
endloop

private static method h takes nothing returns nothing

What's happening man?, wtf is that name xD

edit:

I think that this:

JASS:
                set thistype.mitigated=damage+.406-life
                set thistype.who=tU

can cause recursion problems.

edit: Using a dummy trigger, wouldn't be easier than ExecuteFunc?

edit: You can make remove O(1) with StringHash
 
Try this to callback:

JASS:
struct test extends array
    private static thistype array instances
    private static integer count = 0
    
    method destroy takes nothing returns nothing
        set instances[this] = instances[count]
        set count = count - 1
    endmethod
    
    static method create takes real r returns thistype
        local thistype this
        
        set count = count + 1
        set this = count
        set instances[count] = this
        
        // your code
        
        return this
    endmethod
endstruct

To iterate each instance:

JASS:
local integer i = 0
local thistype this

loop
    set i = i + 1
    set this = instances[i]
    
    // your code
    
    exitwhen i == count
endloop

Won't work because I don't iterate all callbacks, only all the ones attached to a specific unit (That's why it's a linked list)

private static method h takes nothing returns nothing

What's happening man?, wtf is that name xD

h for handler. Sorry, maybe I'll replace this in the first update.


edit:

I think that this:

JASS:
                set thistype.mitigated=damage+.406-life
                set thistype.who=tU

can cause recursion problems.

How?

edit: Using a dummy trigger, wouldn't be easier than ExecuteFunc?
No, because I would have to rebuild the trigger every time I removed a handler from a unit. It might perform better, probably is safer, but it's definitely not easier, nor more intuitive.

edit: You can make remove O(1) with StringHash

And then what, lookup the node using the string's key? That's an O(lg(n)) operation (average case). You should better test it and see how many callback nodes need to be added to the unit before hashing returns better performance. My guess is 5 nodes.

Regardless, the system is already O(lg(n)) in number of nodes (average case).
 
Level 10
Joined
Sep 19, 2011
Messages
527
Won't work because I don't iterate all callbacks, only all the ones attached to a specific unit (That's why it's a linked list)

Oh.

h for handler. Sorry, maybe I'll replace this in the first update.

I know, but damn, that remains me to nes xD.


Reflect I believe.

No, because I would have to rebuild the trigger every time I removed a handler from a unit. It might perform better, probably is safer, but it's definitely not easier, nor more intuitive.

TriggerRemoveCondition?.

And then what, lookup the node using the string's key? That's an O(lg(n)) operation (average case). You should better test it and see how many callback nodes need to be added to the unit before hashing returns better performance. My guess is 5 nodes.

Regardless, the system is already O(lg(n)) in number of nodes (average case).

I will check that out.

edit: Quick test:

JASS:
scope test initializer i
    globals
        private unit u1
        private unit u2
    endglobals
    
    private function cba takes nothing returns nothing
        local unit prevWho = Guardian.who
        
        call UnitDamageTarget(prevWho, u1, 1, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        
        if (prevWho != Guardian.who) then
            call BJDebugMsg("RECURSION PROBLEM")
        endif
        
        set prevWho = null
    endfunction
    
    private function c takes nothing returns boolean
        call Guardian.remove(u1,SCOPE_PRIVATE+"cba")
        call Guardian.remove(u2,SCOPE_PRIVATE+"cba")
        return false
    endfunction
    
    private function i takes nothing returns nothing
        local trigger t=CreateTrigger()
        call TriggerRegisterPlayerEvent(t,Player(0),EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddCondition(t,Condition(function c))
        
        set u1=CreateUnit(Player(0),'hfoo',0.,0.,0.)
        call SetWidgetLife(u1,1.)
        call Guardian.add(u1,SCOPE_PRIVATE+"cba")
        
        set u2=CreateUnit(Player(0),'hfoo',0.,0.,0.)
        call SetWidgetLife(u2,1.)
        call Guardian.add(u2,SCOPE_PRIVATE+"cba")
    endfunction
endscope

Fails (the unit dies) and shows recursion problem.

edit: Dummy trigger + alias = win :3

JASS:
library Guardian requires StructuredDD
    globals
        private constant integer LIFE_SHIM = 'A000'
    endglobals

    globals
        private hashtable hashTable = InitHashtable()
    endglobals
    
    private struct Callback
        readonly trigger trigger
        private unit unit
        private integer count
        
        method destroy takes nothing returns nothing
            call FlushChildHashtable(hashTable, GetHandleId(this.unit))
            call DestroyTrigger(this.trigger)
            
            set this.trigger = null
            set this.unit = null
            set this.count = 0
            
            call this.deallocate()
        endmethod
        
        static method unitHasCallbackInstance takes unit which returns boolean
            return HaveSavedInteger(hashTable, GetHandleId(which), 0)
        endmethod
        
        static method getUnitCallBackInstance takes unit which returns thistype
            return LoadInteger(hashTable, GetHandleId(which), 0)
        endmethod
        
        static method remove takes unit which, string alias returns nothing
            local thistype this = getUnitCallBackInstance(which)
            
            set this.count = this.count - 1
            
            if (this.count <= 0) then
                call this.destroy()
            else
                call TriggerRemoveCondition(this.trigger, LoadTriggerConditionHandle(hashTable, GetHandleId(which), StringHash(alias)))
            endif
        endmethod
        
        static method add takes unit which, code callback, string alias returns nothing
            local thistype this
            local triggercondition triggerCondition
            
            if (unitHasCallbackInstance(which)) then
                set this = getUnitCallBackInstance(which)
            else
                set this = thistype.allocate()
                
                set this.trigger = CreateTrigger()
                set this.unit = which
                set this.count = 0
                
                call SaveInteger(hashTable, GetHandleId(which), 0, this)
            endif
            
            set this.count = this.count + 1
            set triggerCondition = TriggerAddCondition(this.trigger, Condition(callback))
            
            call SaveTriggerConditionHandle(hashTable, GetHandleId(which), StringHash(alias), triggerCondition)
            
            set triggerCondition = null
        endmethod
    endstruct

    struct Guardian extends array
        readonly static real mitigated = 0.
        readonly static unit who = null
        
        static method add takes unit which, code callback, string alias returns nothing
            call Callback.add(which, callback, alias)
        endmethod
        
        static method remove takes unit which, string alias returns nothing
            call Callback.remove(which, alias)
        endmethod
        
        private static method afterDamage takes nothing returns nothing
            local timer expiredTimer = GetExpiredTimer()
            local integer expiredTimerId = GetHandleId(expiredTimer)
            local unit triggerUnit = LoadUnitHandle(hashTable, expiredTimerId, 0)
            
            call UnitRemoveAbility(triggerUnit, LIFE_SHIM)
            call SetWidgetLife(triggerUnit, .406)
            
            call FlushChildHashtable(hashTable, expiredTimerId)
            
            call PauseTimer(expiredTimer)
            call DestroyTimer(expiredTimer)
            
            set expiredTimer = null
            set triggerUnit = null
        endmethod
        
        private static method onDamage takes nothing returns nothing
            local unit triggerUnit = GetTriggerUnit()
            local integer triggerUnitId
            local real eventDamage
            local real triggerUnitLife
            local real prevMitigated
            local unit prevWho
            local timer afterDamageTimer
            local Callback callback
            
            if (Callback.unitHasCallbackInstance(triggerUnit)) then
                set eventDamage = GetEventDamage()
                set triggerUnitLife = GetWidgetLife(triggerUnit)
                
                if (eventDamage > triggerUnitLife - .405) then
                    set triggerUnitId = GetHandleId(triggerUnit)
                
                    set prevMitigated = mitigated
                    set prevWho = who
                
                    set mitigated= eventDamage + .406 - triggerUnitLife
                    set who = triggerUnit
                    
                    if (eventDamage < GetUnitState(triggerUnit, UNIT_STATE_MAX_LIFE)) then
                        call SetWidgetLife(triggerUnit, eventDamage + .406)
                    else
                        call UnitAddAbility(triggerUnit, LIFE_SHIM)
                        call SetWidgetLife(triggerUnit, eventDamage + .406)
                        
                        set afterDamageTimer = CreateTimer()
                        call SaveUnitHandle(hashTable, GetHandleId(afterDamageTimer), 0, triggerUnit)
                        
                        call TimerStart(afterDamageTimer, 0., false, function thistype.afterDamage)
                        
                        set afterDamageTimer = null
                    endif
                    
                    set callback = Callback.getUnitCallBackInstance(triggerUnit)
                    call TriggerEvaluate(callback.trigger)
                    
                    set mitigated = prevMitigated
                    set who = prevWho
                    
                    set prevWho = null
                endif
            endif
            
            set triggerUnit = null
        endmethod
        
        private static method onInit takes nothing returns nothing
            call StructuredDD.addHandler(function thistype.onDamage)
        endmethod
    endstruct
endlibrary

example:

call Guardian.add(unit, function cba, SCOPE_PRIVATE + "cba")
call Guardian.remove(unit, SCOPE_PRIVATE + "cba")

Still has the problem that the unit dies (with the above test), but it shows better fps when testing 1000 handlers :).
 
Last edited:
Reflect I believe.

Why would one want to reflect damage when triggering a death avoided event..? the damage source isn't even in the scope of the system.

TriggerRemoveCondition?.

Right, but that means you have to store all of the triggerconditions in a hashtable (as you have done)

edit: Dummy trigger + alias = win :3

JASS:
library Guardian requires StructuredDD
    globals
        private constant integer LIFE_SHIM = 'A000'
    endglobals

    globals
        private hashtable hashTable = InitHashtable()
    endglobals
    
    private struct Callback
        readonly trigger trigger
        private unit unit
        private integer count
        
        method destroy takes nothing returns nothing
            call FlushChildHashtable(hashTable, GetHandleId(this.unit))
            call DestroyTrigger(this.trigger)
            
            set this.trigger = null
            set this.unit = null
            set this.count = 0
            
            call this.deallocate()
        endmethod
        
        static method unitHasCallbackInstance takes unit which returns boolean
            return HaveSavedInteger(hashTable, GetHandleId(which), 0)
        endmethod
        
        static method getUnitCallBackInstance takes unit which returns thistype
            return LoadInteger(hashTable, GetHandleId(which), 0)
        endmethod
        
        static method remove takes unit which, string alias returns nothing
            local thistype this = getUnitCallBackInstance(which)
            
            set this.count = this.count - 1
            
            if (this.count <= 0) then
                call this.destroy()
            else
                call TriggerRemoveCondition(this.trigger, LoadTriggerConditionHandle(hashTable, GetHandleId(which), StringHash(alias)))
            endif
        endmethod
        
        static method add takes unit which, code callback, string alias returns nothing
            local thistype this
            local triggercondition triggerCondition
            
            if (unitHasCallbackInstance(which)) then
                set this = getUnitCallBackInstance(which)
            else
                set this = thistype.allocate()
                
                set this.trigger = CreateTrigger()
                set this.unit = which
                set this.count = 0
                
                call SaveInteger(hashTable, GetHandleId(which), 0, this)
            endif
            
            set this.count = this.count + 1
            set triggerCondition = TriggerAddCondition(this.trigger, Condition(callback))
            
            call SaveTriggerConditionHandle(hashTable, GetHandleId(which), StringHash(alias), triggerCondition)
            
            set triggerCondition = null
        endmethod
    endstruct

    struct Guardian extends array
        readonly static real mitigated = 0.
        readonly static unit who = null
        
        static method add takes unit which, code callback, string alias returns nothing
            call Callback.add(which, callback, alias)
        endmethod
        
        static method remove takes unit which, string alias returns nothing
            call Callback.remove(which, alias)
        endmethod
        
        private static method afterDamage takes nothing returns nothing
            local timer expiredTimer = GetExpiredTimer()
            local integer expiredTimerId = GetHandleId(expiredTimer)
            local unit triggerUnit = LoadUnitHandle(hashTable, expiredTimerId, 0)
            
            call UnitRemoveAbility(triggerUnit, LIFE_SHIM)
            call SetWidgetLife(triggerUnit, .406)
            
            call FlushChildHashtable(hashTable, expiredTimerId)
            
            call PauseTimer(expiredTimer)
            call DestroyTimer(expiredTimer)
            
            set expiredTimer = null
            set triggerUnit = null
        endmethod
        
        private static method onDamage takes nothing returns nothing
            local unit triggerUnit = GetTriggerUnit()
            local integer triggerUnitId
            local real eventDamage
            local real triggerUnitLife
            local real prevMitigated
            local unit prevWho
            local timer afterDamageTimer
            local Callback callback
            
            if (Callback.unitHasCallbackInstance(triggerUnit)) then
                set eventDamage = GetEventDamage()
                set triggerUnitLife = GetWidgetLife(triggerUnit)
                
                if (eventDamage > triggerUnitLife - .405) then
                    set triggerUnitId = GetHandleId(triggerUnit)
                
                    set prevMitigated = mitigated
                    set prevWho = who
                
                    set mitigated= eventDamage + .406 - triggerUnitLife
                    set who = triggerUnit
                    
                    if (eventDamage < GetUnitState(triggerUnit, UNIT_STATE_MAX_LIFE)) then
                        call SetWidgetLife(triggerUnit, eventDamage + .406)
                    else
                        call UnitAddAbility(triggerUnit, LIFE_SHIM)
                        call SetWidgetLife(triggerUnit, eventDamage + .406)
                        
                        set afterDamageTimer = CreateTimer()
                        call SaveUnitHandle(hashTable, GetHandleId(afterDamageTimer), 0, triggerUnit)
                        
                        call TimerStart(afterDamageTimer, 0., false, function thistype.afterDamage)
                        
                        set afterDamageTimer = null
                    endif
                    
                    set callback = Callback.getUnitCallBackInstance(triggerUnit)
                    call TriggerEvaluate(callback.trigger)
                    
                    set mitigated = prevMitigated
                    set who = prevWho
                    
                    set prevWho = null
                endif
            endif
            
            set triggerUnit = null
        endmethod
        
        private static method onInit takes nothing returns nothing
            call StructuredDD.addHandler(function thistype.onDamage)
        endmethod
    endstruct
endlibrary

example:

call Guardian.add(unit, function cba, SCOPE_PRIVATE + "cba")
call Guardian.remove(unit, SCOPE_PRIVATE + "cba")

Still has the problem that the unit dies (with the above test), but it shows better fps when testing 1000 handlers :).

Right, but how about 1,2,3,4,10 handlers? I can't think of any real application where 1000 is a useful metric...
 
Level 10
Joined
Sep 19, 2011
Messages
527
Why would one want to reflect damage when triggering a death avoided event..? the damage source isn't even in the scope of the system.

Maybe with ability.

Right, but that means you have to store all of the triggerconditions in a hashtable (as you have done)

What's the problem of that?

Right, but how about 1,2,3,4,10 handlers? I can't think of any real application where 1000 is a useful metric...

Just a high number to see if it really worth.
 
That's why I said:

You should better test it and see how many callback nodes need to be added to the unit before hashing returns better performance. My guess is 5 nodes.

And to be honest, the hashtable method would have to be significantly faster even for one node to constitute such an ugly code change and unintuitive API change, in my opinion.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Some ideas for improvement:

  • This seems to bug in combination with your DamageType library. If I implement it into the testmap, units don't get saved from spell damage.
  • Is it intended that units still can take damage unless they are about to be killed? This is a bit unintuitive and should be mentioned in the documentation.
  • Adding a Guardian in an OnDamage handler doesn't work, so this can't be used to block unit specific attacks.
  • When I added a guardian to the damage target in an OnDamage handler, the map crashed after only 10-12 damage events.
 
Some ideas for improvement:

This seems to bug in combination with your DamageType library. If I implement it into the testmap, units don't get saved from spell damage.

Sounds like a bug with handler priorities. DamageType has a basic requirement that all onDamage handlers used in libraries include requires DamageType. I wanted to avoid this but I'll look into it when I have more time.

Is it intended that units still can take damage unless they are about to be killed? This is a bit unintuitive and should be mentioned in the documentation.

Yes, Guardian only prevents units from dying. What you described also sounds useful though. Perhaps Guardian could do both.

Adding a Guardian in an OnDamage handler doesn't work, so this can't be used to block unit specific attacks.
You have to add guardian to the unit first and then check/remove onDamage.

When I added a guardian to the damage target in an OnDamage handler, the map crashed after only 10-12 damage events.

I'll have to look into that, but based on the commentary above, it might sort itself out.

Thanks for taking a look at the system, sorry I lack time to support it properly.
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
Just for lulz:
JASS:
                if damage<GetUnitState(tU,UNIT_STATE_MAX_LIFE) then
                    call SetWidgetLife(tU,damage+.406)
                else
                    call UnitAddAbility(tU,thistype.LIFE_SHIM)
                    call SetWidgetLife(tU,damage+.406)
                    set time=CreateTimer()
                    set tempUS=unitShim.create()
                    set tempUS.who=tU
                    set fromTimers[time]=tempUS
                    call TimerStart(time,0.,false,function thistype.delay)
                    set time=null
                endif
=>
JASS:
                if damage>=GetUnitState(tU,UNIT_STATE_MAX_LIFE) then
                    call UnitAddAbility(tU,thistype.LIFE_SHIM)
                    set time=CreateTimer()
                    set tempUS=unitShim.create()
                    set tempUS.who=tU
                    set fromTimers[time]=tempUS
                    call TimerStart(time,0.,false,function thistype.delay)
                    set time=null
                endif
                call SetWidgetLife(tU,damage+.406)

Anywayz, I hope this can fires event called EVENT_UNIT_BLOCK_DAMAGE which is fired at delay function, has event responses such as GetBlockingUnit(), GetBlockedUnit(), GetBlockedDamageAmount(), etc. So that I can use this in my illusion snippet. This can shorten my code a lot :grin:
 
Just for lulz:
JASS:
                if damage<GetUnitState(tU,UNIT_STATE_MAX_LIFE) then
                    call SetWidgetLife(tU,damage+.406)
                else
                    call UnitAddAbility(tU,thistype.LIFE_SHIM)
                    call SetWidgetLife(tU,damage+.406)
                    set time=CreateTimer()
                    set tempUS=unitShim.create()
                    set tempUS.who=tU
                    set fromTimers[time]=tempUS
                    call TimerStart(time,0.,false,function thistype.delay)
                    set time=null
                endif
=>
JASS:
                if damage>=GetUnitState(tU,UNIT_STATE_MAX_LIFE) then
                    call UnitAddAbility(tU,thistype.LIFE_SHIM)
                    set time=CreateTimer()
                    set tempUS=unitShim.create()
                    set tempUS.who=tU
                    set fromTimers[time]=tempUS
                    call TimerStart(time,0.,false,function thistype.delay)
                    set time=null
                endif
                call SetWidgetLife(tU,damage+.406)

Thanks, I made a note to fix this next time I update it.

I hope this can fires event called EVENT_UNIT_BLOCK_DAMAGE which is fired at delay function, has event responses such as GetBlockingUnit(), GetBlockedUnit(), GetBlockedDamageAmount()

It doesn't do that, but it should be easy to write a shim.
 
Top