| 06-06-2009, 05:33 AM | #1 |
Requires AutoIndex 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. JASS: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 |
| 06-21-2009, 11:39 AM | #2 |
This has waited too long, approved. |
| 06-22-2009, 02:08 PM | #3 |
Updated the thread to include both StatusEvents and TransportEvents, you can kill the other thread. Also updated TransportEvents to include the function GetPassengerBySlot(transport, slot). Aside from the obvious point of getting the passenger in a specific slot of a transport, it enables a way to manually iterate through the units in a transport, as shown in a new example. |
| 07-22-2009, 04:03 PM | #4 |
It would be great if this supports detecting the start/end of reincarnation... I really need that feature. I've made something like below for workaround. JASS:library ReincarnationEvents requires TimerUtils, StatusEvents globals private boolean array reincarnated endglobals //! runtextmacro StatusEvent("ReincarnationStart") //! runtextmacro StatusEvent("ReincarnationFinish") private struct data integer i unit u public static method create takes unit u returns data local data inst = data.allocate() set inst.i = GetUnitId(u) set inst.u = u return inst endmethod endstruct private function CheckStartEvent takes nothing returns nothing local data d = GetTimerData(GetExpiredTimer()) local unit u = d.u local integer n call ReleaseTimer(GetExpiredTimer()) if GetUnitTypeId(u) != 0 then if reincarnated[d.i] then // unit is being reincarnated //! runtextmacro RunStatusEvent("ReincarnationStart") endif else // unit is no longer valid; cleanup set reincarnated[d.i] = false endif set d.u = null call d.destroy() endfunction private function OnDeath takes unit u returns nothing local timer t = NewTimer() set reincarnated[GetUnitId(GetTriggerUnit())] = true call SetTimerData(t, data.create(GetTriggerUnit())) call TimerStart(t, 0.0, false, function CheckStartEvent) set t = null endfunction private function OnResurrect takes unit u returns nothing local integer index = GetUnitId(u) local integer n if reincarnated[index] then set reincarnated[index] = false // unit has been reincarnated //! runtextmacro RunStatusEvent("ReincarnationFinish") endif endfunction private function DeathEvent takes nothing returns boolean // death event fired; unit is not reincarnating. set reincarnated[GetUnitId(GetTriggerUnit())] = false return false endfunction // make StatusEvents responses higher priority for most cases private struct Initializer private static method onInit takes nothing returns nothing local trigger t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH) call TriggerAddCondition(t, Condition(function DeathEvent)) call OnUnitDeath(OnDeath, true) call OnUnitResurrect(OnResurrect, true) endmethod endstruct endlibrary |
| 07-23-2009, 07:06 AM | #5 |
I'll try to add support for that if you think it's useful. In a few days. |
| 07-30-2009, 10:42 PM | #6 |
I've encountered what may be a bug. A creature that doesn't leave a corpse dies. The OnDeath callback is called. Then it completes its decay. The OnLeave callback is called and pops an error message. AutoIndex error: Null unit passed to GetUnitId. Both the callbacks call a struct.get method (implementing AutoData) to retrieve that unit's struct, so I think a null unit is being passed to it, when it shouldn't. I've included a null-unit check in my script to compensate, but OnLeave should be called before the unit is actually removed, yes? |
| 07-31-2009, 05:09 AM | #7 |
I'll check that out sometime soon. |
| 07-31-2009, 05:08 PM | #8 |
I've encountered a similar issue with OnUnload event callbacks, around which I have not yet found a compensatory measure. EDIT: Regarding OnUnload event callbacks, I positioned a debug message call at the top of the callback, commented out the other content and tested. The error message occurs before my debug message, so this must also be a bug. Units being issued on-orders for toggleable state abilities (Immolation, Mana Shield) OnEnter also pop the "filtered unit" error message. I've removed such abilities from units' initially activated ability field to compensate. EDIT: It applies to any initially active ability, it seems; autocasts also popped the error. |
| 08-16-2009, 07:07 PM | #9 |
This doesnt compile. It says, Ive got two macros of the same thing. Both in your systems. |
| 08-16-2009, 07:13 PM | #10 | ||
Quote:
Quote:
|
| 09-15-2009, 06:15 PM | #11 | |
StatusEvents & TransportEvents both updated.
Quote:
|
| 10-23-2009, 02:37 PM | #12 |
Updated.
|
| 10-24-2009, 03:10 PM | #13 |
Updated.
|
| 11-03-2009, 06:21 AM | #14 |
There is one real flaw in using this method, that is native KillUnit takes unit whichUnit returns nothing on neutral aggressive player owned units. Neither defend nor magicdefend are triggered if you KillUnit or SetWidgetLife(0). This exception only applies to neutral aggressive (creeps!), and is particularly hard to work around without some sort of KillUnit hook. I choose to use a replacement KillUnit function, the following will suffice and does correctly trigger neutral aggressive removal detection: JASS:function KillUnitEx takes unit u returns nothing call UnitDamageTarget(u, u, 999999, false, false, null, DAMAGE_TYPE_UNIVERSAL, null) endfunction |
| 11-03-2009, 03:11 PM | #15 |
OK, I've tested what you said. The problem is actually more specific. Units that are currently asleep won't issue undefend orders if they die. Any sort of damage wakes the unit up, so only non-damaging methods of killing the unit fail. I've already updated AutoIndex (and by extension, this library) to fix the issue by detecting the death of Player(12) creeps and forcing the status update method to run. That way, no additional hook usage was required. |
