System GTrigger Event System

Jesus4Lyf

Good Idea™
GTrigger​
Version 1.05​

Requirements:
- Jass NewGen

Code:
If you want to see the code, see below. If you want to understand the premise of what this does, go here instead.
JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ GT ~~ GTrigger ~~ By Jesus4Lyf ~~ Version 1.05 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is GTrigger?
//		 - GTrigger is an event system that replaces the cumbersome WC3
//		   event system.
//		 - GTrigger only launches the necessary threads instead of x threads,
//		   where x is the number of times the event type occurs in the map.
//
//	=Pros=
//		 - Instead of having 16 events (for "16" players) per use of an,
//		   event type, you have 0 per use and 16 total for that event type.
//		 - If you have 100 events of one type in your map, instead of firing
//		   100 triggers each time any spell is cast, you fire only what's needed.
//		 - GTrigger is faster to code with, more efficient to execute, and just
//		   better programming practises and nicer code all round.
//
//	=Cons=
//		 - If a trigger with a GTrigger event is destroyed, it must have its
//		   event unregistered first or it will leak an event (slows firing down).
//		 - Shouldn't use "wait" actions anywhere in the triggers.
//
//	Functions:
//		   // General
//		 - GT_UnregisterTriggeringEvent()
//
//		   // Ability events
//		 - GT_RegisterStartsEffectEvent(trigger, abilityid)       (returns the trigger passed in)
//		 - GT_RegisterBeginsChanellingEvent(trigger, abilityid)   (returns the trigger passed in)
//		 - GT_RegisterBeginsCastingEvent(trigger, abilityid)      (returns the trigger passed in)
//		 - GT_RegisterStopsCastingEvent(trigger, abilityid)       (returns the trigger passed in)
//		 - GT_RegisterFinishesCastingEvent(trigger, abilityid)    (returns the trigger passed in)
//		 - GT_RegisterLearnsAbilityEvent(trigger, abilityid)       (returns the trigger passed in)
//		   // Order events // (can use String2OrderIdBJ("OrderString") for orderid
//		 - GT_RegisterTargetOrderEvent(trigger, orderid)          (returns the trigger passed in)
//		 - GT_RegisterPointOrderEvent(trigger, orderid)           (returns the trigger passed in)
//		 - GT_RegisterNoTargetOrderEvent(trigger, orderid)        (returns the trigger passed in)
//		   // Item events
//		 - GT_RegisterItemUsedEvent(trigger, itemtypeid)          (returns the trigger passed in)
//		 - GT_RegisterItemAcquiredEvent(trigger, itemtypeid)      (returns the trigger passed in)
//		 - GT_RegisterItemDroppedEvent(trigger, itemtypeid)       (returns the trigger passed in)
//		   // Unit events
//		 - GT_RegisterUnitDiesEvent(trigger, unittypeid)          (returns the trigger passed in)
//
//		   // Ability Events
//		 - GT_UnregisterSpellEffectEvent(trigger, abilityid)      (returns the trigger passed in)
//		 - GT_UnregisterBeginsChanellingEvent(trigger, abilityid) (returns the trigger passed in)
//		 - GT_UnregisterBeginsCastingEvent(trigger, abilityid)    (returns the trigger passed in)
//		 - GT_UnregisterStopsCastingEvent(trigger, abilityid)     (returns the trigger passed in)
//		 - GT_UnregisterFinishesCastingEvent(trigger, abilityid)  (returns the trigger passed in)
//		 - GT_UnregisterLearnsAbilityEvent(trigger, abilityid)     (returns the trigger passed in)
//		   // Order events // (can use String2OrderIdBJ("OrderString") for orderid
//		 - GT_UnregisterTargetOrderEvent(trigger, orderid)        (returns the trigger passed in)
//		 - GT_UnregisterPointOrderEvent(trigger, orderid)         (returns the trigger passed in)
//		 - GT_UnregisterNoTargetOrderEvent(trigger, orderid)      (returns the trigger passed in)
//		   // Item events
//		 - GT_UnregisterItemUsedEvent(trigger, itemtypeid)        (returns the trigger passed in)
//		 - GT_UnregisterItemAcquiredEvent(trigger, itemtypeid)    (returns the trigger passed in)
//		 - GT_UnregisterItemDroppedEvent(trigger, itemtypeid)     (returns the trigger passed in)
//		   // Unit events
//		 - GT_UnregisterUnitDiesEvent(trigger, unittypeid)        (returns the trigger passed in)
//
//	Alternative interface (not recommended):
//		If you aren't familiar with how this works, you shouldn't use it.
//		All funcs must return false. (That is the only reason it isn't recommended.)
//		   // General
//		 - GT_RemoveTriggeringAction() // Use this to remove actions.
//		   // Ability Events
//		 - GT_AddStartsEffectAction(func, abilityid)
//		 - GT_AddBeginsChanellingActon(func, abilityid)
//		 - GT_AddBeginsCastingAction(func, abilityid)
//		 - GT_AddStopsCastingAction(func, abilityid)
//		 - GT_AddFinishesCastingAction(func, abilityid)
//		 - GT_AddLearnsAbilityAction(func, abilityid)
//		   // Order events // (can use String2OrderIdBJ("OrderString") for orderid
//		 - GT_AddTargetOrderAction(func, orderid)
//		 - GT_AddPointOrderAction(func, orderid)
//		 - GT_AddNoTargetOrderAction(func, orderid)
//		   // Item events
//		 - GT_AddItemUsedAction(func, itemtypeid)
//		 - GT_AddItemAcquiredAction(func, itemtypeid)
//		 - GT_AddItemDroppedAction(func, itemtypeid)
//		   // Unit events
//		 - GT_AddUnitDiesAction(func, unittypeid)
//
//  Details:
//		 - Due to the storage method, only 8191 GTrigger events are possible at any one time.
//
//  Thanks:
//		 - Daxtreme: For voluntarily testing this system and the UnitDies event idea.
//		 - kenny!: For the Order and Learns Ability event ideas.
//
//  How to import:
//		 - Create a trigger named GT.
//		 - Convert it to custom text and replace the whole trigger text with this.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library GT initializer Init
    //////////////
    // Pointers //
    ////////////////////////////////////////////////////////////////////////////
    // Assigned to abilities, and point to trigger grouping linked lists.
    //
    // Use:
    //  GetPointer --> int (pointer)
    //  FreePointer(int (pointer))
    //  set PointerTarget[int (pointer)]=int (list link)
    //  PointerTarget[int (pointer)] --> int (list link)
    globals
        // Pointer
        private integer array PointerTarget
        private integer PointerMax=0
        // Spare Pointer Stack
        private integer array NextPointer
        private integer NextPointerMaxPlusOne=1
    endglobals
    
    private function GetPointer takes nothing returns integer
        if NextPointerMaxPlusOne==1 then
            set PointerMax=PointerMax+1
            return PointerMax
        endif
        set NextPointerMaxPlusOne=NextPointerMaxPlusOne-1
        return NextPointer[NextPointerMaxPlusOne]
    endfunction
    private function FreePointer takes integer pointer returns nothing
        set PointerTarget[pointer]=0
        set NextPointer[NextPointerMaxPlusOne]=pointer
        set NextPointerMaxPlusOne=NextPointerMaxPlusOne+1
    endfunction
    
    ///////////////////////////////////
    // Trigger Grouping Linked Lists //
    ////////////////////////////////////////////////////////////////////////////
    // Contains a chain of triggers to be executed together.
    //
    // Use:
    //  GetMem() --> int (mem)
    //  FreeMem(int (mem))
    //  Link(int (pointer), int (mem))
    //  Unlink(int (pointer), int (mem))
    globals
        // Spare Link Stack
        private integer array NextMem
        private integer NextMemMaxPlusOne=1
        // Linked list
        private trigger array Trig
        private integer array Next
        private integer array Prev
        private integer TrigMax=0
    endglobals
    
    private function GetMem takes nothing returns integer
        if NextMemMaxPlusOne==1 then
            set TrigMax=TrigMax+1
            return TrigMax
        endif
        set NextMemMaxPlusOne=NextMemMaxPlusOne-1
        return NextMem[NextMemMaxPlusOne]
    endfunction
    private function FreeMem takes integer i returns nothing
        set Trig<i>=null
        set NextMem[NextMemMaxPlusOne]=i
        set NextMemMaxPlusOne=NextMemMaxPlusOne+1
    endfunction
    
    // Linked list functionality
    // NOTE: This means &quot;Next&quot; must be loaded BEFORE executing the trigger, which could delete the current link.
    private function Link takes integer pointer, integer new returns nothing
        set Prev[new]=0
        set Next[new]=PointerTarget[pointer]
        set Prev[PointerTarget[pointer]]=new
        set PointerTarget[pointer]=new
    endfunction
    private function Unlink takes integer pointer, integer rem returns nothing
        if Prev[rem]==0 then
            set PointerTarget[pointer]=Next[rem]
            set Prev[Next[rem]]=0
        endif
        set Next[Prev[rem]]=Next[rem]
        set Prev[Next[rem]]=Prev[rem]
    endfunction
    
    //////////////////////
    // GTrigger General //
    ////////////////////////////////////////////////////////////////////////////
    // Only contains the UnregisterTriggeringEvent action for public use.
    globals
        boolean UnregisterLastEvent=false
    endglobals
    public function UnregisterTriggeringEvent takes nothing returns nothing
        set UnregisterLastEvent=true
    endfunction
    
    /////////////////////////////////////
    // GTrigger Ability Implementation //
    ////////////////////////////////////////////////////////////////////////////
    // The nasty textmacro implementation of special &quot;All Players&quot; events.
    //! textmacro SetupSpecialAllPlayersEvent takes NAME, EVENT, GETSPECIAL
        globals
            private trigger $NAME$Trigger=CreateTrigger()
            // Extendable arrays
            private integer array $NAME$AbilityIdA
            private integer array $NAME$ListPointerA
            private integer array $NAME$AbilityIdB
            private integer array $NAME$ListPointerB
            private integer array $NAME$AbilityIdC
            private integer array $NAME$ListPointerC
            private integer array $NAME$AbilityIdD
            private integer array $NAME$ListPointerD
            private integer array $NAME$AbilityIdE
            private integer array $NAME$ListPointerE
        endglobals
        
        globals//locals
            private integer GetOrCreateListPointer$NAME$AbilHashed
        endglobals
        private function GetOrCreate$NAME$ListPointer takes integer abil returns integer
            set GetOrCreateListPointer$NAME$AbilHashed=abil-(abil/8191)*8191
            if $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]=abil
                set $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                return $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]
            endif
            if $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]=abil
                set $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                return $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]
            endif
            if $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]=abil
                set $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                return $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]
            endif
            if $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]=abil
                set $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                return $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]
            endif
            if $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]=abil
                set $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                return $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]
            endif
            call BJDebugMsg(&quot;GTrigger Error: Ran out of storage locations for pointers on object &quot;+GetObjectName(abil)+&quot;!&quot;)
            set PointerTarget[0]=0
            return 0
        endfunction
        
        globals//locals
            private integer GetListPointer$NAME$AbilHashed
        endglobals
        private function Get$NAME$ListPointer takes integer abil returns integer
            set GetListPointer$NAME$AbilHashed=abil-(abil/8191)*8191
            if $NAME$AbilityIdA[GetListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerA[GetListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdA[GetListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set PointerTarget[0]=0 // Make sure.
                return 0
            endif
            if $NAME$AbilityIdB[GetListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerB[GetListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdB[GetListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set PointerTarget[0]=0 // Make sure.
                return 0
            endif
            if $NAME$AbilityIdC[GetListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerC[GetListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdC[GetListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set PointerTarget[0]=0 // Make sure.
                return 0
            endif
            if $NAME$AbilityIdD[GetListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerD[GetListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdD[GetListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set PointerTarget[0]=0 // Make sure.
                return 0
            endif
            if $NAME$AbilityIdE[GetListPointer$NAME$AbilHashed]==abil then // Correct
                return $NAME$ListPointerE[GetListPointer$NAME$AbilHashed]
            elseif $NAME$AbilityIdE[GetListPointer$NAME$AbilHashed]&lt;1 then // Empty
                set PointerTarget[0]=0 // Make sure.
                return 0
            endif
            call BJDebugMsg(&quot;GTrigger Error: Ran out of storage locations for pointers at ability &quot;+GetObjectName(abil)+&quot;!&quot;)
            set PointerTarget[0]=0
            return 0
        endfunction
        
        globals//locals
            private integer Register$NAME$Mem
        endglobals
        public function Register$NAME$Event takes trigger t, integer abil returns trigger
            set Register$NAME$Mem=GetMem()
            set Trig[Register$NAME$Mem]=t
            call Link(GetOrCreate$NAME$ListPointer(abil),Register$NAME$Mem)
            return t
        endfunction
        
        globals//locals
            private integer Unregister$NAME$Pointer
            private integer Unregister$NAME$Mem
        endglobals
        public function Unregister$NAME$Event takes trigger t, integer abil returns trigger
            set Unregister$NAME$Pointer=Get$NAME$ListPointer(abil)
            set Unregister$NAME$Mem=PointerTarget[Unregister$NAME$Pointer]
            loop
                exitwhen Trig[Unregister$NAME$Mem]==t
                if Unregister$NAME$Mem==0 then
                    return t // Not found.
                endif
                set Unregister$NAME$Mem=Next[Unregister$NAME$Mem]
            endloop
            call Unlink(Unregister$NAME$Pointer,Unregister$NAME$Mem)
            call FreeMem(Unregister$NAME$Mem)
            return t
        endfunction
        
        private function Trigger$NAME$Event takes nothing returns boolean
            local integer Trigger$NAME$Pointer=Get$NAME$ListPointer($GETSPECIAL$)
            local integer Trigger$NAME$Mem=PointerTarget[Trigger$NAME$Pointer]
            local integer Trigger$NAME$NextMem
            set UnregisterLastEvent=false
            loop
                exitwhen Trigger$NAME$Mem&lt;1
                set Trigger$NAME$NextMem=Next[Trigger$NAME$Mem]
                if TriggerEvaluate(Trig[Trigger$NAME$Mem]) then
                    call TriggerExecute(Trig[Trigger$NAME$Mem])
                endif
                if UnregisterLastEvent then
                    set UnregisterLastEvent=false
                    call Unlink(Trigger$NAME$Pointer,Trigger$NAME$Mem)
                    call FreeMem(Trigger$NAME$Mem)
                endif
                set Trigger$NAME$Mem=Trigger$NAME$NextMem
            endloop
            return false
        endfunction
        
        private function Init$NAME$ takes nothing returns nothing
            local integer i=bj_MAX_PLAYER_SLOTS
            call TriggerAddCondition($NAME$Trigger,Condition(function Trigger$NAME$Event))
            loop
                set i=i-1
                call TriggerRegisterPlayerUnitEvent($NAME$Trigger,Player(i),EVENT_PLAYER_$EVENT$,null)
                exitwhen i==0
            endloop
        endfunction
    //! endtextmacro
    
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;StartsEffect&quot;,     &quot;UNIT_SPELL_EFFECT&quot;,        &quot;GetSpellAbilityId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;BeginsChanelling&quot;, &quot;UNIT_SPELL_CHANNEL&quot;,       &quot;GetSpellAbilityId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;BeginsCasting&quot;,    &quot;UNIT_SPELL_CAST&quot;,          &quot;GetSpellAbilityId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;StopsCasting&quot;,     &quot;UNIT_SPELL_ENDCAST&quot;,       &quot;GetSpellAbilityId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;FinishesCasting&quot;,  &quot;UNIT_SPELL_FINISH&quot;,        &quot;GetSpellAbilityId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;TargetOrder&quot;,      &quot;UNIT_ISSUED_TARGET_ORDER&quot;, &quot;GetIssuedOrderId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;PointOrder&quot;,       &quot;UNIT_ISSUED_POINT_ORDER&quot;,  &quot;GetIssuedOrderId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;NoTargetOrder&quot;,    &quot;UNIT_ISSUED_ORDER&quot;,        &quot;GetIssuedOrderId()&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;ItemUsed&quot;,         &quot;UNIT_USE_ITEM&quot;,            &quot;GetItemTypeId(GetManipulatedItem())&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;ItemAcquired&quot;,     &quot;UNIT_PICKUP_ITEM&quot;,         &quot;GetItemTypeId(GetManipulatedItem())&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;ItemDropped&quot;,      &quot;UNIT_DROP_ITEM&quot;,           &quot;GetItemTypeId(GetManipulatedItem())&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;UnitDies&quot;,         &quot;UNIT_DEATH&quot;,               &quot;GetUnitTypeId(GetTriggerUnit())&quot;)
    //! runtextmacro SetupSpecialAllPlayersEvent(&quot;LearnsAbility&quot;,    &quot;HERO_SKILL&quot;,               &quot;GetLearnedSkill()&quot;)
    // Note to self: Remember to update the Init function.
    
    /////////////////////////////////////////
    // GTrigger All Players Implementation //
    ////////////////////////////////////////////////////////////////////////////
    // The textmacro implementation of other &quot;All Players&quot; events.
    //! textmacro SetupAllPlayersEvent takes NAME, EVENT
        globals
            private trigger $NAME$Trigger=CreateTrigger()
            private integer $NAME$ListPointer=0
        endglobals
        
        globals//locals
            private integer Register$NAME$Mem
        endglobals
        public function Register$NAME$Event takes trigger t returns trigger
            set Register$NAME$Mem=GetMem()
            set Trig[Register$NAME$Mem]=t
            call Link($NAME$ListPointer,Register$NAME$Mem)
            return t
        endfunction
        
        globals//locals
            private integer Unregister$NAME$Pointer
            private integer Unregister$NAME$Mem
        endglobals
        public function Unregister$NAME$Event takes trigger t returns trigger
            set Unregister$NAME$Mem=PointerTarget[$NAME$ListPointer]
            loop
                exitwhen Trig[Unregister$NAME$Mem]==t
                if Unregister$NAME$Mem==0 then
                    return t // Not found.
                endif
                set Unregister$NAME$Mem=Next[Unregister$NAME$Mem]
            endloop
            call Unlink($NAME$ListPointer,Unregister$NAME$Mem)
            call FreeMem(Unregister$NAME$Mem)
            return t
        endfunction
        
        private function Trigger$NAME$Event takes nothing returns boolean
            local integer Trigger$NAME$Mem=PointerTarget[$NAME$ListPointer]
            local integer Trigger$NAME$NextMem
            set UnregisterLastEvent=false
            loop
                exitwhen Trigger$NAME$Mem&lt;1
                set Trigger$NAME$NextMem=Next[Trigger$NAME$Mem]
                if TriggerEvaluate(Trig[Trigger$NAME$Mem]) then
                    call TriggerExecute(Trig[Trigger$NAME$Mem])
                endif
                if UnregisterLastEvent then
                    set UnregisterLastEvent=false
                    call Unlink($NAME$ListPointer,Trigger$NAME$Mem)
                    call FreeMem(Trigger$NAME$Mem)
                endif
                set Trigger$NAME$Mem=Trigger$NAME$NextMem
            endloop
            return false
        endfunction
        
        private function Init$NAME$ takes nothing returns nothing
            local integer i=bj_MAX_PLAYER_SLOTS
            call TriggerAddCondition($NAME$Trigger,Condition(function Trigger$NAME$Event))
            loop
                set i=i-1
                call TriggerRegisterPlayerUnitEvent($NAME$Trigger,Player(i),EVENT_PLAYER_UNIT_$EVENT$,null)
                exitwhen i==0
            endloop
            // Initialise the pointer.
            set $NAME$ListPointer=GetPointer()
        endfunction
    //! endtextmacro
    
    // Old: //! runtextmacro SetupAllPlayersEvent(&quot;AnyUnitDies&quot;, &quot;DEATH&quot;)
    
    private function Init takes nothing returns nothing
        // Ability events
        call InitStartsEffect()
        call InitBeginsChanelling()
        call InitBeginsCasting()
        call InitStopsCasting()
        call InitFinishesCasting()
        call InitLearnsAbility()
        // Order events
        call InitTargetOrder()
        call InitPointOrder()
        call InitNoTargetOrder()
        // Item events
        call InitItemUsed()
        call InitItemAcquired()
        call InitItemDropped()
        // Unit events
        call InitUnitDies()
    endfunction
    
    //////////////
    // Wrappers //
    ////////////////////////////////////////////////////////////////////////////
    // Wraps it up, for those who really want this interface.
    
    // General
    public function RemoveTriggeringAction takes nothing returns nothing
        call UnregisterTriggeringEvent()
        call DestroyTrigger(GetTriggeringTrigger())
    endfunction
    
    // Special All Player Events
    //! textmacro AddSpecialAllPlayersWrapper takes EVENT
        public function Add$EVENT$Action takes code func, integer special returns nothing
            call TriggerAddCondition(Register$EVENT$Event(CreateTrigger(),special),Condition(func))
        endfunction
    //! endtextmacro
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;StartsEffect&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;BeginsChanelling&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;BeginsCasting&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;StopsCasting&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;FinishesCasting&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;TargetOrder&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;PointOrder&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;NoTargetOrder&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;ItemUsed&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;ItemAcquired&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;ItemDropped&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;UnitDies&quot;)
    //! runtextmacro AddSpecialAllPlayersWrapper(&quot;LearnsAbility&quot;)
    // Note to self: Remember to update the Init function.
    
    // All Player Events
    //! textmacro AddAllPlayersWrapper takes EVENT
        public function Add$EVENT$Action takes code func returns nothing
            call TriggerAddCondition(Register$EVENT$Event(CreateTrigger()),Condition(func))
        endfunction
    //! endtextmacro
    // Old: //! runtextmacro AddAllPlayersWrapper(&quot;AnyUnitDies&quot;)
endlibrary
</i>

This changes things like:
JASS:
function Trig_Untitled_Trigger_002_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then
        return false
    endif
    return true
endfunction

function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_002 = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_002, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Untitled_Trigger_002, Condition( function Trig_Untitled_Trigger_002_Conditions ) )
    call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
endfunction

to
JASS:
function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_002 = CreateTrigger(  )
    call GT_RegisterStartsEffectEvent(gg_trg_Untitled_Trigger_001,'A000')
    call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
endfunction

or even
JASS:
function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
    call TriggerAddAction(GT_RegisterStartsEffectEvent(CreateTrigger(),'A000'), function Trig_Untitled_Trigger_002_Actions )
endfunction


Why? Three reasons.
  • Instead of having 16 events (for "16" players) per event used, you have 0 per event and 16 total (for each event type).
  • If you have 100 uses of an event in your map, instead of firing 100 triggers each time it fires, you fire only what needs to be fired.
  • This is faster to code with, runs more efficiently, and I'd say it's just better programming practises.

Downsides as per cons in documentation.

Updates:
Version 1.05 - Added LearnsAbility event type.
Version 1.04 - Attached demo map. Added heaps and heaps of event types. Sorry to those who are already using the system within the first week of it's release, you'll need to switch the order of the parameters and such. I found it more intuitive to have them the other way around, and felt I needed to change them early so I don't regret it forever instead. If you get errors, just look up the new command syntax. :)
Version 1.03 - Added alternative interface.
Version 1.02 - Official Release. Removed all previous constraints by adding hashing and linked lists.
Version 1.00 - Unofficial Release.

Check out the demo map for more code demonstrations (released with version 1.04).
 

Attachments

Downsides?
  • One event registration per spell max. (Restriction can be removed through linked lists.)
  • Only 'A000' style spell IDs can be registered. ie not 'AHhb' for Holy Light. In other words, only custom spells (and don't pick some special id). I haven't calculated how many, max, yet. :) (Restriction can be removed through hashing.)

Will update this system in the near future, hopefully. :)
Why not implement it then? Maximum must be 'zzzz', minimum logical must be '0000', so I suspect something like this may work:
JASS:
function Id2I takes integer id returns integer
    set id = id-'0000'
    return id-(id/(8192))*(8192)
endfunction
 

Viikuna

No Marlo no game.
I guess Im gonna use this. Looks nice.

What about making some textmacro for different casting events?

EVENT_PLAYER_UNIT_SPELL_CHANNEL is sometimes useful, because it triggers before casting time.
EVENT_PLAYER_UNIT_SPELL_ENDCAST is needed for channeling spells.
 

Jesus4Lyf

Good Idea™
Why not implement it then? Maximum must be 'zzzz', minimum logical must be '0000', so I suspect something like this may work:
JASS:
function Id2I takes integer id returns integer
    set id = id-'0000'
    return id-(id/(8192))*(8192)
endfunction
Don't worry, I'm on it... I just had uni today, and I wanted to get this new thread up in the morning before I left, so yeah. :)

By the way... Actually...

JASS:
function Id2I takes integer id returns integer
    return id-(id/8191)*8191
endfunction


And I'd inline it explicitly instead. :)

>What about making some textmacro for different casting events?
Don't worry, I'll implement those too. I'll make sure it's nice and stable and feature rich but simple to use. :p

I also intend to imeplement UnregisterAbilityEvent(trigger, ability) for temporary triggers and such. Because otherwise the event will leak. Of course, I don't expect people to actually create a temporary trigger to fire on an ability being cast, but this way they can. :p

Might even implement UnregisterTriggeringEvent(). :D

Anyway, stay tuned. :)

EDIT:
Version 1.02 released. I've been working on it pretty much since this post was first created until now. :p
 

emjlr3

Change can be a good thing
Staff member
why does this take a trigger?

why not just a function or string?
 

Jesus4Lyf

Good Idea™
Because it's an events system. People are meant to think of it as one.

Besides, there's no advantage in making it take a function, for example. How would you remove events if you did that? And there's no efficiency gain. Internally this would have to attach it to a trigger as a condition anyway. :D

There's all kinds of limitations added by making it take a function or string. For example, this way you could remove the event and add another one, so you can hot-swap the effects of spells at runtime.

PS. Seems you caught me half way through releasing the latest version. Maybe the new version will help explain. o_O
 

Builder Bob

Live free or don't
This looks pretty good.

Have you ran any benchmarking to see how much more efficient per cast it is to use this compared to regular registering?

It'd also be interesting to see how the spell cast time scales the more spell cast triggers you register in a map. How many spells would you have to register to see it become a problem?
 

emjlr3

Change can be a good thing
Staff member
just figured it would be easier for ppl not to have to create triggers and actions and whatever - since this is really supposed to do all that for you - you should just be able to code your Actions functions, then call this do have your Actions function ran on some trigger event
 

Viikuna

No Marlo no game.
TriggerExecute is slow, so if you want it fast, use only Condition and only TriggerEvaluate. I use only Condition anyways, so this not a problem for me.

emjlr3 do you mean something like: RegisterSpellEvent( function Action ) ?
 

emjlr3

Change can be a good thing
Staff member
yes, something like

JASS:
call RegisterOnEffect(function Blarg)


or

JASS:
call RegisterOnEndCast( function EEk)


or even

JASS:
call RegisterOnEffect('XXXX', function Blarg)
 

Jesus4Lyf

Good Idea™
Or
JASS:
call TriggerAddAction(GT_RegisterStartsEffectEvent(CreateTrigger(),'A000'), function ActionsFunc )

Or
JASS:
call TriggerAddCondition(GT_RegisterStartsEffectEvent(CreateTrigger(),'A000'), Condition(function ActionsFuncReturningFalse))

?

It already supports that style of coding. That's why it returns the trigger passed in. And if the trigger is temporary, have it call GT_UnregisterTriggeringEven() and DestroyTrigger(GetTriggeringTrigger()) when appropriate.

*Shrugs*
I suppose I'll write AttachActionToSpellEffectEvent('A000', function ActionsFuncReturningFalse) and DetachTriggeringAction() wrappers next version.

>Have you ran any benchmarking to see how much more efficient per cast it is to use this compared to regular registering?
No, actually, but I might soon. I didn't consider it too important anyway because... well... it doesn't lag either way. It's not like a timer system that is firing constantly in the background, up to hundreds of times in a moment. But when I next get a moment, I'll check it out, perhaps.

>It'd also be interesting to see how the spell cast time scales the more spell cast triggers you register in a map. How many spells would you have to register to see it become a problem?
Yeah. Well this system makes it so that will never be a problem, too. I may also bench this and see what it looks like, anyway. :)

Thanks for all the feedback so far. ^_^
 

Builder Bob

Live free or don't
>Have you ran any benchmarking to see how much more efficient per cast it is to use this compared to regular registering?
No, actually, but I might soon. I didn't consider it too important anyway because... well... it doesn't lag either way. It's not like a timer system that is firing constantly in the background, up to hundreds of times in a moment. But when I next get a moment, I'll check it out, perhaps.

>It'd also be interesting to see how the spell cast time scales the more spell cast triggers you register in a map. How many spells would you have to register to see it become a problem?
Yeah. Well this system makes it so that will never be a problem, too. I may also bench this and see what it looks like, anyway. :)

Thanks for all the feedback so far. ^_^
Yeah, that's what makes me not bother benchmarking it as well. It doesn't lag either way as you say. No need to benchmark unless you become too curious. ;) This is obviously more efficient for maps with a large quantity of ability triggers.
 

Jesus4Lyf

Good Idea™
>This is obviously more efficient for maps with a large quantity of ability triggers.

Yeah. And of course, that is the only situation where efficiency would matter anyway.

Of course, I am fairly curious, and will endeavor to provide a reasonable estimate as to how many triggers you need before this system is more efficient, supported by benchmarks. Maybe tonight. Can't right this minute, though.

EDIT: AND THE RESULTS ARE IN!

This is very funny.

Ready for this?

-REGISTER FUNCTIONALITY-
call TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
~470 nanosec (~30 margin of error).
call GT_RegisterStartsEffectEvent(trigger, 'A000')
~4 nanosec (~0.7 margin of error).

Making this GT over 100 times faster for the initial event registration. So go shave a couple of hundred nanoseconds per spell off your map init (who cares). But if you actually do have to add/remove these events mid game, geez it's fast.
I'm not actually that suprised that it's a lot faster, but I didn't expect it to be THAT much faster. Turns out that register native that BJ calls internally 16 times must be slow.

So it takes less than 1% as long to call GTrigger's add function.

And now... for the bit that really matters...

-FIRING SPEED-

We all expected GT to inevitably gain efficiency based on how many different spells are triggered off.

That indeed is a massive number of spells, where "massive" means 2.

My tests have shown that GTrigger's fire speed is more or less exactly in between the fire speed of having 1 spell and 2 spells, around a 15 nanosec difference either way.

So if you trigger off 2 or more spells being cast in your map, your map will gain efficiency from using GTrigger, and probably has no reason not to.

So basically, every map should use GTrigger for ability events.

I'm intending to expand the domain of GTrigger to register different kinds of events, too.

I'd upload my tests here, but I don't want to run out of storage space on TheHelper, which is what would happen if I uploaded all my tests by habit. But I'm happy to if people really want. I'm also happy for people to do their own tests.

Testing the add function was easy, obviously. In case you're wondering how I tested the firing, there's a spell that fires IMMEDIATELY on unit death (as in, if you call KillUnit, the triggers for that spell will fire before the next line of code is run), and that's Phoenix morph. So I simply registered 50 abilities on that spell effect, then started a stop watch, killed a phoenix, and clocked the stop watch. Each test repeated this 50 times, and I repeated each test about 5 times. I then reduced that 50 to 20, still being slower I reduced it to 5, then 2, then 1 failing trigger and 1 working trigger (both attaching using conditions only). Quite a reliable, appropriate test. :)
Just to clarify that, all triggers in my bench test attached with a single condition only.
 

Viikuna

No Marlo no game.
Remember to do benchmarking also by using only conditions, because it should be faster than using actions. Also TriggerExecute should be slower than TriggerEvaluate.
 
JASS:
function TriggerRegisterAnyUnitEventBJ takes trigger trig, playerunitevent whichEvent returns nothing
    local integer index

    set index = 0
    loop
        call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)

        set index = index + 1
        exitwhen index == bj_MAX_PLAYER_SLOTS
    endloop
endfunction


JASS:
        public function Register$NAME$Event takes trigger t, integer abil returns trigger
            set Register$NAME$Mem=GetMem()
            set Trig[Register$NAME$Mem]=t
            call Link(GetOrCreate$NAME$ListPointer(abil),Register$NAME$Mem)
            return t
        endfunction
Are you really suprised that your registration is faster? Maybe cut blizzard some slack and do a proper benchmark?
 

Jesus4Lyf

Good Idea™
Remember to do benchmarking also by using only conditions, because it should be faster than using actions. Also TriggerExecute should be slower than TriggerEvaluate.
I did.
Are you really suprised that your registration is faster? Maybe cut blizzard some slack and do a proper benchmark?
As said, no I'm not. And my benchmark is plenty proper.

Don't randomly say it's not proper, please. I just spent the last couple of hours carefully setting up and executing decent tests. :D
 
Mate, your testing your system against a bj. I can't even comprehend why it didn't occur to you that thát is simply stupid? How can you call that proper benchmarking?

In my honest opinion you'll have to test the best native alternative against your system for a proper benchmarking.
 

Jesus4Lyf

Good Idea™
>I can't even comprehend why it didn't occur to you that thát is simply stupid?
Maybe it's not.

You do realise that the alternative is inlining that BJ?

You need to add the event for all 16 players. I don't know of any alternative, this is always the way I believe it's been done. This is how spells are triggered (unless, like in an AoS, perhaps it is registered on hero selection for only the one player, which would still be apparently slower than GTrigger anyway). This is how spells have always been triggered, to the best of my knowledge.

Furthermore, why do you care? That's the irrelevant test. Ok, GTrigger is clearly faster, but the relevant test is the firing speed, not the event registration speed.

If you want to take this up further, tell me another native way to register on any unit casting a spell, and I'll be happy to bench it against that too.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    It happens in corporations. They just absorb the companies they buy and it is not about the love of making awesome games it is how much money can we make with the least amount of cost.
  • The Helper The Helper:
    Blizzard is watered down now hopefully they can pull it together
  • The Helper The Helper:
    they got a server engineer job opening :)
  • Ghan Ghan:
    I really do not want to move to California otherwise I might consider it.
  • The Helper The Helper:
    yeah California is not anywhere you really want to live
  • The Helper The Helper:
    That is why I did not take the job Blizzard offered me back in the day, there is no way I could have moved my family there on what they were offering, not even close and that was like 20 years ago
  • The Helper The Helper:
    yeah they wanted me on the tech support team when they did not get me they got one of the next MVPs in Dinobot
  • The Helper The Helper:
    Dinobot was one of the youngest of the MVPs tkron probably could have worked for Blizzard but he had a good job in Chicago doing business programming already
  • The Helper The Helper:
    Dinobot probably still works for Blizzard would love to reconnect with that guy
  • The Helper The Helper:
    I wonder what ever happened to Wargasm?
  • The Helper The Helper:
    This new version of Xenforo really is awesome
  • Ghan Ghan:
    Wargasm is still around. He works for the domain registrar where thehelper.net is kept.
  • Varine Varine:
    Is sqrage still around?
  • The Helper The Helper:
    I have not seen him on lately the forum says he was last on 2 years ago
  • The Helper The Helper:
    How are you doing Varine have not seen you around in a minute
  • thewrongvine thewrongvine:
    lol I live in CA
  • thewrongvine thewrongvine:
    I've got some friends in animation department who have been applying for Blizzard, doing interviews and such. They said the workplace seems nice, though now it'd be all remote work I suppose

    Members online

    No members online now.

    Affiliates

    Hive Workshop
    Top