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

Event System

Status
Not open for further replies.
Level 24
Joined
Aug 1, 2013
Messages
4,657
Hi all.

I have been making an event system but it was kind of really slow because it loaded every single action from a hashtable.
Because of that, I tried to save everything inside arrays but that is really hard or you have to give each event a maximum number of fields which would be a pain in the ass to use this system.

Now I made a converter that places all events from the hashtable into arrays.
The only way how the hashtable is still used is to get the event id of the event that you want to call.
It also checks if you have changed the triggers of an event since the last convertion to make it run from the hashtable (like before) or from the arrays.

If anyone has any suggestions on what it could improve or what it could use then feel free to comment.

This system is created to have events with priorities so you can organize the actions by integers.
This system also supports 2 event parameters which can make event calls to address more specific triggers.
You can register a trigger to an event (unique integer) and with optional 2 parameters.
For example, I register a trigger that should run when Blizzard is cast to the event "UNIT_SPELL_EFFECT" and 'AHbz'.
Then I have to call the event using "UNIT_SPELL_EFFECT" and "GetSpellAbilityId()" and then all the triggers of those events are called only if their parameter is the same.
Check out how AEM_TriggerRegisterEvent2() and AEM_TriggerRegisterEvent3() work.

JASS:
library aemSystem
    
    globals
        
        //Dynamic allocation of events.
        integer             udg_AEM_NewEvent                    = 0
        
        hashtable           udg_AEM_Hashtable                   = InitHashtable()
        integer             udg_AEM_NumberOfFields              = 3
        trigger             udg_AEM_ConvertToArray              = CreateTrigger()
        boolean             udg_AEM_IsConverting                = false
        boolean             udg_AEM_IsCallingAnEvent            = false
        integer             udg_AEM_EventIndex
        integer             udg_AEM_TriggerIndex
        integer             udg_AEM_Event2Offset                = 8191 //udg_AEM_ArraySize
        integer             udg_AEM_Event3Offset                = 8191 //udg_AEM_ArraySize
        
        integer             udg_AEM_NextEventIndex              = 0
        integer array       udg_AEM_Events
        integer array       udg_AEM_StartingIndex
        integer array       udg_AEM_EventSize
        boolean array       udg_AEM_IsConverted
        
        //Having more than 8,191 triggers is very probable for large maps.
        //Having more than 24,573 triggers is probably bad coding.
        //Having more than 8,191 events... is not god either.
        integer             udg_AEM_ArraySize                   = 8191
        trigger array       udg_AEM_Triggers1
        integer array       udg_AEM_Priorities1
        boolean array       udg_AEM_Repeating1
        trigger array       udg_AEM_Triggers2
        integer array       udg_AEM_Priorities2
        boolean array       udg_AEM_Repeating2
        trigger array       udg_AEM_Triggers3
        integer array       udg_AEM_Priorities3
        boolean array       udg_AEM_Repeating3
        
        boolean             udg_AEM_StopRemainingActions        = false
        
    endglobals
    
    
    
    
    
    function AEM_TriggerRemoveEvent takes trigger whichTrigger, integer whichEvent returns nothing
        local integer i = 1
        local trigger t
        
        loop
            set t = LoadTriggerHandle(udg_AEM_Hashtable, whichEvent, i)
            exitwhen t == null
            
            if t == whichTrigger then
               loop
                    exitwhen not HaveSavedHandle(udg_AEM_Hashtable, whichEvent, i+3)
                    
                    //Replace all event responses after the found trigger by the ones that follow it afterwards.
                    call SaveTriggerHandle(udg_AEM_Hashtable, whichEvent, i, LoadTriggerHandle(udg_AEM_Hashtable, whichEvent, i+3))
                    call SaveInteger(udg_AEM_Hashtable, whichEvent, i+1, LoadInteger(udg_AEM_Hashtable, whichEvent, i+4))
                    call SaveBoolean(udg_AEM_Hashtable, whichEvent, i+2, LoadBoolean(udg_AEM_Hashtable, whichEvent, i+5))
                    
                    set i = i + udg_AEM_NumberOfFields
                endloop
                
                //Remove the last event response from the event.
                call RemoveSavedHandle(udg_AEM_Hashtable, whichEvent, i)
                call RemoveSavedInteger(udg_AEM_Hashtable, whichEvent, i+1)
                call RemoveSavedBoolean(udg_AEM_Hashtable, whichEvent, i+2)
               
               set udg_AEM_IsConverted[LoadInteger(udg_AEM_Hashtable, whichEvent, 0)] = false
               return
            endif
            
            set i = i + udg_AEM_NumberOfFields
        endloop
    endfunction
    function AEM_TriggerRemoveEvent2 takes trigger whichTrigger, integer whichEvent, integer whichParameter2 returns nothing
        local integer realEvent = LoadInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*2, whichParameter2)
        if realEvent == 0 then
            set realEvent = udg_AEM_Event2Offset
            set udg_AEM_Event2Offset = udg_AEM_Event2Offset +1
            call SaveInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*2, whichParameter2, realEvent)
        endif
        call AEM_TriggerRemoveEvent(whichTrigger, realEvent)
    endfunction
    function AEM_TriggerRemoveEvent3 takes trigger whichTrigger, integer whichEvent, integer whichParameter2, integer whichParameter3 returns nothing
        local integer realEvent = LoadInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*4, whichParameter2)
        if realEvent == 0 then
            set realEvent = udg_AEM_Event2Offset
            set udg_AEM_Event2Offset = udg_AEM_Event2Offset +1
            call SaveInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*4, whichParameter2, realEvent)
        endif
        call AEM_TriggerRemoveEvent2(whichTrigger, realEvent, whichParameter3)
    endfunction
    
    function AEM_CallEvent takes integer whichEvent returns nothing
        local integer eventIndex
        local integer triggerIndex
        local integer lastIndex
        local integer i
        local integer index
        local trigger t
        local trigger array triggers
        local boolean oldStopRemainingActions
        
        if whichEvent == 0 then
            return
        endif
        
        set eventIndex = LoadInteger(udg_AEM_Hashtable, whichEvent, 0)
        if eventIndex == 0 then
            //The event doesnt have actions.
            return
        endif
        
        set udg_AEM_IsCallingAnEvent = true
        set oldStopRemainingActions = udg_AEM_StopRemainingActions
        set udg_AEM_StopRemainingActions = false
        
        if not udg_AEM_IsConverted[eventIndex] then
            //The event has registered triggers or removed triggers after the last ConvertToArray.
            //So we need the data directly from the hashtable.
            debug call BJDebugMsg("Event [" + I2S(eventIndex) + "] isnt converted.")
            
            set i = 1
            set index = 0
            loop
                set t = LoadTriggerHandle(udg_AEM_Hashtable, whichEvent, i)
                exitwhen t == null
                set triggers[index] = t
                
                if not LoadBoolean(udg_AEM_Hashtable, whichEvent, i+2) then
                    //Remove the event response because it is not repeating.
                    call AEM_TriggerRemoveEvent(triggers[index], whichEvent)
                    set i = i - udg_AEM_NumberOfFields
                endif
                
                set index = index + 1
                set i = i + udg_AEM_NumberOfFields
            endloop
            
            set i = 0
            loop
                exitwhen triggers[i] == null
                if not udg_AEM_StopRemainingActions then
                    if TriggerEvaluate(triggers[i]) then
                        call TriggerExecute(triggers[i])
                    endif
                endif
                set triggers[i] = null
                set i = i + 1
            endloop
        else
            //The event is properly registered in the arrays.
            set triggerIndex = udg_AEM_StartingIndex[eventIndex]
            set lastIndex = triggerIndex + udg_AEM_EventSize[eventIndex]
            loop
                exitwhen triggerIndex >= lastIndex
                
                //If the end of this array is reached, we move on to the next array.
                if triggerIndex >= udg_AEM_ArraySize then
                    set lastIndex = lastIndex - udg_AEM_ArraySize
                    set triggerIndex = triggerIndex - udg_AEM_ArraySize
                    loop
                        exitwhen triggerIndex >= lastIndex
                        if triggerIndex >= udg_AEM_ArraySize then
                            set lastIndex = lastIndex - udg_AEM_ArraySize
                            set triggerIndex = triggerIndex - udg_AEM_ArraySize
                            loop
                                exitwhen triggerIndex >= lastIndex
                                if triggerIndex >= udg_AEM_ArraySize then
                                    exitwhen true
                                endif
                                
                                if not udg_AEM_StopRemainingActions then
                                    if TriggerEvaluate(udg_AEM_Triggers3[triggerIndex]) then
                                        call TriggerExecute(udg_AEM_Triggers3[triggerIndex])
                                    endif
                                endif
                                
                                if not udg_AEM_Repeating3[triggerIndex] then
                                    call AEM_AEM_TriggerRemoveEvent(udg_AEM_Triggers3[triggerIndex], whichEvent)
                                endif
                                
                                set triggerIndex = triggerIndex +1
                            endloop
                            exitwhen true
                        endif
                        
                        if not udg_AEM_StopRemainingActions then
                            if TriggerEvaluate(udg_AEM_Triggers2[triggerIndex]) then
                                call TriggerExecute(udg_AEM_Triggers2[triggerIndex])
                            endif
                        endif
                        
                        if not udg_AEM_Repeating2[triggerIndex] then
                            call AEM_AEM_TriggerRemoveEvent(udg_AEM_Triggers3[triggerIndex], whichEvent)
                        endif
                        
                        set triggerIndex = triggerIndex +1
                    endloop
                    exitwhen true
                endif
                
                //Evaluate and Execute the trigger.
                if not udg_AEM_StopRemainingActions then
                    if TriggerEvaluate(udg_AEM_Triggers1[triggerIndex]) then
                        call TriggerExecute(udg_AEM_Triggers1[triggerIndex])
                    endif
                endif
                
                //Remove the trigger from this event if it is set to non-repeating.
                if not udg_AEM_Repeating1[triggerIndex] then
                    call AEM_AEM_TriggerRemoveEvent(udg_AEM_Triggers3[triggerIndex], whichEvent)
                endif
                
                set triggerIndex = triggerIndex +1
            endloop
            
        endif
        set udg_AEM_StopRemainingActions = oldStopRemainingActions
        set udg_AEM_IsCallingAnEvent = false
        
    endfunction
    function AEM_CallEvent2 takes integer whichEvent, integer whichParameter2 returns nothing
        local integer realEvent
        if whichEvent > 0 then
            set realEvent = LoadInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*2, whichParameter2)
            if realEvent > 0 then
                call AEM_CallEvent(realEvent)
            endif
        endif
    endfunction
    function AEM_CallEvent3 takes integer whichEvent, integer whichParameter2, integer whichParameter3 returns nothing
        local integer realEvent
        if whichEvent > 0 then
            set realEvent = LoadInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*4, whichParameter3)
            if realEvent > 0 then
                call AEM_CallEvent2(realEvent, whichParameter3)
            endif
        endif
    endfunction
    
    function AEM_TriggerRegisterEvent takes trigger whichTrigger, integer whichEvent, integer priority, boolean priorize, boolean repeating returns integer
        local integer i = 1
        local integer i2 = 1
        
        if whichEvent == 0 then
            set udg_AEM_NewEvent = udg_AEM_NewEvent +1
            set whichEvent = udg_AEM_NewEvent
        endif
        loop
            exitwhen not HaveSavedHandle(udg_AEM_Hashtable, whichEvent, i)
            
            if priorize then
                exitwhen LoadInteger(udg_AEM_Hashtable, whichEvent, i+1) >= priority
            else
                exitwhen LoadInteger(udg_AEM_Hashtable, whichEvent, i+1) > priority
            endif
            
            set i = i + udg_AEM_NumberOfFields
        endloop
        
        set i2 = i
        loop
            exitwhen not HaveSavedHandle(udg_AEM_Hashtable, whichEvent, i2)
            set i2 = i2 + udg_AEM_NumberOfFields
        endloop
        loop
            exitwhen i2 == i
            
            call SaveTriggerHandle(udg_AEM_Hashtable, whichEvent, i2, LoadTriggerHandle(udg_AEM_Hashtable, whichEvent, i2-3))
            call SaveInteger(udg_AEM_Hashtable, whichEvent, i2+1, LoadInteger(udg_AEM_Hashtable, whichEvent, i2-2))
            call SaveBoolean(udg_AEM_Hashtable, whichEvent, i2+2, LoadBoolean(udg_AEM_Hashtable, whichEvent, i2-1))
            
            set i2 = i2 - udg_AEM_NumberOfFields
        endloop
        
        call SaveTriggerHandle(udg_AEM_Hashtable, whichEvent, i, whichTrigger)
        call SaveInteger(udg_AEM_Hashtable, whichEvent, i+1, priority)
        call SaveBoolean(udg_AEM_Hashtable, whichEvent, i+2, repeating)
        
        set i = LoadInteger(udg_AEM_Hashtable, whichEvent, 0)
        if i == 0 then
            set udg_AEM_NextEventIndex = udg_AEM_NextEventIndex +1
            set udg_AEM_Events[udg_AEM_NextEventIndex] = whichEvent
            call SaveInteger(udg_AEM_Hashtable, whichEvent, 0, udg_AEM_NextEventIndex)
        endif
        set udg_AEM_IsConverted[i] = false
        return whichEvent
    endfunction
    function AEM_TriggerRegisterEvent2 takes trigger whichTrigger, integer whichEvent, integer whichParameter2, integer priority, boolean priorize, boolean repeating returns integer
        local integer realEvent
        if whichEvent == 0 then
            set udg_AEM_NewEvent = udg_AEM_NewEvent +1
            set whichEvent = udg_AEM_NewEvent
        endif
        set realEvent = LoadInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*2, whichParameter2)
        if realEvent == 0 then
            set realEvent = udg_AEM_Event2Offset
            set udg_AEM_Event2Offset = udg_AEM_Event2Offset +1
            call SaveInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*2, whichParameter2, realEvent)
        endif
        call AEM_TriggerRegisterEvent(whichTrigger, realEvent, priority, priorize, repeating)
        return whichEvent
    endfunction
    function AEM_TriggerRegisterEvent3 takes trigger whichTrigger, integer whichEvent, integer whichParameter2, integer whichParameter3, integer priority, boolean priorize, boolean repeating returns integer
        local integer realEvent
        if whichEvent == 0 then
            set udg_AEM_NewEvent = udg_AEM_NewEvent +1
            set whichEvent = udg_AEM_NewEvent
        endif
        set realEvent = LoadInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*4, whichParameter2)
        if realEvent == 0 then
            set realEvent = udg_AEM_Event3Offset
            set udg_AEM_Event3Offset = udg_AEM_Event3Offset +1
            call SaveInteger(udg_AEM_Hashtable, whichEvent + udg_AEM_ArraySize*4, whichParameter2, realEvent)
        endif
        call AEM_TriggerRegisterEvent2(whichTrigger, realEvent, whichParameter3, priority, priorize, repeating)
        return whichEvent
    endfunction
    
    function AEM_ConvertToArray_Callback takes nothing returns nothing
        local integer i = 0
        local integer eventIndex = udg_AEM_EventIndex
        local integer hashtableIndex
        local integer triggerIndex = udg_AEM_TriggerIndex
        local integer eventSize
        
        loop
            if i >= 200 then
                set udg_AEM_EventIndex = eventIndex
                set udg_AEM_TriggerIndex = triggerIndex
                return
            endif
            exitwhen udg_AEM_Events[eventIndex] == 0
            set hashtableIndex = 1
            set eventSize = 0
            set udg_AEM_StartingIndex[eventIndex] = triggerIndex
            loop
                exitwhen not HaveSavedHandle(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex)
                
                //I really dont give a **** for performance in this function... yet.
                //Store the trigger in the array.
                if triggerIndex < udg_AEM_ArraySize then
                    set udg_AEM_Triggers1[triggerIndex] = LoadTriggerHandle(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex)
                    //set udg_AEM_Priorities1[triggerIndex] = LoadInteger(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex+1)
                    set udg_AEM_Repeating1[triggerIndex] = LoadBoolean(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex+2)
                elseif triggerIndex < udg_AEM_ArraySize*2 then
                    set udg_AEM_Triggers2[triggerIndex - udg_AEM_ArraySize] = LoadTriggerHandle(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex)
                    //set udg_AEM_Priorities2[triggerIndex - udg_AEM_ArraySize] = LoadInteger(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex+1)
                    set udg_AEM_Repeating2[triggerIndex - udg_AEM_ArraySize] = LoadBoolean(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex+2)
                elseif triggerIndex < udg_AEM_ArraySize*3 then
                    set udg_AEM_Triggers3[triggerIndex - udg_AEM_ArraySize*2] = LoadTriggerHandle(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex)
                    //set udg_AEM_Priorities3[triggerIndex - udg_AEM_ArraySize*2] = LoadInteger(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex+1)
                    set udg_AEM_Repeating3[triggerIndex - udg_AEM_ArraySize*2] = LoadBoolean(udg_AEM_Hashtable, udg_AEM_Events[eventIndex], hashtableIndex+2)
                //else
                    //you are doing something wrong in your code... you have reached your 24,574 th trigger!
                endif
                set triggerIndex = triggerIndex +1
                set eventSize = eventSize +1
                
                set hashtableIndex = hashtableIndex + udg_AEM_NumberOfFields
            endloop
            set udg_AEM_EventSize[eventIndex] = eventSize
            set udg_AEM_IsConverted[eventIndex] = true
            
            set eventIndex = eventIndex +1
            set i = i +1
        endloop
        
        //Ended converting.
        debug call BJDebugMsg("ConvertToArray ended")
        set udg_AEM_IsConverting = false
        call DestroyTimer(GetExpiredTimer())
    endfunction
    function AEM_ConvertToArrayAttempt takes nothing returns nothing
        
        //Be patient.
        if udg_AEM_IsConverting then
            call DestroyTimer(GetExpiredTimer())
            return
        endif
        
        //The system would break if you rewrite the event arrays while the system is calling an event.
        //Check again on next interval.
        if udg_AEM_IsCallingAnEvent then
            return
        endif
        
        //Start converting.
        set udg_AEM_IsConverting = true
        debug call BJDebugMsg("ConvertToArray started")
        
        //Set the global indexes to their starting values.
        set udg_AEM_EventIndex = 1
        set udg_AEM_TriggerIndex = 0
        
        call TimerStart(GetExpiredTimer(), 0, true, function AEM_ConvertToArray_Callback)
    endif
    function AEM_ConvertToArray takes nothing returns nothing
        
        //Be patient.
        if udg_AEM_IsConverting then
            return
        endif
        
        //The system would break if you rewrite the event arrays while the system is calling an event.
        //So we create a 0 seconds timer that would ofcourse occur after the event has been called to check again.
        if udg_AEM_IsCallingAnEvent then
            call TimerStart(CreateTimer(), 0, true, function AEM_ConvertToArrayAttempt)
            return
        endif
        
        //Start converting.
        set udg_AEM_IsConverting = true
        debug call BJDebugMsg("ConvertToArray started")
        
        //Set the global indexes to their starting values.
        set udg_AEM_EventIndex = 1
        set udg_AEM_TriggerIndex = 0
        
        call TimerStart(CreateTimer(), 0, true, function AEM_ConvertToArray_Callback)
    endfunction
    
endlibrary



//===========================================================================
function InitTrig_AEM_System takes nothing returns nothing
    //Execute this trigger manually to convert the system into arrays.
    call TriggerRegisterTimerEvent(udg_AEM_ConvertToArray, 1, false)
    call TriggerAddAction(udg_AEM_ConvertToArray, function AEM_ConvertToArray)
endfunction

Events can be executed via "AEM_CallEvent()".
Here are a few regular events implemented into the system:
JASS:
globals
    integer udg_AEM_Event_Player_Chat_Message       = 0
    integer udg_AEM_Event_Player_Esc_Button         = 0
    integer udg_AEM_Event_Player_Leave              = 0
    integer udg_AEM_Event_Unit_Pickup_Item          = 0
    integer udg_AEM_Event_Unit_Drop_Item            = 0
    integer udg_AEM_Event_Unit_Attacked             = 0
    integer udg_AEM_Event_Unit_Spell_Channel        = 0
    integer udg_AEM_Event_Unit_Spell_Cast           = 0
    integer udg_AEM_Event_Unit_Spell_Effect         = 0
    integer udg_AEM_Event_Unit_Spell_Finish         = 0
    integer udg_AEM_Event_Unit_Spell_Cancel         = 0
    integer udg_AEM_Event_Unit_Levelup              = 0
    integer udg_AEM_Event_Unit_Die                  = 0
    integer udg_AEM_Event_Unit_Order_Target         = 0
    integer udg_AEM_Event_Unit_Order_Point          = 0
    integer udg_AEM_Event_Unit_Order_Instant        = 0
    integer udg_AEM_Event_Unit_Created              = 0
endglobals


function AEM_Event_Player_Chat_Message takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Player_Chat_Message)
    return false
endfunction

function AEM_Event_Player_Esc_Button takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Player_Esc_Button)
    return false
endfunction

function AEM_Event_Player_Leave takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Player_Leave)
    return false
endfunction

function AEM_Event_Unit_Pickup_Item takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Pickup_Item)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Pickup_Item, GetItemTypeId(GetManipulatedItem()))
    return false
endfunction

function AEM_Event_Unit_Drop_Item takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Drop_Item)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Drop_Item, GetItemTypeId(GetManipulatedItem()))
    return false
endfunction

function AEM_Event_Unit_Attacked takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Attacked)
    return false
endfunction

function AEM_Event_Unit_Spell_Cast takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Spell_Cast,)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Spell_Cast, GetSpellAbilityId())
    return false
endfunction

function AEM_Event_Unit_Spell_Channel takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Spell_Channel)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Spell_Channel, GetSpellAbilityId())
    return false
endfunction

function AEM_Event_Unit_Spell_Effect takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Spell_Effect)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Spell_Effect, GetSpellAbilityId())
    return false
endfunction

function AEM_Event_Unit_Spell_Finish takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Spell_Finish)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Spell_Finish, GetSpellAbilityId())
    return false
endfunction

function AEM_Event_Unit_Spell_Cancel takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Spell_Cancel)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Spell_Cancel, GetSpellAbilityId())
    return false
endfunction

function AEM_Event_Unit_Levelup takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Levelup)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Levelup, GetUnitTypeId(GetLevelingUnit()))
    return false
endfunction

function AEM_Event_Unit_Die takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Die)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Levelup, GetUnitTypeId(GetDyingUnit()))
    return false
endfunction

function AEM_Event_Unit_Order_Target takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Order_Target)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Order_Target, GetIssuedOrderId())
    return false
endfunction

function AEM_Event_Unit_Order_Point takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Order_Point)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Order_Point, GetIssuedOrderId())
    return false
endfunction

function AEM_Event_Unit_Order_Instant takes nothing returns boolean
    call AEM_CallEvent(udg_AEM_Event_Unit_Order_Instant)
    call AEM_CallEvent2(udg_AEM_Event_Unit_Order_Instant, GetIssuedOrderId())
    return false
endfunction

function UnitCreation_Event takes nothing returns boolean
    set bj_lastCreatedUnit = GetFilterUnit()
    
    call AEM_CallEvent(udg_AEM_Event_Unit_Created)
    
    return false
endfunction

function UnitCreation_Start takes nothing returns nothing
    local group g = CreateGroup()
    local unit FoG
    local trigger t = CreateTrigger()
    local region rectRegion = CreateRegion()
    local integer i = 0
    local rect r = GetWorldBounds()
    call DestroyTimer(GetExpiredTimer())
    
    call RegionAddRect(rectRegion, r)
    call TriggerRegisterEnterRegion(t, rectRegion, Filter(function UnitCreation_Event))
    call RemoveRect(r)
    
    loop
        exitwhen i >= 16
        call GroupEnumUnitsOfPlayer(g, Player(i), null)
        loop
            set FoG = FirstOfGroup(g)
            exitwhen FoG == null
            call GroupRemoveUnit(g, FoG)
            
            set bj_lastCreatedUnit = FoG
            call AEM_CallEvent(udg_AEM_Event_Unit_Created)
        endloop
        set i = i + 1
    endloop
    call DestroyGroup(g)
    set g = null
endfunction


//===========================================================================
function InitTrig_AEM_Normal_Events_Implementation takes nothing returns nothing
    local trigger t
    local integer i
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Player_Chat_Message))
    
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_END_CINEMATIC)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Player_Esc_Button))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_LEAVE)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Player_Leave))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_PICKUP_ITEM, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Pickup_Item))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DROP_ITEM, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Drop_Item))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ATTACKED, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Attacked))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_CHANNEL, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Spell_Channel))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Spell_Cast))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Spell_Effect))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_FINISH, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Spell_Finish))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Spell_Cancel))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_HERO_LEVEL, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Levelup))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Die))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Order_Target))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Order_Point))
    
    set t = CreateTrigger()
    set i = 0
    loop
        exitwhen i == bj_MAX_PLAYER_SLOTS
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
        set i = i + 1
    endloop
    call TriggerAddCondition(t, Filter(function AEM_Event_Unit_Order_Instant))
    
    call TimerStart(CreateTimer(), 0.05, false, function UnitCreation_Start)
    
    set t = null
endfunction

Events need a unique number and before you had to initialize all events inside the global tags and have a list of all events to know which numbers are free and which numbers are used.
In this one, you automatically create a value for the event that you give to the system.
This value is returned to the return address and should be set into the variable.
JASS:
function Ability_Effect_Blizzard takes nothing returns boolean
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Blizzard has being cast.")
    return false
endfunction
function Ability_Effect_SummonWaterElemental takes nothing returns boolean
    call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Summon Water Elemental has being cast.")
    return false
endfunction

//===========================================================================
function InitTrig_Blizzard takes nothing returns nothing
    local trigger t = CreateTrigger()
    call BJDebugMsg("started Blizzard")
    set udg_AEM_Event_Unit_Spell_Effect = AEM_TriggerRegisterEvent2(t, udg_AEM_Event_Unit_Spell_Effect, 'AHbz', 50, true, true)
    call TriggerAddCondition(t, Filter(function Ability_Effect_Blizzard))
    
    set t = CreateTrigger()
    call BJDebugMsg("started SWE")
    call AEM_TriggerRegisterEvent2(t, udg_AEM_Event_Unit_Spell_Effect, 'AHwe', 0, true, true)
    call TriggerAddCondition(t, Filter(function Ability_Effect_SummonWaterElemental))
endfunction
In the first register, the value of "udg_AEM_Event_Unit_Spell_Effect" is set to the returned value.
After that, you can use that value over and over again.
It is not necessary to set the value again and this would still work:
JASS:
function InitTrig_Blizzard takes nothing returns nothing
    local trigger t = CreateTrigger()
    call BJDebugMsg("started Blizzard")
    set udg_AEM_Event_Unit_Spell_Effect = AEM_TriggerRegisterEvent2(t, udg_AEM_Event_Unit_Spell_Effect, 'AHbz', 50, true, true)
    call TriggerAddCondition(t, Filter(function Ability_Effect_Blizzard))
    
    set t = CreateTrigger()
    call BJDebugMsg("started SWE")
    set udg_AEM_Event_Unit_Spell_Effect = AEM_TriggerRegisterEvent2(t, udg_AEM_Event_Unit_Spell_Effect, 'AHwe', 0, true, true)
    call TriggerAddCondition(t, Filter(function Ability_Effect_SummonWaterElemental))
endfunction
 
Last edited:

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
206796-albums7968-picture98745.jpg
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Ill explain how the system works as best as possible:

You have a list of global integers which simulate events.
All "events" must be unique to each other.
Here are some examples of events:
- Unit_Spell_Cast (when a unit starts casting an ability)
- Unit_Spell_Effect (when a unit starts the effect of an ability)
- Unit_Spell_Finish (when a unit finishes casting an ability)
- Unit_Levelup (when a unit is leveled up)
- Unit_Died (when a unit dies)
- Unit_Order_Target (when a unit is issued an order targeting an object)
- Unit_Order_Point (when a unit is issued an order targeting a point)
- Unit_Order_Instant (when a unit is issued an order with no target)

Every event has it's own row in the hashtable under it's own number.
(All events must be a number between 0 and 8191 NOT 0 or 8191 itself.)

In the hashtable, the first column is reserved.
After that, the hashtable has 3 columns for each trigger that you attach to an event.
- The trigger.
- The priority.
- If the trigger must only be called once or must be used over and over again.

So the hashtable will look like this:
VG4g4hv.png


When an event is called, it will loop through the hashtable and will evaluate and execute each trigger in it.

When you register a trigger, you add it to the hashtable.
When you remove a trigger, you remove it from the hashtable.



This system also supports priority.
What happens is when you add a trigger to an event, the system will loop through all already existing triggers inside that event and finds the place where your trigger's priority is bigger than the one before him and lower than the one after him.
That way, you can control the order in which the triggers are called.

There is one exception though.
What if you register a trigger on priority 50 while there already is a trigger on priority 50?
There is a boolean parameter that you give with the register.
If that boolean is true, it will be placed before it. If not, then it will be placed behind it.



Now there is the second big feature: the event parameters.
What if you want to call an event with a parameter?
For example: (Unit_Spell_Effect, GetSpellAbilityId())
That would make events more interesting right?
Lets see how we do it.

There is for each function (TriggerRegisterEvent, TriggerRemoveEvent and CallEvent) another version that takes a bonus integer parameter. (Ofcourse as this is JASS, we have to give it another name as well so I put a '2' behind the function name.)
That one will create a new index starting from 8191 (the maximum number of the origina events) and saves that in the hashtable under the Event + 8191*2 and the parameter.
Then it will redirect to the original TriggerRegisterEvent function with that new created index.

So we now have 3 parts of our hashtable:
pZvvEVE.png

The first part is the same... It is now just limited until 8191 (will come later why).
The third part (16382 - 24572) is where the indexes are saved of events that have 1 parameter.
The second part (8191 - 16381) is where the triggers, priorities and isRepeating are saved of the triggers on those events.

Now why dont we add another one?
I assume that there are quite some things that could be usefull under certain situations.
(A fourth one is not really necessary because then you are just overreacting.)
So we copy and paste:
PffcNky.png


Now as you can see, the event responses are stored in the first two parts of the hashtable.
The first part is when you manually call an event with no parameters and using the normal global variables.
The second part is where the event responses are stored when redirected from CallEvent2().
When we use CallEvent3(), we load the index that is used in the 4th part from the 5h part and use that to call CallEvent2() which then redirects to the second part.

Now that is still unclear but you have to deal with it... or just dont want to understand how it works.

Now there is one big thing that makes it bad... it uses a hashtable.
Lets imagine that I have the event Unit_Spell_Effect and use that one instead of the regular TriggerRegisterAnyUnitEvent thingy.
Can you imagine that I would have 200 triggers on that single event?
Now lets assume that that event is called... now we check if a trigger exist, load that trigger, and load a boolean from a hashtable... 200 times in that moment.
That is quite slow.
Now you are considering if the benefit from the priorities is worth all that processing time.
Ofcourse we can split them up which would reduce the loads etc to only one time and also remove 200 integer checks but hey, it is still slow.

So what we want is that all those triggers are stored inside arrays.
Now there is one problem, you cant create an array for Trigger1 and an array for Trigger2 and an array for Trigger3, etc (200 times) nor do you want to create a trigger array for U_Spell_Cast, a trigger array for U_Spell_Effect and modify the source code of the system every time.

Also, the amount of triggers that you can have in a big map will quite exceed 8191 which is the limit of our arrays.
We also have the nice vJASS feature that allows us to create arrays with 405k entries but that is even slower than a hashtable.

Now there is the second problem...
How are you going to store 'A000', 'AHbz', etc in arrays so you can still use those parameteres?
Answer: not.

What we can do is convert the event responses (triggers) into the arrays so the arrays will be properly filled.
This cannot be done so easily with structs because there, you cannot give events a dynamic size. Imagine one event that needs 200 triggers and 200 other events that only need one. Now we have to make a 200 size storage because there is an event that wants 200 triggers but all those other events also get 200 entries.
So we will create a function that converts the data of the triggers into one massive array.
We will also need a starting index and size (starting+size = endIndex).

So we will keep the hashtable and store a unique number inside the first (reserved) index of each event (1 - 16382).
That is the index of our event array.
In the event array, we store the index of where our event is stored inside the hashtable.
We also store the size and the starting index of our triggerarray.
And we also store a boolean if the event has changed since the last convertion (a trigger added or removed).

Now, when an event is called and the event is not changed, we set the iteration integer to the starting index of our event.
We also calculate the end value so we will know when to stop.
The all the triggers between those are successfully executed.

When an event has changed, it will load the triggers from the array instead.
However, this will only happen if you are adding or removing events during gametime.



Now to the convertion.

The convertion is quite simple:
Loop through every used hashtable entry of the first two parts. (We store those indexes in another array.)
For each of those, we loop through the hashtable and save the triggers into the arrays.
Then we save the starting index, size and set IsConverted to true.

However there is one big problem... actually two but that second one will come later.
The first problem is that there can be more triggers than 8191 in your map that have to be registered to an event.
So I created three arrays to hold that data.
It is probably not going to happen that you will have more than 24573 triggers attached to the events in your map.
The iteration when an event is called will stop when the end of the first array is reached and then go to another loop that continues with the second (same for third).
In the convertion, I wasnt in the mood of doing it there as well :D

The second problem (which is much bigger) is that the function can exceed 300k operations before you know it.
So I have made a 0 seconds timer that will execute the function in groups of 200 events.
That will solve it... nope.
Now there is another problem... what if you call that function again when it is still converting?
Save a boolean wether the system is converting or not and check that one.
What if you call that function during an event?
Save a boolean wether an event is called an check that one.
Ofcourse we will be calling another function that checks every second if there are events called because we still want it to convert the event responses.

So there can come a lot of stuff that is really bad for it's behavior.
If someone has a better solution then I am glad to hear it.



Now it should be quite clear what it does and how it does it...
But I assume that you are all now: :vw_wtf:

(EDIT: woops, should have editted other comment)
 
I may not have a full understanding, but I feel like this system could be reworked. When designing a system, it is crucial to select your target audience: is this system for vJASS users, or for GUI users? Sure, you can always make it friendly to both, but choosing one or the other will allow you to make simplifications based on assumptions. (if you are making it for personal use, then my comments are less relevant)

Event parameters are a nice feature, but I feel that the system is complex about it, and suffers from trying to offer priorities and parameters at the same time. It is better to have a specialized system. Think of something along the lines of SpellEffectEvent:
http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
You could make a SpellEffectEvent clone for unit events and item events that respond to particular unit/item types.

If you just focus on priorities, you can end up with a coherent system with a clear purpose. And it also means there are fewer things to consider--you don't even need to consider events. You can either choose to support individual triggers or individual trigger actions/conditions, or both. But my point is that you gain a lot of freedom and can simplify like crazy by making your system focused on one particular goal.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I may not have a full understanding, but I feel like this system could be reworked. When designing a system, it is crucial to select your target audience: is this system for vJASS users, or for GUI users? Sure, you can always make it friendly to both, but choosing one or the other will allow you to make simplifications based on assumptions. (if you are making it for personal use, then my comments are less relevant)
Nah, GUI support cannot be done.
I find that custom scripts that are function calls with a handle as parameter are not GUI friendly otherwise everything is GUI friendly.

In GUI, you cannot create an event in the event blocks, you have to call this function from another trigger that will add the events for all other triggers then... that is in many ways not GUI friendly.

My main audience is JASS mappers (like me) who have JNGP.

Event parameters are a nice feature, but I feel that the system is complex about it, and suffers from trying to offer priorities and parameters at the same time. It is better to have a specialized system. Think of something along the lines of SpellEffectEvent:
http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
You could make a SpellEffectEvent clone for unit events and item events that respond to particular unit/item types.
The parameters do not really make it complex.
It is just that I have to organize my hashtable a bit more and need good logic for how to save those.
Their integration is pretty simple: you have a trigger registered on event ("Spell_Effect", 'AHbz') then that is a new event.
Every other time that you do anything with the same combination uses that same event again.
The hard thing about parameters is how to use those.
This is as far as I know the best way to do it so...

About the priorities...
They do not really make it difficult either.
They just add the trigger to a place in the middle of the list of triggers.
No big difference at all.

The big problem is that I want multiple triggers on one event.
That is why I have to index everything and simulate arrays inside the hashtable.
That is what makes the code complex.
SpellEffectEvent does not support multiple triggers on one combination.

Ofcourse bribe couldve have made global variables like I have and he can register tiggers on every single case.
His static index 0 which would then be replaced by the integer of the global variable.

If you just focus on priorities, you can end up with a coherent system with a clear purpose. And it also means there are fewer things to consider--you don't even need to consider events. You can either choose to support individual triggers or individual trigger actions/conditions, or both. But my point is that you gain a lot of freedom and can simplify like crazy by making your system focused on one particular goal.

I have split it up in two.
As you can see, when I call the events, I look at the parameters.
When CallEvent() (without parameters) is called, noone cares about those parameters any more.

Inside the CallEvent2() and CallEvent3(), all I do is redirect to a lower level until the number 1 is reached.
When you remove those nr2 and nr3 functions, this system has no parameters any more.

All that the system changes while using those parameters is that I have to put a limit on 8191 events.
Can you imagine how much systems you need to create 8 thousand events?


I am trying to store and remove the triggers and their data directly from the arrays but that is quite hard and when removing or better yet adding, I have to loop through the whole array to place every single one of the others one place further.
 
The big problem is that I want multiple triggers on one event.
That is why I have to index everything and simulate arrays inside the hashtable.
That is what makes the code complex.
SpellEffectEvent does not support multiple triggers on one combination.

But I'm not sure where you would need multiple triggers on one combination. If you have a trigger dedicated to one spell combo, then there isn't any good reason to register it again and put it in a different location. That is just needlessly separating it, so there is no reason to support it. :)

For non-spell events, I can understand your view. But for non-spell events, I never felt the need to have the same "parameterization". But I have different demands than other people--my views on the potential use is nothing more than an opinion. So if you see purpose in your system, then you should feel free to press on.

As for the actual system, I'm curious why you use AEM_ as a prefix, and use udg_ as a prefix for globals. Prefixes are redundant--the only reason we use them is when we have to (e.g. GUI). But with encapsulation, you really don't need prefixes except when referring to a public variable.

And rather than explaining things in depth in the first post, it is really helpful to document the script (e.g. comments before the script, see Track for an example). When the function names and/or system purpose isn't self-explanatory, it is useful to include clear documentation on (1) the purpose (2) what particular functions do. And it serves as a good reference for others in case they want to see which functions they have available to them.
 
Status
Not open for further replies.
Top