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

GUI Unit Event (now with a state-of-the-art Lua version)

Unit Event for GUI gives you access to all kinds of events which normal GUI events can't do:

FunctionLuavJass
Subject unit of the eventUnitEvent_unitUDexUnits[UDex]
Event index (for use within an array).UnitEvent_indexUDex
Data attachment to unitSet 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 unitSet 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

You can distinguish between types of unit revival with three conditions:
1) If it is summoned, that means it was reanimated (e.g. "Animate Dead (Death Knight Ultimate)").
2) If UnitEvent_reincarnating (Lua version) / IsUnitReincarnating (JASS version) is true, that means it finished reincarnating (e.g. "Reincarnation (Tauren Chieftan Ultimate)").
3) If the above are False, the unit was resurrected (e.g. "Ressurrection (Paladin Ultimate)").

Lua:
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)

How to install:

- Delete Unit Indexer from your map (if you already have it)
- Copy the "Unit Event Ability" ability from Object Editor
- Copy the Unit Event trigger category
- Paste the Unit Event Ability from object editor twice
- Set the DetectRemoveAbility variable in the Unit Event Config trigger to one of the abilities.
- Set the DetectTransformAbility variable in the same trigger to the other of the two.


Inspiration:

- AutoEvents by grim001 (was hosted on the now-defunct wc3c.net)
- UnitEvent by Nestharus: https://github.com/nestharus/JASS/blob/master/jass/Systems/Unit%20Event/script.j
- Transport by Jesus4Lyf: https://www.thehelper.net/threads/transport-enter-leave-detection.126051/

nestharus, grim001, unitevent, autoevents, unit indexer, reincarnate, reincarnation, reanimation, resurrect, resurrection, detect, death, removal, load, unload, transport, transform, unit type
Contents

Lua Unit Event Test Map (Map)

Unit Event 2.5.3.1 (Map)

Reviews
Approved. Makes easy to detect many events that do not exist in the editor. Very useful system.
Level 18
Joined
Mar 16, 2008
Messages
721
what would the event response for reincarnate be? Like 'Dying Unit'?

Also I already have an indexer from a creep respawn system and the units that will be using the reincarnate are not preset on the map, they will be trained later. Can I just delete the indexer and change it to check if it's in one of my arrays already? I just need reincarnate for 8 units that I already have set to variables. Is there a way I could trim this down for that without it being too much work?

Any feed back or tips would be appreciated.
 
Last edited:
what would the event response for reincarnate be? Like 'Dying Unit'?

Also I already have an indexer from a creep respawn system and the units that will be using the reincarnate are not preset on the map, they will be trained later. Can I just delete the indexer and change it to check if it's in one of my arrays already? I just need reincarnate for 8 units that I already have set to variables. Is there a way I could trim this down for that without it being too much work?

Any feed back or tips would be appreciated.
  • Detect the instant when a unit starts reincarnating
    Event: Game - DeathEvent Becomes Equal to 0.50

  • Detect when a unit is brought back to life via Reincarnation, Resurrect or Reanimate
    Event: Game - DeathEvent Becomes Equal to 2.00
 
Level 18
Joined
Mar 16, 2008
Messages
721
I understand the event would be "Event: Game - DeathEvent Becomes Equal to 0.50" i'm asking what the event response would be to refer to the reincarnating unit. For my map I need to manipulate some items in their inventory when they use reincarnate.
 
Bribe said:
[...] the way to get the unit with that custom value is via UDexUnits[(Custom value of Unit) --- In this case "UDex"].

(...)

Detect when a unit is brought back to life via Reincarnation, Resurrect or Reanimate
Event: Game - DeathEvent Becomes Equal to 2.00

You can differentiate between the three of these as well. If it is summoned, that means it was reanimated. If IsUnitReincarnating[UDex] is true, that means it finished reincarnating. Otherwise, it was simply resurrected.

If I'm not mistaken, you can use UDexUnits[UDex] to refer to the reincarnating unit.
 
Level 18
Joined
Mar 16, 2008
Messages
721
1) is there a way to distinguish between different types of reincarnations?
2) less importantly, can i delete the index and set it equal to one of my array variables? can i delete all the other triggers for that matter?
 
1) is there a way to distinguish between different types of reincarnations?
2) less importantly, can i delete the index and set it equal to one of my array variables? can i delete all the other triggers for that matter?
1. I believe by checking IsUnitReincarnating[UDex] you can determine if the revival of a unit is Reincarnation or Resurrection. Additionally, by checking if SummonerOfUnit[UDex] equal to/not equal to No unit, you can determine if the revival is Animate Dead.
2. I don't think you can do that, the whole function of the system is consolidated inside the "Unit Event" trigger. If either the "Unit Event" or "Unit Event Config" trigger is missing, nothing will work.
 
Level 11
Joined
Jul 4, 2016
Messages
626
I know that having another defend ability can interfere with this system, so how does this work with the default units like the footman that already has defend?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
it uses a custom defend ability so it shouldn't be a problem???
Correct. This system takes advantage of the "Undefend" issued order bug/feature in order to catch certain unit events. I have tested it thoroughly with units who have the normal Defend ability, and any bugs that I detected along the way were squashed.
 
Level 1
Joined
May 31, 2021
Messages
4
Is it possible to get the killer unit of a resurrecting unit?

JASS:
scope Revival initializer init
    private function beginReincarnation takes nothing returns boolean
        call BJDebugMsg(GetUnitName(udg_UDexUnits[udg_UDex]) + "has died.")
        call BJDebugMsg("KILLER: " + GetUnitName(udg_KillerOfUnit[udg_UDex]) )

        return false
    endfunction

    private function endReincarnation takes nothing returns boolean
        local player User = GetOwningPlayer(udg_UDexUnits[udg_UDex])
        local real SpawnX = GetRectCenterX(gg_rct_startingSpawnPoint)
        local real SpawnY = GetRectCenterY(gg_rct_startingSpawnPoint)

        call SetUnitX(udg_UDexUnits[udg_UDex], SpawnX)
        call SetUnitY(udg_UDexUnits[udg_UDex], SpawnY)
        call GameMessage(User, "has revived.")

        if GetLocalPlayer() == User then
            call ClearSelection()
            call SelectUnit(udg_UDexUnits[udg_UDex], true)
            call PanCameraToTimed(SpawnX, SpawnY, 0)
        endif

        set User = null
        return false
    endfunction

    private function init takes nothing returns nothing
        local trigger trgBegin = CreateTrigger()
        local trigger trgEnd = CreateTrigger()

        call TriggerRegisterVariableEvent(trgBegin, "udg_DeathEvent", EQUAL, .5)
        call TriggerAddCondition(trgBegin, Condition(function beginReincarnation))

        call TriggerRegisterVariableEvent(trgEnd, "udg_DeathEvent", EQUAL, 2)
        call TriggerAddCondition(trgEnd, Condition(function endReincarnation))

        set trgBegin = null
        set trgEnd = null
    endfunction
endscope
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Is it possible to get the killer unit of a resurrecting unit?

JASS:
scope Revival initializer init
    private function beginReincarnation takes nothing returns boolean
        call BJDebugMsg(GetUnitName(udg_UDexUnits[udg_UDex]) + "has died.")
        call BJDebugMsg("KILLER: " + GetUnitName(udg_KillerOfUnit[udg_UDex]) )

        return false
    endfunction

    private function endReincarnation takes nothing returns boolean
        local player User = GetOwningPlayer(udg_UDexUnits[udg_UDex])
        local real SpawnX = GetRectCenterX(gg_rct_startingSpawnPoint)
        local real SpawnY = GetRectCenterY(gg_rct_startingSpawnPoint)

        call SetUnitX(udg_UDexUnits[udg_UDex], SpawnX)
        call SetUnitY(udg_UDexUnits[udg_UDex], SpawnY)
        call GameMessage(User, "has revived.")

        if GetLocalPlayer() == User then
            call ClearSelection()
            call SelectUnit(udg_UDexUnits[udg_UDex], true)
            call PanCameraToTimed(SpawnX, SpawnY, 0)
        endif

        set User = null
        return false
    endfunction

    private function init takes nothing returns nothing
        local trigger trgBegin = CreateTrigger()
        local trigger trgEnd = CreateTrigger()

        call TriggerRegisterVariableEvent(trgBegin, "udg_DeathEvent", EQUAL, .5)
        call TriggerAddCondition(trgBegin, Condition(function beginReincarnation))

        call TriggerRegisterVariableEvent(trgEnd, "udg_DeathEvent", EQUAL, 2)
        call TriggerAddCondition(trgEnd, Condition(function endReincarnation))

        set trgBegin = null
        set trgEnd = null
    endfunction
endscope
No, you have to use a damage engine to determine if the damage will put the unit’s life below 0.41 (or, better, use LethalDamageEvent). If so, then do something like:

JASS:
//globals
    private unit array killedBy

//in the damage event:
set killedBy[GetUnitUserData(udg_DamageEventTarget)] = udg_DamageEventSource

//in the reincarnation event:
GetUnitName(killedBy[udg_UDex])
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I have updated this bundle with a feature requested by @Spellbound who needed something which could fire after a unit has fully entered the scope of the map.

There is now an event one can use:

UnitIndexEvent becomes Equal to 1.50

This event would ensure, for example, that a Ghoul created dead into a meat wagon would already be loaded and declared dead prior to the UnitIndexEvent 1.50 going off.

It is also beneficial for buildings where the building-construction start event hadn't already deployed.

It is possible, as well, that not all units' abilities would be pre-loaded onto them at the original UnitIndexEvent 1.00 point.

Therefore, it is recommended to most users to switch their events which have been using UnitIndex 1.00 to use 1.50 instead.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Interestingly, the "Unit in action" script in the Test Map still uses UnitIndexEvent = 1.00 event. Should maps that are using this script change to 1.50 also 🤔
I didn’t update the “Unit In Action” portion, but if I had thought of it in advance then I’d definitely have it using the new 1.50 event.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I've run out of time for tonight, was hoping to get a Lua map published with this code already, but I just want to leave this here:

Lua:
if  Timed                   --https://www.hiveworkshop.com/threads/timed-call-and-echo.339222/
and GlobalRemap             --https://www.hiveworkshop.com/threads/global-variable-remapper.339308
and AnyPlayerUnitEvent      --https://www.hiveworkshop.com/threads/collection-gui-repair-kit.317084/
and Event then              --https://www.hiveworkshop.com/threads/event.339451/

OnGlobalInit(1, function()
    local _USER_DATA    = true --Whether to use SetUnitUserData to map the unit's handle Id to its userdata (true) or just use GetHandleId(false)
    
    local _REMOVE_ABIL      --= FourCC('A001')
    local _TRANSFORM_ABIL   --= FourCC('A002') --un-comment these and assign them to the respective abilities if you prefer not to initialize via GUI
    
    local _USE_ACTIVITY = true --adds a small overhead if true and you don't use the "active/inactive" events
    
    UnitEvent = { --Lua Unit Event 0.1.0.0
        onIndex         = Event.create("udg_OnUnitIndex"),
        onCreate        = Event.create("udg_OnUnitCreate"),
        onRemoval       = Event.create("udg_OnUnitRemoval"),
        onDeath         = Event.create("udg_OnUnitDeath"),
        onRevival       = Event.create("udg_OnUnitRevival"),
        onLoaded        = Event.create("udg_OnUnitLoaded"),
        onUnloaded      = Event.create("udg_OnUnitUnloaded"),
        onTransform     = Event.create("udg_OnUnitTransform"),
        onReincarnate   = Event.create("udg_OnUnitReincarnate"),
        onActive        = Event.create("udg_UnitActiveEvent"),
        onInactive      = Event.create("udg_UnitInactiveEvent")
    }

    ---@class unitIndex
    ---@field cargo group
    ---@field transporter unit
    ---@field summoner unit
    ---@field private new boolean
    ---@field private alive boolean
    ---@field unit unit
    ---@field preplaced boolean
    ---@field reincarnating boolean
    ---@field private unloading boolean

    local lastUnit  = nil
    local lastId    = 0
    local unitIndices = {}    ---@type unitIndex[]

    --The below two functions are useful for GUI when _USER_DATA is set to "false". To use them:
    --Set UnitIndexUnit = (Triggering unit)
    -- OR 
    --Set UnitIndexId = (Index that belongs to said unit)
    --Once either of those were set, you can reference "UnitIndexUnit" and "UnitIndexId" as a unit and integer, respectively.
    --For GUI, it is obviously easier to just use (Custom value of Unit) so in such cases I just recommend setting _USER_DATA to "true"
    GlobalRemap("udg_UnitIndexUnit", function() return lastUnit end, function(whichUnit) lastUnit = whichUnit; lastId = GetHandleId(whichUnit) end)
    GlobalRemap("udg_UnitIndexId", function() return lastId end, function(whichId) lastUnit = unitIndices[whichId].unit; lastId = whichId end)

    GlobalRemap("udg_UDex", function() return Event.args[1].id end)
    
    local function map(arrStr, valStr)
        GlobalRemapArray(arrStr, function(id) return unitIndices[id][valStr] end)
    end
    
    map("udg_UDexUnits", "unit")
    map("udg_IsUnitPreplaced", "preplaced")
    map("udg_UnitTypeOf", "unitType")
    map("udg_IsUnitNew", "new")
    map("udg_IsUnitAlive", "alive")
    map("udg_IsUnitReincarnating", "reincarnating")
    map("udg_CargoTransportUnit", "transporter")
    map("udg_SummonerOfUnit", "summoner")
    
    do
        local cargo = udg_CargoTransportGroup
        if cargo then
            DestroyGroup(cargo[0])
            DestroyGroup(cargo[1])
            map("udg_CargoTransportGroup", "cargo")
        end
    end
    
    if _USE_ACTIVITY then
        local function setActive()
            local unitTable = Event.args[1]
            if unitTable and not unitTable.active and UnitAlive(unitTable.unit) then
                unitTable.active = true
                UnitEvent.onActive:run(unitTable)
            end
        end
        local function setInactive()
            local unitTable = Event.args[1]
            if unitTable and unitTable.active then
                unitTable.active = nil
                UnitEvent.onInactive:run(unitTable)
            end
        end
        
        UnitEvent.onCreate(setActive)
        UnitEvent.onRevival(setActive)
        UnitEvent.onUnloaded(setActive)
        
        UnitEvent.onDeath(setInactive)
        UnitEvent.onRemoval(setInactive)
        UnitEvent.onLoaded(setInactive)
        UnitEvent.onReincarnate(setInactive)
    end
    
    --UnitEvent.onIndex:register(function(id) print(id.id) end)
    
    OnTrigInit(function()
        local func = Trig_Unit_Event_Config_Actions
        if func then
            func()
            _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(function()
                    unitTable.checking = nil
                    if unitTable.new then
                        unitTable.new = nil
                        UnitEvent.onCreate:run(unitTable) --thanks to Spellbound for the idea
                    elseif unitTable.transforming then
                       UnitEvent.onTransform:run(unitTable)
                       unitTable.unitType = GetUnitTypeId(unitTable.unit) --Set this afterward to give the user extra reference
                       unitTable.transforming = nil
                       UnitAddAbility(unitTable.unit, _TRANSFORM_ABIL)
                    elseif unitTable.alive then
                        unitTable.reincarnating = true
                        unitTable.alive = false
                        UnitEvent.onReincarnate:run(unitTable)
                    end
                end)
            end
        end
    
        local re = CreateRegion()
        local r = GetWorldBounds()
        local maxX, maxY = GetRectMaxX(r), GetRectMaxY(r)
        RegionAddRect(re, r); RemoveRect(r)
        
        local function unload(unitTable)
            GroupRemoveUnit(unitIndices[GetHandleId(unitTable.transporter)].cargo, unitTable.unit)
            unitTable.unloading = true
            UnitEvent.onUnloaded:run(unitTable)
            unitTable.unloading = nil
            if not IsUnitLoaded(unitTable.unit) or not UnitAlive(unitTable.transporter) or GetUnitTypeId(unitTable.transporter) == 0 then
                unitTable.transporter = nil
            end
        end
        
        local preplaced = true
        local onEnter = Filter(
        function()
            local u = GetFilterUnit()
            local id = GetHandleId(u)
            local unitTable = unitIndices[id]
            if not unitTable then
                unitTable = {
                    unit    = u,
                    id      = id,
                    new     = true,
                    alive   = true,
                    unitType= GetUnitTypeId(u)
                }
                
                UnitAddAbility(u, _REMOVE_ABIL)
                UnitMakeAbilityPermanent(u, true, _REMOVE_ABIL)
                UnitAddAbility(u, _TRANSFORM_ABIL)

                unitIndices[id] = unitTable
                
                --print(GetUnitName(u) .. " has been indexed to " .. id)

                if _USER_DATA then SetUnitUserData(u, id) end

                unitTable.preplaced = preplaced
                UnitEvent.onIndex:run(unitTable)
                
                checkAfter(unitTable)
            elseif unitTable.transporter and not IsUnitLoaded(u) then
                --the unit was dead, but has re-entered the map (unloaded from meat wagon)
                unload(unitTable)
            end
        end)
        TriggerRegisterEnterRegion(CreateTrigger(), re, onEnter)
        
        AnyPlayerUnitEvent.add(EVENT_PLAYER_UNIT_LOADED,
        function()
            local u = GetTriggerUnit()
            local unitTable = unitIndices[GetHandleId(u)]
            if unitTable then
                if unitTable.transporter then
                    unload(unitTable)
                end
                --Loaded corpses do not issue an order when unloaded, therefore must
                --use the enter-region event method taken from Jesus4Lyf's Transport.
                if not unitTable.alive then
                    SetUnitX(u, maxX)
                    SetUnitY(u, maxY)
                end
               
                unitTable.transporter = GetTransportUnit()
                if not unitTable.transporter.cargo then
                    unitTable.transporter.cargo = CreateGroup()
                end
                GroupAddUnit(unitTable.transporter.cargo, u)
                
                UnitEvent.onLoaded:run(unitTable)
            end
        end)
        
        AnyPlayerUnitEvent.add(EVENT_PLAYER_UNIT_DEATH,
        function()
            local unitTable = unitIndices[GetTriggerUnit()]
            if unitTable then
                unitTable.alive = false
                UnitEvent.onDeath:run(unitTable)
                if unitTable.transporter then
                    unload(unitTable)
                end
            end
        end)
        
        AnyPlayerUnitEvent.add(EVENT_PLAYER_UNIT_SUMMON,
        function()
            local unitTable = GetHandleId(GetTriggerUnit())
            if unitTable.new then
                unitTable.summoner = GetSummoningUnit()
            end
        end)
        
        local orderB = Filter(
        function()
            local u = GetFilterUnit()
            local unitTable = unitIndices[GetHandleId(u)]
            if unitTable then
                if GetUnitAbilityLevel(u, _REMOVE_ABIL) == 0 then
                    unitTable[GetHandleId(u)] = nil

                    if unitTable.cargo then DestroyGroup(unitTable.cargo) end
                    
                    UnitEvent.onRemoval:run(unitTable)
                    
                elseif not unitTable.alive then
                    if UnitAlive(u) then
                        unitTable.alive = true
                        UnitEvent.onRevival:run(unitTable)
                        unitTable.reincarnating = nil
                    end
                elseif not UnitAlive(u) then
                    if unitTable.new then
                        --This unit was created as a corpse.
                        unitTable.alive = nil
                        UnitEvent.onDeath:run(unitTable)

                    elseif unitTable.transporter or not IsUnitType(u, UNIT_TYPE_HERO) then
                        --The unit may have just started reincarnating.
                        checkAfter(unitTable)
                    end
                elseif GetUnitAbilityLevel(u, _TRANSFORM_ABIL) == 0 and not unitTable.transforming then
                    unitTable.transforming = true
                    checkAfter(unitTable)
                end
                if unitTable.transporter and not unitTable.unloading and (not IsUnitLoaded(u) or not UnitAlive(u)) then
                    unload(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 = nil
    end)
end)
end
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Lua Unit Event is now here! This is the first of my systems to explore the potential of "CreateEvent", a Lua system that allows a trigger to wait for another event (therefore allowing you to use one trigger for multiple events, which will only run for the relevant unit).

I have completely changed the API compared to all previous versions of Unit Event, and I have also taken the "brave" step of removing "Custom value of unit" / SetUnitUserData due to the ability to "typecast" "integer" to unit.

Event responses:
UDex -> UnitEvent_index
UDexUnits[UDex] -> UnitEvent_unit

General data attachment:
MyArray[(Custom value of unit)] = data ->
Set UnitEvent_setKey = unit
MyArray[UnitEvent_getKey] = data

This functionality will only ever be possible in Lua, thanks to coroutines, "GUI typecasting" and Global Variable Remapper.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Updated the JASS and Lua versions of this resource.

JASS: Updated the script to force the Reincarnation-finishes event to wait 0.00 seconds (discussion here: Jedi Reflection (Missile reflection))

Lua: Did the same as I did with the JASS version, but updated the script with updates to my other systems as well. Bear in mind the map is really quite busy, as I use the same map for testing Spell Event.
 
Last edited:
Level 12
Joined
Feb 27, 2019
Messages
399
Hello,

Thanks for the system again, it's so usefull and generic !

For info, I just met a WTF bug with "UnitInActionEvent" and the JASS version of your system (with the latest W3 reforged version).

You can reproduce it very easily with your "Unit Event 2.5.3.2.w3x" map. Just add the following test trigger and launch the map.

1667398159756.png


The new trigger does nothing particularly. But you will see that now "On Create" doesn't work anymore (never triggered) because of "UnitInActionEvent".

The bug is really akward.

In a custom map of my own I found it dependent of the order of the triggers. But it doesn't seem to be the case in "Unit Event 2.5.3.2.w3x".

After some dichotomy I saw that commenting the following lines fixes "On Create", but "UnitInActionEvent" doesn't have the proper behavior anymore:
1667398556773.png

1667398570877.png


Any idea if this bug is due to one of the patches from Blizzard for W3 Reforged ? Your code seems OK/perfect to me for those simple "UnitInActionEvent"s.
 

Uncle

Warcraft Moderator
Level 63
Joined
Aug 10, 2018
Messages
6,457
Anyone have a legacy friendly version for reincarnation detection?
If need be I think you can recreate it yourself fairly easily. Add the Defend ability to your unit that has the Reincarnation ability, hide the Defend ability, and then create a trigger which checks for the Issued order "undefend", as this is issued by units with the Defend ability when they die:
  • Events
    • Unit - A unit is issued an order with no target
  • Conditions
    • (Issued order) Equal to (Order(undefend))
    • (Level of Reincarnation for (Triggering unit) Greater than 0
  • Actions
    • This unit is either dead, reincarnating, or just reincarnated
There's extra steps needed to confirm whether the unit is still alive (reincarnating) or actually dead. Maybe the (Is unit alive) condition would do the trick but I'm not sure. I'll mess around with it as I'm curious myself, I know in the past I managed to get this working.

Edit: Got it working with a few triggers, can't say it's ideal or any better than what Bribe was doing:
This order is issued any time a unit dies (reincarnating or not), it also gets issued when the unit reincarnates:
  • Undefend
    • Events
      • Unit - A unit Is issued an order with no target
    • Conditions
      • (Issued order) Equal to (Order(undefend))
      • (Level of Reincarnation (Test) for (Triggering unit)) Not equal to 0
    • Actions
      • Set VariableSet Reincarnate_Counter = (Reincarnate_Counter + 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Reincarnate_Counter Equal to 1
        • Then - Actions
          • Countdown Timer - Start Reincarnate_Timer as a One-shot timer that will expire in 0.00 seconds
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Reincarnate_Counter Equal to 2
            • Then - Actions
              • Game - Display to (All players) for 30.00 seconds the text: Reincarnated
              • Set VariableSet Reincarnate_Counter = 0
            • Else - Actions
This runs when the unit dies without any reincarnation abilities. It will happen before our timer expires which is important for making this all work:
  • Dies
    • Events
      • Unit - A unit Dies
    • Conditions
      • (Level of Reincarnation (Test) for (Triggering unit)) Not equal to 0
    • Actions
      • Game - Display to (All players) for 30.00 seconds the text: Dies
      • Set VariableSet Reincarnate_Counter = -1
      • Countdown Timer - Pause Reincarnate_Timer
This checks the state of the counter, if it's -1 (dead) then reset it back to 0, otherwise, we know the unit is reincarnating:
  • Timer
    • Events
      • Time - Reincarnate_Timer expires
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Reincarnate_Counter Equal to -1
        • Then - Actions
          • Game - Display to (All players) for 30.00 seconds the text: Is Dead
          • Set VariableSet Reincarnate_Counter = 0
        • Else - Actions
          • Game - Display to (All players) for 30.00 seconds the text: Is Reincarnating
Obviously Arrays would need to be introduced to make everything MUI.

Note that I'm unsure of what else can cause the "undefend" order to get issued so that's a potential issue right there. Also, that timer runs on a 1 frame delay which could cause issues.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I found that when the Crypt Lord casts Locust Swarm, this system indexes locusts as newly created units, but locusts aren't considered summoned by the Crypt Lord, so there's no way to assign summoner-related data to the locust.
Do Locusts fire the "summoned" event? If not, then correlating them to the Crypt Fiend would require a totally different system.
 
Level 4
Joined
Mar 8, 2020
Messages
14
Hi, I used to use the vJass version of UDexUnits[UDex] to remember the connection between units (for example, an worker and what he extracted last time) (a non-standard resource) by writing to custom value. Can you suggest something new for these purposes in the Lua version?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Hi, I used to use the vJass version of UDexUnits[UDex] to remember the connection between units (for example, an worker and what he extracted last time) (a non-standard resource) by writing to custom value. Can you suggest something new for these purposes in the Lua version?
That API still works with the Lua variant. GUI has no better option.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Does anyone have a version that works on patch 1.28.5?
I downloaded it last year, but i can't find it anymore..and i've been looking arround, thank you!
I would like to help, but I don't have my old backups anymore. A few years ago, when I uninstalled Reforged, all of my backups had been in the WarCraft 3 folder, and the uninstallation deleted them.
 
Level 12
Joined
Oct 28, 2019
Messages
453
  • EnterLeather
    • Events
      • Game - CargoEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • Set VariableSet IntegerLeather = (IntegerLeather + 1)
I would know if is possible detect in wich unit the unit is loaded?? EX: I need load/unload peasants into 3 diferents buildings
 
Level 12
Joined
Oct 28, 2019
Messages
453
You UnitEvent_transporter/UnitEvent_cargo should work as far as I know.

FunctionLuavJass
Unit who is carrying the subject unit
All units carried by the unit
UnitEvent_transporter
UnitEvent_cargo
CargoTransportUnit
CargoTransportGroup
Read the description of this resource for more info
I´ve made this, and i dont know what error I did

  • EnterMill
    • Events
      • Game - CargoEvent becomes Equal to 1.00
    • Conditions
      • (Unit-type of CargoTransportUnit[1]) Equal to Windmill
    • Actions
      • Set VariableSet IntegerMill = (IntegerMill + 1)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Can it check whether the reincarnation is made with unit ability or item ability (Ankh of Reincarnation)?
The Ankh can be detected without this system, because it triggers an item event.

The normal ability, I'm not sure if we can distinguish with this event. I haven't tested it, but I'd imagine it's possible with the new natives to check the cooldown of the unit's Reincarnation ability.

I could alternatively add an extra check for the item event. Depending on whether Undefend or the item event fire first, I could use the async timer to see which one triggered the reincarnation. I am not sure if this level of effort is needed to distinguish between the two, however, because it seems quite niche.
 
Last edited:
Level 7
Joined
May 12, 2018
Messages
96
The map I created is using this resource, and I'm experiencing symptoms of this GUI UNIT EVENT completely breaking down during the game.
Is 'Ignore AI Guard Position' or 'RemoveUnit', or ' killing or removing units of the player that lost and excluded from the game' likely to interfere with unit indexing?
 
Level 12
Joined
Feb 27, 2019
Messages
399
The map I created is using this resource, and I'm experiencing symptoms of this GUI UNIT EVENT completely breaking down during the game.
Is 'Ignore AI Guard Position' or 'RemoveUnit', or ' killing or removing units of the player that lost and excluded from the game' likely to interfere with unit indexing?
Maybe you should provide more data to help him (Bribe) or the community sort out the issue with you:
  • What's the exact "break-down" you're talking about: does the game crash ? Or lag ? Or desync (=some players are disconnected) ? Or is Bribe's GUI Unit Event malfunctioning ? Or are some of your triggers malfunctionning ?
  • Can you provide your map to Bribe, or a scenario to reproduce your issue ?
 
Level 7
Joined
May 12, 2018
Messages
96
Maybe you should provide more data to help him (Bribe) or the community sort out the issue with you:
  • What's the exact "break-down" you're talking about: does the game crash ? Or lag ? Or desync (=some players are disconnected) ? Or is Bribe's GUI Unit Event malfunctioning ? Or are some of your triggers malfunctionning ?
  • Can you provide your map to Bribe, or a scenario to reproduce your issue ?
This does not detect most events on this resource sometime, such as DeathEvent 2.00. There was no problem at the beginning of the game, but this happens after 20 minutes of game time - It becomes not to be able to detect the unit's revival properly, the unit's complete death, or the unit's transformation.
Is it because it's a large campaign map where many units are created, then die and disappear?

I didn't see these errors in my previous work, but only in the chapter I'm currently working on. If Bribe wants, I will send him this file.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Check your map for any other unit indexers. Make sure GUI Unit Indexer is deleted as well, because modern GUI Unit Event incorporates it already.

Also check your map for rogue uses of SetUnitUserData (JASS) or Unit - Set Custom value (GUI). You can use the Object Manager popup window in World Editor to find all usages of Unit - Set Custom value. You could also check the war3map.j file and Ctrl+F to see if there are more than one hits on SetUnitUserData.

The symptoms you described match the behavior of a conflict in userdata.
 
Top