1. Fill your cup and take your pick among the maps best suited for this year's Hive Cup. The 6th Melee Mapping Contest Poll is up!
    Dismiss Notice
  2. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  3. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] DamagePackage

Discussion in 'JASS Resources' started by Flux, Aug 3, 2016.

  1. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    A modular Damage Detection System.
    Contains libraries for your damage detection, distinction and manipulation needs.

    Documentation

    Code (vJASS):

    //! novjass
       
        /*
                                 DamagePackage v1.44
                                    Documentation
                                      by Flux
               
            Contains libraries for your damage detection, distinction and manipulation needs.
               
            -----------
            DamageEvent
            -----------
                A lightweight damage detection system that
                detects when a unit takes damage.
                Can distinguish physical and magical damage.
           
            ------------
            DamageModify
            ------------
                An add-on to DamageEvent that allows modification
                of damage taken before it is applied.
       
               
            -------------
            DamageObjects
            -------------
                Automatically generates required Objects by DamageEvent and
                DamageModify.
       
        CONTENTS:
            - API
            - How to use DamagePackage
            - Important Notes
            - Credits
            - Changelog
               
        //==================================================================//
                                          API:
        //==================================================================//
        */

       
        DamageEvent:
            -----------------------------
                  DAMAGE PROPERTIES
            -----------------------------
            Damage.source
                // Unit that dealt the damage.
            Damage.target
                // Unit that took the damage.
            Damage.amount
                // Amount of damage taken.
            Damage.type
                // The type of damage taken.
                // Values can be DAMAGE_TYPE_PHYSICAL or DAMAGE_TYPE_MAGICAL.
               
            -----------------------------
                   DAMAGE CALLBACKS
            -----------------------------
            Damage.register(code)
                // Registers a code that will permanently run when a registered
                // unit takes damage.
            Damage.registerTrigger(trigger)
                // Registers a trigger that will run when a registered unit takes
                // damage. The trigger can be disabled to avoid an infinite loop.
                // Triggers will execute depending on the order they are registered.
            Damage.unregisterTrigger(trigger)
                // Removes the event in the trigger, causing the trigger to no longer
                // run when a registered unit takes damage.
            Damage.add(unit)
                // For manual registration of units to DamageEvent. You
                // won't use this if AUTO_REGISTER is set to true.
           
            -----------------------------
                   MISCELLANEOUS
            -----------------------------
            Damage.enabled
                //Turns on/off the entire DamageEvent.
            Damage.lockAmount()
                //Prevents further modification of damage on this damage instance.
           
        DamageModify
            set Damage.amount = <new amount>
                // Modify the damage taken before it is applied.
           
            Damage.registerModifier(code)
                // Registers a code that will permanently run when a
                // registered unit takes damage executing before any callbacks
                // registered via Damage.register(code)/Damage.registerTrigger(trigger).  
           
            Damage.registerModifierTrigger(trigger)
                // Registers a trigger that will run when a registered unit
                // takes damage executing before any callbacks registered
                // via Damage.register(code)/Damage.registerTrigger(trigger).
                // The trigger can be disabled to avoid an infinite loop.
           
            Damage.unregisterModifierTrigger(trigger)
                // Removes the event in the trigger, it will no longer evaluate
                // and execute when a registered unit takes damage.

        /*
       
        //==================================================================//
                              HOW TO USE DAMAGE PACKAGE:
        //==================================================================//
       
            1. Decide whether you need to use DamageEvent or DamageEvent with DamageModify.
               If you only want to detect the damage and the type of damage, DamageEvent
               will suffice, but if you want to modify the Damage taken, then you need
               DamageModify. Note that DamageEvent without DamageModify is designed to be
               lightweight, therefore it is better not to have DamageModify if you do not
               need it. Using DamageModify is 90 to 100 microseconds slower.
           
            2. Define Basic configuration of DamageEvent
                */

                private constant integer DAMAGE_TYPE_DETECTOR
                    //An ability based on Runed Bracer that is utilized by DamageEvent to distinguish
                    //PHYSICAL and MAGICAL damage.
                   
                private constant real ETHEREAL_FACTOR
                    //Using DamageEvent disables the ethereal factor configured in Gameplay Constants as
                    //a side effect of Runed Bracer. However, the system simulates ethereal amplification
                    //and this is the new ethereal factor for magic damage. The configured ethereal factor
                    //in Gameplay Constants will be completely ignored.
                .
                private constant boolean AUTO_REGISTER
                    //Determines whether units entering the map are automatically registered
                    //to the DamageEvent system
               
                private constant boolean PREPLACE_INIT
                    //Auto registers units initially placed in World Editor.
                   
                private constant integer COUNT_LIMIT
                    //When the number of registered individual unit in the current DamageBucket
                    //reaches COUNT_LIMIT, the system will find a new DamageBucket with units less
                    //than COUNT_LIMIT and use it as the new current DamageBucket. If none is found,
                    //the system will create a new DamageBucket.
                   
                private constant real REFRESH_TIMEOUT
                    //Periodic Timeout of Trigger Refresh.
                    //Every REFRESH_TIMEOUT, the system will refresh a signle DamageBucket.
               
                private constant integer SET_MAX_LIFE
                    //An ability based on Item Life Bonus that is utilized by DamageModify to
                    //manipulate damage taken.
            /*
               
               
            3. Register a code or a trigger that will run when a registered unit takes damage. Example:
                */

                //USING CODE PARAMETER
                    library L initializer Init
                       
                        private function OnDamage takes nothing returns nothing
                            //This will run whenever a unit registered takes damage.
                            //Do you thing here
                        endfunction
                       
                        private function Init takes nothing returns nothing
                            call Damage.register(function OnDamage)
                        endfunction
                       
                    endlibrary
               
                //USING TRIGGER PARAMETER
                    library L initializer Init
                       
                        globals
                            private trigger trg = CreateTrigger()
                        endglobals
                       
                        private function OnDamage takes nothing returns boolean
                            //This will run whenever a unit registered takes damage.
                            return false
                        endfunction
                       
                        private function Init takes nothing returns nothing
                            call Damage.registerTrigger(trg)
                            call TriggerAddCondition(trg, Condition(function OnDamage))
                        endfunction
                       
                    endlibrary
                   
                    // You would want to use Damage.registerTrigger when avoiding recursion loop
                    // because the trigger can be disabled unlike code.
               
                /*
           
            4. If you want to modify the damage taken, you need the DamageModify library.
               Simply change Damage.amount to whatever you want the new damage value to be.
               Example:
               */

                    library L initializer Init
                       
                        private function OnDamage takes nothing returns nothing
                            //All damage taken will be amplied by two
                            set Damage.amount = 2*Damage.amount
                        endfunction
                       
                        private function Init takes nothing returns nothing
                            call Damage.registerModifier(function OnDamage)
                        endfunction
                       
                    endlibrary
                /*
                DamageModify callbacks and triggers runs first before DamageEvent callbacks
                and triggers. Example:
                */

                    library L initializer Init
                       
                        private function OnDamageModifier takes nothing returns nothing
                            set Damage.amount = 0   //This will cause all damage taken to be zero
                        endfunction
                       
                        private function OnDamage takes nothing returns nothing
                            call BJDebugMsg(GetUnitName(Damage.target) " takes " + R2S(Damage.amount) + " damage")
                            //Will print:
                            //"<Target Name> takes 0 damage"
                        endfunction
                       
                        private function Init takes nothing returns nothing
                            call Damage.registerModifier(function OnDamageModifier)
                            call Damage.register(function OnDamageModifier)
                        endfunction
                       
                    endlibrary
                /*
       
            5. If you want to deal damage inside onDamage callback without causing infinite loops,
               you can do so using Damage.registerTrigger(trigger) and disabling the trigger before
               the new damage is applied then enabling it again after damage is applied. Example:
               */

                    library L initializer Init
                       
                        globals
                            private trigger trg = CreateTrigger()
                        endglobals
                       
                        private function OnDamage takes nothing returns boolean
                            call BJDebugMsg(GetUnitName(Damage.target) " takes " + R2S(Damage.amount) + " damage")
                            call DisableTrigger(thistype.trg)
                            call UnitDamageTarget(Damage.source, Damage.target, 42.0, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
                            call EnableTrigger(thistype.trg)
                            call BJDebugMsg(GetUnitName(Damage.target) " takes an extra 42 damage.")
                            return false
                        endfunction
                       
                        private function Init takes nothing returns nothing
                            call Damage.registerTrigger(trg)
                            call TriggerAddCondition(trg, Condition(function OnDamage))
                        endfunction
                       
                    endlibrary
                /*
       
       
        //==================================================================//
                                   IMPORTANT NOTES:
        //==================================================================//
           
            - Life Drain will not work with this system.
           
            - Locust Swarm abilities will still work, but the "Data - Damage Return Factor"
              defined in Object Editor must be multiplied to -1. Example, to fix the
              default Locust Swarm ability, change the value from 0.75 to -0.75
           
            - Runed Bracer items and abilities will not work with this system. But one
              can easily make a trigger for that.
           
            - Mana Shield works normally.
             
            - Artillery attacks that causes unit to explode on death works normally.
           
            - Finger of Death works normally.
           
            - Spirit Link will not work with this system. However, it is possible to
              recreate a triggered version of Spirit Link using this system.
           
            - Magic Attacks are detected as DAMAGE_TYPE_PHYSICAL while Spells Attack
              are detected as DAMAGE_TYPE_MAGICAL.
       
       
        //==================================================================//
                                       CREDITS:
        //==================================================================//
       
            looking_for_help
                - for the Runed Bracer trick allowing this system to distinguish PHYSICAL
                  and MAGICAL damage.
                - for Physical Damage Detection System which was used as a reference for
                  creating this system.
           
            Bribe
                - for the optional Table
           
            Cokemonkey11 and PurplePoot
                - for the bucket-based damage detection systems for less processes per refresh.

            Aniki, Quilnez and Wietlol
                - for finding bugs, giving feedbacks and suggestions.
               
               
       
        //==================================================================//
                                       CHANGELOG:
        //==================================================================//
            v1.00 - [3 Aug 2016]
             - Initial Release
           
            v1.10 - [7 Aug 2016]
             - Fixed unremoved and unintentional BJDebug Messages.
             - Fixed unremoved group in preplace.
             - Fixed uncleaned Table/hashtable timer handle id.
             - Fixed "Nonrecursive Damage bug".
             - Fixed HP Bar flickering bug.
             - Fixed a bug where revived units are not registered.
             - Optimized the script, now only uses 1 static timer.
             - Replaced UnitAlive by GetUnitTypeId as the condition for removal.
             - Removed optional requirements TimerUtils and TimerUtilsEx.
             - Implemented a periodic refresh mechanism.
             - Implemented the bucket technique to limit the number of units per refresh.

            v1.11 - [7 Aug 2016]
             - Fixed a bug where it does not auto-register preplaced units when using Table.
             - Fixed some functions compiling to trigger evaluation due to the order.
             - Added a filter when using AUTO_REGISTER.
           
            v1.12 - [8 August 2016]
             - Fixed a bug when all DamageBuckets are removed.
             - Fixed a bug where currentBucket points to a destroyed DamageBucket
               when it is destroyed.
             - Fixed unremoved saved boolean in Table/hashtable.
             - AutoRegisterFilter is now also applied to preplaced units.
           
            v1.20 - [17 August 2016]
             - Fixed recursion bug.
             - Added Damage.enabled to control whether DamageEvent callbacks are ON/OFF.
             - Added Damage.registerPermanent(code).
             - Renamed Damage.registerFirst(code) to Damage.registerModifier(code).
             - Damage.registerModifier(code) only comes within DamageModify.
             - SET_MAX_LIFE of DamageModify is now preloaded.
           
            v1.30 - [15 October 2016]
             - Added more detailed documentation with examples.
             - Now uses life change event instead of a timer avoiding several bugs.
             - Fixed recursion damage workaround.
             - Optimized and shortened the code.
           
            v1.40 - [29 March 2017]
             - Implemented a stack to make Damage.source, Damage.target, Damage.amount and Damage.type behave like local variables in the callback.
             - Fixed Damage.source and Damage.target changing unit value withing callback due to recursion.
             - Fixed Damage.amount and Damage.type changing value within callback due to recursion.
             - Improved documentation on how it affects default Warcraft 3 abilities.
           
            v1.41 - [24 May 2017]
             - Added Damage.lockAmount() feature.
             - Fixed bug occuring when units with very high hp takes damage.
             - Changing Damage.amount will no longer work on non-modifier codes/triggers.
           
            v1.42 - [25 May 2017]
             - Fixed bug occuring when units with very high hp takes very small damage.
             
            v1.43 - [29 May 2017]
             - Fixed bug when magic damage amount is between 0.125 to 0.2.
             
            v1.44 - [3 June 2017]
             - Fixed bug when magic damage exceeds target's max health.
           
        */

       
       
    //! endnovjass
     


    DamageEvent

    Code (vJASS):

    library DamageEvent /*
                ----------------------------------
                        DamageEvent v1.44
                            by Flux
                ----------------------------------

            A lightweight damage detection system that
            detects when a unit takes damage.
            Can distinguish physical and magical damage.

        */
    requires /*
          (nothing)

        */
    optional Table /*
            If not found, DamageEvent will create 2 hashtables. Hashtables are limited to 255 per map.

        */


        //Basic Configuration
        //See documentation for details
        globals
            private constant integer DAMAGE_TYPE_DETECTOR = 'ADMG'
            private constant real ETHEREAL_FACTOR = 1.6666
        endglobals

        //Advanced Configuration
        //Default values are recommended, edit only if you understand how the system works (See documentation).
        globals
            private constant boolean AUTO_REGISTER = true
            private constant boolean PREPLACE_INIT = true
            private constant integer COUNT_LIMIT = 50
            private constant real REFRESH_TIMEOUT = 30.0
        endglobals

        static if not AUTO_REGISTER and not PREPLACE_INIT then
        //Equivalent to AUTO_REGISTER or PREPLACE_INIT
        else
            //Autoregister Filter
            //If it returns true, it will be registered automatically
            private function AutoRegisterFilter takes unit u returns boolean
                local integer id = GetUnitTypeId(u)
                return id != 'dumi'
            endfunction
        endif

        //Globals not meant to be edited.
        globals
            constant integer DAMAGE_TYPE_PHYSICAL = 1
            constant integer DAMAGE_TYPE_MAGICAL = 2
            private constant real MIN_LIFE = 0.406
            private DamageBucket pickedBucket = 0
            private DamageBucket currentBucket = 0
        endglobals

        struct DamageBucket

            readonly integer count
            readonly trigger trg
            readonly group grp
            readonly thistype next
            readonly thistype prev

            private static timer t = CreateTimer()

            method destroy takes nothing returns nothing
                set this.next.prev = this.prev
                set this.prev.next = this.next
                if thistype(0).next == 0 then
                    call PauseTimer(thistype.t)
                endif
                if this == currentBucket then
                    set currentBucket = thistype(0).next
                endif
                call DestroyTrigger(this.trg)
                call DestroyGroup(this.grp)
                set this.trg = null
                set this.grp = null
                call this.deallocate()
            endmethod

            //Returns the DamageBucket where unit u belongs.
            static method get takes unit u returns thistype
                static if LIBRARY_Table then
                    return Damage.tb[GetHandleId(u)]
                else
                    return LoadInteger(Damage.hash, GetHandleId(u), 0)
                endif
            endmethod

            method remove takes unit u returns nothing
                call GroupRemoveUnit(this.grp, u)
                static if LIBRARY_Table then
                    call Damage.tb.remove(GetHandleId(u))
                else
                    call RemoveSavedInteger(Damage.hash, GetHandleId(u), 0)
                endif
            endmethod

            //Add unit u to this DamageBucket.
            method add takes unit u returns nothing
                call TriggerRegisterUnitEvent(this.trg, u, EVENT_UNIT_DAMAGED)
                call GroupAddUnit(this.grp, u)
                set this.count = this.count + 1
                static if LIBRARY_Table then
                    set Damage.tb[GetHandleId(u)] = this
                else
                    call SaveInteger(Damage.hash, GetHandleId(u), 0, this)
                endif
            endmethod

            private static thistype temp

            //Enumerate DamageBucket units, removing it if it is removed from the game
            private static method cleanGroup takes nothing returns nothing
                local unit u = GetEnumUnit()
                local thistype this = temp
                if GetUnitTypeId(u) != 0 then
                    call TriggerRegisterUnitEvent(this.trg, u, EVENT_UNIT_DAMAGED)
                    set this.count = this.count + 1
                else
                    call GroupRemoveUnit(this.grp, u)
                    static if LIBRARY_Table then
                        call Damage.tb.remove(GetHandleId(u))
                    else
                        call RemoveSavedInteger(Damage.hash, GetHandleId(u), 0)
                    endif
                endif
                set u = null
            endmethod

            //Refreshes this DamageBucket
            method refresh takes nothing returns nothing
                local unit u
                call DestroyTrigger(this.trg)
                set this.trg = CreateTrigger()
                call TriggerAddCondition(this.trg, Filter(function Damage.core))
                set this.count = 0
                set thistype.temp = this
                call ForGroup(this.grp, function thistype.cleanGroup)
                if this.count == 0 then
                    call this.destroy()
                endif
            endmethod

            static method create takes nothing returns thistype
                local thistype this = thistype.allocate()
                set this.count = 0
                set this.trg = CreateTrigger()
                set this.grp = CreateGroup()
                call TriggerAddCondition(this.trg, Filter(function Damage.core))
                set this.next = thistype(0)
                set this.prev = thistype(0).prev
                set this.next.prev = this
                set this.prev.next = this
                if this.prev == 0 then
                    call TimerStart(thistype.t, REFRESH_TIMEOUT, true, function Damage.refresh)
                endif
                return this
            endmethod

        endstruct

        struct DamageTrigger

            private trigger trg
            private thistype next
            private thistype prev

            method destroy takes nothing returns nothing
                set this.next.prev = this.prev
                set this.prev.next = this.next
                static if LIBRARY_Table then
                    call Damage.tb.remove(GetHandleId(this.trg))
                else
                    call RemoveSavedInteger(Damage.hash, GetHandleId(this.trg), 0)
                endif
                set this.trg = null
                call this.deallocate()
            endmethod

            static method unregister takes trigger t returns nothing
                local integer id = GetHandleId(t)
                static if LIBRARY_Table then
                    if Damage.tb.has(id) then
                        call thistype(Damage.tb[id]).destroy()
                    endif
                else
                    if HaveSavedInteger(Damage.hash, id, 0) then
                        call thistype(LoadInteger(Damage.hash, id, 0)).destroy()
                    endif
                endif
            endmethod

            static method register takes trigger t returns nothing
                local thistype this = thistype.allocate()
                set this.trg = t
                set this.next = thistype(0)
                set this.prev = thistype(0).prev
                set this.next.prev = this
                set this.prev.next = this
                static if LIBRARY_Table then
                    set Damage.tb[GetHandleId(t)] = this
                else
                    call SaveInteger(Damage.hash, GetHandleId(t), 0, this)
                endif
            endmethod

            static method executeAll takes nothing returns nothing
                local thistype this = thistype(0).next
                loop
                    exitwhen this == 0
                    if IsTriggerEnabled(this.trg) then
                        if TriggerEvaluate(this.trg) then
                            call TriggerExecute(this.trg)
                        endif
                    endif
                    set this = this.next
                endloop
            endmethod

        endstruct


        struct Damage extends array

            private static thistype stackTop = 0
            private static thistype global
            private static integer array allocator
            private unit stackSource
            private unit stackTarget
            private real stackAmount
            private integer stackType
            private thistype stackNext

            private static real hp

            static if LIBRARY_Table then
                readonly static Table tb
            else
                readonly static hashtable hash = InitHashtable()
            endif

            //Allows the DamageModify module to access the configuration
            static if LIBRARY_DamageModify then
                private static constant real S_ETHEREAL_FACTOR = ETHEREAL_FACTOR
                private static constant real S_MIN_LIFE = MIN_LIFE
            endif

            static method remove takes unit u returns nothing
                call DamageBucket.get(u).remove(u)
            endmethod

            //Add unit u to the current DamageBucket
            static method add takes unit u returns nothing
                local DamageBucket temp
                local DamageBucket b
                //If unit does not belong to any DamageBucket yet
                if DamageBucket.get(u) == 0 then
                    call UnitAddAbility(u, DAMAGE_TYPE_DETECTOR)
                    call UnitMakeAbilityPermanent(u, true, DAMAGE_TYPE_DETECTOR)
                    if currentBucket != 0 then
                        //When the current DamageBucket exceeds the limit
                        if currentBucket.count >= COUNT_LIMIT then
                            set temp = DamageBucket(0).next
                            loop
                                exitwhen temp == 0
                                //Find a DamageBucket with few units
                                if temp.count < COUNT_LIMIT then
                                    exitwhen true
                                endif
                                set temp = temp.next
                            endloop
                            if temp == 0 then //If none is found
                                set currentBucket = DamageBucket.create()
                            else             //If a DamageBucket is found, use it
                                set currentBucket = temp
                            endif
                        endif
                    else
                        set currentBucket = DamageBucket.create()
                        set pickedBucket = currentBucket
                    endif
                    call currentBucket.add(u)
                endif
            endmethod

            //Periodic Refresh only refreshing one DamageBucket per REFRESH_TIMEOUT
            //to avoid lag spike.
            static method refresh takes nothing returns nothing
                call pickedBucket.refresh()
                loop
                    set pickedBucket = pickedBucket.next
                    exitwhen pickedBucket != 0
                endloop
            endmethod

            static method operator amount takes nothing returns real
                return thistype.stackTop.stackAmount
            endmethod

            static method operator type takes nothing returns integer
                return thistype.stackTop.stackType
            endmethod

            static method operator target takes nothing returns unit
                return thistype.stackTop.stackTarget
            endmethod

            static method operator source takes nothing returns unit
                return thistype.stackTop.stackSource
            endmethod

            private static boolean prevEnable = true

            static method operator enabled takes nothing returns boolean
                return thistype.prevEnable
            endmethod

            static method operator enabled= takes boolean b returns nothing
                local DamageBucket bucket = DamageBucket(0).next
                if b != thistype.prevEnable then
                    loop
                        exitwhen bucket == 0
                        if b then
                            call EnableTrigger(bucket.trg)
                        else
                            call DisableTrigger(bucket.trg)
                        endif
                        set bucket = bucket.next
                    endloop
                endif
                set thistype.prevEnable = b
            endmethod

            //All registered codes will go to this trigger
            private static trigger registered

            static method register takes code c returns boolean
                call TriggerAddCondition(thistype.registered, Condition(c))
                return false    //Prevents inlining
            endmethod

            static method registerTrigger takes trigger trig returns nothing
                call DamageTrigger.register(trig)
            endmethod

            static method unregisterTrigger takes trigger trig returns nothing
                call DamageTrigger.unregister(trig)
            endmethod

            implement optional DamageModify

            static if not LIBRARY_DamageModify then

                private static method afterDamage takes nothing returns boolean
                    call SetWidgetLife(thistype.stackTop.stackTarget, thistype.hp - thistype.stackTop.stackAmount)
                    call DestroyTrigger(GetTriggeringTrigger())
                    if thistype.global > 0 then
                        set thistype.allocator[thistype.global] = thistype.allocator[0]
                        set thistype.allocator[0] = thistype.global
                        set thistype.stackTop = thistype.stackTop.stackNext
                    endif
                    return false
                endmethod

                static method core takes nothing returns boolean
                    local real amount = GetEventDamage()
                    local thistype this
                    local real newHp
                    local trigger trg

                    if amount == 0.0 then
                        return false
                    endif

                    set this = thistype.allocator[0]
                    if (thistype.allocator[this] == 0) then
                        set thistype.allocator[0] = this + 1
                    else
                        set thistype.allocator[0] = thistype.allocator[this]
                    endif
                    set this.stackSource = GetEventDamageSource()
                    set this.stackTarget = GetTriggerUnit()
                    set this.stackNext = thistype.stackTop
                    set thistype.stackTop = this

                    if amount > 0.0 then
                        set this.stackType = DAMAGE_TYPE_PHYSICAL
                        set this.stackAmount = amount
                        call DamageTrigger.executeAll()
                        set thistype.allocator[this] = thistype.allocator[0]
                        set thistype.allocator[0] = this
                        set thistype.stackTop = thistype.stackTop.stackNext

                    elseif amount < 0.0 then
                        set this.stackType = DAMAGE_TYPE_MAGICAL
                        if IsUnitType(this.stackTarget, UNIT_TYPE_ETHEREAL) then
                            set amount = amount*ETHEREAL_FACTOR
                        endif
                        set this.stackAmount = -amount
                        call DamageTrigger.executeAll()

                        set thistype.hp = GetWidgetLife(this.stackTarget)
                        set newHp = thistype.hp + amount
                        if newHp < MIN_LIFE then
                            set newHp = MIN_LIFE
                        endif
                        call SetWidgetLife(this.stackTarget, newHp)

                        set trg = CreateTrigger()
                        if amount < -1.0 then
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 1.0)
                        elseif amount < -0.125 then
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.125)
                        else
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.01)
                        endif
                        call TriggerAddCondition(trg, Condition(function thistype.afterDamage))
                        set trg = null
                        set thistype.global = this
                    endif

                    return false
                endmethod
            endif

            static if PREPLACE_INIT then
                private static method preplace takes nothing returns nothing
                    local group g = CreateGroup()
                    local unit u
                    call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
                    loop
                        set u = FirstOfGroup(g)
                        exitwhen u == null
                        call GroupRemoveUnit(g, u)
                        if AutoRegisterFilter(u) then
                            call thistype.add(u)
                        endif
                    endloop
                    call DestroyGroup(g)
                    call DestroyTimer(GetExpiredTimer())
                    set g = null
                endmethod
            endif

            static if AUTO_REGISTER then
                private static method entered takes nothing returns boolean
                    local unit u = GetTriggerUnit()
                    if AutoRegisterFilter(u) then
                        call thistype.add(u)
                    endif
                    set u = null
                    return false
                endmethod
            endif

            implement DamageInit

        endstruct

        module DamageInit
            private static method onInit takes nothing returns nothing
                static if AUTO_REGISTER then
                    local trigger t = CreateTrigger()
                    local region reg = CreateRegion()
                    call RegionAddRect(reg, bj_mapInitialPlayableArea)
                    call TriggerRegisterEnterRegion(t, reg, null)
                    call TriggerAddCondition(t, function thistype.entered)
                endif
                static if LIBRARY_Table then
                    set thistype.tb = Table.create()
                endif
                static if PREPLACE_INIT then
                    call TimerStart(CreateTimer(), 0.0000, false, function thistype.preplace)
                endif
                set thistype.registered = CreateTrigger()
                call DamageTrigger.register(thistype.registered)
                set thistype.allocator[0] = 1
                set thistype(0).stackSource = null
                set thistype(0).stackTarget = null
                set thistype(0).stackAmount = 0.0
                set thistype(0).stackType = 0
            endmethod

        endmodule

    endlibrary
     


    DamageModify

    Code (vJASS):

    library DamageModify uses DamageEvent/*
                ---------------------------------
                        DamageModify v1.44
                            by Flux
                ---------------------------------

            An add-on to DamageEvent that allows modification
            of damage taken before it is applied.
        */


        globals
            private constant integer SET_MAX_LIFE = 'ASML'
        endglobals

        struct DamageTrigger2

            private trigger trg
            private thistype next
            private thistype prev

            method destroy takes nothing returns nothing
                set this.next.prev = this.prev
                set this.prev.next = this.next
                static if LIBRARY_Table then
                    call Damage.tb.remove(GetHandleId(this.trg))
                else
                    call RemoveSavedInteger(Damage.hash, GetHandleId(this.trg), 0)
                endif
                set this.trg = null
                call this.deallocate()
            endmethod

            static method unregister takes trigger t returns nothing
                local integer id = GetHandleId(t)
                static if LIBRARY_Table then
                    if Damage.tb.has(id) then
                        call thistype(Damage.tb[id]).destroy()
                    endif
                else
                    if HaveSavedInteger(Damage.hash, id, 0) then
                        call thistype(LoadInteger(Damage.hash, id, 0)).destroy()
                    endif
                endif
            endmethod

            static method register takes trigger t returns nothing
                local thistype this = thistype.allocate()
                set this.trg = t
                set this.next = thistype(0)
                set this.prev = thistype(0).prev
                set this.next.prev = this
                set this.prev.next = this
                static if LIBRARY_Table then
                    set Damage.tb[GetHandleId(t)] = this
                else
                    call SaveInteger(Damage.hash, GetHandleId(t), 0, this)
                endif
            endmethod

            static method executeAll takes nothing returns nothing
                local thistype this = thistype(0).next
                loop
                    exitwhen this == 0
                    if IsTriggerEnabled(this.trg) then
                        if TriggerEvaluate(this.trg) then
                            call TriggerExecute(this.trg)
                        endif
                    endif
                    set this = this.next
                endloop
            endmethod

        endstruct

        module DamageModify

            private static boolean changed = false
            private static trigger registered2 = CreateTrigger()
            private static boolean locked = false
           
            static method registerModifier takes code c returns boolean
                call TriggerAddCondition(thistype.registered2, Condition(c))
                return false    //Prevents inlining
            endmethod

            static method registerModifierTrigger takes trigger trg returns nothing
                call DamageTrigger2.register(trg)
            endmethod

            static method unregisterModifierTrigger takes trigger trg returns nothing
                call DamageTrigger2.unregister(trg)
            endmethod

            static method lockAmount takes nothing returns nothing
                set thistype.locked = true
            endmethod

            private static method afterDamage takes nothing returns boolean
                if GetUnitAbilityLevel(thistype.stackTop.stackTarget, SET_MAX_LIFE) > 0 then
                    call UnitRemoveAbility(thistype.stackTop.stackTarget, SET_MAX_LIFE)
                endif
                call SetWidgetLife(thistype.stackTop.stackTarget, thistype.hp - thistype.stackTop.stackAmount)
                call DestroyTrigger(GetTriggeringTrigger())
                if thistype.global > 0 then
                    set thistype.allocator[thistype.global] = thistype.allocator[0]
                    set thistype.allocator[0] = thistype.global
                    set thistype.stackTop = thistype.stackTop.stackNext
                endif
                return false
            endmethod

            static method core takes nothing returns boolean
                local real amount = GetEventDamage()
                local boolean changed = false
                local thistype this
                local trigger trg
                local real newHp

                if amount == 0.0 then
                    return false
                endif

                set this = thistype.allocator[0]
                if (thistype.allocator[this] == 0) then
                    set thistype.allocator[0] = this + 1
                else
                    set thistype.allocator[0] = thistype.allocator[this]
                endif
                set this.stackSource = GetEventDamageSource()
                set this.stackTarget = GetTriggerUnit()
                set this.stackNext = thistype.stackTop
                set thistype.stackTop = this

                if amount > 0.0 then
                    set this.stackType = DAMAGE_TYPE_PHYSICAL
                    set this.stackAmount = amount
                    call DamageTrigger2.executeAll()
                    set changed = thistype.changed
                    if changed then
                        set thistype.changed = false
                    endif
                    set thistype.locked = true
                    call DamageTrigger.executeAll()
                    set thistype.locked = false

                elseif amount < 0.0 then
                    set this.stackType = DAMAGE_TYPE_MAGICAL
                    if IsUnitType(this.stackTarget, UNIT_TYPE_ETHEREAL) then
                        set amount = amount*S_ETHEREAL_FACTOR
                    endif
                    set this.stackAmount = -amount
                    call DamageTrigger2.executeAll()
                    set changed = thistype.changed
                    if changed then
                        set thistype.changed = false
                    endif
                    set thistype.locked = true
                    call DamageTrigger.executeAll()
                    set thistype.locked = false
                endif

                if amount < 0.0 or (changed and amount > 0.125) then
                    set thistype.hp = GetWidgetLife(this.stackTarget)
                    set trg = CreateTrigger()
                    if amount > 0.0 then
                        set newHp = thistype.hp + amount
                        if newHp > GetUnitState(this.stackTarget, UNIT_STATE_MAX_LIFE) then
                            call UnitAddAbility(this.stackTarget, SET_MAX_LIFE)
                        endif

                        call SetWidgetLife(this.stackTarget, newHp)
                        if amount > 1.0 then
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 1.0)
                        elseif amount > 0.125 then
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, LESS_THAN, newHp - 0.125)
                        endif
                    else
                        set newHp = thistype.hp + amount
                        if newHp < S_MIN_LIFE then
                            set newHp = S_MIN_LIFE
                        endif
                        call SetWidgetLife(this.stackTarget, newHp)
                        if amount < -1.0 then
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 1.0)
                        elseif amount < -0.125 then
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.125)
                        else
                            call TriggerRegisterUnitStateEvent(trg, this.stackTarget, UNIT_STATE_LIFE, GREATER_THAN, newHp + 0.01)
                        endif
                    endif
                    call TriggerAddCondition(trg, Condition(function thistype.afterDamage))
                    set trg = null
                    set thistype.global = this

                else
                    set thistype.allocator[this] = thistype.allocator[0]
                    set thistype.allocator[0] = this
                    set thistype.stackTop = thistype.stackTop.stackNext

                endif

                return false
            endmethod

            static method operator amount= takes real r returns nothing
                if not thistype.locked then
                    set thistype.stackTop.stackAmount = r
                    set thistype.changed = true
                endif
            endmethod

            private static method onInit takes nothing returns nothing
                local unit u = CreateUnit(Player(14), 'hfoo', 0, 0, 0)
                call UnitAddAbility(u, SET_MAX_LIFE)
                call RemoveUnit(u)
                set thistype.registered2 = CreateTrigger()
                call DamageTrigger2.register(thistype.registered2)
                set u = null
            endmethod
        endmodule

    endlibrary
     


    Basic Example

    Code (vJASS):

    library L initializer Init
             
        private function OnDamage takes nothing returns nothing
            //This will run whenever a unit registered takes damage.
            //Do you thing here
        endfunction
             
        private function Init takes nothing returns nothing
            call Damage.register(function OnDamage)
        endfunction
           
    endlibrary
     


    Changelog

    Changelog
    v1.00 - [3 Aug 2016]
    - Initial Release

    v1.10 - [7 Aug 2016]
    - Fixed unremoved BJDebug Messages.
    - Fixed unremoved group in preplace.
    - Fixed uncleaned Table/hashtable timer handle id.
    - Fixed "Nonrecursive Damage bug".
    - Fixed HP Bar flickering bug.
    - Fixed a bug where revived units are not registered.
    - Optimized the script, now only uses 1 static timer.
    - Replaced UnitAlive by GetUnitTypeId as the condition for removal.
    - Removed optional requirements TimerUtils and TimerUtilsEx.
    - Implemented a periodic refresh mechanism.
    - Implemented the bucket technique to limit the number of units per refresh.

    v1.11 - [7 Aug 2016]
    - Fixed a bug where it does not auto-register preplaced units when using Table.
    - Fixed some functions compiling to trigger evaluation due to the order.
    - Added a filter when using AUTO_REGISTER.

    v1.12 - [8 Aug 2016]
    - Fixed a bug when all DamageBuckets are removed.
    - Fixed a bug where currentBucket points to a destroyed DamageBucket when it is destroyed.
    - Fixed unremoved saved boolean in Table/hashtable.
    - AutoRegisterFilter is now also applied to preplaced units.

    v1.20 - [17 August 2016]
    - Fixed recursion bug.
    - Added Damage.enabled to control whether DamageEvent callbacks are ON/OFF.
    - Added Damage.registerPermanent(code).
    - Renamed Damage.registerFirst(code) to Damage.registerModifier(code).
    - Damage.registerModifier(code) only comes within DamageModify.
    - SET_MAX_LIFE of DamageModify is now preloaded.

    v1.30 - [15 October 2016]
    - Added more detailed documentation with examples.
    - Now uses life change event instead of a timer avoiding several bugs.
    - Fixed recursion damage workaround.
    - Optimized and shortened the code.

    v1.40 - [29 March 2017]
    - Implemented a stack to make Damage.source, Damage.target, Damage.amount and Damage.type behave like local variables in the callback.
    - Fixed Damage.source and Damage.target changing unit value withing callback due to recursion.
    - Fixed Damage.amount and Damage.type changing value within callback due to recursion.
    - Improved documentation on how it affects default Warcraft 3 abilities.

    v1.41 - [24 May 2017]
    - Added Damage.lockAmount() feature.
    - Fixed bug occuring when units with very high hp takes damage.
    - Changing Damage.amount will no longer work on non-modifier codes/triggers.

    v1.42 - [25 May 2017]
    - Fixed bug occuring when units with very high hp takes very small damage.

    v1.43 - [29 May 2017]
    - Fixed bug when magic damage amount is between 0.125 to 0.2.

    v1.44 - [3 June 2017]
    - Fixed bug when magic damage exceeds target's max health.

     

    Attached Files:

    Last edited: Jun 3, 2017
  2. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Before anyone asks, What lead me to create DamagePackage?

    While modding, I realized I need a vJASS DDS that can distinguish Physical and Magical Damage. Physical Damage Detection System (PDDS) by looking_for_help is the only candidate so I initially used it. However, there are things I don't like about PDDS:
    (No offense to lfh)

    - Not modular enough
    When a user doesn't need the damage modification feature, PDDS still uses extra computation for it.

    - Has room for microoptimization
    Can be improved such as only using 1 hashtable read and write with a struct instead of putting source, target, amount, etc to each of their own hashtable space.

    - Looks outdated
    Doesn't use UnitAlive and uses a trigger with variable changes value event to trigger registered function calls.
    Also, lfh wasn't as good as he is now when he created PDDS (PDDS.source/target/damageType is not readonly).
    Also, DamagePackage utilizes other systems such as TimerUtils and Table when found/present.

    - Looks bloated
    There are things like function UnitHasItemOfType and other wrapper functions which seems unnecessary to me.
    When I look at function DamageEngine, it seems like a huge overhead.
    Mine (when Damage is not modified) will only call 5 native functions everytime a unit is physically damaged compared to PDDS which calls >15 natives and a few custom functions.

    - I hate the idea of trigger refresh periodically.
    DamagePackage uses a different approach, it will only refresh once a certain number of units is registered.


    - Doesn't allow full control who gets registered.
    Imagine a map where I have full control of units appearing in the map via CreateUnit. Then instead of using a trigger with event "Unit enters Region",
    I can just add (or hook) Damage.add(unit) after CreateUnit saving more performance.

    - Slightly a pain to import.
    Requires to copy two abilities. DamagePackage will use ObjectMerger in the future.
    Latest versions already has an ObjectMerger.



    By the way, I'm expecting this still has a lot of bugs, but for now my tests went flawlessly.

    Things I will consider in the future: (Tell me if I miss something)
    - Exploding unit
    - Locust Swarm returning to caster
    - At first I saw flickering hp, but now I can't even though I didn't change anything in the code (Need help on this).
    - Coke's bucket technique.
    - Priority Queue? (not sure)
     
    Last edited: Aug 8, 2016
  3. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,146
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    I think that Coke's library already fills the niche here.
     
  4. You can't be talking about this
     
  5. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Coke's SDDS can't distinguish physical from magical.
     
  6. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    550
    Resources:
    6
    Tools:
    1
    Maps:
    1
    Spells:
    1
    JASS:
    3
    Resources:
    6
    It seems that a
    Damage.is_killing_blow
    flag could be useful, rather than registering *_DEATH event.

    It can be computed from within the callback but it's tedious
    Code (vJASS):

    if GetWidgetLife(Damage.target) - Damage.amount < 0.405 then
        // Damage.target died, although UnitAlive reports that it's alive, it will be dead ("next tick"?)
    endif
     


    The "Nonrecursive Damage" example doesn't work, the damage dealt is always 20 (regardless of the attacker), and the "flickering hp" occured.

    PS: the two abilities in ObjectMerger syntax:

    Code (vJASS):

    // ADMG:
    //
    //! external ObjectMerger w3a AIsr ADMG isr2 1 2 aart "" anam "Damage.DAMAGE_TYPE_DETECTOR"
    // or
    //! externalblock extension=lua ObjectMerger $FILENAME$
        //! i setobjecttype("abilities")

        //! i createobject("AIsr", "ADMG")
            //! i makechange(current, "isr2", "1" , "2")
            //! i makechange(current, "aart", "")
            //! i makechange(current, "anam", "Damage.DAMAGE_TYPE_DETECTOR")
    //! endexternalblock

    // ASML:
    //
    //! external ObjectMerger w3a AIl1 ASML Ilif 1 1000000 ansf "" anam "Damage.SET_MAX_LIFE"
    // or
    //! externalblock extension=lua ObjectMerger $FILENAME$
        //! i setobjecttype("abilities")

        //! i createobject("AIl1", "ASML")
            //! i makechange(current, "Ilif", "1" , "1000000")
            //! i makechange(current, "ansf", "")
            //! i makechange(current, "anam", "Damage.SET_MAX_LIFE")
    //! endexternalblock
     
     
  7. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Why Damage.is_killing_blow (hate that underscore) over registering DEATH event? What's the advantage/purpose?

    Good find, added you to the credit lists. I was able to track down the cause and found a solution. It turns out that the constant 10 damage is applied twice (hence 20 damage). It is fixed now in the version on my hard drive.
    The flickering hp is also fixed at least from what I can see. I'll upload the next version as soon as I fully tested and checked it unlike the Initial Release.

    This is a huge help, thanks! Can't give you +rep, it says "5 different users must be given reputation before hitting the same person again."
     
  8. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    "Why Damage.is_killing_blow (hate that underscore) over registering DEATH event? What's the advantage/purpose?"
    onDeath has a few disadvantages.
    In my Damage Engine, I have a boolean "isFatalDamage" and an event "onFatalDamage".
    The difference is for example with buffs, one can not read buffs from an already dead unit (and the unit is dead on the onDeath event).
    Or if you want to prevent death, you also have to be before the damage has been taken.

    Also... you dont need to create a new timer every time a unit takes damage, the duration is supposed to be minimal (0), so one timer fixing all the units should do... next to which, you dont need timers at all... you just need a trigger with a life change event.

    Also... I might have read it wrong, but if I am not mistaken, your system doesnt properly handle nesting damages.
    Have a trigger running on the "thistype.registered" event and have the actions of that trigger deal damage to a unit.

    Also... I think the event functionality should be in a separate system.
    Just how I feel that it makes sense.

    Also... ... why do I even care?
    Im out.
     
  9. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    If that's the case, users can just check the difference between current life and amount of damage and if it is less than 0.405, the damage is fatal.

    Using a single static timer would result to a significant improve of performance, however it will bugged when multiple units are damaged at the same time (e.g. Flamestrike from Bloodmage). I'll try to research the life change event implementation if its better.

    Yes it was bugged, but I fixed it now in the version in my hard drive (which is not published yet).

    What event functionality?

    Because you're an active member?? Because I will give anyone +rep for feedback??
    EDIT: Damn it, same reason "5 different users must be given reputation before hitting the same person again."
     
  10. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Not true.
    What you have to do is start the timer and add the damaged unit to a stack of units.
    When the timer expires, the stack will be iterated over and the units will be reset to normal life.
    (Just dont forget to empty the stack ;))

    The event register and stuff.
    Something like this
    Code (vJASS):

    struct Damage
     
        readonly static Event onDamage
     
    endstruct

    struct Event
     
        public method run takes nothing returns nothing
        public method register takes code func returns nothing
        public method unregister takes code func returns nothing
     
    endstruct
     


    Nes and Bribe already have the life change event.
    It is pretty neat because you can do:

    call UnitDamageTarget(u, u, blablabla)
    call BJDebugMsg("HP: " + R2S(GetWidgetLife(u)))

    And it will show the correct value... not a +500k value.
     
  11. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Yeah, but you weren't specific at first so I had to "guess" what you mean.
    Anyways, I'll ditch the timer stuff and use Life Event instead.

    I don't see any advantage of doing so.

    Got it.
     
  12. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    "I don't see any advantage of doing so."
    There is no real advantage... except you separate different things.
    As mentioned before, it is no big deal, but it would be nice.
    Especially when you have different events (beforeDamage, afterDamage, etc).
     
  13. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,146
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    Life event won't work. DDS is currently bugged because of it.
     
  14. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Why do they not work?
    Because the damage is too little?
     
  15. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,146
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    That is correct
     
  16. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Updated to v1.10.
    I ditched the Life Event because of the bug and because that approach means I have to create a trigger every onDamage. Using GetWidgetLife returns the hp before damage is applied and I was not able to make GetWidgetLife return a value of 500k+ in my tests which is good.
     
  17. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    None of my tests failed, but if it is really a problem, you could potentially make 2 events, one with the smallest amount possible, and one with half the damage amount.

    If the damage would be 0.01 (which is hardly anywhere near possible death, or reasonable damage), then it would fail.
    (Dunno what the exact minimum was though gotta check that out.)
     
  18. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,502
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    If I may ask, why
    ATTACK_TYPE_NORMAL
    and
    DAMAGE_TYPE_UNIVERSAL
    ?
     
  19. Wietlol

    Wietlol

    Joined:
    Aug 1, 2013
    Messages:
    4,638
    Resources:
    3
    Spells:
    3
    Resources:
    3
    In theory, it doesnt matter, a unit has to deal damage to the target unit, and the damage is quite a lot, just to kill the unit.
    However, if you for some reason change the damage type bonus from normal to 0 against whatever armor the target is wearing, it wont work.

    Therefor, it is safer to use
    ConvertAttackType(7)
    which is not present in the gameplay constants.
    All attack types above that go to
    null
    which goes to the standard... I think
    ATTACK_TYPE_NORMAL
    .
    (On a side note,
    ConvertAttackType(7)
    deals increased damage to all armor types I used in my tests, I didnt really check the values, but it was a serious factor.)

    Also... (before some people start to complain about it).
    Siege weapons (aka units with Artillery as weapon type), explode units when they die.
    Triggered damage doesnt do that.
    If the target should die, and the original damage is positive, you should set the current life to the minimum amount and let the original damage kill the target.
    Then Artillery damage will explode the unit.
     
  20. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,333
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    Using that combination damages the unit whether it is ethereal or magic immune.

    Unfortunately, ConvertAttackType(7) cannot damage ethereal units that's why I'm not using it.

    My system still explodes units when attacked by a Meat Wagon unless the Damage was modified. I'm not sure if there is any solution to that. My DamageModify algorithm cancels the amount of damage taken then apply the Damage.amount in the after damage.