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

[System] Damage Modification Effect

JASS:
library DamageModificationEffect /* v1.0.1.2
*************************************************************************************
*
*   Apply damage modifiers to units
*
*************************************************************************************
*
*   */uses/*
*
*       */ AdvDamageEvent       /*          hiveworkshop.com/forums/submissions-414/extension-advanced-damage-event-213572/
*       */ AVL                  /*          hiveworkshop.com/forums/jass-resources-412/snippet-avl-tree-203168/
*       */ Table                /*          hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       */ DamagePriority       /*          hiveworkshop.com/forums/submissions-414/repo-damage-priority-213750/
*
*************************************************************************************
*
*   Outgoing Vs Incoming Damage
*       -   If incoming, will evaluate code whenever the unit takes damage
*       -   If outgoing, will evaluate code whenever the unit applies damage
*       -   Incoming damage always runs after outgoing damage regardless of priority
*
*   struct DamageModificationEffect extends AdvDamageEvent, UnitIndexStructMethods
*
*       readonly static DamageModificationEffect event
*           -   The evaluated DamageModificationEffect. Use to retrieve instance within passed in code.
*
*       integer priority
*
*       boolexpr code
*           -   Code to be evaluated when effect runs
*           -   Must return true
*
*       method apply takes boolexpr func, boolean incoming, integer priority returns thistype
*           -   Applies damage effect to unit (this is UnitIndex)
*           -   DamageModificationEffect[whichUnit].apply(...)
*
*       method destroy takes nothing returns nothing
*           -   Destroys a damage modification effect (this is DamageModificationEffect)
*           -   set effect = DamageModificationEffect[whichUnit].apply(...)
*           -   call effect.destroy()
*
*
*       method clearOutgoing takes nothing returns nothing
*           -   Clears all outgoing effects
*           -   call DamageModificationEffect[whichUnit].clearOutgoing()
*       method clearIncoming takes nothing returns nothing
*           -   Clears all incoming effects
*           -   call DamageModificationEffect[whichUnit].clearIncoming()
*       method clear takes nothing returns nothing
*           -   Clears unit of all damage modification effects currently on it (this is UnitIndex)
*           -   call DamageModificationEffect[whichUnit].clear()
*
*   module DamageModificationEffect extends AdvDamageEvent, UnitIndexStructMethods
*
*       method apply takes boolean incoming returns thistype                    (returns 0 if neither onDamageOutgoing or onDamageIncoming exist)
*           -   Applies damage effect to unit (this is UnitIndex)
*           -   thistype[whichUnit].apply(...)
*
*       method destroy takes nothing returns nothing
*           -   Destroys a damage modification effect (this is thistype)
*           -   set effect = thistype[whichUnit].apply(...)
*           -   call effect.destroy()
*
*       method clearOutgoing takes nothing returns nothing                      (only exists when onDamageOutgoing exists)
*           -   Clears all outgoing effects
*           -   call thistype[whichUnit].clearOutgoing()
*       method clearIncoming takes nothing returns nothing                      (only exists when onDamageIncoming exists)
*           -   Clears all incoming effects
*           -   call thistype[whichUnit].clearIncoming()
*       method clear takes nothing returns nothing
*           -   Clears unit of all damage modification effects currently on it (this is UnitIndex)
*           -   call thistype[whichUnit].clear()
*
*       Interface:
*           private static constant integer PRIORITY takes nothing returns integer      (optional)
*               -   defined priority for module
*           private method onDamageIncoming takes nothing returns nothing               (optional)
*               -   this is the current struct instance returned from apply
*           private method onDamageOutgoing takes nothing returns nothing               (optional)
*               -   this is the current struct instance returned from apply
*
*************************************************************************************/
    private struct Tree extends array
        method lessThan takes thistype value returns boolean
            return integer(this) < integer(value)
        endmethod
        
        method greaterThan takes thistype value returns boolean
            return integer(this) > integer(value)
        endmethod
        
        implement AVL
    endstruct
    
    private module PriorityListMod
        private static method onInit takes nothing returns nothing
            set next_pp = Table.create()
            set prev_pp = Table.create()
            set head_pp = Table.create()
            set superhead_pp = Table.create()
        endmethod
    endmodule
    private struct PriorityList extends array
        private static integer instanceCount = 0
        
        private static Table next_pp
        private static Table prev_pp
        private static Table head_pp
        private static Table superhead_pp
        
        implement PriorityListMod
        
        method operator next_p takes nothing returns thistype
            return next_pp[this]
        endmethod
        method operator next_p= takes thistype val returns nothing
            set next_pp[this] = val
        endmethod
        method operator prev_p takes nothing returns thistype
            return prev_pp[this]
        endmethod
        method operator prev_p= takes thistype val returns nothing
            set prev_pp[this] = val
        endmethod
        method operator head_p takes nothing returns thistype
            return head_pp[this]
        endmethod
        method operator head_p= takes thistype val returns nothing
            set head_pp[this] = val
        endmethod
        method operator superhead_p takes nothing returns thistype
            return superhead_pp[this]
        endmethod
        method operator superhead_p= takes thistype val returns nothing
            set superhead_pp[this] = val
        endmethod
        
        thistype first_p
        
        private static method allocate takes nothing returns thistype
            local thistype this = thistype(0).next_p
            
            if (0 == this) then
                set this = instanceCount + 1
                set instanceCount = this
            else
                set thistype(0).next_p = next_p
            endif
            
            return this
        endmethod
        private method deallocate takes nothing returns nothing
            set next_p = thistype(0).next_p
            set thistype(0).next_p = this
        endmethod
        private static method deallocateRange takes thistype first, thistype last returns nothing
            set last.next_p = thistype(0).next_p
            set thistype(0).next_p = first
        endmethod
        
        static method create takes nothing returns thistype
            return Tree.create()
        endmethod
        method destroy takes nothing returns nothing
            local Tree priority = this
            
            /*
            *   Loop through priorities
            */
            loop
                set priority = priority.next
                exitwhen priority.head
                
                /*
                *   Deallocate list on priority
                */
                if (0 != thistype(priority).first_p) then
                    call deallocateRange(thistype(priority).first_p, thistype(priority).first_p.prev_p)
                    set thistype(priority).first_p = 0
                endif
            endloop
            
            call Tree(this).destroy()
        endmethod
        
        method clear takes nothing returns nothing
            local Tree priority = this
            
            /*
            *   Loop through priorities
            */
            loop
                set priority = priority.next
                exitwhen priority.head
                
                /*
                *   Deallocate list on priority
                */
                if (0 != thistype(priority).first_p) then
                    call deallocateRange(thistype(priority).first_p, thistype(priority).first_p.prev_p)
                    set thistype(priority).first_p = 0
                endif
            endloop
            
            call Tree(this).clear()
        endmethod
        
        private method add_p takes thistype node returns nothing
            /*
            *   Add to list
            */
            if (0 == first_p) then
                set first_p = node
                set node.next_p = node
                set node.prev_p = node
            else
                set node.next_p = first_p
                set node.prev_p = first_p.prev_p
                set first_p.prev_p.next_p = node
                set first_p.prev_p = node
            endif
            
            set node.head_p = this
        endmethod
        method add takes integer priority returns thistype
            local thistype node = allocate()
            
            call thistype(Tree(this).add(priority)).add_p(node)
            set node.superhead_p = this
            
            return node
        endmethod
        private method remove_p takes nothing returns nothing
            local thistype node = this
            
            /*
            *   Get list
            */
            set this = head_p
            
            /*
            *   Remove from list
            */
            set node.prev_p.next_p = next_p
            set node.next_p.prev_p = prev_p
            
            if (node == first_p) then
                set first_p = node.next_p
                if (node == first_p) then
                    set first_p = 0
                endif
            endif
        endmethod
        method remove takes nothing returns nothing
            call remove_p()
            call deallocate()
        endmethod
        method operator priority takes nothing returns integer
            return Tree(head_p).value
        endmethod
        method operator priority= takes integer priority returns nothing
            if (this.priority != priority) then
                call remove_p()
                call thistype(Tree(superhead_p).add(priority)).add_p(this)
            endif
        endmethod
    endstruct
    
    private module DamageModificationEffectMod
        private static method onInit takes nothing returns nothing
            set evalForce = CreateForce()
        endmethod
    endmodule

    struct DamageModificationEffect extends array
        private static constant method PRIORITY takes nothing returns integer
            return DAMAGE_PRIORITY_EXECUTION_DAMAGE_MODIFIER_EFFECTS
        endmethod
    
        private PriorityList listOutgoing
        private PriorityList listIncoming
        
        boolexpr code
        
        readonly static thistype event = 0
        
        private static force evalForce
        
        implement DamageModificationEffectMod
        
        method operator priority takes nothing returns integer
            return PriorityList(this).priority
        endmethod
        method operator priority= takes integer priority returns nothing
            set PriorityList(this).priority = priority
        endmethod
        
        method apply takes boolexpr func, boolean incoming, integer priority returns thistype
            local thistype node
            
            if (incoming) then
                set node = listIncoming.add(priority)
            else
                set node = listOutgoing.add(priority)
            endif
            
            set node.code = func
            
            return node
        endmethod
        method destroy takes nothing returns nothing
            call PriorityList(this).remove()
        endmethod
        method clearOutgoing takes nothing returns nothing
            call listOutgoing.clear()
        endmethod
        method clearIncoming takes nothing returns nothing
            call listIncoming.clear()
        endmethod
        method clear takes nothing returns nothing
            call clearOutgoing()
            call clearIncoming()
        endmethod
        
        private static method applyDamage takes Tree priority returns nothing
            local thistype node
            local thistype prevEvent
            
            if (0 != priority) then
                /*
                *   Loop through all priorities
                */
                loop
                    set priority = priority.prev
                    exitwhen priority.head
                    
                    /*
                    *   Loop through all nodes
                    */
                    set node = PriorityList(priority).first_p
                    
                    if (0 != node) then
                        set PriorityList(priority).first_p.prev_p.next_p = 0
                        set prevEvent = event
                        loop
                            set event = node
                            call ForceEnumPlayersCounted(evalForce, node.code, 1)
                            
                            set node = PriorityList(node).next_p
                            exitwhen 0 == node
                        endloop
                        set event = prevEvent
                        set PriorityList(priority).first_p.prev_p.next_p = PriorityList(priority).first_p
                    endif
                endloop
            endif
        endmethod
        
        private static method onDamage takes nothing returns nothing
            call applyDamage(thistype(sourceId).listOutgoing)
            call applyDamage(thistype(targetId).listIncoming)
        endmethod
        
        private method index takes nothing returns nothing
            set listIncoming = PriorityList.create()
            set listOutgoing = PriorityList.create()
        endmethod
        private method deindex takes nothing returns nothing
            call listIncoming.destroy()
            call listOutgoing.destroy()
        endmethod
        
        implement DamageEvent
        implement UnitIndexStruct
    endstruct
    
    private module ModuleListMod
        private static Table next_p
        private static Table prev_p
        private static Table head_p
        
        method operator next takes nothing returns thistype
            return next_p[this]
        endmethod
        method operator next= takes thistype val returns nothing
            set next_p[this] = val
        endmethod
        method operator prev takes nothing returns thistype
            return prev_p[this]
        endmethod
        method operator prev= takes thistype val returns nothing
            set prev_p[this] = val
        endmethod
        method operator head takes nothing returns thistype
            return head_p[this]
        endmethod
        method operator head= takes thistype val returns nothing
            set head_p[this] = val
        endmethod
        
        private static method onInit takes nothing returns nothing
            set next_p = Table.create()
            set prev_p = Table.create()
            set head_p = Table.create()
        endmethod
    endmodule
    
    private struct ModuleList extends array
        implement ModuleListMod
        
        private static integer instanceCount = 0
        
        static method create takes nothing returns thistype
            local thistype this = thistype(0).next
            
            if (0 == this) then
                set this = instanceCount + 1
                set instanceCount = this
            else
                set thistype(0).next = next
            endif
            
            set next = this
            set prev = this
            
            return this
        endmethod
        method clear takes nothing returns nothing
            if (this != next) then
                set prev.next = thistype(0).next
                set thistype(0).next = next
                set next = this
                set prev = this
            endif
        endmethod
        method destroy takes nothing returns nothing
            set prev.next = thistype(0).next
            set thistype(0).next = this
        endmethod
        method add takes nothing returns thistype
            local thistype node = create()
            
            set node.next = this
            set node.prev = prev
            set prev.next = node
            set prev = node
            
            set node.head = this
            
            return node
        endmethod
        method remove takes nothing returns nothing
            local thistype node = this
            
            /*
            *   Get list
            */
            set this = head
            
            /*
            *   Remove from list
            */
            set node.prev.next = node.next
            set node.next.prev = node.prev
            
            set node.next = thistype(0).next
            set thistype(0).next = node
        endmethod
    endstruct
    
    module DamageModificationEffect
        static if thistype.onDamageIncoming.exists then
            private static boolexpr codeIncoming
            private ModuleList listIncoming
        endif
        
        static if thistype.onDamageOutgoing.exists then
            private static boolexpr codeOutgoing
            private ModuleList listOutgoing
        endif
        
        static if thistype.onDamageIncoming.exists then
            private static method onDamageIncoming_p takes nothing returns boolean
                local ModuleList list = thistype(targetId).listIncoming
                local thistype node
                
                set list.prev.next = 0
                
                /*
                *   Loop through all nodes
                */
                set node = list.next
                loop
                    exitwhen 0 == node
                    call node.onDamageIncoming()
                    
                    set node = ModuleList(node).next
                endloop
                set list.prev.next = list
                
                return true
            endmethod
        endif
        
        static if thistype.onDamageOutgoing.exists then
            private static method onDamageOutgoing_p takes nothing returns boolean
                local ModuleList list = thistype(sourceId).listOutgoing
                local thistype node
                
                set list.prev.next = 0
                
                /*
                *   Loop through all nodes
                */
                set node = list.next
                loop
                    exitwhen 0 == node
                    call node.onDamageOutgoing()
                    
                    set node = ModuleList(node).next
                endloop
                set list.prev.next = list
                
                return true
            endmethod
        endif
        
        method apply takes boolean incoming returns thistype
            local thistype node = 0
            
            if (incoming) then
                static if thistype.onDamageIncoming.exists then
                    set node = listIncoming.add()
                endif
            else
                static if thistype.onDamageOutgoing.exists then
                    set node = listOutgoing.add()
                endif
            endif
            
            return node
        endmethod
        method destroy takes nothing returns nothing
            call ModuleList(this).remove()
        endmethod
        static if thistype.onDamageOutgoing.exists then
            method clearOutgoing takes nothing returns nothing
                call listOutgoing.clear()
            endmethod
        endif
        static if thistype.onDamageIncoming.exists then
            method clearIncoming takes nothing returns nothing
                call listIncoming.clear()
            endmethod
        endif
        method clear takes nothing returns nothing
            static if thistype.onDamageOutgoing.exists then
                call clearOutgoing()
            endif
            
            static if thistype.onDamageIncoming.exists then
                call clearIncoming()
            endif
        endmethod
        
        private static method index_p takes nothing returns boolean
            local thistype this = GetIndexedUnitId()
            
            static if thistype.onDamageIncoming.exists then
                set listIncoming = ModuleList.create()
                
                static if thistype.PRIORITY.exists then
                    call DamageModificationEffect(this).apply(codeIncoming, true, PRIORITY())
                else
                    call DamageModificationEffect(this).apply(codeIncoming, true, 0)
                endif
            endif
            
            static if thistype.onDamageOutgoing.exists then
                set listOutgoing = ModuleList.create()
                
                static if thistype.PRIORITY.exists then
                    call DamageModificationEffect(this).apply(codeOutgoing, false, PRIORITY())
                else
                    call DamageModificationEffect(this).apply(codeOutgoing, false, 0)
                endif
            endif
            
            return false
        endmethod
        private static method deindex_p takes nothing returns boolean
            local thistype this = GetIndexedUnitId()
        
            static if thistype.onDamageIncoming.exists then
                call listIncoming.destroy()
            endif
            
            static if thistype.onDamageOutgoing.exists then
                call listOutgoing.destroy()
            endif
            
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            static if thistype.onDamageIncoming.exists then
                set codeIncoming = Condition(function thistype.onDamageIncoming_p)
            endif
            
            static if thistype.onDamageOutgoing.exists then
                set codeOutgoing = Condition(function thistype.onDamageOutgoing_p)
            endif
            
            call RegisterUnitIndexEvent(Condition(function thistype.index_p), UnitIndexer.INDEX)
            call RegisterUnitIndexEvent(Condition(function thistype.deindex_p), UnitIndexer.DEINDEX)
        endmethod
        
        implement ExtendDamageEvent
        implement UnitIndexStructMethods
    endmodule
endlibrary
 
Last edited:
fixed typo in module

edit
Ok, I'm redesigning this so that it only runs damage modifications when they should be running. For example, a damage modification on fire damage shouldn't run when physical damage is applied =P.

This will require a lot of added complexity to the code : |. Not to worry, only background code will become complicated /cry

But the stuff in the background now looks like this
incoming[damageType][damageSource].add(priority)

rather than
incoming.add(priority)

so it's pretty crazy =p

edit
ok, changing the design again to allow modifiers to apply to all damage types and what not. Well, I'm not sure how I'm going to allow for this, but I'll figure out a way. My first idea was to just put all modifiers into the same lists, but then I realized that I'd be iterating over a lot of modifiers that aren't going to run. As the new design is right now, I'm only iterating over modifiers that are always going to run.

I can't just run global lists as that will screw up priority. I might have to just put all modifiers into global outgoing/incoming lists and only run them if they are -1 or are the right types.

edit
solved how to do this : ), will put the implementation up later today
 
Last edited:
Top