Moderator
M
Moderator
Maker, Unit Event v2.0.0.0, 17:48, 24th Nov 2011
Approved.
Makes easy to detect many events that do not exist in the editor. Very useful system.
Function | Lua | vJass |
Subject unit of the event | UnitEvent_unit | UDexUnits[UDex] |
Event index (for use within an array). | UnitEvent_index | UDex |
Data attachment to unit | Set UnitEvent_setKey = unit Set MyArray[UnitEvent_getKey] = data Set MyArray2[UnitEvent_getKey] = data2 | Set TempInt = (Custom value of unit) Set MyArray[TempInt] = data Set MyArray2[TempInt] = data2 |
Data retrieval from unit | Set UnitEvent_setKey = unit Set data = MyArray[UnitEvent_getKey] Set data2 = MyArray2[UnitEvent_getKey] | Set TempInt = (Custom value of unit) Set data = MyArray[TempInt] Set data2 = MyArray2[TempInt] |
Manually created in World Editor Unit who summoned the subject unit Know the previous type in a transform event Is the unit reincarnating Unit who is carrying the subject unit All units carried by the unit Detect the unit's killer in a death event | UnitEvent_preplaced UnitEvent_summoner UnitEvent_unitType UnitEvent_reincarnating UnitEvent_transporter UnitEvent_cargo (Killing unit) | IsUnitPreplaced SummonerOfUnit UnitTypeOf IsUnitReincarnating CargoTransportUnit CargoTransportGroup KillerOfUnit |
Starts existing Fully created Stops existing Starts reincarnating Resurrects, reanimated or finishes reincarnating Dies Loaded into a transport Unloaded from a transport Transforms into a new unit Is playable (alive/not loaded) Is unplayable (dead/loaded) | OnUnitIndexed OnUnitCreation OnUnitRemoval OnUnitReincarnating OnUnitRevival OnUnitDeath OnUnitLoaded OnUnitUnloaded OnUnitTransform OnUnitActive OnUnitPassive | UnitIndexEvent == 1.00 UnitIndexEvent == 1.50 UnitIndexEvent == 2.00 DeathEvent == 0.50 DeathEvent == 2.00 DeathEvent == 1.00 CargoEvent == 1.00 CargoEvent == 2.00 UnitTypeEvent == 1.00 UnitInActionEvent == 1.00 UnitInActionEvent == 2.00 |
OnGlobalInit(function()
Require "Timed" --https://www.hiveworkshop.com/threads/timed-call-and-echo.339222/
Require "AddHook" --https://www.hiveworkshop.com/threads/hook.339153
Require "GlobalRemap" --https://www.hiveworkshop.com/threads/global-variable-remapper.339308
Require "RegisterAnyPlayerUnitEvent" --https://www.hiveworkshop.com/threads/collection-gui-repair-kit.317084/
Require "CreateEvent" --https://www.hiveworkshop.com/threads/event-gui-friendly.339451/
Require "PreciseWait" --https://www.hiveworkshop.com/threads/precise-wait-gui-friendly.316960/
--[[
Lua Unit Event 1.2.0.2
In addition to the existing benefits enjoyed over the past years, this Lua version supports linked events that allow
your trigger to Wait until another event runs (meaning you can do attachment and cleanup from one trigger).
Variable names have been completely changed from all prior Unit Event incarnations.
> All real variable event names are now prefixed with OnUnit...
> All array references (unit properties) are now prefixed with UnitEvent_
> Lua users can access a unit's properties via UnitEvent[unit].property (e.g. reincarnating/cargo)
> Lua users can easily add a readonly GUI property to a unit via UnitEvent.addProperty("propertyName")
>>> GUI accesses it via UnitEvent_propertyName, the second is readable and writable within Lua via UnitEvent[unit].propertyName
> UnitUserData (custom value of unit) has been completely removed. This is the first unit event/indexer to not use UnitUserData nor hashtables.
>>> UnitEvent_unit is the subject unit of the event.
>>> UnitEvent_index is an integer in GUI, but points to a the unit.
>>> UnitEvent_setKey lets you assign a unit to the key.
>>> UnitEvent_getKey is an integer in GUI, but points to the unit you assigned as the key.
>>>>> Lua doesn't care about array max sizes, nor the type of information used as an index in that array (because it uses tables and not arrays).
>>>>> GUI is over 20 years old and can easily be fooled. As long as the variable is defined with the correct type, it doesn't care what happens to that variable behind the scenes.
--]]
UnitEvent={}
local _REMOVE_ABIL = FourCC('A001')
local _TRANSFORM_ABIL = FourCC('A002') --be sure to assign these to their respective abilities if you prefer not to initialize via GUI
--Un-comment this next line if you want to use the custom value of units functionality (for backwards compatibility):
--GetUnitUserData = function(unit) return unit end
--[[
Full list of GUI variables:
real udg_OnUnitIndexed
real udg_OnUnitCreation
real udg_OnUnitRemoval
real udg_OnUnitReincarnating
real udg_OnUnitRevival
real udg_OnUnitLoaded
real udg_OnUnitUnloaded
real udg_OnUnitTransform
real udg_OnUnitDeath
real udg_OnUnitActive
real udg_OnUnitPassive
ability udg_DetectRemoveAbility
ability udg_DetectTransformAbility
unit udg_UnitEvent_unit
integer udg_UnitEvent_index
unit udg_UnitEvent_setKey
integer udg_UnitEvent_getKey
boolean array udg_UnitEvent_preplaced
unit array udg_UnitEvent_summoner
unittype array udg_UnitEvent_unitType
boolean array udg_UnitEvent_reincarnating
unit array udg_UnitEvent_transporter
unitgroup array udg_UnitEvent_cargo
--]]
local eventList={}
local udg_prefix="udg_OnUnit"
local makeAPI = function(name)
UnitEvent["on"..name],
eventList["on"..name] = CreateEvent(udg_prefix..name, true)
end
local unitIndices={} ---@type UnitEventTable[]
--onIndexed and onCreation occur at roughly the same time, but the unit's creation should be used instead as it will have more data.
--Combined, they are the counterparts to onRemoval.
makeAPI("Indexed")
makeAPI("Creation")
makeAPI("Removal")
--counterparts (though revival doesn't only come from reincarnation):
makeAPI("Reincarnating")
makeAPI("Revival")
--perfect counterparts:
makeAPI("Loaded")
makeAPI("Unloaded")
--stand-alone events:
makeAPI("Transform")
makeAPI("Death")
--perfect counterparts that generalize all but the "transform" event:
makeAPI("Active")
makeAPI("Passive")
--Used to get the UnitEvent table from the unit to detect UnitEvent-specific properties.
UnitEvent.__index = function(_, unit) return unitIndices[unit] end
---@param name string
UnitEvent.addProperty = function(name)
GlobalRemapArray("udg_UnitEvent_"..name, function(unit) return unitIndices[unit][name] end)
end
---@class UnitEventTable : table
---@field unit unit
---@field preplaced boolean
---@field summoner unit
---@field transporter unit
---@field cargo group
---@field reincarnating boolean
---@field private new boolean
---@field private alive boolean
---@field private unloading boolean
--The below two variables are intended for GUI typecasting, because you can't use a unit as an array index.
--What it does is bend the rules of GUI (which is still bound by strict JASS types) by transforming those
--variables with Global Variable Remapper (which isn't restricted by any types).
--"setKey" is write-only (assigns the key to a unit)
--"getKey" is read-only (retrieves the key and tells GUI that it's an integer, allowing it to be used as an array index)
local lastUnit
GlobalRemap("udg_UnitEvent_setKey", nil, function(unit)lastUnit=unit end) --assign to a unit to unlock the getKey variable.
GlobalRemap("udg_UnitEvent_getKey", function() return lastUnit end) --type is "integer" in GUI but remains a unit in Lua.
local runEvent
do
local eventUnit
local getEventUnit = function() return eventUnit end
runEvent = function(event, unitTable)
local cached = eventUnit
eventUnit = unitTable.unit
eventList[event](unitTable)
eventUnit = cached
end
GlobalRemap("udg_UnitEvent_unit", getEventUnit) --the subject unit for the event.
GlobalRemap("udg_UnitEvent_index", getEventUnit) --fools GUI into thinking unit is an integer
end
--add a bunch of read-only arrays to access GUI data. I've removed the "IsUnitAlive" array as the GUI living checks are fixed with the GUI Enhancer Colleciton.
UnitEvent.addProperty("preplaced")
UnitEvent.addProperty("unitType")
UnitEvent.addProperty("reincarnating")
UnitEvent.addProperty("transporter")
UnitEvent.addProperty("summoner")
if rawget(_G, "udg_UnitEvent_cargo") then
UnitEvent.addProperty("cargo")
end
--Flag a unit as being able to move or attack on its own:
local function setActive(unitTable)
if unitTable and not unitTable.active and UnitAlive(unitTable.unit) then --be sure not to run the event when corpses are created/unloaded.
unitTable.active = true
runEvent("onActive", unitTable)
end
end
---Flag a unit as NOT being able to move or attack on its own:
local function setPassive(unitTable)
if unitTable and unitTable.active then
unitTable.active = nil
runEvent("onPassive", unitTable)
end
end
local function getFunc(active)
return function(unitTable) (active and setActive or setPassive)(unitTable) end
end
UnitEvent.onCreation(getFunc(true), 2, true)
UnitEvent.onUnloaded(getFunc(true), 2, true)
UnitEvent.onRevival(getFunc(true), 2, true)
UnitEvent.onLoaded(getFunc(), 2, true)
UnitEvent.onReincarnating(getFunc(), 2, true)
UnitEvent.onDeath(getFunc(), 2, true)
UnitEvent.onRemoval(getFunc(), 2, true)
--UnitEvent.onIndex(function(unitTable) print(tostring(unitTable.unit).."/"..GetUnitName(unitTable.unit).." has been indexed.") end)
setmetatable(UnitEvent, UnitEvent)
--Wait until GUI triggers and events have been initialized.
OnTrigInit(function()
if rawget(_G, "Trig_Unit_Event_Config_Actions") then
Trig_Unit_Event_Config_Actions()
_REMOVE_ABIL = udg_DetectRemoveAbility or _REMOVE_ABIL
_TRANSFORM_ABIL = udg_DetectTransformAbility or _TRANSFORM_ABIL
end
local function checkAfter(unitTable)
if not unitTable.checking then
unitTable.checking = true
Timed.call(0, function()
unitTable.checking = nil
if unitTable.new then
unitTable.new = nil
runEvent("onCreation", unitTable) --thanks to Spellbound for the idea
elseif unitTable.transforming then
local unit = unitTable.unit
runEvent("onTransform", unitTable)
unitTable.unitType = GetUnitTypeId(unit) --Set this afterward to give the user extra reference
--Reset the transforming flags so that subsequent transformations can be detected.
unitTable.transforming = nil
UnitAddAbility(unit, _TRANSFORM_ABIL)
elseif unitTable.alive then
unitTable.reincarnating = true
unitTable.alive = false
runEvent("onReincarnating", unitTable)
elseif UnitAlive(unitTable.unit) then
unitTable.alive = true
runEvent("onRevival", unitTable)
unitTable.reincarnating = false
end
end)
end
end
local re = CreateRegion()
local r = GetWorldBounds()
local maxX, maxY = GetRectMaxX(r), GetRectMaxY(r)
RegionAddRect(re, r); RemoveRect(r)
local function unloadUnit(unitTable)
local unit, transport = unitTable.unit, unitTable.transporter
GroupRemoveUnit(unitIndices[transport].cargo, unit)
unitTable.unloading = true
runEvent("onUnloaded", unitTable)
unitTable.unloading = nil
if not IsUnitLoaded(unit) or not UnitAlive(transport) or GetUnitTypeId(transport) == 0 then
unitTable.transporter = nil
end
end
local preplaced = true
local onEnter = Filter(
function()
local unit = GetFilterUnit()
local unitTable = unitIndices[unit]
if not unitTable then
unitTable = {
unit = unit,
new = true,
alive = true,
unitType= GetUnitTypeId(unit)
}
UnitAddAbility(unit, _REMOVE_ABIL)
UnitMakeAbilityPermanent(unit, true, _REMOVE_ABIL)
UnitAddAbility(unit, _TRANSFORM_ABIL)
unitIndices[unit] = unitTable
unitTable.preplaced = preplaced
runEvent("onIndexed", unitTable)
checkAfter(unitTable)
elseif unitTable.transporter and not IsUnitLoaded(unit) then
--the unit was dead, but has re-entered the map (e.g. unloaded from meat wagon)
unloadUnit(unitTable)
end
end)
TriggerRegisterEnterRegion(CreateTrigger(), re, onEnter)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_LOADED,
function()
local unit = GetTriggerUnit()
local unitTable = unitIndices[unit]
if unitTable then
if unitTable.transporter then
unloadUnit(unitTable)
end
--Loaded corpses do not issue an order when unloaded, therefore must
--use the enter-region event method taken from Jesus4Lyf's Transport: https://www.thehelper.net/threads/transport-enter-leave-detection.126051/
if not unitTable.alive then
SetUnitX(unit, maxX)
SetUnitY(unit, maxY)
end
local transporter = GetTransportUnit()
unitTable.transporter = transporter
local g = unitIndices[transporter].cargo
if not g then
g=CreateGroup()
unitIndices[transporter].cargo = g
end
GroupAddUnit(g, unit)
runEvent("onLoaded", unitTable)
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH,
function()
local unitTable = unitIndices[GetTriggerUnit()]
if unitTable then
unitTable.alive = false
runEvent("onDeath", unitTable)
if unitTable.transporter then
unloadUnit(unitTable)
end
end
end)
RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SUMMON,
function()
local unitTable = unitIndices[GetTriggerUnit()]
if unitTable.new then
unitTable.summoner = GetSummoningUnit()
end
end)
local orderB = Filter(
function()
local unit = GetFilterUnit()
local unitTable = unitIndices[unit]
if unitTable then
if GetUnitAbilityLevel(unit, _REMOVE_ABIL) == 0 then
runEvent("onRemoval", unitTable)
unitIndices[unit] = nil
unitTable.cargo = nil
elseif not unitTable.alive then
if UnitAlive(unit) then
checkAfter(unitTable)
end
elseif not UnitAlive(unit) then
if unitTable.new then
--This unit was created as a corpse.
unitTable.alive = false
runEvent("onDeath", unitTable)
elseif not unitTable.transporter or not IsUnitType(unit, UNIT_TYPE_HERO) then
--The unit may have just started reincarnating.
checkAfter(unitTable)
end
elseif GetUnitAbilityLevel(unit, _TRANSFORM_ABIL) == 0 and not unitTable.transforming then
unitTable.transforming = true
checkAfter(unitTable)
end
if unitTable.transporter and not unitTable.unloading and not (IsUnitLoaded(unit) and UnitAlive(unit)) then
unloadUnit(unitTable)
end
end
end)
local p
local order = CreateTrigger()
for i = 0, bj_MAX_PLAYER_SLOTS - 1 do
p = Player(i)
GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, p, onEnter)
SetPlayerAbilityAvailable(p, _REMOVE_ABIL, false)
SetPlayerAbilityAvailable(p, _TRANSFORM_ABIL, false)
TriggerRegisterPlayerUnitEvent(order, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, orderB)
end
preplaced = false
end)
end)