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

Damage Detection System

Status
Not open for further replies.
This is a damage detection system that detects damage dealt. It has three events, a detection event, a finalization of damage event, and the actual dealing of damage event.

The ability to modify damage via variable has been outsourced and simplified. The library will also adjust some abilities automatically for the spell detection to work properly with some of them.

[note="Adage"]
From Rising_Dusk's priority damage registration, I got some of my damage trigger design.
From jesus4lyf's DDS, I figured out how to block damage
From Leandrotp's bracers trick, I got spell detection.
From Wietlol's discussion on AfterDamage events, I got an after damage event.
From Unit Indexers, I automated registration of units to the damage trigger.

Thank you all for inspiring me to make this damage detection library.
[/note]

Code:

Documentation

Demo



Core Damage Detection:
JASS:
library DamageDetection uses AllocationAndLinks, /*
  
    */ optional EventStruct, /*
    */ optional UnitEventV, /* not Bribe's version.
    */ optional UnitDex, /*
    */ optional Table,/*
    */ optional OneTimer
  
    native UnitAlive takes unit id returns boolean
  
    globals
        private constant real ETHEREAL_OFFSET = 1.66
    endglobals
  
    function IsUnitAffectByMagic takes unit which returns boolean
        return not IsUnitType(which, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(which, UNIT_TYPE_MECHANICAL) and not IsUnitType(which, UNIT_TYPE_ANCIENT) and not IsUnitType(which, UNIT_TYPE_STRUCTURE)
    endfunction
  
    //  You should set the global health to the same value as the externalblock HEALTH...
  
    /*
    //! externalblock extension=lua ObjectMerger $FILENAME$
        //! i HEALTH = 50000000
      
        //! i CONDITION = false
      
        //! i function health_cheat_create()
            //! i if CONDITION then
                //! i setobjecttype("abilities")
              
                //! i createobject("AIlf", "@hth")
                //! i makechange(current, "anam", "Health Cheat")
              
                //! i makechange(current, "alev", 1)
              
                //! i makechange(current, "Ilif", 1, HEALTH)
            //! i end
        //! i end
      
        //! i function spell_differentiation()
            //! i if CONDITION then
                //! i setobjecttype("abilities")
              
                //  Make new Bracers
                //! i createobject("AIsr", "@spl")
                //! i makechange(current, "isr2", 1, 2.00)
                //! i makechange(current, "anam", "Spell Detection")
            //! i end
        //! i end
      
        //! i function mana_shield()
            //! i setobjecttype("abilities")
          
            //! i modifyobject("ACmf")
          
            //! i makechange(current, "Nms2", 1, 1.)
            //! i makechange(current, "Nms1", 1, 0.)
          
            //! i modifyobject("ANms")
          
            //! i makechange(current, "Nms2", 1, 1.)
            //! i makechange(current, "Nms2", 2, 1.)
            //! i makechange(current, "Nms2", 3, 1.)
            //! i makechange(current, "Nms1", 1, 0.)
            //! i makechange(current, "Nms1", 2, 0.)
            //! i makechange(current, "Nms1", 3, 0.)
        //! i end
      
        //! i function mechanics_change()
            //! i if CONDITION then
                //! i setobjecttype("abilities")
              
                //  Modify Locust Swarm
                //! i modifyobject("AUls")
                //! i makechange(current, "Uls4", 1, -0.75)
              
                //  Make new Anti-Magic Shell
                //! i createobject("AUfa", ";ams")
              
                //! i makechange(current, "aart", "ReplaceableTextures\\CommandButtons\\BTNAntiMagicShell.blp")
              
                //! i makechange(current, "Ufa2", 1, 0.00)
                //! i makechange(current, "Ufa1", 1, 90.00)
              
                //! i makechange(current, "abuf", 1, "Bams,Bam2")
              
                //! i makechange(current, "aran", 1, 500.00)
              
                //! i makechange(current, "acdn", 1, 0.00)
              
                //! i makechange(current, "ahdu", 1, 0.01)
                //! i makechange(current, "adur", 1, 0.01)
              
                //! i makechange(current, "aher", 0)
              
                //! i makechange(current, "alev", 1)
                //! i makechange(current, "atar", 1, "air,ground,vuln,invu")
              
                //! i makechange(current, "areq", "Ruba")
              
                //! i makechange(current, "anam", "Anti-magic Shield")
                //! i makechange(current, "ansf", "(Triggered)")
                //! i makechange(current, "ahky", "N")
              
                //! i makechange(current, "atp1", 1, "A|cffffcc00n|rti-magic Shell")
                //! i makechange(current, "aub1", 1, "Creates a barrier that stops spells from affecting a target unit. |nLasts <;ams:AUfa,Dur1> seconds. |n|n|cffffcc00Triggered|r")
          
                //  Give ability to banshee
                //! i setobjecttype("units")
              
                //! i modifyobject("uban")
              
                //! i makechange(current, "uabi", ";ams,Acrs,Apos,Aiun")
              
                //  Modify Mana Shield
                //! i mana_shield()
            //! i end
        //! i end
      
        //! i health_cheat_create()
        //! i spell_differentiation()
        //! i mechanics_change()
    //! endexternalblock
    */
  
    /*
    globals
        constant real HEALTH = 50000000
    endglobals
    */
  
    globals
        private constant trigger DAMAGE_TRIG = CreateTrigger()
        private constant integer DMG_MAX_REG = 60
    endglobals
  
    globals
        constant integer HEALTH_CHEAT = '@hth'
        constant integer BRACERS = '@spl'
    endglobals
  
    private struct DmgEventS extends array
static if LIBRARY_EventStruct then
        static Event DmgEvent = 0
        static Event ZeroDmgEvent = 0
else
        static real DmgEvent = -8191.
        static real ZeroDmgEvent = -8191.
endif
    endstruct
  
static if LIBRARY_EventStruct then
    constant function GetDamageEventType takes integer value returns Event
        if value == 0 then
            return DmgEventS.ZeroDmgEvent
        elseif value == 1 then
            return DmgEventS.DmgEvent
        endif
        return 0
    endfunction

    private module Init_m
        private static method onInit takes nothing returns nothing
            set DmgEventS.ZeroDmgEvent = Event.create()
            set DmgEventS.DmgEvent = Event.create()
        endmethod
    endmodule
    private struct Init_s extends array
        implement Init_m
    endstruct
endif
  
static if LIBRARY_Table then
    private module DamageEvent_Bucket_m
        private static method onInit takes nothing returns nothing
            set Bucket = Table.create()
        endmethod
    endmodule
endif
  
    public struct DamageEventInterface extends array
        static real currHP = 0.
    endstruct
  
    struct DamageEvent extends array
        implement Alloc_MyPad
static if not LIBRARY_Table then
        implement DoubleLink
endif

        //  Trigger Creation portion
        private trigger trig
        private group group
        private integer event_count
        private integer unit_count
      
        //  Damage Detection mechanism
        private static real finalHP = 0.
      
        readonly static unit source = null
        readonly static unit target = null
      
        readonly static real dmg = 0.
        readonly static real finalDmg = 0.
      
        readonly static player p_source = null
        readonly static player p_target = null
      
        readonly static boolean spell = false
      
        //  Arrays
        private static boolean detect = true
        private static integer count = 0
      
        private static boolean array detect_a
      
        private static real array finalHP_a
        private static trigger array trigger_a
      
        readonly static unit array source_a
        readonly static unit array target_a
      
        readonly static real array dmg_a
        readonly static real array finalDmg_a
      
        readonly static player array p_source_a
        readonly static player array p_target_a
      
        readonly static boolean array spell_a
      
static if LIBRARY_Table then
        private static Table Bucket = 0
      
        implement DamageEvent_Bucket_m
      
        private static method bucket_s takes unit u, thistype that returns nothing
            set Bucket.integer[GetUnitData(u)] = that
        endmethod
      
        private static method bucket takes unit u returns thistype
            return Bucket.integer[GetUnitData(u)]
        endmethod
endif

        private static method get takes unit u returns thistype
static if LIBRARY_Hashtable then
            return bucket(u)
else
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0 or IsUnitInGroup(u, group)
                set this = next
            endloop
            return this
endif
        endmethod
      
        //  UnitDamageTarget made safe
        static method damage takes unit u, unit u2, real r, boolean b1, boolean b2, attacktype a, damagetype d, weapontype w returns boolean
            local thistype this = get(u2)
            local boolean br = false
          
            set detect = false
            set br = UnitDamageTarget(u, u2, r, b1, b2, a, d, w)
            set detect = true
          
            return br
        endmethod
      
        //  Pure damage is detectable by the damage trigger, but does not fire any more events...
        static method deal_pure takes unit u, unit u2, real r returns boolean
            local boolean br = false
          
            if GetWidgetLife(u2) - r >= 0.406 then
                set br = damage(u, u2, 0., true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                if br then
                    call SetWidgetLife(u2, GetWidgetLife(u2) - r)
                endif
                return br
            endif
          
            set br = UnitDamageTarget(u, u2, r, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            return br
        endmethod
      
        private static method get_trigger takes trigger t returns integer
            local integer i = 1
            loop
                exitwhen i > count or trigger_a[i] == t
                set i = i + 1
            endloop
            if i > count then
                set i = 0
            endif
            return i
        endmethod
      
        private static method onDamage_null_vars takes integer i returns nothing
            set source_a[i] = null
            set target_a[i] = null
          
            set dmg_a[i] = 0.
          
            set p_source_a[i] = null
            set p_target_a[i] = null
          
            set detect_a[i] = false
            set spell_a[i] = false
          
            set finalDmg_a[i] = 0.
            set finalHP_a[i] = 0.
                      
            set trigger_a[i] = null
          
            set count = count - 1
            if count <= 0 then
                set count = 0
            endif
        endmethod
      
        private static method onDamage_reset_vars takes integer i returns nothing
            set source = source_a[i]
            set target = target_a[i]
          
            set dmg = dmg_a[i]
          
            set p_source = p_source_a[i]
            set p_target = p_target_a[i]
          
            set detect = detect_a[i]
            set spell = spell_a[i]
        endmethod
      
        //  Damage Detection Event handling
        private static method onLifeEvent takes nothing returns nothing
            local trigger t = GetTriggeringTrigger()
            local integer arr = get_trigger(t)
          
            if GetTriggerEventId() == EVENT_UNIT_STATE_LIMIT then
                call UnitRemoveAbility(target_a[arr], HEALTH_CHEAT)
                call SetWidgetLife(target_a[arr], finalHP_a[arr])
                set DamageEventInterface.currHP = GetWidgetLife(target_a[arr])
              
                if detect_a[arr] then
static if LIBRARY_EventStruct then
                    set DmgEventS.DmgEvent.fire = 2.
else
                    set DmgEventS.DmgEvent = 2.
                    set DmgEventS.DmgEvent = -8191.
endif
                endif
            endif
          
            call DisableTrigger(t)
            call DestroyTrigger(t)
            call onDamage_null_vars(arr)
            set t = null
        endmethod
      
        private static method onDamage_init_vars takes nothing returns nothing
            set source_a[count] = source
            set target_a[count] = target
          
            set dmg_a[count] = dmg
          
            set p_source_a[count] = p_source
            set p_target_a[count] = p_target
          
            set detect_a[count] = detect
            set spell_a[count] = spell
          
            set trigger_a[count] = CreateTrigger()
        endmethod
      
        private static method onDamage takes nothing returns nothing
            local trigger t
          
            local real modHP
            local real maxHP
            local real curHP
          
            local integer i
          
            set source = GetEventDamageSource()
            set target = GetTriggerUnit()
          
            set dmg = GetEventDamage()
      
            set p_source = GetOwningPlayer(source)
            set p_target = GetTriggerPlayer()
          
            set maxHP = GetUnitState(target, UNIT_STATE_MAX_LIFE)
            set curHP = GetWidgetLife(target)
          
            set spell = (dmg <= 0.)
            set count = count + 1
            set i = count
          
            if spell and dmg != 0 then
                set dmg = -dmg
            endif
          
            call onDamage_init_vars()
            if dmg != 0 then
                if dmg > maxHP then
                    call UnitAddAbility(target_a[i], HEALTH_CHEAT)
                    call SetWidgetLife(target_a[i], curHP)
                  
                    if spell then
                        set maxHP = GetUnitState(target_a[i], UNIT_STATE_MAX_LIFE)
                    endif
                endif
              
                set DamageEventInterface.currHP = curHP
              
                if detect_a[i] then
static if LIBRARY_EventStruct then
                    set DmgEventS.DmgEvent.fire = 0.
else
                    set DmgEventS.DmgEvent = 0.
                    set DmgEventS.DmgEvent = -8191.
endif
                endif
                call onDamage_reset_vars(i)
              
                set modHP = GetWidgetLife(target)
                set finalHP = DamageEventInterface.currHP - dmg_a[i]
                set finalDmg = curHP - finalHP
              
                set finalHP_a[i] = finalHP
                set finalDmg_a[i] = finalDmg
              
                if detect_a[i] then
static if LIBRARY_EventStruct then
                    set DmgEventS.DmgEvent.fire = 1.
else
                    set DmgEventS.DmgEvent = 1.
                    set DmgEventS.DmgEvent = -8191.
endif
                endif
                call onDamage_reset_vars(i)
              
                if not spell_a[i] then
                    call SetWidgetLife(target_a[i], dmg_a[i] + 64)
                else
                    call SetWidgetLife(target_a[i],  RMaxBJ(GetWidgetLife(target_a[i]) - dmg_a[i], 0.406))
                endif
                set modHP = GetWidgetLife(target_a[i])
              
                set t = trigger_a[i]
              
                //  Inaccuracies may be produced ...
                if spell_a[i] then
                    call TriggerRegisterUnitStateEvent(t, target_a[i], UNIT_STATE_LIFE, GREATER_THAN, RMinBJ(modHP + dmg, maxHP))
                    call TriggerRegisterUnitStateEvent(t, target_a[i], UNIT_STATE_LIFE, LESS_THAN, RMinBJ(modHP + dmg, maxHP))
                    call BJDebugMsg("to detect: " + R2S(RMinBJ(modHP + dmg, maxHP)))
                  
                    call BJDebugMsg("Final life should be: " + R2S(finalHP))
                else              
                    call TriggerRegisterUnitStateEvent(t, target_a[i], UNIT_STATE_LIFE, GREATER_THAN, modHP - dmg_a[i])
                    call TriggerRegisterUnitStateEvent(t, target_a[i], UNIT_STATE_LIFE, LESS_THAN, modHP - dmg_a[i])
                endif
                call TriggerRegisterTimerEvent(t, 0., false)
                call TriggerAddCondition(t, function thistype.onLifeEvent)
              
                set t = null
            else
                if detect_a[i] then
static if LIBRARY_EventStruct then
                    set DmgEventS.ZeroDmgEvent.fire = 0.
else
                    set DmgEventS.ZeroDmgEvent = 0.
                    set DmgEventS.ZeroDmgEvent = -8191.
endif
                endif
                call onDamage_null_vars(i)
            endif
        endmethod
      
        //  Trigger Registry and Creation portion      
        private static thistype current = 0
      
        private method destroy takes nothing returns nothing
            call DestroyTrigger(trig)
            call GroupClear(group)
          
            set event_count = 0
            set unit_count = 0
          
static if not LIBRARY_Table then
            call pop()
endif
            call deallocate()
        endmethod
      
        private static method create takes nothing returns thistype
            local thistype this = allocate()
          
            set trig = CreateTrigger()
            call TriggerAddCondition(trig, function thistype.onDamage)
          
            if GetHandleId(group) == 0 then
                set group = CreateGroup()
            endif
          
            set event_count = 0
            set unit_count = 0
          
static if not LIBRARY_Table then
            call push()
endif
            return this
        endmethod
      
        static method register takes unit u returns nothing
            local thistype this = get(u)
            if this == 0 then
                if current == 0 then
                    set current = create()
                endif
                set this = current
              
                call UnitAddAbility(u, BRACERS)
                call UnitMakeAbilityPermanent(u, true, BRACERS)
                call TriggerRegisterUnitEvent(trig, u, EVENT_UNIT_DAMAGED)
                call GroupAddUnit(group, u)

static if LIBRARY_Table then
                call bucket_s(u, this)
endif

                set event_count = event_count + 1
                set unit_count = unit_count + 1
              
                if event_count >= DMG_MAX_REG then
                    set current = 0
                endif
            endif
        endmethod
      
        static method unregister takes unit u returns nothing
            local thistype this = get(u)
            if this == 0 then
                return
            endif
          
            call GroupRemoveUnit(group, u)
            set unit_count = unit_count - 1
          
            if unit_count <= 0 and event_count >= DMG_MAX_REG then
                call destroy()
            endif
        endmethod
      
        private static method onUnitScope takes nothing returns nothing
            if GetRealEvent() == 1.00 or GetRealEvent() == 0.5 then
                call register(GetEventTriggerUnit())
            elseif GetRealEvent() == 2.00 then
                call unregister(GetEventTriggerUnit())
            endif
        endmethod
      
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local unit u
          
static if LIBRARY_UnitEventV then
            call TriggerRegisterAuxUnitEvent(t, 1.00)
            call TriggerRegisterAuxUnitEvent(t, 0.50)
            call TriggerRegisterAuxUnitEvent(t, 2.00)
else

endif
            call TriggerAddCondition(t, function thistype.onUnitScope)
            set t = null
          
          
            set u = CreateUnit(Player(15), 'uloc', 0, 0, 0)
            call UnitAddAbility(u, HEALTH_CHEAT)
            call UnitAddAbility(u, BRACERS)
            call RemoveUnit(u)
          
            set u = null
        endmethod
    endstruct
  
    function TriggerRegisterDamageEvent takes trigger t, real r returns nothing
static if LIBRARY_EventStruct then
        call EventTrigs.register(DmgEventS.DmgEvent, r, t)
else
        call TriggerRegisterVariableEvent(t, "s__" + SCOPE_PREFIX + "_DmgEventS_DmgEvent", EQUAL, 0.)
endif
    endfunction
    function TriggerRegisterZeroDamageEvent takes trigger t returns nothing
static if LIBRARY_EventStruct then
        call EventTrigs.register(DmgEventS.ZeroDmgEvent, 0., t)
else
        call TriggerRegisterVariableEvent(t, "s__" + SCOPE_PREFIX + "_DmgEventS_ZeroDmgEvent", EQUAL, 0.)
endif
    endfunction
  
    //  =========================   //
    //      Utilities               //
    //  =========================   //
  
    function UnitDamageTargetPure takes unit u, unit u2, real r returns boolean
        return DamageEvent.deal_pure(u, u2, r)
    endfunction
    function UnitDamageTargetEx takes unit u, unit u2, real r, boolean b1, boolean b2, attacktype a, damagetype d, weapontype w returns boolean
        return DamageEvent.damage(u, u2, r, b1, b2, a, d, w)
    endfunction
  
endlibrary

Damage Manipulation
JASS:
library DamageDetectionAddOns requires DamageDetection, AllocationAndLinks, /*

    */ optional EventStruct /* https://www.hiveworkshop.com/threads/library-pseudo-var-event.292870/#post-3148385
    */
  
    //  =========================   //
    //  Damage Modification         //
    //  =========================   //
  
    private struct DmgModEventS extends array
static if LIBRARY_EventStruct then
        static Event DmgModEvent = 0
else
        static real DmgModEvent = -8191
endif
    endstruct
  
static if LIBRARY_EventStruct then
        function GetDamageModEvent takes nothing returns Event
            return DmgModEventS.DmgModEvent
        endfunction
else
        function GetDamageModEvent takes nothing returns real
            return DmgModEventS.DmgModEvent
        endfunction  
endif

static if LIBRARY_EventStruct then
    private module Init_m2
        private static method onInit takes nothing returns nothing
            set DmgModEventS.DmgModEvent = Event.create()
        endmethod
    endmodule
    private struct Init_s2 extends array
        implement Init_m2
    endstruct
endif

    struct DamageModEvent extends array
        implement AllocLinkBundle
      
        private static constant boolean HIGHER_PRIO_FIRST = true
      
        private real real
      
        static real appliedDmg = 0.
      
        private static method get takes real r returns thistype
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0 or real == r
                set this = next
            endloop
            return this
        endmethod
      
        private method list_sort takes nothing returns nothing
            local thistype that = thistype(0).next
            loop
                exitwhen (that.real > real and that.prev.real < real) or that == 0
                set that = that.next
            endloop
            call insert(that)
        endmethod
      
        //  Explicit call
        private static method create takes real r returns thistype
            local thistype this = allocate()
          
            set real = r
          
            call list_sort()
            return this
        endmethod
      
        static method register takes trigger t, real r returns nothing
            local thistype this = get(r)
          
            if this == 0 then
                set this = create(r)
            endif
          
static if LIBRARY_EventStruct then
            call EventTrigs.register(DmgModEventS.DmgModEvent, r, t)
else
            call TriggerRegisterVariableEvent(t, "s__" + SCOPE_PRIVATE + "_DmgModEventS_DmgModEvent", EQUAL, r)
endif
        endmethod
      
        private static method onDamage takes nothing returns nothing
            local thistype this = 0
            local real curHP
            local real maxHP
          
            set appliedDmg = DamageEvent.dmg
            static if HIGHER_PRIO_FIRST then
                set this = prev
                loop
                    exitwhen this == 0
static if LIBRARY_EventStruct then
                    set DmgModEventS.DmgModEvent.fire = real
else
                    set DmgModEventS.DmgModEvent = real
endif
                    set this = prev
                endloop
static if not LIBRARY_EventStruct then
                set DmgModEventS.DmgModEvent = -8191.
endif
            else
                set this = next
                loop
                    exitwhen this == 0
static if LIBRARY_EventStruct then
                    set DmgModEventS.DmgModEvent.fire = real
else
                    set DmgModEventS.DmgModEvent = real
endif
                    set this = next
                endloop
static if not LIBRARY_EventStruct then
                set DmgModEventS.DmgModEvent = -8191.
endif
            endif
          
            set curHP = GetWidgetLife(DamageEvent.target)
            set maxHP = GetUnitState(DamageEvent.target, UNIT_STATE_MAX_LIFE)
          
            set DamageDetection_DamageEventInterface.currHP = curHP + DamageEvent.dmg - appliedDmg
            set curHP = RMaxBJ(DamageDetection_DamageEventInterface.currHP, 0.406)
            if curHP > maxHP then
                call UnitAddAbility(DamageEvent.target, HEALTH_CHEAT)
            endif
            call SetWidgetLife(DamageEvent.target, curHP)
        endmethod
      
        private static method onSecInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterDamageEvent(t, 0.)
            call TriggerAddCondition(t, function thistype.onDamage)
            set t = null
          
            call DestroyTimer(GetExpiredTimer())
        endmethod
      
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), 0., false, function thistype.onSecInit)
        endmethod
    endstruct
  
    function TriggerRegisterDamageModEvent takes trigger t, real r returns nothing
        call DamageModEvent.register(t, r)
    endfunction
endlibrary

Ability Fixes:
Drain Life Fix:
JASS:
library DrainLifeFix requires Hashtable, AllocationAndLinks, DamageDetection, optional Illusion

    private module LifeDrainMod
        private static method abil_Init takes nothing returns nothing
            call register('ACdr')
            call register('ANdr')
        endmethod
       
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
           
            set Registered = GenerateKey()
            //set TrigIndex = GenerateKey()
            //set TrigIndexR = GenerateKey()
           
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
           
            call TriggerAddCondition(t, function thistype.onEffect)
           
            set t = CreateTrigger()
            call TriggerRegisterDamageEvent(t, 2.00)
            call TriggerAddCondition(t, function thistype.onDamage)
           
            call abil_Init()
           
            set t = null
        endmethod
    endmodule
   
    ///! runtextmacro link_module("unlink", "private")
   
    public struct LifeDrain extends array
        implement AllocLinkBundle
       
        //private static constant timer timer = CreateTimer()
       
        //! runtextmacro Add_ReadonlyOp("Registered", "registered", "boolean", "Boolean", "Boolean", "false")
        ///! runtextmacro AddOp("private", "TrigIndex", "index", "integer", "Integer", "Integer", "0")
        ///! runtextmacro AddOp("private", "TrigIndexR", "life", "real", "Real", "Real", "0.")
       
        readonly unit caster
        readonly unit target
       
        //implement DoubleLink_unlink
       
        private method destroy takes nothing returns nothing
            if this == 0 then
                debug call BJDebugMsg("Error, attempted to deallocate head.")
                return
            endif
           
            set caster = null
            set target = null
           
            call pop()
            call deallocate()
        endmethod
       
        private static method get takes unit caster returns thistype
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0 or .caster == caster
                set this = next
            endloop
            return this
        endmethod
       
        private static method create takes unit caster, unit target returns thistype
            local thistype this = get(caster)
           
            if this == 0 then
                set this = allocate()
                set .caster = caster
                set .target = target
               
                call push()
            endif
            return this
        endmethod
       
        static method register takes integer abil returns nothing
            set thistype(abil).registered = true
        endmethod
       
        /*
        private static method onDamage_Debug takes nothing returns nothing
            local trigger t = GetTriggeringTrigger()
            local thistype this = thistype(GetHandleId(t)).index
           
            call SetWidgetLife(caster, thistype(GetHandleId(t)).life)
           
            set thistype(GetHandleId(t)).index = 0
            set thistype(GetHandleId(t)).life = 0.
           
            call DestroyTrigger(t)
            set t = null
        endmethod
        */
       
        private static method onDamage takes nothing returns nothing
            local trigger t
            local thistype this = get(DamageEvent.source)
           
            if IsUnitEnemy(DamageEvent.target, DamageEvent.p_source) and this != 0 then
                call SetWidgetLife(caster, GetWidgetLife(caster) + DamageEvent.finalDmg)
            endif
        endmethod
       
        private static method onDestroy_ex takes nothing returns nothing
        endmethod
       
        private static method onEffect takes nothing returns nothing
            local unit caster
            local thistype this
            if GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_EFFECT then
                set caster = GetTriggerUnit()
               
                if thistype(GetSpellAbilityId()).registered then
                    call create(caster, GetSpellTargetUnit())
                endif
            elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_SPELL_ENDCAST then
                set caster = GetTriggerUnit()
               
                if thistype(GetSpellAbilityId()).registered then
                    call get(caster).destroy()
                endif
                call PauseTimer(timer)
                call TimerStart(timer, 0., false, function thistype.onDestroy_ex)
            endif
            set caster = null
        endmethod
       
        implement LifeDrainMod
    endstruct
   
endlibrary

Bracers Fix
JASS:
library BracersFix initializer Init requires Hashtable, AllocationAndLinks, DamageDetection, optional DrainLifeFix

    function GetUnitItemCount takes unit which, integer itemtyper returns integer
        local integer i = 0
        local integer result = 0
        loop
            exitwhen i > 5
            if GetItemTypeId(UnitItemInSlot(which, i)) == itemtyper then
                set result = result + 1
            endif
            set i = i + 1
        endloop
        return result
    endfunction
   
    struct IA extends array
        implement AllocLinkBundle
       
        readonly integer typer
       
        readonly boolean ability_type
        readonly boolean stacks
       
        readonly integer max
       
        readonly RealGroup multi
       
        private static method get takes integer typ returns thistype
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0 or typer == typ
                set this = next
            endloop
            return this
        endmethod
       
        method setStacks takes boolean stacker, integer amount returns nothing
            if not ability_type then
                set stacks = stacker
                if stacks then
                    set max = amount
                endif
            endif
        endmethod
       
        static method create takes integer typers, boolean isAbil returns thistype
            local thistype this = get(typers)
           
            if this == 0 then
                set this = allocate()
                set typer = typers
                set ability_type = isAbil
               
                set multi = RealGroup.create()
                call push()
            endif
            return this
        endmethod
       
        public static method onCond takes nothing returns nothing
            local thistype this = thistype(0).next
           
            if DamageEvent.spell then
                loop
                    exitwhen this == 0
                   
                    if not ability_type then
                        if GetUnitItemCount(DamageEvent.target, typer) != 0 then
                            if not stacks then
                                set Dmg_IF.system_damage = DamageModEvent.appliedDmg*(1 - multi.real)
                            else
                                set Dmg_IF.system_damage = DamageModEvent.appliedDmg*Pow(1 - multi.real, Math.max(GetUnitItemCount(DamageEvent.target, typer), max))
                            endif
                        endif
                    else
                        if GetUnitAbilityLevel(DamageEvent.target, typer) != 0 then
                            set Dmg_IF.system_damage = DamageModEvent.appliedDmg*(1 - multi[GetUnitAbilityLevel(DamageEvent.target, typer)].real)
                        endif
                    endif
                    set this = next
                endloop
            endif
        endmethod
    endstruct
   
    //! runtextmacro ModInitStart("SecInit")
        local IA that = IA.create('brac', false)
        set that.multi.real = 0.33
        call that.setStacks(false, 0)
    //! runtextmacro ModInitEnd("SecInit")
   
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
       
        call TriggerRegisterDamageModEvent(t, 4.00)
        call TriggerAddCondition(t, function IA.onCond)
        set t = null
    endfunction
   
endlibrary

A straightforward damage detection system that detects physical and spell damage, and the after-damage event that utilizes a bucket technique for saving mass trigger creation. It uses either the UnitDex library for catching the indexing and deindexing event and registering the affected unit to the system.

(Speculation at this point)
Though redundant at this point, this damage detection trigger provides near-flawless after-damage event detection, using a specialized


Modifier Event
JASS:
scope DemoCode initializer Init
 
    private function Cond takes nothing returns nothing
        set DamageModEvent.appliedDmg = 25
        // This sets the damage to 25
        set DamageModEvent.appliedDmg = DamageEvent.dmg
        // This sets the damage to the event damage...
        set DamageModEvent.appliedDmg = -25
        // This heals the unit by about 25 hp. Float inaccuracies may occur to units with a lot of health.
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterDamageModEvent(t, 1.00)
        call TriggerAddCondition(t, Filter(function Cond))
        set t = null
    endfunction

endscope

Damage Instance Event
JASS:
scope DemoCode2 initializer Init

    private function Cond takes nothing returns nothing
        call UnitDamageTargetEx(DamageEvent.source, DamageEvent.target, 25, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterDamageEvent(t, 1.00)
        call TriggerAddCondition(t, Filter(function Cond))
        set t = null
    endfunction

endscope

 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,010
Warning: It's not automatic, you may need to do things yourself.
I could justify graveyarding a system resource based on that tagline alone. That is exactly the opposite of what a system should be: easy, user-friendly, and automatic (most of the time). Especially since this is a DDS, which is something that modders have a lot of choice for, so picking something easy to use is almost always a priority.

Here's my real question Jason: why? The 562 other DDSes weren't good enough for you? The 29 DDSes that automatically clear and refresh the trigger events periodiclly weren't good enough? The 14 DDses that block damage and allow for healing weren't good enough?

As I understand looking over the the code yours doesn't do anything new or different. Your system can't even differentiate between attacks and spells. Where is the innovation? Arguably using a buttload of triggers with events added in a UNIT_ATTACKED event response function instead of one megatrigger is 'different' but you're adding a lot of overhead to a problem that can be solved much more simply. (For example, you are doing a massive number of LoadUnitHandle() calls.) What benefits does your method have? Why would I do it your way? Why would I choose to use your system over any one of the multitude of others?

Then we come to the fact that your test map doesn't... DO anything. I would recommend you actually make a test map that, ya know, tests the stuff it's supposed to let you test.
 
Last edited:
Ah.....a reply. Thanks for the reply:
It seems that I forgot to add some sample code by
which I could have shown how it is used.
I will try to submit it once more with a debug section
of the test map as I have just recently
mapped my own and that I would like to optimize it.

Once again, thank you for replying..
Thanks, Pyrogasm

I did use other people's systems though, like Rising_Dusk's, jesus4lyf's, and maybe Nestharus' systems, and I can say
that they are, with one exception, absolutely brilliant systems. Bribe's and maybe Weep's DDS, though I
haven't tested it, is what I can say to be the GUI equivalents of those systems aforementioned,
just... powerful.

But I wanted to make my own system for fun.
 
Last edited by a moderator:
Level 39
Joined
Feb 27, 2007
Messages
5,010
But I wanted to make my own system because *this is the only
system I have full knowledge on as to *its' configuration.
Cool, great reason to want to make your own system. Not so great a reason to submit it to our database. Is there any reason I would objectively pick your system over any other DDS aside from "because MyPad made it"?
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Though I understand your reasons for posting your attempts on certain systems, you are posting it in the JASS submission category.
By which, you automatically tell us that you intend to share it with other people so they can use it in their maps.
If you "just" want feedback and insight on the programming, then I recommend to post it in either the lab or in the triggers & scripts (if your code is "for some reason" not working and you are uncapable of fixing it).
I reported this thread so it can be moved to a more appropriate section.

On the actual coding, a lot of people (like me for example) do not really feel like downloading a map, opening the world editor and then looking through the triggers. "We" prefer to just have the code in [code=jass][/code] tags so we can see them right away.
 
@MyPad , your first thread (this one) was moved to The Lab, because it's description content was implying that you kind of experiment with this system, and just want some people to test it.
I'm not very sure it makes more sense to have it in Triggers & Scritps, because there is actually no clear problem, but you mostly seek towards critiques and attention towards your concept.
Your new thread in Triggers & Scripts has been removed, as it was the very same. I think it makes more sense right here.
 
Okay, I'm currently reworking it from scratch. Sorry about the hashtable, I like using them.

EDIT:

Here's a part of the code I'm reworking on.

JASS:
library UnitHashSystem uses AllocationSystem

    globals
        private constant hashtable UNIT_HASH = InitHashtable()
        private constant integer UNIT_HASH_ID = GetHandleId(UNIT_HASH)
    endglobals
   
    globals
        private integer IndexMax = 0
        private integer IndexLost = 0        //IndexLost tells us about the total number of Indices lost.
        private integer IndexToRestore = 1  //IndexToRestore should logically be below IndexLost; it is only called when an index is leaking.
        private integer IndicesLost = 0        //IndicesLost tells us currently about the number of indices lost.
    endglobals
   
    private function GetUnitIndex takes unit u returns integer
        return LoadInteger(UNIT_HASH, GetHandleId(u), UNIT_HASH_ID)
    endfunction
   
    function UnitHash takes unit u returns integer
        local integer i = 0
        local integer i_return = 0
        local integer j = 0
        //
        if u == null then
            return 0
        endif
        //
        set j = GetUnitIndex(u)
        if j != 0 then
            return j
        endif
        //
        if IndicesLost > 0 then
            set i = LoadInteger(UNIT_HASH, IndexToRestore, 1)
            set i_return = i
            call SaveUnitHandle(UNIT_HASH, i, 0, u)
            call SaveInteger(UNIT_HASH, GetHandleId(u), UNIT_HASH_ID, i)
            set IndicesLost = IndicesLost - 1
            set IndexToRestore = IndexToRestore + 1
        else
            //No Indices lost, so update.
            set i = IndexMax + 1
            set i_return = i
            call SaveUnitHandle(UNIT_HASH, i, 0, u)
            call SaveInteger(UNIT_HASH, GetHandleId(u), UNIT_HASH_ID, i)
            set IndexMax = i
        endif
        return i_return
    endfunction
   
    function UnitUnhash takes unit u returns nothing
        local integer i = GetUnitIndex(u)
        local integer j = 0
        if i != 0 then
            set j = IndexLost + 1
            call RemoveSavedInteger(UNIT_HASH, GetHandleId(u), UNIT_HASH_ID)
            call RemoveSavedHandle(UNIT_HASH, i, 0)
            call SaveInteger(UNIT_HASH, j, 1, i)
            set IndexLost = j
            set IndicesLost = IndicesLost + 1
        endif
    endfunction
   
endlibrary

I hope it reduces the operations involved computing as it works in order, that is to say:
If you have indices 1, 2, 3, and 4 and you lose 3 (The third index), it will be captured by one of the variables and the next time you register, it will be assigned to 3. If it is complete, it will move on to the next index.
 
Last edited:
@MyPad , your first thread (this one) was moved to The Lab, because it's description content was implying that you kind of experiment with this system, and just want some people to test it.
I'm not very sure it makes more sense to have it in Triggers & Scritps, because there is actually no clear problem, but you mostly seek towards critiques and attention towards your concept.
Your new thread in Triggers & Scripts has been removed, as it was the very same. I think it makes more sense right here.

Teach me your ways, o wise moderator.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
The problem isn't using a hashtable. You should be using one. But using it as a simple array defeats its whole purpose of mapping objects to other objects.
Why are you storing indices and then looping all of them, when you can store the units directly, and simply query if they exist in the table?
 
The problem isn't using a hashtable. You should be using one. But using it as a simple array defeats its whole purpose of mapping objects to other objects.
Why are you storing indices and then looping all of them, when you can store the units directly, and simply query if they exist in the table?

Okay, I'll make some changes to the system. Thanks for that.
Sorry for the late reply.
 
Status
Not open for further replies.
Top