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

Trigger v1.1.0.2

  • Like
Reactions: Vinz
Trigger
v1.1.0.2
by Nestharus
__

[td]

Trigger is used to replace native triggers. It has a lot of additional functionality that native triggers do
not have and it runs slightly faster than them too. What's not to like?

Trigger is split up into three structs

  • Trigger
  • TriggerCondition
  • TriggerReference

A TriggerCondition is just like a triggercondition, except that it can be removed at any point, you don't need
access to a Trigger to destroy it, and the expressions inside of it can be changed.

A TriggerReference is TriggerCondition for Triggers. Yes, Triggers can be added to Triggers.

Trigger is the main struct. It has registration (code) and referencing (Trigger). It can also clear out events, clear
out registered code, clear references, and even drop itself from all Triggers referencing it.

A Trigger's code is stored in a single boolexpr on a single trigger. This makes it run really fast and makes
it more reliable than native triggers. This supports TriggerRemoveCondition (essentially) while that condition is
running. There is an example in the map that shows this and compares it to a native trigger.

Trigger makes event registration easy, allowing for many entry points (see event example in map). The new UnitIndexer
and DDS would not be possible without this library. It is used for extreme system development, but also can completely
replace native triggers.

Trigger has no real instance limit as it uses a hashtable to store all of its data.

Be sure to check out the map. It has a lot of interesting examples.

JASS:
library Trigger /* v1.1.0.2
************************************************************************************
*
*   */ uses /*
*   
*       */ ErrorMessage         /*
*       */ BooleanExpression    /*
*       */ NxListT              /*
*		*/ UniqueNxListT		/*
*       */ Init                 /*
*		*/ AllocT				/*
*
************************************************************************************
*
*   struct Trigger extends array
*           
*       Fields
*       -------------------------
*
*           readonly trigger trigger
*			-   use to register events, nothing else
*			-   keep in mind that triggers referencing this trigger won't fire when events fire
*			-   this trigger will fire when triggers referencing this trigger are fired
*
*           boolean enabled
*
*       Methods
*       -------------------------
*
*           static method create takes boolean reversed returns Trigger
*			-	when reverse is true, the entire expression is run in reverse
*
*           method destroy takes nothing returns nothing
*
*           method register takes boolexpr expression returns TriggerCondition
*
*           method reference takes Trigger trig returns TriggerReference
*			-   like register, but for triggers instead
*
*           method fire takes nothing returns nothing
*
*           method clear takes nothing returns nothing
*			-   clears expressions
*           method clearReferences takes nothing returns nothing
*			-   clears trigger references
*           method clearBackReferences takes nothing returns nothing
*			-   removes references for all triggers referencing this trigger
*           method clearEvents takes nothing returns nothing
*			-   clears events
*
*           debug static method calculateMemoryUsage takes nothing returns integer
*           debug static method getAllocatedMemoryAsString takes nothing returns string
*
************************************************************************************
*
*   struct TriggerReference extends array
*           
*       Methods
*       -------------------------
*
*           method destroy takes nothing returns nothing
*
*           method replace takes Trigger trigger returns nothing
*
************************************************************************************
*
*   struct TriggerCondition extends array
*
*       Methods
*       -------------------------
*
*           method destroy takes nothing returns nothing
*
*           method replace takes boolexpr expr returns nothing
*
************************************************************************************/
    private struct TriggerMemory extends array
        //! runtextmacro CREATE_TABLE_FIELD("public", "trigger", "trig", "trigger")
        //! runtextmacro CREATE_TABLE_FIELD("public", "triggercondition", "tc", "triggercondition")
        
        //! runtextmacro CREATE_TABLE_FIELD("public", "integer", "expression", "BooleanExpression")                 //the trigger's expression
        
        //! runtextmacro CREATE_TABLE_FIELD("public", "boolean", "enabled", "boolean")
        
        method updateTrigger takes nothing returns nothing
            if (tc != null) then
                call TriggerRemoveCondition(trig, tc)
            endif
        
            if (enabled and expression.expression != null) then
                set tc = TriggerAddCondition(trig, expression.expression)
            else
				call tc_clear()
            endif
        endmethod
        
        private static method init takes nothing returns nothing
            //! runtextmacro INITIALIZE_TABLE_FIELD("trig")
            //! runtextmacro INITIALIZE_TABLE_FIELD("tc")
            
            //! runtextmacro INITIALIZE_TABLE_FIELD("expression")
            
            //! runtextmacro INITIALIZE_TABLE_FIELD("enabled")
        endmethod
        
        implement Init
    endstruct

    private struct TriggerAllocator extends array
        implement AllocT
    endstruct
    
    private keyword TriggerReferencedList
    
    private struct TriggerReferenceListData extends array
        //! runtextmacro CREATE_TABLE_FIELD("public", "integer", "trig", "TriggerMemory")           //the referenced trigger
        //! runtextmacro CREATE_TABLE_FIELD("public", "integer", "ref", "TriggerReferencedList")    //the TriggerReferencedList data for that trigger (relationship in 2 places)
        //! runtextmacro CREATE_TABLE_FIELD("public", "integer", "expr", "BooleanExpression")
    
        implement NxListT
        
        private static method init takes nothing returns nothing
            //! runtextmacro INITIALIZE_TABLE_FIELD("trig")
            //! runtextmacro INITIALIZE_TABLE_FIELD("ref")
            //! runtextmacro INITIALIZE_TABLE_FIELD("expr")
        endmethod
        
        implement Init
    endstruct

    /*
    *   List of triggers referencing current trigger
    */
    private struct TriggerReferencedList extends array
        //! runtextmacro CREATE_TABLE_FIELD("public", "integer", "trig", "TriggerMemory")               //the trigger referencing this trigger
        //! runtextmacro CREATE_TABLE_FIELD("public", "integer", "ref", "TriggerReferenceListData")     //the ref 
    
        implement NxListT
        
        method updateExpression takes nothing returns nothing
            local thistype node
            local boolexpr expr
            
            /*
            *   Retrieve the expression of the referenced trigger
            */
            if (TriggerMemory(this).enabled) then
                set expr = TriggerMemory(this).expression.expression
            else
                set expr = null
            endif
            
            /*
            *   Iterate over all triggers referencing this trigger
            */
            set node = first
            loop
                exitwhen node == 0
                
                /*
                *   Replace expression and then update the target trigger
                */
                call node.ref.expr.replace(expr)
                call node.trig.updateTrigger()
                call TriggerReferencedList(node.trig).updateExpression()
                
                set node = node.next
            endloop
            
            set expr = null
        endmethod
        
        method purge takes nothing returns nothing
            local thistype node = first
            
            loop
                exitwhen node == 0
                
                /*
                *   Unregister the expression from the referencing trigger
                *   Update that trigger
                */
                call node.ref.expr.unregister()
                call node.trig.updateTrigger()
                call node.ref.remove()
                call TriggerReferencedList(node.trig).updateExpression()
                
                set node = node.next
            endloop
            
            call destroy()
        endmethod
        
        method clearReferences takes nothing returns nothing
            local thistype node = first
            
            loop
                exitwhen node == 0
                
                /*
                *   Unregister the expression from the referencing trigger
                *   Update that trigger
                */
                call node.ref.expr.unregister()
                call node.trig.updateTrigger()
                call node.ref.remove()
                call TriggerReferencedList(node.trig).updateExpression()
                
                set node = node.next
            endloop
            
            call clear()
        endmethod
        
        private static method init takes nothing returns nothing
            //! runtextmacro INITIALIZE_TABLE_FIELD("trig")
            //! runtextmacro INITIALIZE_TABLE_FIELD("ref")
        endmethod
        
        implement Init
    endstruct
    
    /*
    *   List of triggers current trigger references
    */
    private struct TriggerReferenceList extends array
        method add takes TriggerReferencedList trig returns thistype
            local TriggerReferenceListData node = TriggerReferenceListData(this).enqueue()
            
            /*
            *   Register the trigger as a reference
            */
            set node.trig = trig
            set node.ref = TriggerReferencedList(trig).enqueue()
            set node.ref.trig = this
            set node.ref.ref = node
            
            /*
            *   Add the reference's expression
            *
            *   Add even if null to ensure correct order
            */
            if (TriggerMemory(trig).enabled) then
                set node.expr = TriggerMemory(this).expression.register(TriggerMemory(trig).expression.expression)
            else
				set node.expr = TriggerMemory(this).expression.register(null)
            endif
            
            call TriggerMemory(this).updateTrigger()
            
            /*
            *   Update the expressions of triggers referencing this trigger
            */
            call TriggerReferencedList(this).updateExpression()
            
            /*
            *   Return the reference
            */
            return node
        endmethod
        
        method erase takes nothing returns nothing
            local TriggerReferenceListData node = this          //the node
            set this = node.ref.trig                            //this trigger        
            
            call node.expr.unregister()
            call TriggerMemory(this).updateTrigger()
            call TriggerReferencedList(this).updateExpression()
            
            call node.ref.remove()
            call node.remove()
        endmethod
        
        method replace takes TriggerMemory trig returns nothing
            local TriggerReferenceListData node = this
            set this = node.list
            
            call node.ref.remove()
            
            set node.trig = trig
            set node.ref = TriggerReferencedList(trig).enqueue()
            set node.ref.trig = this
            set node.ref.ref = node
            
            if (trig.enabled) then
                call node.expr.replace(trig.expression.expression)
            else
                call node.expr.replace(null)
            endif
            
            call TriggerMemory(this).updateTrigger()
            
            call TriggerReferencedList(this).updateExpression()
        endmethod
        
        /*
        *   Purges all references
        */
        method purge takes nothing returns nothing
            local TriggerReferenceListData node = TriggerReferenceListData(this).first
            
            loop
                exitwhen node == 0
                
                /*
                *   Removes the reference from the referenced list
                *   (triggers no longer referenced by this)
                */
                call node.ref.remove()
                
                set node = node.next
            endloop
            
            /*
            *   Destroy all references by triggers referencing this
            */
            call TriggerReferencedList(this).purge()
            
            call TriggerReferenceListData(this).destroy()
        endmethod
        
        method clearReferences takes nothing returns nothing
            local TriggerReferenceListData node = TriggerReferenceListData(this).first
            
            loop
                exitwhen node == 0
                
                /*
                *   Removes the reference from the referenced list
                *   (triggers no longer referenced by this)
                */
                call node.ref.remove()
				
				/*
				*	unregisters code
				*/
				call node.expr.unregister()
                
                set node = node.next
            endloop
            
            call TriggerReferenceListData(this).clear()
        endmethod
    endstruct
    
    private struct TriggerReferenceData extends array
        static if DEBUG_MODE then
            //! runtextmacro CREATE_TABLE_FIELD("private", "boolean", "isTriggerReference", "boolean")
        endif
        
        static method create takes TriggerReferenceList origin, TriggerMemory ref returns thistype
            local thistype this = origin.add(ref)
            
            debug set isTriggerReference = true
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            debug call ThrowError(this == 0,                "Trigger", "destroy", "TriggerReferenceData", this, "Attempted To Destroy Null TriggerReferenceData.")
            debug call ThrowError(not isTriggerReference,   "Trigger", "destroy", "TriggerReferenceData", this, "Attempted To Destroy Invalid TriggerReferenceData.")
            
            debug set isTriggerReference = false
            
            call TriggerReferenceList(this).erase()
        endmethod
        
        method replace takes Trigger trig returns nothing
            debug call ThrowError(this == 0,                "Trigger", "destroy", "TriggerReferenceData", this, "Attempted To Destroy Null TriggerReferenceData.")
            debug call ThrowError(not isTriggerReference,   "Trigger", "destroy", "TriggerReferenceData", this, "Attempted To Destroy Invalid TriggerReferenceData.")
            
            call TriggerReferenceList(this).replace(trig)
        endmethod
        
        private static method init takes nothing returns nothing
            static if DEBUG_MODE then
                //! runtextmacro INITIALIZE_TABLE_FIELD("isTriggerReference")
            endif
        endmethod
        
        implement Init
    endstruct
    
	private struct TriggerConditionDataCollection extends array
		implement UniqueNxListT
	endstruct
	
    private struct TriggerConditionData extends array
        static if DEBUG_MODE then
            //! runtextmacro CREATE_TABLE_FIELD("private", "boolean", "isCondition", "boolean")
        endif
        
        //! runtextmacro CREATE_TABLE_FIELD("private", "integer", "trig", "TriggerMemory")
        
        private static method updateTrigger takes TriggerMemory trig returns nothing
            call trig.updateTrigger()
            call TriggerReferencedList(trig).updateExpression()
        endmethod
    
        static method create takes TriggerMemory trig, boolexpr expression returns thistype
            local thistype this = trig.expression.register(expression)
            
            set this.trig = trig
            
            debug set isCondition = true
			
			call TriggerConditionDataCollection(trig).enqueue(this)
            
            call updateTrigger(trig)
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            debug call ThrowError(this == 0,        "Trigger", "destroy", "TriggerConditionData", this, "Attempted To Destroy Null TriggerConditionData.")
            debug call ThrowError(not isCondition,  "Trigger", "destroy", "TriggerConditionData", this, "Attempted To Destroy Invalid TriggerConditionData.")
            
            call BooleanExpression(this).unregister()
			
			call TriggerConditionDataCollection(this).remove()
            
            debug set isCondition = false
            
            /*
            *   Update the expression
            */
            call updateTrigger(trig)
        endmethod
        
        method replace takes boolexpr expr returns nothing
            debug call ThrowError(this == 0,        "Trigger", "destroy", "TriggerConditionData", this, "Attempted To Destroy Null TriggerConditionData.")
            debug call ThrowError(not isCondition,  "Trigger", "destroy", "TriggerConditionData", this, "Attempted To Destroy Invalid TriggerConditionData.")
            
            call BooleanExpression(this).replace(expr)
            
            call updateTrigger(trig)
        endmethod
		
        private static method init takes nothing returns nothing
            static if DEBUG_MODE then
                //! runtextmacro INITIALIZE_TABLE_FIELD("isCondition")
            endif
            
            //! runtextmacro INITIALIZE_TABLE_FIELD("trig")
        endmethod
        
        implement Init
    endstruct
    
    struct TriggerReference extends array
        method destroy takes nothing returns nothing
            call TriggerReferenceData(this).destroy()
        endmethod
        method replace takes Trigger trig returns nothing
            call TriggerReferenceData(this).replace(trig)
        endmethod
    endstruct
    
    struct TriggerCondition extends array
        method destroy takes nothing returns nothing
            call TriggerConditionData(this).destroy()
        endmethod
        method replace takes boolexpr expr returns nothing
            call TriggerConditionData(this).replace(expr)
        endmethod
    endstruct
    
    struct Trigger extends array
        static if DEBUG_MODE then
            //! runtextmacro CREATE_TABLE_FIELD("private", "boolean", "isTrigger", "boolean")
        endif
    
        static method create takes boolean reversed returns thistype
            local thistype this = TriggerAllocator.allocate()
            
            debug set isTrigger = true
            
            set TriggerMemory(this).enabled = true
            
            call TriggerReferencedList(this).clear()
            call TriggerReferenceListData(this).clear()
			call TriggerConditionDataCollection(this).clear()
            
            set TriggerMemory(this).expression = BooleanExpression.create(reversed)
            
            set TriggerMemory(this).trig = CreateTrigger()
            
            return this
        endmethod
		
		static if DEBUG_MODE then
			method destroy takes nothing returns nothing
				call destroy_p()
			endmethod
		
			private method destroy_p takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "destroy", "Trigger", this, "Attempted To Destroy Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "destroy", "Trigger", this, "Attempted To Destroy Invalid Trigger.")
				
				debug set isTrigger = false
			
				call TriggerReferenceList(this).purge()
				call TriggerConditionDataCollection(this).destroy()
				
				if (TriggerMemory(this).tc != null) then
					call TriggerRemoveCondition(TriggerMemory(this).trig, TriggerMemory(this).tc)
				endif
				call TriggerMemory(this).tc_clear()
				call DestroyTrigger(TriggerMemory(this).trig)
				call TriggerMemory(this).trig_clear()
				
				call TriggerMemory(this).expression.destroy()
				
				call TriggerAllocator(this).deallocate()
			endmethod
		else
			method destroy takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "destroy", "Trigger", this, "Attempted To Destroy Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "destroy", "Trigger", this, "Attempted To Destroy Invalid Trigger.")
				
				debug set isTrigger = false
			
				call TriggerReferenceList(this).purge()
				call TriggerConditionDataCollection(this).destroy()
				
				if (TriggerMemory(this).tc != null) then
					call TriggerRemoveCondition(TriggerMemory(this).trig, TriggerMemory(this).tc)
				endif
				call TriggerMemory(this).tc_clear()
				call DestroyTrigger(TriggerMemory(this).trig)
				call TriggerMemory(this).trig_clear()
				
				call TriggerMemory(this).expression.destroy()
				
				call TriggerAllocator(this).deallocate()
			endmethod
		endif

		static if DEBUG_MODE then
			method register takes boolexpr expression returns TriggerCondition
				return register_p(expression)
			endmethod
			private method register_p takes boolexpr expression returns TriggerCondition
				debug call ThrowError(this == 0,            "Trigger", "register", "Trigger", this, "Attempted To Register To Null Trigger.")
				debug call ThrowError(not isTrigger,        "Trigger", "register", "Trigger", this, "Attempted To Register To Invalid Trigger.")
			
				/*
				*   Register the expression
				*/
				return TriggerConditionData.create(this, expression)
			endmethod
		else
			method register takes boolexpr expression returns TriggerCondition
				debug call ThrowError(this == 0,            "Trigger", "register", "Trigger", this, "Attempted To Register To Null Trigger.")
				debug call ThrowError(not isTrigger,        "Trigger", "register", "Trigger", this, "Attempted To Register To Invalid Trigger.")
			
				/*
				*   Register the expression
				*/
				return TriggerConditionData.create(this, expression)
			endmethod
		endif
        
		static if DEBUG_MODE then
			method clear takes nothing returns nothing
				call clear_p()
			endmethod
			private method clear_p takes nothing returns nothing
				local TriggerConditionDataCollection node = TriggerConditionDataCollection(this).first
			
				debug call ThrowError(this == 0,        "Trigger", "clear", "Trigger", this, "Attempted To Clear Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clear", "Trigger", this, "Attempted To Clear Invalid Trigger.")
				
				loop
					exitwhen node == 0
					
					call BooleanExpression(node).unregister()
					
					set node = node.next
				endloop
				
				call TriggerConditionDataCollection(this).clear()
				
				call TriggerMemory(this).updateTrigger()
				call TriggerReferencedList(this).updateExpression()
			endmethod
		else
			method clear takes nothing returns nothing
				local TriggerConditionDataCollection node = TriggerConditionDataCollection(this).first
			
				debug call ThrowError(this == 0,        "Trigger", "clear", "Trigger", this, "Attempted To Clear Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clear", "Trigger", this, "Attempted To Clear Invalid Trigger.")
				
				loop
					exitwhen node == 0
					
					call BooleanExpression(node).unregister()
					
					set node = node.next
				endloop
				
				call TriggerConditionDataCollection(this).clear()
				
				call TriggerMemory(this).updateTrigger()
				call TriggerReferencedList(this).updateExpression()
			endmethod
		endif
		
		static if DEBUG_MODE then
			method clearReferences takes nothing returns nothing
				call clearReferences_p()
			endmethod
			private method clearReferences_p takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear References Of Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear References Of Invalid Trigger.")
				
				call TriggerReferenceList(this).clearReferences()
				
				call TriggerMemory(this).updateTrigger()
				call TriggerReferencedList(this).updateExpression()
			endmethod
		else
			method clearReferences takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear References Of Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear References Of Invalid Trigger.")
				
				call TriggerReferenceList(this).clearReferences()
				
				call TriggerMemory(this).updateTrigger()
				call TriggerReferencedList(this).updateExpression()
			endmethod
		endif
        
		static if DEBUG_MODE then
			method clearBackReferences takes nothing returns nothing
				call clearBackReferences_p()
			endmethod
			
			private method clearBackReferences_p takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear Back References Of Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear Back References Of Invalid Trigger.")
				
				call TriggerReferencedList(this).clearReferences()
			endmethod
		else
			method clearBackReferences takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear Back References Of Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clearReferences", "Trigger", this, "Attempted To Clear Back References Of Invalid Trigger.")
				
				call TriggerReferencedList(this).clearReferences()
			endmethod
		endif
        
		static if DEBUG_MODE then
			method reference takes thistype trig returns TriggerReference
				return reference_p(trig)
			endmethod
			
			private method reference_p takes thistype trig returns TriggerReference
				debug call ThrowError(this == 0,            "Trigger", "reference", "Trigger", this, "Attempted To Make Null Trigger Reference Trigger.")
				debug call ThrowError(not isTrigger,        "Trigger", "reference", "Trigger", this, "Attempted To Make Invalid Trigger Reference Trigger.")
				debug call ThrowError(trig == 0,            "Trigger", "reference", "Trigger", this, "Attempted To Reference Null Trigger (" + I2S(trig) + ").")
				debug call ThrowError(not trig.isTrigger,   "Trigger", "reference", "Trigger", this, "Attempted To Reference Invalid Trigger (" + I2S(trig) + ").")
				
				return TriggerReferenceData.create(this, trig)
			endmethod
		else
			method reference takes thistype trig returns TriggerReference
				debug call ThrowError(this == 0,            "Trigger", "reference", "Trigger", this, "Attempted To Make Null Trigger Reference Trigger.")
				debug call ThrowError(not isTrigger,        "Trigger", "reference", "Trigger", this, "Attempted To Make Invalid Trigger Reference Trigger.")
				debug call ThrowError(trig == 0,            "Trigger", "reference", "Trigger", this, "Attempted To Reference Null Trigger (" + I2S(trig) + ").")
				debug call ThrowError(not trig.isTrigger,   "Trigger", "reference", "Trigger", this, "Attempted To Reference Invalid Trigger (" + I2S(trig) + ").")
				
				return TriggerReferenceData.create(this, trig)
			endmethod
		endif
		
		static if DEBUG_MODE then
			method clearEvents takes nothing returns nothing
				call clearEvents_p()
			endmethod
			
			private method clearEvents_p takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "clearEvents", "Trigger", this, "Attempted To Clear Events Of Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clearEvents", "Trigger", this, "Attempted To Clear Events Of Invalid Trigger.")
			
				if (TriggerMemory(this).tc != null) then
					call TriggerRemoveCondition(TriggerMemory(this).trig, TriggerMemory(this).tc)
				endif
				call DestroyTrigger(TriggerMemory(this).trig)
				
				set TriggerMemory(this).trig = CreateTrigger()
				if (TriggerMemory(this).enabled) then
					set TriggerMemory(this).tc = TriggerAddCondition(TriggerMemory(this).trig, TriggerMemory(this).expression.expression)
				else
					call TriggerMemory(this).tc_clear()
				endif
			endmethod
		else
			method clearEvents takes nothing returns nothing
				debug call ThrowError(this == 0,        "Trigger", "clearEvents", "Trigger", this, "Attempted To Clear Events Of Null Trigger.")
				debug call ThrowError(not isTrigger,    "Trigger", "clearEvents", "Trigger", this, "Attempted To Clear Events Of Invalid Trigger.")
			
				if (TriggerMemory(this).tc != null) then
					call TriggerRemoveCondition(TriggerMemory(this).trig, TriggerMemory(this).tc)
				endif
				call DestroyTrigger(TriggerMemory(this).trig)
				
				set TriggerMemory(this).trig = CreateTrigger()
				if (TriggerMemory(this).enabled) then
					set TriggerMemory(this).tc = TriggerAddCondition(TriggerMemory(this).trig, TriggerMemory(this).expression.expression)
				else
					call TriggerMemory(this).tc_clear()
				endif
			endmethod
		endif
        
        method fire takes nothing returns nothing
            debug call ThrowError(this == 0,        "Trigger", "fire", "Trigger", this, "Attempted To Fire Null Trigger.")
            debug call ThrowError(not isTrigger,    "Trigger", "fire", "Trigger", this, "Attempted To Fire Invalid Trigger.")
        
            call TriggerEvaluate(TriggerMemory(this).trig)
        endmethod
        
        method operator trigger takes nothing returns trigger
            debug call ThrowError(this == 0,        "Trigger", "trigger", "Trigger", this, "Attempted To Read Null Trigger.")
            debug call ThrowError(not isTrigger,    "Trigger", "trigger", "Trigger", this, "Attempted To Read Invalid Trigger.")
        
            return TriggerMemory(this).trig
        endmethod
        
        method operator enabled takes nothing returns boolean
            debug call ThrowError(this == 0,                                "Trigger", "enabled", "Trigger", this, "Attempted To Read Null Trigger.")
            debug call ThrowError(not isTrigger,                            "Trigger", "enabled", "Trigger", this, "Attempted To Read Invalid Trigger.")
            
            return TriggerMemory(this).enabled
        endmethod
		
		static if DEBUG_MODE then
			method operator enabled= takes boolean enable returns nothing
				set enabled_p = enable
			endmethod
			private method operator enabled_p= takes boolean enable returns nothing
				debug call ThrowError(this == 0,                                "Trigger", "enabled=", "Trigger", this, "Attempted To Set Null Trigger.")
				debug call ThrowError(not isTrigger,                            "Trigger", "enabled=", "Trigger", this, "Attempted To Set Invalid Trigger.")
				debug call ThrowWarning(TriggerMemory(this).enabled == enable,  "Trigger", "enabled=", "Trigger", this, "Setting Enabled To Its Value.")
			
				set TriggerMemory(this).enabled = enable
				
				call TriggerMemory(this).updateTrigger()
				call TriggerReferencedList(this).updateExpression()
			endmethod
		else
			method operator enabled= takes boolean enable returns nothing
				debug call ThrowError(this == 0,                                "Trigger", "enabled=", "Trigger", this, "Attempted To Set Null Trigger.")
				debug call ThrowError(not isTrigger,                            "Trigger", "enabled=", "Trigger", this, "Attempted To Set Invalid Trigger.")
				debug call ThrowWarning(TriggerMemory(this).enabled == enable,  "Trigger", "enabled=", "Trigger", this, "Setting Enabled To Its Value.")
			
				set TriggerMemory(this).enabled = enable
				
				call TriggerMemory(this).updateTrigger()
				call TriggerReferencedList(this).updateExpression()
			endmethod
		endif
        
        static if DEBUG_MODE then
            static method calculateMemoryUsage takes nothing returns integer
                return /*
				*/	TriggerAllocator.calculateMemoryUsage() + /*
				*/	TriggerConditionDataCollection.calculateMemoryUsage() + /*
				*/	TriggerReferenceListData.calculateMemoryUsage() + /*
				*/	TriggerReferencedList.calculateMemoryUsage() + /*
				*/	BooleanExpression.calculateMemoryUsage()
            endmethod
            
            static method getAllocatedMemoryAsString takes nothing returns string
                return /*
				*/	"(Trigger)[" + TriggerAllocator.getAllocatedMemoryAsString() + "], " + /*
				*/	"(Trigger TriggerConditionDataCollection)[" + TriggerConditionDataCollection.getAllocatedMemoryAsString() + "], " + /*
				*/	"(Trigger Reference)[" + TriggerReferenceListData.getAllocatedMemoryAsString() + "], " + /*
				*/	"(Trigger Reference Back)[" + TriggerReferencedList.getAllocatedMemoryAsString() + "], " + /*
				*/	"(Boolean Expression (all))[" + BooleanExpression.getAllocatedMemoryAsString() + "]"
            endmethod
        endif
        
        private static method init takes nothing returns nothing
            static if DEBUG_MODE then
                //! runtextmacro INITIALIZE_TABLE_FIELD("isTrigger")
            endif
        endmethod
        
        implement Init
    endstruct
endlibrary
____________________________________________________________________________________________________

Features

Reliability

Easy Correct Exceution Order Support

Complex Events



Triggers can handle TriggerRemoveCondition. Native triggers can't.

JASS:
/*
*   Here we have 4 triggers
*
*       OnEscape runs whenever player 0 hits escape
*
*   C references B
*   B references A
*
*   OnEscape references C so that C will run when the player hits escape, it is otherwise empty (another way to register events)
*
*   When the player hits escape 5 times, B will be destroyed
*
*   When OnEscape is run, it will run in this order (remember references run first)
*
*       A, B, C
*
*   When B is destroyed, only C will run as C loses its connection to A
*
*   One interesting thing to note is that when B is destroyed, C is still run
*
*   Try the same example with plain triggers and triggerconditions to see what happens (native trigger stability example)
*/

struct OnEscape extends array
    readonly static Trigger trigger
    
    private static method onInit takes nothing returns nothing
        set trigger = Trigger.create(false)
        
        call TriggerRegisterPlayerEvent(trigger.trigger, Player(0), EVENT_PLAYER_END_CINEMATIC)
    endmethod
endstruct

struct A extends array
    readonly static Trigger trigger
    
    private static method onFire takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Ran A")
        
        return false
    endmethod
        
    private static method onInit takes nothing returns nothing
        set trigger = Trigger.create(false)
        
        call trigger.register(Condition(function thistype.onFire))
    endmethod
endstruct

struct B extends array
    readonly static Trigger trigger
    private static integer escapeCount = 0
    
    private static method onFire takes nothing returns boolean
        set escapeCount = escapeCount + 1
        
        if (escapeCount == 5) then
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Destroyed B")
            call trigger.destroy()
        else
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Ran B: " + I2S(escapeCount))
        endif
        
        return false
    endmethod
        
    private static method onInit takes nothing returns nothing
        set trigger = Trigger.create(false)
		
		call trigger.reference(A.trigger)
        
        call trigger.register(Condition(function thistype.onFire))
    endmethod
endstruct

struct C extends array
    readonly static Trigger trigger
    
    private static method onFire takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Ran C\n------------------------------------------------")
        
        return false
    endmethod
        
    private static method onInit takes nothing returns nothing
        set trigger = Trigger.create(false)
        
        call trigger.reference(B.trigger)
		
		call trigger.register(Condition(function thistype.onFire))
        
        call OnEscape.trigger.reference(trigger)
    endmethod
endstruct

JASS:
/*
*   Notice that when B is destroyed, C is never run
*/

struct OnEscape extends array
    readonly static trigger trigger
    
    private static method onInit takes nothing returns nothing
        set trigger = CreateTrigger()
        
        call TriggerRegisterPlayerEvent(trigger, Player(0), EVENT_PLAYER_END_CINEMATIC)
    endmethod
endstruct

struct A extends array
    private static method onFire takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Ran A")
        
        return false
    endmethod
        
    private static method onInit takes nothing returns nothing
        call TriggerAddCondition(OnEscape.trigger, Condition(function thistype.onFire))
    endmethod
endstruct

struct B extends array
    private static triggercondition tc
    private static integer escapeCount = 0
    
    private static method onFire takes nothing returns boolean
        set escapeCount = escapeCount + 1
        
        if (escapeCount == 5) then
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Destroyed B")
            call TriggerRemoveCondition(GetTriggeringTrigger(), tc)
            set tc = null
        else
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Ran B: " + I2S(escapeCount))
        endif
        
        return false
    endmethod
        
    private static method onInit takes nothing returns nothing
        set tc = TriggerAddCondition(OnEscape.trigger, Condition(function thistype.onFire))
    endmethod
endstruct

struct C extends array
    private static method onFire takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Ran C\n------------------------------------------------")
        
        return false
    endmethod
        
    private static method onInit takes nothing returns nothing
        call TriggerAddCondition(OnEscape.trigger, Condition(function thistype.onFire))
    endmethod
endstruct
____________________________________________________________________________________________________


What happens when data relies on other data? Triggers can be run in reverse order!
Furthermore, reversing a Trigger only effects that Trigger's direct data, not the
data of referenced Triggers. This means that on 1 Trigger, some of the data can run
in reverse and some of the data can run in order. Why does this matter? See the below.

It would be best to open the map and run this example. It's included in the map.

JASS:
/*
*	Here, the entry point of a system for some event is A
*
*	B depends on the data of A
*/

library A uses Trigger
	struct A extends array
		static Trigger event
		static Trigger start
		static Trigger end
		static integer data = 0
	
		private static method s takes nothing returns boolean
			set data = 100
			
			call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "A Start -> " + I2S(data))
			
			return false
		endmethod
		
		private static method e takes nothing returns boolean
			set data = 0
			
			call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "A End -> " + I2S(data))
			
			return false
		endmethod
	
		private static method onInit takes nothing returns nothing
			set event = Trigger.create(false)
			set start = Trigger.create(false)
			set end = Trigger.create(true)
			
			call start.register(Condition(function thistype.s))
			call end.register(Condition(function thistype.e))
			
			call event.reference(start)
			call event.reference(end)
		endmethod
	endstruct
endlibrary

library B uses A
	/*
	*	can do sub events if desired with triggers in B
	*/
	struct B extends array
		static integer data = 0
	
		private static method s takes nothing returns boolean
			set data = A.data*2
			
			call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "B Start -> " + I2S(A.data) + ", " + I2S(data))
			
			return false
		endmethod
		
		private static method e takes nothing returns boolean
			set data = 0
			
			call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "B End -> " + I2S(A.data) + ", " + I2S(data))
			
			return false
		endmethod
	
		private static method onInit takes nothing returns nothing
			call A.start.register(Condition(function thistype.s))
			call A.end.register(Condition(function thistype.e))
		endmethod
	endstruct
endlibrary

library User uses A, B
	struct User extends array
		static integer data = 0
		
		private static method s takes nothing returns boolean
			set data = B.data + A.data
			
			call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "User Start -> " + I2S(A.data) + ", " + I2S(B.data) + ", " + I2S(data))
			
			return false
		endmethod
		
		private static method e takes nothing returns boolean
			set data = 0
		
			call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "User End -> " + I2S(A.data) + ", " + I2S(B.data) + ", " + I2S(data))
			
			return false
		endmethod
	
		private static method onInit takes nothing returns nothing
			call A.start.register(Condition(function thistype.s))
			call A.end.register(Condition(function thistype.e))
		endmethod
	endstruct
endlibrary

struct Run extends array
	private static method onInit takes nothing returns nothing
		call A.event.fire()
	endmethod
endstruct
____________________________________________________________________________________________________


Are simple events not enough? Triggers can reference other Triggers, which means that a Trigger
can have several entry points. This means that, for example, in a combat system, you could have
several phases of combat, all modular, all plug and play. Set up the combat system core as well
as basic phases and then let other libraries add new phases + new effects.

It also supports mixtures between global events (apply to all units) and local events (apply
to specific units) without the need for if-statements.

JASS:
/*
*   Here we have a very simple library
*
*   Whenever a unit is created, the eventTrigger will run
*
*   The library contains two triggers
*       eventTrigger
*       event
*
*   eventTrigger contains the library's own internal code (even a single function)
*   event is for users to register code to the library
*
*   There are two triggers that are registered in the following order
*
*       OnUnitCreationUserLibrary
*       OnUnitCreationUserLibrary2
*
*   From here, user code is registered in the following order
*
*       UserLib3    -> OnUnitCreationUserLibrary2
*       UserLib1    -> OnUnitCreationUserLibrary
*       User1       -> OnUnitCreation
*       UserLib2    -> OnUnitCreationUserLibrary
*
*   The resulting order of the code will be
*
*       UserLib1
*       UserLib2
*       UserLib3
*       User1
*
*   To understand order
*
*		OnUnitCreation
*			OnUnitCreationUserLibrary
*			OnUnitCreationUserLibrary2
*			User1
*
*       OnUnitCreationUserLibrary
*           UserLib1
*           UserLib2
*
*       OnUnitCreationUserLibrary2
*           UserLib3
*
*   If any of the functions return true, all of the functions after them will not be run
*
*   Only the primary system should ever return true. If wanting to enable/disable things, run internal
*   triggers via trigger.fire().
*
*   One possible design for a VERY fast DDS would be to create 1 trigger for each unit and then make it reference
*   the DDS library followed by the global DDS event. Anything registered to that specific unit will then fire only for that
*   unit, and anything registered to the global trigger will fire for every unit. No traditional trigger.fire() is used in
*   this design, but the design uses extra memory as each unit gets its very own trigger.
*/

library OnUnitCreation uses Trigger
    struct OnUnitCreation extends array
        readonly static Trigger event                               //event response trigger
        
        private static integer instanceCount = 0
        
        private static method onCreate takes nothing returns boolean
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Main Library: Indexed Unit")
        
            set instanceCount = instanceCount + 1
            call SetUnitUserData(GetTriggerUnit(), instanceCount)
            
            return false
        endmethod
    
        private static method init takes nothing returns nothing
            local rect worldBounds = GetWorldBounds()
            local region worldBoundsRegion = CreateRegion()
			
            call RegionAddRect(worldBoundsRegion, worldBounds)
            call RemoveRect(worldBounds)
            set worldBounds = null
			
            set event = Trigger.create(false)
			
            call TriggerRegisterEnterRegion(event.trigger, worldBoundsRegion, null)
            
            set worldBoundsRegion = null
            
            call event.register(Condition(function thistype.onCreate))
        endmethod
    
        implement Init
    endstruct
endlibrary

/*
*   Libraries?
*/
struct OnUnitCreationUserLibrary extends array
    readonly static Trigger event

    private static method onInit takes nothing returns nothing
        set event = Trigger.create(false)
    
        call OnUnitCreation.event.reference(event)
    endmethod
endstruct

/*
*   Libraries?
*/
struct OnUnitCreationUserLibrary2 extends array
    readonly static Trigger event

    private static method onInit takes nothing returns nothing
        set event = Trigger.create(false)
    
        call OnUnitCreation.event.reference(event)
    endmethod
endstruct

/*
*   Event Response
*/
struct UserLib3 extends array
    private static method onCreate takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Uses OnUnitCreationUserLibrary2 (3)")
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        call OnUnitCreationUserLibrary2.event.register(Condition(function thistype.onCreate))
    endmethod
endstruct

struct UserLib1 extends array
    private static method onCreate takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Uses OnUnitCreationUserLibrary (1)")
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        call OnUnitCreationUserLibrary.event.register(Condition(function thistype.onCreate))
    endmethod
endstruct

struct User1 extends array
    private static method onCreate takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Uses OnUnitCreation (1)")
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        call OnUnitCreation.event.register(Condition(function thistype.onCreate))
    endmethod
endstruct

struct UserLib2 extends array
    private static method onCreate takes nothing returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,"Uses OnUnitCreationUserLibrary (2)")
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        call OnUnitCreationUserLibrary.event.register(Condition(function thistype.onCreate))
    endmethod
endstruct

/*
*   Run
*/
struct UserCode extends array
    private static method onInit takes nothing returns nothing
        call CreateUnit(Player(0), 'hfoo', 0, 0, 270)
    endmethod
endstruct
____________________________________________________________________________________________________
Additional Information

Change Log


v1.1.0.2


[td]
Packed BooleanExpression and Trigger together. I don't think anyone's going to use BooleanExpression.
____________________________________________________________________________________________________
.
[/td]

Change Log

Author's Notes

v1.1.0.2

Older Versions


____________________________________________________________________________________________________

Known Issues
  • THW Table doesn't work with Table Field. Need to use special distribution.
____________________________________________________________________________________________________
[/td]

Keywords:
Trigger
Contents

Just another Warcraft III map (Map)

Reviews
15:09, 10th Mar 2015 IcemanBo: Great system! Approved.
Level 31
Joined
Jul 10, 2007
Messages
6,306
Submitted this because the Event library saw widespread use. Event libraries in general are used a lot. This is sort of an Event 2.0, so it should be pretty useful.

BooleanExpression isn't getting submitted because I don't think anyone would use it. Why use that when there is Trigger ^)^. Trigger does run on top of BooleanExpression, and BooleanExpression isn't integrated into Trigger, so you can use it for something, I just think that everyone will just use Trigger.


Trigger is really used for system development. The only things I've ever seen Event used in were systems. You can certainly use it for other things, but it's kind of overkill for everything but systems.
 

Deleted member 219079

D

Deleted member 219079

Can you enable ur VM/PM? I have a suggestion for you about event.
 

Deleted member 219079

D

Deleted member 219079

Because he said he doesn't want it to be reviewed. And spell section is known to have lotta pending spells. In jass sections ppl come and criticize for the most dumbest stuff.

post it here :)
It has nothing to do with this resource, so I kind won't.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Neat features! However, I have no idea when do I need to remove a trigger condition/action? That's why people call those features as code bloats for a snippet/system.

Do you mean why do you ever need to use TriggerRemoveCondition?

It's actually very common :)


A lot of the time, an effect ends while its own code is running, so you need to remove the condition then.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
When you only ever have one condition on a trigger and it's never going to be added to or add to other triggers in any way. I do this in TriggerRefresh with DDS for example. I think UnitIndexer also does this with its core triggers.

Native triggers may also be better for critical things like timer systems because a lot of timers only run once, so the overhead of add/remove, which isn't large to begin with. I'd have to bench it.

add/remove and create/destroy are slower than natives, but evaluation is either the same or faster. With only one condition, it's the same speed. The more conditions there are, the faster Teigger is relative to native triggers.

It's especially good for event APIs, but I don't know just how heavy create/destroy and add/remove are. I made it all significantly faster, but it's still a tree in the background.

Remove: tree remove operation where node is known. O(log2(n))

Add: tree add operation at end, like a heap. Still has to be balanced, so still O(log2(n))

Destroy: O(n + k + y) where n is conditions, k is added triggers, and y is triggers with this trigger on it. Exact is 2n - 1 + k + y.
 
Level 8
Joined
Feb 3, 2013
Messages
277
sorry this is off topic, but your dds isn't working for me :/
i forgot the error specifically, but it gives me an error related to Trigger.

this happened whenever I try to use DDS in another map.
It even crashes in your test map on github.
 
Level 8
Joined
Feb 3, 2013
Messages
277
Are you sure? The one on your github is running 'attempted to fire null trigger' errors on anything using a DDS module lol. None of the labs are working for me and I freshly downloaded it multiple times.

This happens on debug mode, If i don't turn it on - there are no errors but also nothing happens in game.
 
Level 8
Joined
Feb 3, 2013
Messages
277
Okay I have a new problem related to DDS:

I have a passive ability that on normal attack, nullifies the initial damage and starts a timer to create a periodic damage effect. But every normal attack seems to also nullify the last instance of damage dealt to the target.

Why does this happen?
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
1. I also had a case where damage was completly nulliefied. The damage the unit dealt was based on minimum and maximum values stored in a third struct.
set damage = GetRandomReal(minimum, maximum)
Original Damage was 1-2, set in the object editor.
Instead of dealing damage the system applied the life safer bonus, but never removed it.
Sadly it happened some time ago and I don't remember how I fixed it.

2. In my case, when I enter "whosyourdaddy", life is added instead of an instant kill.
This also must be a reason of the custom damage calculation, with a base damage of 1-2. Actually a nice unwanted side effect for my purpose.

3. Also had a case when some units started with -100000 armor

I can give you the code I used, but I doubt it helps. Now everything works flawless so I can't use this script to evaluate critical issues :(
JASS:
scope DamageSystem
// Entire damage modification.
// Uses DDS by Nestharus.

    private struct DamageSystem extends array
        boolean crit
        boolean block
        real blockAmount
        
        // Runs like any other damage system.
        private static method onDamageBefore takes nothing returns nothing
            local UnitData attacker   = sourceId
            local UnitData defender   = targetId
            local real blockedDamage  = 0.
            local integer unitid      = GetUnitTypeId(source)
            
            debug call ThrowWarning(not attacker.isAllocated, "DamageSystem", "onDamageBefore", "attacker", attacker, "Attacker Is Not Allocated!")
            debug call ThrowWarning(not defender.isAllocated, "DamageSystem", "onDamageBefore", "defender", defender, "Defender Is Not Allocated!")
            
            // Check for damage modification events.
            set thistype(sourceId).crit  = (GetRandomReal(0., 100.) < attacker.criticalHitChance)
            set thistype(targetId).block = (GetRandomReal(0., 100.) < defender.blockChance)
            
            if (archetype == Archetype.PHYSICAL) then
                set damage = GetRandomReal(attacker.minimumDamage, attacker.maximumDamage)
            endif

            if thistype(sourceId).crit then
                set damage      = damage*attacker.criticalHitDamage
            endif
            
            if thistype(targetId).block then
                set blockedDamage            = GetRandomReal(defender.minimumBlock, defender.maximumBlock)
                if (blockedDamage > damage) then
                    set blockedDamage        = damage
                    set damage               = 0.
                else
                    set damage               = damage - blockedDamage
                endif
                set thistype(targetId).block       = (blockedDamage > 0.)
                set thistype(targetId).blockAmount = blockedDamage
            endif
            
            set damage                       = damage - damage*defender.reduction

            if damage < 0. then
                set damage = 0.
            elseif (damage > 0.) then
                // Code running for unit type id. May be moved to local dds code.
                if (archetype == Archetype.PHYSICAL) and unitid == Integers.barbarian then
                    if Frenzy(sourceId).exists then
                        implement optional BARBARIAN_FRENZY_ONHIT_CODE
                    endif
                endif
            endif
        endmethod

        private static method onDamageAfter takes nothing returns nothing
            local thistype attacker      = sourceId
            local thistype defender      = targetId
            local boolean attackerIsHero = IsUnitInGroup(source, Hero.group) 
            local boolean defenderIsHero = IsUnitInGroup(target, Hero.group) 
            local string text            = ""
            local integer id             = GetPlayerId(sourcePlayer)
            local integer id2            = GetPlayerId(targetPlayer)
            
            if (damage > 0.) then
            
                //IAmBot implementation
                call IAmBot.onDamageFunc(defender, target)
            
                if attacker.crit then
                
                    if (attackerIsHero) then
                        call CriticalHit(sourceId).evaluate()
                        
                        if Variables.showDamage[id] then
                            if Variables.localPlayer == sourcePlayer then
                                set text = Strings.red + I2S(R2I(damage + .5))
                            endif
                        
                            call ArcingTextTag.allocate(text, target)
                        endif
                        
                        call BonusExperience(sourceId).resetTimer()
                            
                    elseif defenderIsHero then
                        
                        if Variables.showDamage[id2] then
                            if Variables.localPlayer == targetPlayer then
                                set text = Strings.red + I2S(R2I(damage + .5))
                            endif
                        
                            call ArcingTextTag.allocate(text, target)
                        endif
                        
                    endif
                    
                elseif attackerIsHero then
                    
                    if Variables.showDamage[id] then
                        if Variables.localPlayer == sourcePlayer then
                            set text = I2S(R2I(damage + .5))
                        endif
                        call ArcingTextTag.allocate(text, target)                    
                    endif
                    
                    call BonusExperience(sourceId).resetTimer()
                    
                endif
            endif
            
            if (defender.blockAmount > 0.) and defenderIsHero then
                
                if Variables.showDamage[id2] then
                    set text = ""
                    if Variables.localPlayer == targetPlayer then
                        set text = Strings.green + I2S(R2I(defender.blockAmount + .5))
                    endif    
                
                    call ArcingTextTag.allocate(text, target)
                endif
            endif
            
        endmethod
        
        implement DDS

    endstruct

endscope
 
Level 8
Joined
Feb 3, 2013
Messages
277
to its core its basically this:
JASS:
struct data extends array
    implement SharedList

    unit cast
    unit targ
    real dmg
    integer count

    static timer tmr = CreateTimer()
    static real period = .200
    static thistype l
    static method onLoop takes nothing returns nothing
        local thistype this = l.first
        local thistype nn

        loop
            exitwhen this == l.sentinel
            set nn = .next

            if (.count <= 0) then
                set .cast = null
                set .targ = null
                call .remove()
            else
                call UnitDamageTarget(.cast, .targ, .dmg, false, false, null,null,null)
            endif

            set this = .nn
        endloop
    endmethod

    static method onDamageBefore takes nothing returns nothing
        local integer lvl = GetUnitAbilityLevel(source, ABIL_ID)
        local thistype this

        if (lvl > 0 and archetype == Archetype.PHYSICAL) then
            set this = l.enqueue()
            set .cast = source
            set .targ = target
            set .dmg = damage
            set .count = (insert number)
            
            set damage = 0.0
            
            if (l.first == this) then
                call TimerStart(tmr, period, true, function thistype.onLoop)
            endif
        endif
    endmethod

    implement DDS

    static method onInit takes nothing returns nothing
        set l = create()
    endmethod
endstruct

by the way, I like the idea of DamageBefore and After but how can we have more phases than that?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Adding phases and what not is covered in the labs and tutorials. I'd suggest you go through them to learn all of the features of DDS : ).

I'll look through your code in a bit.

edit

Ran your code and it seems to be working for me. Maybe I don't understand your exact problem?

I took a unit A and I attacked a unit B. Unit A had a super fast attack. The only damage that seemed to be nullified was the damage from the physical attack.


Now, if you are saying last instance of damage via set damage, then this is because you aren't using more phases : ). If you mean last instance of damage via the unit getting damaged before (meaning they'd both have to occur at the same instant), I have no idea and am not even sure how to test that.


I used the DisplayDamage example in the map to view the damage along with the following code

edit
Yea, running into some strange stuff

The life bonus ability is getting applied to a unit with your spell for some unknown reason and then never being removed (because the damage is practically 0). Why is the life bonus ability being applied in the first place? I have no idea.

edit
Ok, fixed the life bonus issue. Code uploaded to GitHub. Still haven't been able to reproduce your issue.


Here is test code

JASS:
struct data extends array
    implement SharedList

    unit cast
    unit targ
    real dmg
    integer count

    static timer tmr = CreateTimer()
    static real period = 1
    static thistype l
    static method onLoop takes nothing returns nothing
        local thistype this = l.first
        local thistype nn

        loop
            exitwhen this == l.sentinel
            set nn = .next
			
            if (.count <= 0) then
                set .cast = null
                set .targ = null
                call .remove()
            else
				set .count = .count - 1
                call UnitDamageTarget(.cast, .targ, .dmg, false, false, null,null,null)
            endif

            set this = nn
        endloop
    endmethod

    static method onDamageBefore takes nothing returns nothing
        local thistype this

        if (archetype == Archetype.PHYSICAL) then
            set this = l.enqueue()
            set .cast = source
            set .targ = target
            set .dmg = damage
            set .count = 3
            
            set damage = 0.0
            
            if (l.first == this) then
                call TimerStart(tmr, period, true, function thistype.onLoop)
            endif
        endif
    endmethod

    implement DDS

    static method onInit takes nothing returns nothing
        set l = create()
    endmethod
endstruct

library SharedList /* v1.0.0.2
************************************************************************************
*
*   */uses/*
*   
*       */ ErrorMessage /*         hiveworkshop.com/forums/submissions-414/snippet-error-message-239210/
*
************************************************************************************
*
*   module SharedList
*
*       Description
*       -------------------------
*
*           NA
*
*       Fields
*       -------------------------
*
*           readonly static integer sentinel
*
*           readonly thistype list
*
*           readonly thistype first
*           readonly thistype last
*
*           readonly thistype next
*           readonly thistype prev
*
*       Methods
*       -------------------------
*
*           static method create takes nothing returns thistype
*           method destroy takes nothing returns nothing
*               - May only destroy lists
*
*           method push takes nothing returns thistype
*           method enqueue takes nothing returns thistype
*
*           method pop takes nothing returns nothing
*           method dequeue takes nothing returns nothing
*
*           method remove takes nothing returns nothing
*
*           method clear takes nothing returns nothing
*
*           debug static method calculateMemoryUsage takes nothing returns integer
*           debug static method getAllocatedMemoryAsString takes nothing returns string
*
************************************************************************************/
    module SharedList
        private static thistype instanceCount = 0
        debug private boolean isNode
        debug private boolean isCollection
        
        private thistype _list
        method operator list takes nothing returns thistype
            debug call ThrowError(this == 0,    "SharedList", "list", "thistype", this, "Attempted To Read Null Node.")
            debug call ThrowError(isCollection, "SharedList", "next", "thistype", this, "Attempted To Read List, Expecting Node.")
            debug call ThrowError(not isNode,   "SharedList", "list", "thistype", this, "Attempted To Read Invalid Node.")
            return _list
        endmethod
        
        private thistype _next
        method operator next takes nothing returns thistype
            debug call ThrowError(this == 0,        "SharedList", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
            debug call ThrowError(isCollection,     "SharedList", "next", "thistype", this, "Attempted To Read List, Expecting Node.")
            debug call ThrowError(not isNode,       "SharedList", "next", "thistype", this, "Attempted To Read Invalid Node.")
            return _next
        endmethod
        
        private thistype _prev
        method operator prev takes nothing returns thistype
            debug call ThrowError(this == 0,        "SharedList", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
            debug call ThrowError(isCollection,     "SharedList", "prev", "thistype", this, "Attempted To Read List, Expecting Node.")
            debug call ThrowError(not isNode,       "SharedList", "prev", "thistype", this, "Attempted To Read Invalid Node.")
            return _prev
        endmethod
        
        method operator first takes nothing returns thistype
            debug call ThrowError(this == 0,            "SharedList", "first", "thistype", this, "Attempted To Read Null List.")
            debug call ThrowError(isNode,               "SharedList", "first", "thistype", this, "Attempted To Read Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "first", "thistype", this, "Attempted To Read Invalid List.")
            return _next
        endmethod
        
        method operator last takes nothing returns thistype
            debug call ThrowError(this == 0,            "SharedList", "last", "thistype", this, "Attempted To Read Null List.")
            debug call ThrowError(isNode,               "SharedList", "last", "thistype", this, "Attempted To Read Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "last", "thistype", this, "Attempted To Read Invalid List.")
            return _prev
        endmethod
        
        static method operator sentinel takes nothing returns integer
            return 0
        endmethod
        
        private static method allocate takes nothing returns thistype
            local thistype this = thistype(0)._next
            
            if (0 == this) then
                debug call ThrowError(instanceCount == 8191, "SharedList", "allocate", "thistype", 0, "Overflow.")
                
                set this = instanceCount + 1
                set instanceCount = this
            else
                set thistype(0)._next = _next
            endif
            
            return this
        endmethod
        
        static method create takes nothing returns thistype
            local thistype this = allocate()     
            
            debug set isCollection = true
            
            set _next = 0
            
            return this
        endmethod
        
        method push takes nothing returns thistype
            local thistype node = allocate()
            
            debug call ThrowError(this == 0,            "SharedList", "push", "thistype", this, "Attempted To Push On To Null List.")
            debug call ThrowError(isNode,               "SharedList", "push", "thistype", this, "Attempted To Push On To Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "push", "thistype", this, "Attempted To Push On To Invalid List.")
            
            debug set node.isNode = true
            
            set node._list = this
        
            if (_next == 0) then
                set _next = node
                set _prev = node
                set node._next = 0
            else
                set _next._prev = node
                set node._next = _next
                set _next = node
            endif
            
            set node._prev = 0
            
            return node
        endmethod
        
        method enqueue takes nothing returns thistype
            local thistype node = allocate()
            
            debug call ThrowError(this == 0,            "SharedList", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
            debug call ThrowError(isNode,               "SharedList", "enqueue", "thistype", this, "Attempted To Enqueue On To Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
            
            debug set node.isNode = true
            
            set node._list = this
        
            if (_next == 0) then
                set _next = node
                set _prev = node
                set node._prev = 0
            else
                set _prev._next = node
                set node._prev = _prev
                set _prev = node
            endif
            
            set node._next = 0
            
            return node
        endmethod
        method pop takes nothing returns nothing
            local thistype node = _next
            
            debug call ThrowError(this == 0,            "SharedList", "pop", "thistype", this, "Attempted To Pop Null List.")
            debug call ThrowError(isNode,               "SharedList", "pop", "thistype", this, "Attempted To Pop Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "pop", "thistype", this, "Attempted To Pop Invalid List.")
            debug call ThrowError(node == 0,            "SharedList", "pop", "thistype", this, "Attempted To Pop Empty List.")
            
            debug set node.isNode = false
            
            set _next._list = 0
            
            set _next = _next._next
            if (_next == 0) then
                set _prev = 0
            else
                set _next._prev = 0
            endif
            
            set node._next = thistype(0)._next
            set thistype(0)._next = node
        endmethod
        method dequeue takes nothing returns nothing
            local thistype node = _prev
            
            debug call ThrowError(this == 0,            "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Null List.")
            debug call ThrowError(isNode,               "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Invalid List.")
            debug call ThrowError(node == 0,            "SharedList", "dequeue", "thistype", this, "Attempted To Dequeue Empty List.")
            
            debug set node.isNode = false
            
            set _prev._list = 0
        
            set _prev = _prev._prev
            if (_prev == 0) then
                set _next = 0
            else
                set _prev._next = 0
            endif
            
            set node._next = thistype(0)._next
            set thistype(0)._next = node
        endmethod
        method remove takes nothing returns nothing
            local thistype node = this
            set this = node._list
            
            debug call ThrowError(node == 0,            "SharedList", "remove", "thistype", this, "Attempted To Remove Null Node.")
            debug call ThrowError(not node.isNode,      "SharedList", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
            debug call ThrowError(not isCollection,     "SharedList", "remove", "thistype", this, "Attempted To Remove Node (" + I2S(node) + ") From Invalid List.")
            debug call ThrowError(this == 0,            "SharedList", "remove", "thistype", this, "Attempted To Remove Node (" + I2S(node) + ") Not Belonging To A List.")
            
            debug set node.isNode = false
            
            set node._list = 0
        
            if (0 == node._prev) then
                set _next = node._next
            else
                set node._prev._next = node._next
            endif
            if (0 == node._next) then
                set _prev = node._prev
            else
                set node._next._prev = node._prev
            endif
            
            set node._next = thistype(0)._next
            set thistype(0)._next = node
        endmethod
        method clear takes nothing returns nothing
            debug local thistype node = _next
        
            debug call ThrowError(this == 0,            "SharedList", "clear", "thistype", this, "Attempted To Clear Null List.")
            debug call ThrowError(isNode,               "SharedList", "clear", "thistype", this, "Attempted To Clear Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "clear", "thistype", this, "Attempted To Clear Invalid List.")
            
            static if DEBUG_MODE then
                loop
                    exitwhen node == 0
                    set node.isNode = false
                    set node = node._next
                endloop
            endif
            
            if (_next == 0) then
                return
            endif
            
            set _prev._next = thistype(0)._next
            set thistype(0)._next = _next
            
            set _next = 0
            set _prev = 0
        endmethod
        method destroy takes nothing returns nothing
            debug call ThrowError(this == 0,            "SharedList", "destroy", "thistype", this, "Attempted To Destroy Null List.")
            debug call ThrowError(isNode,               "SharedList", "destroy", "thistype", this, "Attempted To Destroy Node, Expecting List.")
            debug call ThrowError(not isCollection,     "SharedList", "destroy", "thistype", this, "Attempted To Destroy Invalid List.")
            
            static if DEBUG_MODE then
                debug call clear()
                
                debug set isCollection = false
            else
                if (_next != 0) then
                    set _prev._next = thistype(0)._next
                    set thistype(0)._next = _next
                    
                    set _prev = 0
                endif
            endif
            
            set _next = thistype(0)._next
            set thistype(0)._next = this
        endmethod
        
        static if DEBUG_MODE then
            static method calculateMemoryUsage takes nothing returns integer
                local thistype start = 1
                local thistype end = instanceCount
                local integer count = 0
                
                loop
                    exitwhen integer(start) > integer(end)
                    if (integer(start) + 500 > integer(end)) then
                        return count + checkRegion(start, end)
                    else
                        set count = count + checkRegion(start, start + 500)
                        set start = start + 501
                    endif
                endloop
                
                return count
            endmethod
              
            private static method checkRegion takes thistype start, thistype end returns integer
                local integer count = 0
            
                loop
                    exitwhen integer(start) > integer(end)
                    if (start.isNode) then
                        set count = count + 1
                    elseif (start.isCollection) then
                        set count = count + 1
                    endif
                    set start = start + 1
                endloop
                
                return count
            endmethod
            
            static method getAllocatedMemoryAsString takes nothing returns string
                local thistype start = 1
                local thistype end = instanceCount
                local string memory = null
                
                loop
                    exitwhen integer(start) > integer(end)
                    if (integer(start) + 500 > integer(end)) then
                        if (memory != null) then
                            set memory = memory + ", "
                        endif
                        set memory = memory + checkRegion2(start, end)
                        set start = end + 1
                    else
                        if (memory != null) then
                            set memory = memory + ", "
                        endif
                        set memory = memory + checkRegion2(start, start + 500)
                        set start = start + 501
                    endif
                endloop
                
                return memory
            endmethod
              
            private static method checkRegion2 takes thistype start, thistype end returns string
                local string memory = null
            
                loop
                    exitwhen integer(start) > integer(end)
                    if (start.isNode) then
                        if (memory == null) then
                            set memory = I2S(start)
                        else
                            set memory = memory + ", " + I2S(start) + "N"
                        endif
                    elseif (start.isCollection) then
                        if (memory == null) then
                            set memory = I2S(start)
                        else
                            set memory = memory + ", " + I2S(start) + "C"
                        endif
                    endif
                    set start = start + 1
                endloop
                
                return memory
            endmethod
        endif
    endmodule
endlibrary
 
Last edited:
Level 8
Joined
Feb 3, 2013
Messages
277
Well the issue was to be more specific-> So the initial physical hit would be voided as intended, then it would deal 4 ticks of damage. Then on the next physical hit, the last tick of the previous set of damages was voided, and the rest would follow through properly. But after the update you posted, it's working properly. :>

I did read through the lab and they seem pretty self explanatory but I'll re read them again.
 
Level 8
Joined
Feb 3, 2013
Messages
277
Kkkk.. a new problem lolz,

JASS:
scope OnDamage

    globals
        constant integer DUMMY_CASTER_ID            = 'e008'
        constant integer VISION_DUMMY_ID            = 'e003'
        
        constant integer LEECHER_MASK_ID            = 'I000'
        constant integer SLIPPERY_FEET_ID           = 'I001'
    endglobals

    public struct MyDDS extends array
        real evaC
        real criC
        real criD
        real amr
        real lchL
        real lchM
        real armP

        static TableArray tb
        
        static method onDamageAfter takes nothing returns nothing
            local thistype this
            local thistype t
            local thistype s
            local string str
            local real size = msr
            
            if (damage > 0.0) then
                set t = UnitIndex[target]
                set s = UnitIndex[source]
                
                if (GetRandomReal(0, 1.0) <= t.evaC)then
                    set damage = 0.0
                else
                    if (GetRandomReal(0, 1.0) <= s.criC) then
                        set damage = damage * s.criD
                    endif
                    set damage = damage - t.amr + s.armP
                endif
                
                if (damage*s.lchL > 0.0) then
                    call UnitPlusLife(source, damage*s.lchL)
                endif
                if (damage*s.lchM > 0.0) then
                    call UnitPlusLife(source, damage*s.lchM)
                endif
            endif
        endmethod
        
        implement DDS
        
        static method onUnitIndex takes nothing returns nothing
            local thistype this = UnitIndexer.eventIndex
            
            set .amr = 0.0
            set .criC = .15
            set .criD = 1.5
            set .evaC = .10
            set .lchL = 0.
            set .lchM = 0.
            set .armP = 0.0
            
            set .preX = 0.0
            set .preY = 0.0
        endmethod
        
        implement GlobalUnitIndex
        
        static method onPickup takes nothing returns nothing
            local unit u = GetTriggerUnit()
            local thistype this = UnitIndex[u]
            local item i = GetManipulatedItem()
            local integer iid = GetItemTypeId(i)
            
            
            set .amr = .amr + tb[0].real[iid]
            set .criC = .criC + tb[1].real[iid]
            set .criD = .criD + tb[2].real[iid]
            set .evaC = .evaC + tb[3].real[iid]
            set .lchL = .lchL + tb[4].real[iid]
            set .lchM = .lchM + tb[5].real[iid]
            set .armP = .armP + tb[6].real[iid]
            
            set u = null
            set i = null
        endmethod
        
        static method onDrop takes nothing returns nothing
            local unit u = GetTriggerUnit()
            local thistype this = UnitIndex[u]
            local item i = GetManipulatedItem()
            local integer iid = GetItemTypeId(i)
            
            
            set .amr = .amr - tb[0].real[iid]
            set .criC = .criC - tb[1].real[iid]
            set .criD = .criD - tb[2].real[iid]
            set .evaC = .evaC - tb[3].real[iid]
            set .lchL = .lchL - tb[4].real[iid]
            set .lchM = .lchM - tb[5].real[iid]
            set .armP = .armP - tb[6].real[iid]
            
            set u = null
            set i = null
        endmethod
        
        static method onInit takes nothing returns nothing
            set tb = TableArray[0x2000]
            
            set tb[0].real[LEECHER_MASK_ID] = 0.0
            set tb[1].real[LEECHER_MASK_ID] = 0.35
            set tb[2].real[LEECHER_MASK_ID] = 0.75
            set tb[3].real[LEECHER_MASK_ID] = 0.0
            set tb[4].real[LEECHER_MASK_ID] = 0.1
            set tb[5].real[LEECHER_MASK_ID] = 0.1
            set tb[6].real[LEECHER_MASK_ID] = 0.0
            
            set tb[0].real[SLIPPERY_FEET_ID] = 0.0
            set tb[1].real[SLIPPERY_FEET_ID] = 0.0
            set tb[2].real[SLIPPERY_FEET_ID] = 0.0
            set tb[3].real[SLIPPERY_FEET_ID] = 0.30
            set tb[4].real[SLIPPERY_FEET_ID] = 0.0
            set tb[5].real[SLIPPERY_FEET_ID] = 0.0
            set tb[6].real[SLIPPERY_FEET_ID] = 0.0
            
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function thistype.onPickup)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function thistype.onDrop)
        endmethod
    endstruct
endscope

So i ahve something like this going on for me.
When I don't have any items, the number seem to be right upon evasion and stuff. But If I equip a hero with like 6 mask and another wih 6 feet and have them attack each other - the numbers start to get more inaccurate. Like missing an attack will deal like 1 or 2 damage, also any damage modified to 0 on a target with full hp will deal full damage of the hero's physical attack without any code related bonuses.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
I know what the problem is. I deleted an important line in the last thing so I had to put it back in.

The life change detection event isn't working for tiny values, which causes the life bonus ability to be applied permanently. I'll have to find some way to get it to detect the change in life.


For now, this isn't ganna work well ; P.


I'll work on it to find a fix in a bit.


This is the original damage modification event code with the important deleted lines included.

Right now I'm really sick, so I can't really work on this. Plus, I have to focus on a compiler that I need stuff for. I don't really have time.

JASS:
library DamageEventModification /* v1.1.0.0
*************************************************************************************
*
*   Damage Event Modification plugin for DDS
*
*************************************************************************************
*
*   */uses/*
*
*       */ DDS                      /*
*       */ DamageEvent              /*
*
*************************************************************************************
*
*   SETTINGS
*/
globals
    /*************************************************************************************
    *
    *   Configure to life bonus ability type id
    *
    *************************************************************************************/
    constant integer LIFE_SAVER_ABILITY_ID     = 'A001'
    constant integer LIFE_SAVER_ABILITY_ID_2   = 'A003'
    constant integer ARMOR_ABILITY_ID          = 'A004'
endglobals
/*
*************************************************************************************
*
*   API
*
*       static real damage
*           -   may now be changed
*       readonly static real damageOriginal
*           -   result of GetEventDamage()
*       readonly static real damageModifiedAmount
*           -   how much the damage variable was changed
*
*************************************************************************************
*
*   Plugin Information (can only be used by other plugins)
*
*       GLOBALS
*
*           boolean array saved
*               -   does the unit have life bonus
*
*           unit killUnit
*               -   a dummy unit that can be used to deal damage
*
*       LOCALS
*
*           real life
*               -   life of target unit
*
*           unit u
*               -   stores target, improved speed
*
*************************************************************************************/
    //! textmacro DAMAGE_EVENT_MODIFICATION_CODE
    private keyword saved
    private keyword killUnit
    
    private struct FixLife extends array
        static boolexpr exprKill
        static boolexpr exprLife
        
        private unit source
        private player sourcePlayer
        private real life
        private real damage
        
        static method funcKill takes nothing returns boolean
            local unit u = GetTriggerUnit()
            local thistype target = GetUnitUserData(u)
            local real maxLife = GetUnitState(UnitIndex(target).unit, UNIT_STATE_MAX_LIFE)
            
            call DestroyTrigger(GetTriggeringTrigger())
            
            set DDS[target].enabled = false
        
            call UnitAddAbility(u, ARMOR_ABILITY_ID)
            if (GetUnitTypeId(target.source) == 0) then
                call SetUnitX(killUnit, GetUnitX(u))
                call SetUnitY(killUnit, GetUnitY(u))
                call SetUnitOwner(killUnit, target.sourcePlayer, false)
                
                call SetWidgetLife(UnitIndex(target).unit, maxLife*.5)
                call UnitDamageTarget(killUnit, u, 10000000, false, true, null, DAMAGE_TYPE_UNIVERSAL, null)
                call SetWidgetLife(UnitIndex(target).unit, maxLife*.5)
                call UnitDamageTarget(killUnit, u, 10000000, false, true, null, DAMAGE_TYPE_NORMAL, null)
            else
                call SetWidgetLife(UnitIndex(target).unit, maxLife*.5)
                call UnitDamageTarget(target.source, u, 10000000, false, true, null, DAMAGE_TYPE_UNIVERSAL, null)
                call SetWidgetLife(UnitIndex(target).unit, maxLife*.5)
                call UnitDamageTarget(target.source, u, 10000000, false, true, null, DAMAGE_TYPE_NORMAL, null)
            endif
            call UnitRemoveAbility(u, ARMOR_ABILITY_ID)
            
            set DDS[target].enabled = true
            
            call SetWidgetLife(u, 0)
            
            set u = null
            
            return false
        endmethod
        
        static method funcLife takes nothing returns boolean
            local thistype target = GetUnitUserData(GetTriggerUnit())
            
            call DestroyTrigger(GetTriggeringTrigger())
            
            call SetWidgetLife(UnitIndex(target).unit, GetUnitState(UnitIndex(target).unit, UNIT_STATE_MAX_LIFE))
            call UnitRemoveAbility(UnitIndex(target).unit, LIFE_SAVER_ABILITY_ID)
            call UnitRemoveAbility(UnitIndex(target).unit, LIFE_SAVER_ABILITY_ID_2)
            call SetWidgetLife(UnitIndex(target).unit, target.life)
            
            return false
        endmethod
        
        static method applyKill takes thistype target, unit source, player sourcePlayer, real damage returns nothing
            local trigger t
            
            call SetWidgetLife(UnitIndex(target).unit, GetUnitState(UnitIndex(target).unit, UNIT_STATE_MAX_LIFE))
            
            set target.source = source
            set target.sourcePlayer = sourcePlayer
            
            set t = CreateTrigger()
            call TriggerRegisterUnitStateEvent(t, UnitIndex(target).unit, UNIT_STATE_LIFE, GREATER_THAN, GetWidgetLife(UnitIndex(target).unit)*.99)
            call TriggerAddCondition(t, exprKill)
            call SetWidgetLife(UnitIndex(target).unit, GetWidgetLife(UnitIndex(target).unit)*.99)
            
            set t = null
        endmethod
        
        static method applyLife takes thistype target, real life returns nothing
            local trigger t
            
            set target.life = life
            call SetWidgetLife(UnitIndex(target).unit, GetUnitState(UnitIndex(target).unit, UNIT_STATE_MAX_LIFE))
            if (GetWidgetLife(UnitIndex(target).unit) < 10) then
                call UnitAddAbility(UnitIndex(target).unit, LIFE_SAVER_ABILITY_ID_2)
            else
                call UnitAddAbility(UnitIndex(target).unit, LIFE_SAVER_ABILITY_ID)
            endif
            
            set t = CreateTrigger()
            if (GetEventDamage() < 0) then
                call TriggerRegisterUnitStateEvent(t, UnitIndex(target).unit, UNIT_STATE_LIFE, GREATER_THAN, GetWidgetLife(UnitIndex(target).unit)*.99)
                call SetWidgetLife(UnitIndex(target).unit, GetWidgetLife(UnitIndex(target).unit)*.99)
            else
                call TriggerRegisterUnitStateEvent(t, UnitIndex(target).unit, UNIT_STATE_LIFE, LESS_THAN_OR_EQUAL, GetWidgetLife(UnitIndex(target).unit) - GetEventDamage())
            endif
            call TriggerAddCondition(t, exprLife)
            
            set t = null
        endmethod
    endstruct
    
    scope DamageEventModification
        globals
            unit killUnit
        endglobals
        
        /*
        *   DDS API
        *
        *       DDS.damage                  Can Now Be Set
        *       DDS.damageOriginal
        *       DDS.damageModifiedAmount
        *
        */
        module DAMAGE_EVENT_MODIFICATION_API
            readonly static real damageOriginal = 0
            
            static method operator damageModifiedAmount takes nothing returns real
                return damage_p - damageOriginal
            endmethod
            static method operator damage= takes real newDamage returns nothing
                set damage_p = newDamage
            endmethod
            
        endmodule
        module DAMAGE_EVENT_MODIFICATION_INIT
            set UnitIndexer.enabled = false
            set killUnit = CreateUnit(Player(15), 'hfoo', WorldBounds.maxX - 128, WorldBounds.maxY - 128, 0)
            set UnitIndexer.enabled = true
            
            call UnitAddAbility(killUnit, 'Aloc')
            call UnitAddAbility(killUnit, 'Avul')
            call ShowUnit(killUnit, false)
            call PauseUnit(killUnit, true)
            
            set FixLife.exprKill = Condition(function FixLife.funcKill)
            set FixLife.exprLife = Condition(function FixLife.funcLife)
        endmodule

        /*
        *   DDS Interface
        */
        module DAMAGE_EVENT_MODIFICATION_INTERFACE
            
        endmodule

        /*
        *   DDS Event Handling
        */
module DAMAGE_EVENT_MODIFICATION_RESPONSE_LOCALS
                local real actualDamage
                local real prevDamageOriginal = damageOriginal
                local real life
                local unit u
endmodule
module DAMAGE_EVENT_MODIFICATION_RESPONSE_BEFORE
                set actualDamage = damage_p
                set damageOriginal = actualDamage                   //original damage as seen by user
                set u = targetId_p.unit
endmodule
module DAMAGE_EVENT_MODIFICATION_RESPONSE
                
endmodule
module DAMAGE_EVENT_MODIFICATION_RESPONSE_AFTER
                set life = GetWidgetLife(u)
                
                if (actualDamage < 0) then
                    if (life - damage_p < .4051) then
                        call FixLife.applyKill(targetId_p, source, sourcePlayer_p, actualDamage)
                    elseif (life + actualDamage - damage_p < .406) then
                        call FixLife.applyLife(targetId_p, life - damage_p)
                    else
                        call SetWidgetLife(u, life + actualDamage - damage_p)
                    endif
                elseif (life - damage_p < .4051) then
                    call SetWidgetLife(u, actualDamage)
                elseif (life + actualDamage - damage_p > GetUnitState(u, UNIT_STATE_MAX_LIFE)) then
                    call FixLife.applyLife(targetId_p, life + actualDamage - damage_p)
                else
                    call SetWidgetLife(u, life + actualDamage - damage_p)
                endif
endmodule
module DAMAGE_EVENT_MODIFICATION_RESPONSE_CLEANUP
                set damageOriginal = prevDamageOriginal
                set u = null
endmodule        
    endscope
    //! endtextmacro
endlibrary
 
Level 8
Joined
Feb 3, 2013
Messages
277
hey don't worry man take it easy, no one's paying you to do it anyways.

really appreciate your work on this anyhow - it's very flexible and fun to use too.

edit: I now understand how to use Trigger and make DDS even more flexible with phases derpity derp.
Awesome :eek:
 
Last edited:
Level 11
Joined
Dec 3, 2011
Messages
366
Hello, I got the bug when I try to call someTrigger.fire() Idk why but each times I call fire() it cause crash game.

JASS:
        private static method onHover takes nothing returns boolean
            local trackable t = GetTriggeringTrackable()
            local thistype this = thistype[t]
            
            set Triggering = this
            
            if this.enabled then
                call this.hoverTrigger.fire()
            endif
            
            return false
        endmethod
        
        private static method onClick takes nothing returns boolean
            local trackable t = GetTriggeringTrackable()
            local thistype this = thistype[t]
            
            set Triggering = this
            
            if this.enabled then
                call this.clickTrigger.fire()
            endif
            
            return false
        endmethod

Here is full trigger
JASS:
library Track uses Table, Trigger
    globals
        private constant integer PLATFORM = 'OTip'
    endglobals
    
    struct Track extends array
        readonly static HashTable hashtable
        readonly static Table table
        readonly static Trigger anyHover
        readonly static Trigger anyClick
        readonly static Track Triggering
        
        implement AllocT
        
        static method operator [] takes trackable t returns thistype
            return table[GetHandleId(t)]
        endmethod
        
        method operator player takes nothing returns player
            return hashtable[this.id].player[1]
        endmethod
        
        method operator path takes nothing returns string
            return hashtable[this.id].string[1]
        endmethod
        
        method operator x takes nothing returns real
            return hashtable[this.id].real[1]
        endmethod
        
        method operator y takes nothing returns real
            return hashtable[this.id].real[2]
        endmethod
        
        method operator z takes nothing returns real
            return hashtable[this.id].real[3]
        endmethod
        
        method operator facing takes nothing returns real
            return hashtable[this.id].real[4]
        endmethod
        
        method operator data takes nothing returns integer
            return hashtable[this.id][3]
        endmethod
        
        method operator data= takes integer data returns nothing
            set hashtable[this.id][3] = data
        endmethod
        
        method operator object takes nothing returns trackable
            return hashtable[this.id].trackable[this.id]
        endmethod
        
        method operator id takes nothing returns integer
            return table[this]
        endmethod
        
        method operator hoverTrigger takes nothing returns Trigger
            return hashtable[this.id][1]
        endmethod
        
        method operator clickTrigger takes nothing returns Trigger
            return hashtable[this.id][2]
        endmethod
        
        method operator enabled= takes boolean flag returns nothing
            set hashtable[this.id].boolean[1] = flag
        endmethod
        
        method operator enabled takes nothing returns boolean
            return hashtable[this.id].boolean[1]
        endmethod
        
        private static method onHover takes nothing returns boolean
            local trackable t = GetTriggeringTrackable()
            local thistype this = thistype[t]
            
            set Triggering = this
            
            if this.enabled then
                call this.hoverTrigger.fire()
            endif
            
            return false
        endmethod
        
        private static method onClick takes nothing returns boolean
            local trackable t = GetTriggeringTrackable()
            local thistype this = thistype[t]
            
            set Triggering = this
            
            if this.enabled then
                call this.clickTrigger.fire()
            endif
            
            return false
        endmethod
        
        method clearHoverActions takes nothing returns nothing
            call this.hoverTrigger.clear()
        endmethod
        
        method clearClickActions takes nothing returns nothing
            call this.clickTrigger.clear()
        endmethod
        
        method clear takes nothing returns nothing  
            call clearHoverActions()
            call clearClickActions()
        endmethod
        
        static method registerAnyHoverEvent takes boolexpr c returns TriggerCondition
            return anyHover.register(c)
        endmethod
        
        static method registerAnyClickEvent takes boolexpr c returns TriggerCondition
            return anyClick.register(c)
        endmethod
        
        method registerHoverEvent takes boolexpr c returns TriggerCondition
            return this.hoverTrigger.register(c)
        endmethod
        
        method registerClickEvent takes boolexpr c returns TriggerCondition
            return this.clickTrigger.register(c)
        endmethod
        
        method destroy takes nothing returns nothing
            call this.hoverTrigger.destroy()
            call this.clickTrigger.destroy()
            call hashtable.remove(this.id)
            set table[this.id] = 0
            set table[this] = 0
            call this.deallocate()
        endmethod
        
        static method createEx takes string path, real x, real y, real z, real facing, player p, integer data returns thistype
            local thistype this = create(path,x,y,z,facing,p)
                set hashtable[this.id][3] = data
            return this
        endmethod
        
        static method create takes string path, real x, real y, real z, real facing, player p returns thistype
            local thistype this = allocate()
            local destructable d = null
            local string s = ""
            local trackable t
            local integer id
            local Trigger trig
            
            if p != null then
                if p == GetLocalPlayer() then
                    set s = path
                endif
            else
                set s = path
            endif
            
            if z != 0 then
                set d = CreateDestructableZ(PLATFORM, x, y, z, 0, 1, 0)
                set t = CreateTrackable(s, x, y, facing)
                call RemoveDestructable(d)
            else
                set t = CreateTrackable(s, x, y, facing)
            endif
            
            
            set id = GetHandleId(t)
            call BJDebugMsg(s + " " + I2S(id))
            set table[id] = this
            set table[this] = id
            
            set hashtable[id].trackable[id] = t
            
            set hashtable[id].real[1] = x
            set hashtable[id].real[2] = y
            set hashtable[id].real[3] = z
            set hashtable[id].real[4] = facing
            
            set hashtable[id].player[1] = p
            set hashtable[id].boolean[1] = true // Enabled
            set hashtable[id].string[1] = path 
            
            set trig = Trigger.create(false)
            set hashtable[id][1] = trig // On Hover
            call TriggerRegisterTrackableTrackEvent(trig.trigger, t)  
            call TriggerAddCondition(trig.trigger, Filter(function thistype.onHover))
            call trig.reference(anyHover)
            
            set trig = Trigger.create(false)
            set hashtable[id][2] = Trigger.create(false) // On Click
            call TriggerRegisterTrackableHitEvent(trig.trigger, t)
            call TriggerAddCondition(trig.trigger, Filter(function thistype.onClick))
            call trig.reference(anyClick)
            
            
            return this
        endmethod
        
        private static method onInit takes nothing returns nothing
            set hashtable = HashTable.create()
            set table = Table.create()
            set anyHover = Trigger.create(false)
            set anyClick = Trigger.create(false)
        endmethod
    endstruct
// FUNCTION WRAPPERS
    function CreateTrack takes string path, real x, real y, real z, real facing, player p returns Track
        return Track.create(path,x,y,z,facing,p)
    endfunction
    
    function CreateTrackEx takes string path, real x, real y, real z, real facing, player p, integer data returns Track
        return Track.createEx(path,x,y,z,facing,p,data)
    endfunction
    
    function DestroyTrack takes Track t returns nothing
        call t.destroy()
    endfunction
    
    function GetTriggeringTrack takes nothing returns Track
        return Track.Triggering
    endfunction
    
//! textmacro GET_TRACK takes NAME, SUPER, TYPE
    function GetTrack$NAME$ takes Track t returns $TYPE$
        return t.$SUPER$
    endfunction
//! endtextmacro

//! runtextmacro GET_TRACK("Data","data", "integer")    
//! runtextmacro GET_TRACK("X","x", "real")    
//! runtextmacro GET_TRACK("Y","y", "real")    
//! runtextmacro GET_TRACK("Z","z", "real")
//! runtextmacro GET_TRACK("Facing","facing", "real")
//! runtextmacro GET_TRACK("Path","path", "string")                
//! runtextmacro GET_TRACK("Player","player", "player")
//! runtextmacro GET_TRACK("Object","object", "trackable")
//! runtextmacro GET_TRACK("HoverTrigger", "hoverTrigger", "Trigger")  
//! runtextmacro GET_TRACK("ClickTrigger", "clickTrigger", "Trigger")

    function SetTrackData takes Track t, integer data returns nothing
        set t.data = data
    endfunction
    
    function IsTrackEnabled takes Track t returns boolean
        return t.enabled
    endfunction
    
    function EnableTrack takes Track t returns nothing
        set t.enabled = true
    endfunction
    
    function DisableTrack takes Track t returns nothing
        set t.enabled = false
    endfunction

//! textmacro REGISTER_TRACK takes NAME    
    function RegisterAnyTrack$NAME$Event takes boolexpr c returns TriggerCondition
        return Track.registerAny$NAME$Event(c)
    endfunction
    
    function RegisterTrack$NAME$Event takes Track t, boolexpr c returns TriggerCondition
        return t.register$NAME$Event(c)
    endfunction
    
    function ClearTrack$NAME$Actions takes Track t returns nothing
        call t.clear$NAME$Actions()
    endfunction
//! endtextmacro

//! runtextmacro REGISTER_TRACK("Hover")
//! runtextmacro REGISTER_TRACK("Click")

    function ClearTrackActions takes Track t returns nothing
        call t.clear()
    endfunction
endlibrary

Below is attached map. :ogre_frown:
 

Attachments

  • Plugin Install Pack.w3x
    138.5 KB · Views: 53
Top