AutoEvents

Scripts

14 years
edited 8 years
AutoEvents is an add-on library for AutoIndex. It gives you events that detect the following things: when units resurrect, when units are raised with Animate Dead, when units begin reincarnating, when units finish reincarnatinging, and when transports load and unload units. It also provides other useful functions. You can check if a unit is currently raised with Animate Dead, get the transport carrying a unit, get the number of a units in a transport, get the passenger in a specific slot of a transport, and enumerate through all of the units in a transport.

Code (jass) Select

library AutoEvents requires AutoIndex
//===========================================================================
// Information:
//==============
//
//     AutoEvents is an add-on library for AutoIndex. It gives you events that
// detect the following things: when units resurrect, when units are raised with
// Animate Dead, when units begin reincarnating, when units finish reincarnating,
// when transports load units, and when transports unload units. It also provides
// other useful functions: you can check if a unit is currently raised with Ani-
// mate Dead, get the transport carrying a unit, get the number of a units in a
// transport, get the passenger in a specific slot of a transport, and enumerate
// through all of the units in a transport.
//
//===========================================================================
// How to use AutoEvents:
//========================
//
//     You can use the events in this library to run specific functions when
// an event occurs. Unit-related events require a function matching the Stat-
// usEvent function interface, and transport-related events require a function
// matching the TransportEvent function interface:
//
// function interface AutoEvent takes unit u returns nothing
// function interface TransportEvent takes unit transport, unit passenger returns nothing
//
//     The following examples use the AutoEvent function interface:
/*
    function UnitDies takes unit u returns nothing
        call BJDebugMsg(GetUnitName(u)+" has died.")
    endfunction

    function UnitResurrects takes unit u returns nothing
        call BJDebugMsg(GetUnitName(u)+" has been resurrected.")
    endfunction

    function Init takes nothing returns nothing
        call OnUnitDeath(UnitDies)
        call OnUnitResurrect(UnitResurrects)
    endfunction
*/
//     And the following examples use the TransportEvents function interface:
/*
    function UnitLoads takes unit transport, unit passenger returns nothing
        call BJDebugMsg(GetUnitName(transport)+" loaded "+GetUnitName(passenger))
    endfunction

    function UnitUnloads takes unit transport, unit passenger returns nothing
        call BJDebugMsg(GetUnitName(transport)+" unloaded "+GetUnitName(passenger))
    endfunction

    function Init takes nothing returns nothing
        call OnUnitLoad(UnitLoads)
        call OnUnitUnload(UnitUnloads)
    endfunction
*/
//     Here is an example of using ForPassengers to enumerate each unit in
// a transport and heal them for 100 life:
/*
    function HealPassenger takes unit transport, unit passenger returns nothing
        call SetWidgetLife(passenger, GetWidgetLife(passenger) + 100.)
    endfunction
    function HealAllPassengers takes unit transport returns nothing
        call ForPassengers(transport, HealPassenger)
    endfunction
*/
//     GetPassengerBySlot provides an alternative way to enumerate the
// units within a transport. (The following example would heal a unit
// that occupies multiple slots in the transport only one time, since
// GetPassengerBySlot assumes that each unit occupies only one slot.)
/*
    function HealAllPassengers takes unit transport returns nothing
        local integer slot = 1 //Start at slot 1.
        local unit passenger
            loop
                set passenger = GetPassengerBySlot(transport, slot)
                exitwhen passenger == null
                call SetWidgetLife(passenger, GetWidgetLife(passenger) + 100.)
                set slot = slot + 1
            endloop
    endfunction
*/
//===========================================================================
// AutoEvents API:
//=================
//
// OnUnitDeath(AutoEvent)
//   This event runs when any unit dies. It fires after the unit is dead, but
//   before any death triggers fire.
//
// OnUnitResurrect(AutoEvent)
//   This event runs when any unit is resurrected. It also fires when units
//   are raised with Animate Dead or Reincarnation, as those are forms of
//   resurrection as well.
//
// OnUnitAnimateDead(AutoEvent)
//   This event runs when any unit is raised with Animate Dead. It fires after
//   the resurrection event.
//
// IsUnitAnimateDead(unit) -> boolean
//   This function returns a boolean that indicates if the specified unit
//   has been raised with Animate Dead.
//
// OnUnitReincarnateStart(AutoEvent)
//   This event runs when any unit begins reincarnating. The OnUnitDeath event
//   will run first.
//
// OnUnitReincarnateEnd(AutoEvent)
//   This event runs when any unit finishes reincarnating. The OnUnitResurrect
//   event will occur immediately after.
//
// OnUnitLoad(TransportEvent)
//   This event runs when any transport loads a passenger.       
//
// OnUnitUnload(TransportEvent)
//   This event runs when any transport unloads a passenger.
//
// GetUnitTransport(unit)
//   Returns the transport that a unit is loaded in. Returns null if the
//   unit is not riding in any transport.
//
// CountPassengers(transport) -> integer
//   Returns the number of passengers in the specified transport.
//
// GetPassengerBySlot(transport, slot) -> unit
//   Returns the passenger in the given slot of the specified transport.
//   However, if a unit takes  more than one transport slot, it will only be
//   treated as occupying one transport slot.
//
// ForPassengers(transport, TransportEvent)
//   This function runs a TransportEvent immediately for each passenger in
//   the specified transport.
//
//===========================================================================

function interface AutoEvent takes unit u returns nothing

//! textmacro RunAutoEvent takes EVENT
    set n = 0
    loop
        exitwhen n > $EVENT$funcs_n
        call $EVENT$funcs[n].evaluate(u)
        set n = n + 1
    endloop
//! endtextmacro

//Injecting this textmacro into AutoIndex will cause the events to actually run.

//! textmacro AutoEvent takes EVENT, EVENTTYPE
    globals
        $EVENTTYPE$ array $EVENT$funcs
        integer $EVENT$funcs_n = -1
    endglobals
    function OnUnit$EVENT$ takes $EVENTTYPE$ func returns nothing
        set $EVENT$funcs_n = $EVENT$funcs_n + 1
        set $EVENT$funcs[$EVENT$funcs_n] = func
    endfunction
//! endtextmacro

//Instantiate the function to register events of each type.
//! runtextmacro AutoEvent("Death", "AutoEvent")
//! runtextmacro AutoEvent("Resurrect", "AutoEvent")
//! runtextmacro AutoEvent("AnimateDead", "AutoEvent")

//===========================================================================
//The code below this point adds Reincarnation support to AutoEvents.
//Credit to ToukoAozaki for the idea behind this detection method.

//! runtextmacro AutoEvent("ReincarnationStart", "AutoEvent")
//! runtextmacro AutoEvent("ReincarnationFinish", "AutoEvent")
//Create registration functions for reincarnation start and stop events.

globals
    private timer ReincarnateTimer = CreateTimer()
    private boolean array Reincarnated
    private unit array Reincarnating
    private integer Reincarnating_N = -1
endglobals

private function OnResurrect takes unit u returns nothing
    local integer index = GetUnitId(u)
    local integer n
        if Reincarnated[index] then
            set Reincarnated[index] = false
            //If a resurrecting unit is flagged as reincarnating,
            //it's time to run the ReincarnationFinish event.
            //! runtextmacro RunAutoEvent("ReincarnationFinish")
        endif
endfunction

private function ReincarnateCheck takes nothing returns nothing
    local integer n = Reincarnating_N
    local unit u
        loop
            exitwhen n < 0
            set u = Reincarnating[n]
            if GetUnitTypeId(u) != 0 and Reincarnated[GetUnitId(u)] then
                //If the unit is still flagged as reincarnating, it means DeathDetect didn't run.
                //The unit is actually reincarnating, so run the ReincarnationStart event.
                //! runtextmacro RunAutoEvent("ReincarnationStart")
            endif
            set Reincarnating[n] = null
            set n = n - 1
        endloop
        set Reincarnating_N = -1
    set u = null
endfunction

private function OnDeath takes unit u returns nothing
    set Reincarnated[GetUnitId(u)] = true
    //Assume any unit that dies is going to reincarnate, unless this
    //flag is set to false later by the DeathDetect function.
    set Reincarnating_N = Reincarnating_N + 1 //Add the dying unit to a stack and
    set Reincarnating[Reincarnating_N] = u    //check the flag 0. seconds later.
    call TimerStart(ReincarnateTimer, 0., false, function ReincarnateCheck)
endfunction
   
private function DeathDetect takes nothing returns boolean
        set Reincarnated[GetUnitId(GetTriggerUnit())] = false
    return false //Set the Reincarnated flag to false if the unit will not reincarnate.
endfunction

private function OnEnter takes unit u returns nothing
    set Reincarnated[GetUnitId(u)] = false
    //When a unit enters the map, initialize its Reincarnated flag to false.
endfunction
   
private struct ReincarnationInit extends array
    private static method onInit takes nothing returns nothing
        local trigger deathdetect = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(deathdetect, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(deathdetect, function DeathDetect)
            //This trigger runs 0. seconds after OnUnitDeath events,
            //but does not fire if the unit is going to Reincarnate.
            call OnUnitIndexed(OnEnter)
            call OnUnitDeath(OnDeath)
            call OnUnitResurrect(OnResurrect)
    endmethod
endstruct

//===========================================================================
// All of the remaining code deals with transports.

function interface TransportEvent takes unit transport, unit passenger returns nothing

//! runtextmacro AutoEvent("Load", "TransportEvent")
//! runtextmacro AutoEvent("Unload", "TransportEvent")
//Create registration functions for load and unload events.

//! textmacro RunTransportEvent takes EVENT
    set n = 0
    loop
        exitwhen n > $EVENT$funcs_n
        call $EVENT$funcs[n].evaluate(transport, passenger)
        set n = n + 1
    endloop
//! endtextmacro
//The above textmacro is used to run the Load/Unload events in the Transport struct below.

//===========================================================================
//A transport struct is created and attached to any unit detected loading another unit.
//It keeps track of the units within a transport and updates when they load or unload.

private keyword getUnitTransport
private keyword countPassengers
private keyword getPassengerBySlot
private keyword forPassengers

struct Transport
    private static unit array loadedin
    private static Transport array transports
    private static integer array loadedindex
    private static group array groups
    private static integer groups_n = -1
    private static real MaxX
    private static real MaxY
   
    private unit array loaded[10] //Transports can only carry 10 units.
    private integer loaded_n = -1
   
    //===========================================================================
   
    static method getUnitTransport takes unit u returns unit
        return loadedin[GetUnitId(u)]
    endmethod
   
    static method countPassengers takes unit transport returns integer
        return transports[GetUnitId(transport)].loaded_n + 1
    endmethod
   
    static method getPassengerBySlot takes unit transport, integer slot returns unit
            if slot < 1 or slot > 10 then
                return null
            endif
        return transports[GetUnitId(transport)].loaded[slot - 1]
    endmethod
   
    static method forPassengers takes unit transport, TransportEvent func returns nothing
        local Transport this = transports[GetUnitId(transport)]
        local integer n = 0
            if loaded_n == -1 then
                return //Return if transport has no units loaded inside.
            endif
            loop
                exitwhen n > loaded_n
                call func.evaluate(transport, loaded[n])
                //Loop through each passenger and call the TransportEvent func on it.
                set n = n + 1
            endloop
    endmethod
   
    //===========================================================================

    static method loadUnit takes nothing returns boolean
        local unit transport = GetTransportUnit()
        local unit passenger = GetTriggerUnit()
        local Transport this = transports[GetUnitId(transport)]
        local integer n
            if this == 0 then        //If this is the first unit loaded by this transport...
                set this = allocate()                       //allocate a Transport struct,
                set transports[GetUnitId(transport)] = this //and attach it to the transport.
            endif
            set loaded_n = loaded_n + 1      //Increment the passenger counter.
            set loaded[loaded_n] = passenger //Put the passenger in the unit array.
            set loadedindex[GetUnitId(passenger)] = loaded_n //Attach the index to the passenger.
            set loadedin[GetUnitId(passenger)] = transport   //Attach the transport struct to the transport.
            //! runtextmacro RunTransportEvent("Load") //Run the OnUnitLoad events.
            call SetUnitX(passenger, MaxX) //Move the passenger to the edge of the map so that
            call SetUnitY(passenger, MaxY) //unloading will trigger a "unit enters region" event.
        set transport = null
        set passenger = null
        return false
    endmethod
   
    static method unloadUnit takes unit passenger returns nothing
        local unit transport = getUnitTransport(passenger)      //Get the transport unit.
        local Transport this = transports[GetUnitId(transport)] //Get the transport struct.
        local integer n = loadedindex[GetUnitId(passenger)]     //Get the passenger's index.
            loop
                exitwhen n == loaded_n
                set loaded[n] = loaded[n + 1]
                set loadedindex[GetUnitId(loaded[n])] = n
                set n = n + 1 //Starting from the position of the removed unit,
            endloop           //shift everything down by one and update the index.
            set loaded[n] = null
            set loaded_n = loaded_n - 1                  //Decrement the passenger counter.
            set loadedin[GetUnitId(passenger)] = null    //Null the unloaded unit's transport.
            //! runtextmacro RunTransportEvent("Unload") //Run the OnUnitUnload events.
            if loaded_n == -1 then                       //If the transport is now empty...
                call destroy()                           //Destroy the transport struct.
                set transports[GetUnitId(transport)] = 0 //Disassociate it from the unit.
            endif
        set transport = null
    endmethod
   
    //===========================================================================
   
    private static method unitEntersMap takes nothing returns boolean
            if getUnitTransport(GetFilterUnit()) != null then //If the entering unit is in a transport...
                call unloadUnit(GetFilterUnit())              //The unit was unloaded.
            endif
        return false
    endmethod
   
    private static method unitDies takes nothing returns boolean
            if getUnitTransport(GetTriggerUnit()) != null then //If the dying unit is in a transport...
                call unloadUnit(GetTriggerUnit())              //Unload the unit from its transport.
            endif
        return false
    endmethod
   
    private static method onInit takes nothing returns nothing
        local region maparea = CreateRegion()
        local rect bounds = GetWorldBounds()
        local trigger unload = CreateTrigger()
        local trigger load = CreateTrigger()
        local trigger death = CreateTrigger()
            call RegionAddRect(maparea, bounds)
            call TriggerRegisterEnterRegion(unload, maparea, function Transport.unitEntersMap) //When a unit enters the map area,
            call TriggerRegisterAnyUnitEventBJ(load, EVENT_PLAYER_UNIT_LOADED)                 //it may have been unloaded.
            call TriggerAddCondition(load, function Transport.loadUnit) //When a unit loads a unit, run the loadUnit method.
            call TriggerRegisterAnyUnitEventBJ(death, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(death, function Transport.unitDies) //Detect when a unit dies in order to unload it.
            call OnUnitDeindexed(Transport.unloadUnit) //When a unit leaves the game, unload it from its transport.
            set Transport(0).loaded_n = -1 //Initialize this to -1 to make CountUnitsInTransport work properly.
            set MaxX = GetRectMaxX(bounds) //Record the coordinates of a corner of the map so
            set MaxY = GetRectMaxY(bounds) //that loaded units can be moved to that location.
        call RemoveRect(bounds)
        set bounds = null
    endmethod
   
endstruct

//===========================================================================
// User functions:
//=================

function IsUnitAnimateDead takes unit u returns boolean
    return AutoIndex.isUnitAnimateDead(u)
endfunction

function GetUnitTransport takes unit u returns unit
    return Transport.getUnitTransport(u)
endfunction

function CountPassengers takes unit transport returns integer
    return Transport.countPassengers(transport)
endfunction

function GetPassengerBySlot takes unit transport, integer slot returns unit
    return Transport.getPassengerBySlot(transport, slot)
endfunction

function ForPassengers takes unit transport, TransportEvent func returns nothing
    call Transport.forPassengers(transport, func)
endfunction

endlibrary