System GTrigger Event System

Jesus4Lyf

Good Idea™
Reaction score
397
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() == &#039;A000&#039; ) ) 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,&#039;A000&#039;)
    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(),&#039;A000&#039;), 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

  • GTrigger.w3x
    43.4 KB · Views: 908
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-&#039;0000&#039;
    return id-(id/(8192))*(8192)
endfunction
 
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.
 
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-&#039;0000&#039;
    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
 
why does this take a trigger?

why not just a function or string?
 
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
 
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?
 
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
 
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 ) ?
 
yes, something like

JASS:
call RegisterOnEffect(function Blarg)


or

JASS:
call RegisterOnEndCast( function EEk)


or even

JASS:
call RegisterOnEffect(&#039;XXXX&#039;, function Blarg)
 
Or
JASS:
call TriggerAddAction(GT_RegisterStartsEffectEvent(CreateTrigger(),&#039;A000&#039;), function ActionsFunc )

Or
JASS:
call TriggerAddCondition(GT_RegisterStartsEffectEvent(CreateTrigger(),&#039;A000&#039;), 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. ^_^
 
>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.
 
>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.
 
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?
 
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.
 
>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.
  • Varine Varine:
    We have some elderly guests that regularly come hang out at the bar at the end of the night, and every once in a while we don't see someone for a few weeks and then someone shows up with their obituary.
  • Varine Varine:
    We usually let them do their memorials there in the morning if they want to and I'll make them some snacks and drinks. There was one guy named Tom that came in like every night and would sit by himself and get a bunch of soup and a glass of wine. idk why but he LOVED our fucking soup, like he would order a fucking quart of it at a time and would always get so sad when we stop doing it for the summer.
    +1
  • Varine Varine:
    But he also loved our calamari, which is another thing I hate but it sells super well so I can't change it. There was one day he came in and was asking me how to make it, because he tried to at home once in the off season when we stop running it and he really wanted it lol
  • Varine Varine:
    I think he's one of the only people I've made recipes for for free because he really wanted a broccoli cheddar, and it was like dude I don't have a recipe, it's just whatever I have, but here, this is how you do it
  • Varine Varine:
    I don't think he ever figured out how to do the calamari in a pan though, like idk how to do that either. He was afraid of the at home deep fryers though and it's like yeah, that's fair, I am too
  • Varine Varine:
    He was just such a sweet old man, we had two servers pregnant and they held a baby shower together, he was soooooo fucking excited to get to see a baby. Unfortunately he died a month or so before they were born
  • The Helper The Helper:
    So I decided to Google some people that I had not seen or heard from in a while and sure enough one of my old best friends, we had a falling out years ago but whatever, find out he died of Pancreatic Cancer in January. I have also lost a few of my closer acquaintances from growing up the last year. Getting old - people die - I kinda thought it was going to be this way a few years ago....
    +2
  • The Helper The Helper:
    Forum running super slow again
  • Ghan Ghan:
    Not really clear from the stats as to what is causing the slowness.
  • Ghan Ghan:
    We get a lot of guest traffic so it may just be the load is getting too high and not from any particular source.
  • Ghan Ghan:
    Looks like the server is maxed out on CPU.
  • Ghan Ghan:
    Oh it looks like a lot of the traffic is Silkroad Forums. That domain isn't protected by Cloudflare.
  • Ghan Ghan:
    But the old Silkroad site is still on its own server. I just had a test site set up on this server for it.
  • Ghan Ghan:
    I just disabled that test site. Let's see if that helps the load.
  • Ghan Ghan:
    Looks much better already.
  • The Helper The Helper:
    I had actually forgot about the Silkroad site. I had asked
  • The Helper The Helper:
    SD Ryoko about it and he said the couple of people left on there really like it, that was a few years ago, maybe I should check back
  • jonas jonas:
    I guess when you're getting old, and the last day of soup season draws near, you start wondering
  • jonas jonas:
    will I make it to the start of the next season? or was this the last time I'll ever have my favorite dish?
  • The Helper The Helper:
    I am doing my first Vibe Coding project. In installed the environment and tools according to instructions but it is all chat doing this for me at my direction. It is fun really and holy shit I might finish in 2 hours what it would have taken a day to in my Access and this would be an electron app complete new
  • Ghan Ghan:
    Good stuff.
  • Ghan Ghan:
    Just make sure it is secure. :)
    +1
  • The Helper The Helper:
    It will only be on internal network
  • jonas jonas:
    Man the AI is good about gaslighting about security though. I've had several times where I pointed out security problems and it tried to convince me that with a tiny tweak it suddenly becomes secure
  • jonas jonas:
    Like using a distrobox as a "secure" container, and when I point out that's not secure at all, it claimed that specifying home will make it secure

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials
      Top