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

Jedi Reflection (Missile reflection)

A system to simulate projectile reflecting from attacks but not spells. Two version are included. One that uses the "defend" ability to make ranged piercing attacks reflect more cleanly and one that's pure JASS

How to import:

Copy the triggers into your map. If you already have the latest versions of damage engine and relativistic missiles and UnitEvent you can skip copying the "systems" folder.

If you are using the JASS version there are no further steps other then configuring the defend condition to suit your needs.

If you are using the defend version:

Assign the DEFEND_ID variable to an ability based on Defend ('Adef'). Update 10/22/2022: This is now done in the jass code's initialization block.

Change the gameplay constant "Defend can deflect" to true. Otherwise ranged piercing attacks won't be supported.

Be sure to add your DEFEND_ID ability to units that you want to have reflect attacks otherwise ranged piercing attacks won't be reflected.

You can adjust the chance to deflect by editing your defend ability. Shift click to set it above 30.0.

Update 10/22/2022: The defend ability should not be affected by silences as easily as before if at all. Before it was fully silence-able.

Update 11/1/2022:

Added a version of the system using Elune's Grace. Unit Event is NOT required for this version since you don't need to toggle it on via triggers. This version will not reflect ranged piercing attacks the first time the unit is attacked and the reflecting is handled by this ability. (this is a hard coded WC3 bug).

Update 11/11/2023: Corrected behavior with attacks reflected using damage engine and the IsDamagedRanged feature of damage engine.

Limitations:
The following applies to all types of attacks if using the pure JASS version and non ranged piercing attacks if using the defend or Elune's grace version:

Though the damage will be 0 on hit effects will still apply and secondary effects such as corruption won't apply to the reflected projectile.
See https://www.hiveworkshop.com/threads/damage-engine-5-9-0-0.201016/ frequently asked questions for more info.

TODO:
Make revived units (its already implemented for heroes) turn defend back on when revived. Update 10/22/2022: I still can't get this to work even when following the instructions, sorry

Update 10/23/22. Fixed this with the latest version of unit event.

Credits:
Bribe for damage engine and UnitEvent
Chopinski for Relativistic missiles
Kwaliti for the optional Jedi model.
Contents

Jedi Reflection Test Map (Map)

Reviews
Wrda
The changes are appreciated. The system works and is nice, despite the known limitations. All I ask is to remove RegisterPlayerUnitEvent, SpellEffectEvent, Utilities and TimedHandles scripts since they're not used in any shape or form in your...

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
Mandatory: If the disabled trigger "Jedi Reflection Defend Unit Revived" is supposed to be when a unit is reicarnated or animated dead etc, then use DeathEvent 0.50, according to the GUI Unit Event. (I know since it's on your to do list :) )
I would make the special effect that is created on the success of deflection configurable, since one might not even use the one from the Defend Ability.
Using the default Defend ability might not be a good idea if one is already using that. Making it a custom one is safer. Also, I was attempting to find the configuration where you defined the variable DEFEND_ID and then realised you used the initial value. The problem with this is when one wants to copy only the version they're interested in, the variable is either in one folder or the other, not both. An initialization trigger would still be better in this case for the configuration, in both versions.
Also since this requires GUI Unit Event, Damage Engine, Missiles (also MissileEffect - MissileUtils isn't actually required), these also use Alloc, WorldBounds and TimerUtils. They should be in a "Requirements" folder. So it would look like this:
Requirements:
- [the libraries I just mentioned]
- Jedi Reflection (uses defend ability)
- triggers
- Jedi Reflection (Pure JASS)
- triggers
The user can then remove theversion he doesn't want, and then it's easy to copy paste.

Not critical: "Jedi Reflection Reflect Defend Version" and "Jedi Reflection JASS" trigger's indention appears to be slightly off when declaring the struct and the "endstruct" part.
"Jedi Reflection Defend Unit Ready" trigger is in JASS, I don't understand why others like "Jedi Reflection Defend Hero Revived" aren't.

All in all, it's a simple and cool idea, despite having its limitations.
Fix the mandatory ones and it's certain approval.
 
Mandatory: If the disabled trigger "Jedi Reflection Defend Unit Revived" is supposed to be when a unit is reicarnated or animated dead etc, then use DeathEvent 0.50, according to the GUI Unit Event. (I know since it's on your to do list :) )
I would make the special effect that is created on the success of deflection configurable, since one might not even use the one from the Defend Ability.
Using the default Defend ability might not be a good idea if one is already using that. Making it a custom one is safer. Also, I was attempting to find the configuration where you defined the variable DEFEND_ID and then realised you used the initial value. The problem with this is when one wants to copy only the version they're interested in, the variable is either in one folder or the other, not both. An initialization trigger would still be better in this case for the configuration, in both versions.
Also since this requires GUI Unit Event, Damage Engine, Missiles (also MissileEffect - MissileUtils isn't actually required), these also use Alloc, WorldBounds and TimerUtils. They should be in a "Requirements" folder. So it would look like this:
Requirements:
- [the libraries I just mentioned]
- Jedi Reflection (uses defend ability)
- triggers
- Jedi Reflection (Pure JASS)
- triggers
The user can then remove theversion he doesn't want, and then it's easy to copy paste.

Not critical: "Jedi Reflection Reflect Defend Version" and "Jedi Reflection JASS" trigger's indention appears to be slightly off when declaring the struct and the "endstruct" part.
"Jedi Reflection Defend Unit Ready" trigger is in JASS, I don't understand why others like "Jedi Reflection Defend Hero Revived" aren't.

All in all, it's a simple and cool idea, despite having its limitations.
Fix the mandatory ones and it's certain approval.
Thanks for the review. Even when following your instructions I couldn't get the defend ability to reflect again for a non-hero unit after its revived. I debug printed and the unit revived trigger does execute but even removing and re-adding the ability then seemingly won't fix it. Also for the record its DeathEvent = 2.0
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
Odd, but it most likely is the nature of the fake event on "GUI Unit Event", where when the unit is revived, it still isn't actually revived, it fires before that happens.
I found the safest solution by making this require the "Table" script that you have on the map. And using a 0s timer.

Top of the trigger Jedi Reflection Reflect Defend Version should be like this:

JASS:
scope JediReflectionDefend

globals
    private string DEFEND_FX = "Abilities\\Spells\\Human\\Defend\\DefendCaster.mdl" //Effect to play when a non ranged piercing attack is deflected
        public Table Hash
endglobals

    public function callback takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer id = GetHandleId(t)
        local unit u = Hash.unit[id]
        call IssueImmediateOrder(u, "defend" )
        call PauseTimer(t)
        call DestroyTimer(t)
        call Hash.unit.remove(id)
        set u = null
        set t = null
    endfunction

The onInit part, here you declare the table:
JASS:
    private static method onInit takes nothing returns nothing
        local trigger t= CreateTrigger()
        call TriggerRegisterVariableEvent(t, "udg_PreDamageEvent", EQUAL, 1.00)
        call TriggerAddCondition(t,Condition(function thistype.ReflectCondition))
        call TriggerAddAction(t,function thistype.ReflectAction)
        set t=null
        set Hash = Table.create()
        set udg_DEFEND_ID = 'A000' //Change this to your defend ability rawcode
    endmethod


Jedi Reflection Defend Unit Revived's actions:

JASS:
function Trig_Jedi_Reflection_Defend_Unit_Revived_Actions takes nothing returns nothing
    local timer t = CreateTimer()
    set JediReflectionDefend_Hash.unit[GetHandleId(t)] = udg_UDexUnits[udg_UDex]
    call TimerStart(t, 0., false, function JediReflectionDefend_callback)
    set t = null
endfunction
Also for the record its DeathEvent = 2.0
Yes, you are right. I failed to read the corresponding line of the DeathEvent.
Since both versions are/have now (v)JASS, having DEFEND_ID as a GUI variable doesn't seem necessary at all. Make it public a public variable on top and use it as such in the rest of the triggers that use it.
Please remember to move Jedi Reflection (uses defend ability) and Jedi Reflection (Pure JASS) folders inside requirements so that users can copy it all in one go.
TimedHandles, Utilities, SpellEffectEvent and RegisterPlayerUnitEvent aren't required. You can move them anywhere else execept inside the requirements folder, can be in "this map only".
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Could you try this hotfix of Unit Event to see if this fixes the problem?

The below script adds a slight modification to last night's version, which I have tested and is working correctly.

@Wrda I think a problem you may have faced yesterday was with killing the Tauren Chieftan, as it doesn't have Reincarnation by default. The Archmage does. However, the issue you brought up with the event deploying for normally-killed units is now sorted.


Okay, this version below is now tested with reincarnation, resurrection and animate dead, and they are all caught correctly. I changed the boolean from checking if IsUnitReincarnating is true (which it would have only been if it would have been a finish-reincarnating event) to just check if the unit is alive.

JASS:
//===========================================================================
function UnitEventDestroyGroup takes integer i returns nothing
    if udg_CargoTransportGroup[i] != null then
        call DestroyGroup(udg_CargoTransportGroup[i])
        set udg_CargoTransportGroup[i] = null
    endif
endfunction
function UnitEventCheckAfter takes nothing returns nothing
    local integer id = 0
    local unit u
    loop
        set id = udg_CheckDeathList[id]
        exitwhen id == 0
        set udg_UDex = id
        set u = udg_UDexUnits[id]
        if udg_IsUnitNew[id] then
            //The unit was just created.
            set udg_IsUnitNew[id] = false
            set udg_UnitIndexEvent = 1.50 //New event requested by SpellBound to detect when unit fully enters scope.
            set udg_UnitIndexEvent = 0.00
        elseif udg_IsUnitTransforming[id] then
           //Added 21 July 2017 to fix the issue re-adding this ability in the same instant
           set udg_UnitTypeEvent = 0.00
           set udg_UnitTypeEvent = 1.00
           set udg_UnitTypeOf[id] = GetUnitTypeId(u) //Set this afterward as otherwise the user won't know what the previous unittype was.
           set udg_IsUnitTransforming[id] = false
           call UnitAddAbility(u, udg_DetectTransformAbility)
        elseif udg_IsUnitAlive[id] then
            //The unit has started reincarnating.
            set udg_IsUnitReincarnating[id] = true
            set udg_IsUnitAlive[id] = false
            set udg_DeathEvent = 0.50
            set udg_DeathEvent = 0.00
        elseif GetUnitTypeId(u) != 0 and not IsUnitType(u, UNIT_TYPE_DEAD) then //with vJass, could just use UnitAlive instead of both of these values.
            //Moved this code to fire after a 0 second timer instead.
            set udg_IsUnitAlive[id] = true
            set udg_DeathEvent = 2.00
            set udg_DeathEvent = 0.00
            set udg_IsUnitReincarnating[id] = false
        endif
        set udg_CheckDeathInList[id] = false
    endloop
    set u = null
    //Empty the list
    set udg_CheckDeathList[0] = 0
endfunction
function UnitEventCheckAfterProxy takes integer i returns nothing
    if udg_CheckDeathList[0] == 0 then
        call TimerStart(udg_CheckDeathTimer, 0.00, false, function UnitEventCheckAfter)
    endif
    if not udg_CheckDeathInList[i] then
        set udg_CheckDeathList[i] = udg_CheckDeathList[0]
        set udg_CheckDeathList[0] = i
        set udg_CheckDeathInList[i] = true
    endif
endfunction

function UnitEventOnUnload takes nothing returns nothing
    local integer i = udg_UDex
    call GroupRemoveUnit(udg_CargoTransportGroup[GetUnitUserData(udg_CargoTransportUnit[i])], udg_UDexUnits[i])
    set udg_IsUnitBeingUnloaded[i] = true
    set udg_CargoEvent = 0.00
    set udg_CargoEvent = 2.00
    set udg_CargoEvent = 0.00
    set udg_IsUnitBeingUnloaded[i] = false
    if not IsUnitLoaded(udg_UDexUnits[i]) or IsUnitType(udg_CargoTransportUnit[i], UNIT_TYPE_DEAD) or GetUnitTypeId(udg_CargoTransportUnit[i]) == 0 then
        set udg_CargoTransportUnit[i] = null
    endif
endfunction

function UnitEventOnDeath takes nothing returns boolean
    local integer pdex = udg_UDex
    set udg_UDex = GetUnitUserData(GetTriggerUnit())
    if udg_UDex != 0 then
        set udg_KillerOfUnit[udg_UDex] = GetKillingUnit() //Added 29 May 2017 for GIMLI_2 
        set udg_IsUnitAlive[udg_UDex] = false
        set udg_DeathEvent = 0.00
        set udg_DeathEvent = 1.00
        set udg_DeathEvent = 0.00
        set udg_KillerOfUnit[udg_UDex] = null
        if udg_CargoTransportUnit[udg_UDex] != null then
            call UnitEventOnUnload()
        endif
    endif
    set udg_UDex = pdex
    return false
endfunction
  
function UnitEventOnOrder takes nothing returns boolean
    local integer pdex = udg_UDex
    local unit u = GetFilterUnit()
    local integer i = GetUnitUserData(u)
    if i > 0 then
        set udg_UDex = i
        if GetUnitAbilityLevel(u, udg_DetectRemoveAbility) == 0 then
            if not udg_IsUnitRemoved[i] then
                set udg_IsUnitRemoved[i] = true
                set udg_IsUnitAlive[i] = false
                set udg_SummonerOfUnit[i] = null
                
                //For backwards-compatibility:
                set udg_DeathEvent = 0.00
                set udg_DeathEvent = 3.00
                set udg_DeathEvent = 0.00
                
                //Fire deindex event for UDex:
                set udg_UnitIndexEvent = 0.00
                set udg_UnitIndexEvent = 2.00
                set udg_UnitIndexEvent = 0.00
                
                set udg_UDexNext[udg_UDexPrev[i]] = udg_UDexNext[i]
                set udg_UDexPrev[udg_UDexNext[i]] = udg_UDexPrev[i]
                
                // Recycle the index for later use
                set udg_UDexUnits[i] = null
                set udg_UDexPrev[i] = udg_UDexLastRecycled
                set udg_UDexLastRecycled = i
                call UnitEventDestroyGroup(i)
            endif
        elseif not udg_IsUnitAlive[i] then
            if not IsUnitType(u, UNIT_TYPE_DEAD) then
                call UnitEventCheckAfterProxy(i) //modified 22 Oct 2022 to ensure the unit is fully revived before firing the event.
            endif
        elseif IsUnitType(u, UNIT_TYPE_DEAD) then
            if udg_IsUnitNew[i] then
                //This unit was created as a corpse.
                set udg_IsUnitAlive[i] = false
                set udg_DeathEvent = 0.00
                set udg_DeathEvent = 1.00
                set udg_DeathEvent = 0.00
            elseif udg_CargoTransportUnit[i] == null or not IsUnitType(u, UNIT_TYPE_HERO) then
                //The unit may have just started reincarnating.
                call UnitEventCheckAfterProxy(i)
            endif
        elseif GetUnitAbilityLevel(u, udg_DetectTransformAbility) == 0 and not udg_IsUnitTransforming[i] then
            set udg_IsUnitTransforming[i] = true
            call UnitEventCheckAfterProxy(i)  //This block has been updated on 21 July 2017
        endif
        if udg_CargoTransportUnit[i] != null and not udg_IsUnitBeingUnloaded[i] and not IsUnitLoaded(u) or IsUnitType(u, UNIT_TYPE_DEAD) then
            call UnitEventOnUnload()
        endif
        set udg_UDex = pdex
    endif
    set u = null
    return false
endfunction
function UnitEventOnSummon takes nothing returns boolean
    local integer pdex = udg_UDex
    set udg_UDex = GetUnitUserData(GetTriggerUnit())
    if udg_IsUnitNew[udg_UDex] then
        set udg_SummonerOfUnit[udg_UDex] = GetSummoningUnit()
        set udg_UnitIndexEvent = 0.00
        set udg_UnitIndexEvent = 0.50
        set udg_UnitIndexEvent = 0.00
    endif
    set udg_UDex = pdex
    return false
endfunction
function UnitEventOnLoad takes nothing returns boolean
    local integer pdex = udg_UDex
    local integer i = GetUnitUserData(GetTriggerUnit())
    local integer index
    if i != 0 then
        set udg_UDex = i
        if udg_CargoTransportUnit[i] != null then
            call UnitEventOnUnload()
        endif
        //Loaded corpses do not issue an order when unloaded, therefore must
        //use the enter-region event method taken from Jesus4Lyf's Transport.
        if not udg_IsUnitAlive[i] then
            call SetUnitX(udg_UDexUnits[i], udg_WorldMaxX)
            call SetUnitY(udg_UDexUnits[i], udg_WorldMaxY)
        endif
        
        set udg_CargoTransportUnit[i] = GetTransportUnit()
        set index = GetUnitUserData(udg_CargoTransportUnit[i])
        if udg_CargoTransportGroup[index] == null then
            set udg_CargoTransportGroup[index] = CreateGroup()
        endif
        call GroupAddUnit(udg_CargoTransportGroup[index], udg_UDexUnits[i])
        set udg_CargoEvent = 0.00
        set udg_CargoEvent = 1.00
        set udg_CargoEvent = 0.00
        set udg_UDex = pdex
    endif
    return false
endfunction
function UnitEventEnter takes nothing returns boolean
    local integer pdex = udg_UDex
    local integer i = udg_UDexLastRecycled
    local unit u = GetFilterUnit()
    if udg_UnitIndexerEnabled and GetUnitAbilityLevel(u, udg_DetectRemoveAbility) == 0 then
        //Generate a unique integer index for this unit
        if i == 0 then
            set i = udg_UDexMax + 1
            set udg_UDexMax = i
        else
            set udg_UDexLastRecycled = udg_UDexPrev[i]
        endif
        //Link index to unit, unit to index
        set udg_UDexUnits[i] = u
        call SetUnitUserData(u, i)
        
        //For backwards-compatibility, add the unit to a linked list
        set udg_UDexNext[i] = udg_UDexNext[0]
        set udg_UDexPrev[udg_UDexNext[0]] = i
        set udg_UDexNext[0] = i
        set udg_UDexPrev[i] = 0

        set udg_CheckDeathInList[i] = false

        call UnitAddAbility(u, udg_DetectRemoveAbility)
        call UnitMakeAbilityPermanent(u, true, udg_DetectRemoveAbility)
        call UnitAddAbility(u, udg_DetectTransformAbility)
        set udg_UnitTypeOf[i] = GetUnitTypeId(u)
        set udg_IsUnitNew[i] = true
        set udg_IsUnitAlive[i] = true
        set udg_IsUnitRemoved[i] = false
        set udg_IsUnitReincarnating[i] = false
        set udg_IsUnitPreplaced[i] = udg_IsUnitPreplaced[0] //Added 29 May 2017 for Spellbound
        call UnitEventCheckAfterProxy(i)
        
        //Fire index event for UDex
        set udg_UDex = i
        set udg_UnitIndexEvent = 0.00
        set udg_UnitIndexEvent = 1.00
        set udg_UnitIndexEvent = 0.00
    else
        set udg_UDex = GetUnitUserData(u)
        if udg_CargoTransportUnit[udg_UDex] != null and not IsUnitLoaded(u) then
            //The unit was dead, but has re-entered the map.
            call UnitEventOnUnload()
        endif
    endif
    set udg_UDex = pdex
    set u = null
    return false
endfunction
//===========================================================================
function UnitEventInit takes nothing returns nothing
    local integer i = bj_MAX_PLAYER_SLOTS //update to make it work with 1.29 
    local player p
    local trigger t = CreateTrigger()
    local trigger load = CreateTrigger()
    local trigger death = CreateTrigger()
    local trigger summon = CreateTrigger()
    local rect r = GetWorldBounds()
    local region re = CreateRegion()
    local boolexpr enterB = Filter(function UnitEventEnter)
    local boolexpr orderB = Filter(function UnitEventOnOrder)
    set udg_WorldMaxX = GetRectMaxX(r)
    set udg_WorldMaxY = GetRectMaxY(r)
    call RegionAddRect(re, r)
    call RemoveRect(r)
    call UnitEventDestroyGroup(0)
    call UnitEventDestroyGroup(1)
    
    set udg_CheckDeathList[0] = 0
    set udg_UnitIndexerEnabled = true
    call TriggerRegisterEnterRegion(CreateTrigger(), re, enterB)
    call TriggerAddCondition(load, Filter(function UnitEventOnLoad))
    call TriggerAddCondition(death, Filter(function UnitEventOnDeath))
    call TriggerAddCondition(summon, Filter(function UnitEventOnSummon))
    loop
        set i = i - 1
        set p = Player(i)
        call SetPlayerAbilityAvailable(p, udg_DetectRemoveAbility, false)
        call SetPlayerAbilityAvailable(p, udg_DetectTransformAbility, false)
        call TriggerRegisterPlayerUnitEvent(summon, p, EVENT_PLAYER_UNIT_SUMMON, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, orderB)
        call TriggerRegisterPlayerUnitEvent(death, p, EVENT_PLAYER_UNIT_DEATH, null)
        call TriggerRegisterPlayerUnitEvent(load, p, EVENT_PLAYER_UNIT_LOADED, null)
        call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, p, enterB)
        exitwhen i == 0
    endloop
    set summon = null
    set death = null
    set load = null
    set re = null
    set enterB = null
    set orderB = null
    set p = null
    set r = null
    set t = null
endfunction
function InitTrig_Unit_Event takes nothing returns nothing
endfunction
 
Last edited:

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
Could you try this hotfix of Unit Event to see if this fixes the problem?

JASS:
//===========================================================================
function UnitEventDestroyGroup takes integer i returns nothing
    if udg_CargoTransportGroup[i] != null then
        call DestroyGroup(udg_CargoTransportGroup[i])
        set udg_CargoTransportGroup[i] = null
    endif
endfunction
function UnitEventCheckAfter takes nothing returns nothing
    local integer i = 0
    loop
        set i = udg_CheckDeathList[i]
        exitwhen i == 0
        set udg_UDex = i
        if udg_IsUnitNew[i] then
            //The unit was just created.
            set udg_IsUnitNew[i] = false
            set udg_UnitIndexEvent = 1.50 //New event requested by SpellBound to detect when unit fully enters scope.
            set udg_UnitIndexEvent = 0.00
        elseif udg_IsUnitTransforming[i] then
           //Added 21 July 2017 to fix the issue re-adding this ability in the same instant
           set udg_UnitTypeEvent = 0.00
           set udg_UnitTypeEvent = 1.00
           set udg_UnitTypeOf[i] = GetUnitTypeId(udg_UDexUnits[i]) //Set this afterward to give the user extra reference
           set udg_IsUnitTransforming[i] = false
           call UnitAddAbility(udg_UDexUnits[i], udg_DetectTransformAbility)
        elseif udg_IsUnitAlive[i] then
            //The unit has started reincarnating.
            set udg_IsUnitReincarnating[i] = true
            set udg_IsUnitAlive[i] = false
            set udg_DeathEvent = 0.50
            set udg_DeathEvent = 0.00
        else
            //Moved this code to fire after a 0 second timer instead.
            set udg_IsUnitAlive[i] = true
            set udg_DeathEvent = 0.00
            set udg_DeathEvent = 2.00
            set udg_DeathEvent = 0.00
            set udg_IsUnitReincarnating[i] = false
        endif
        set udg_CheckDeathInList[i] = false
    endloop
    //Empty the list
    set udg_CheckDeathList[0] = 0
endfunction
function UnitEventCheckAfterProxy takes integer i returns nothing
    if udg_CheckDeathList[0] == 0 then
        call TimerStart(udg_CheckDeathTimer, 0.00, false, function UnitEventCheckAfter)
    endif
    if not udg_CheckDeathInList[i] then
        set udg_CheckDeathList[i] = udg_CheckDeathList[0]
        set udg_CheckDeathList[0] = i
        set udg_CheckDeathInList[i] = true
    endif
endfunction

function UnitEventOnUnload takes nothing returns nothing
    local integer i = udg_UDex
    call GroupRemoveUnit(udg_CargoTransportGroup[GetUnitUserData(udg_CargoTransportUnit[i])], udg_UDexUnits[i])
    set udg_IsUnitBeingUnloaded[i] = true
    set udg_CargoEvent = 0.00
    set udg_CargoEvent = 2.00
    set udg_CargoEvent = 0.00
    set udg_IsUnitBeingUnloaded[i] = false
    if not IsUnitLoaded(udg_UDexUnits[i]) or IsUnitType(udg_CargoTransportUnit[i], UNIT_TYPE_DEAD) or GetUnitTypeId(udg_CargoTransportUnit[i]) == 0 then
        set udg_CargoTransportUnit[i] = null
    endif
endfunction

function UnitEventOnDeath takes nothing returns boolean
    local integer pdex = udg_UDex
    set udg_UDex = GetUnitUserData(GetTriggerUnit())
    if udg_UDex != 0 then
        set udg_KillerOfUnit[udg_UDex] = GetKillingUnit() //Added 29 May 2017 for GIMLI_2
        set udg_IsUnitAlive[udg_UDex] = false
        set udg_DeathEvent = 0.00
        set udg_DeathEvent = 1.00
        set udg_DeathEvent = 0.00
        set udg_KillerOfUnit[udg_UDex] = null
        if udg_CargoTransportUnit[udg_UDex] != null then
            call UnitEventOnUnload()
        endif
    endif
    set udg_UDex = pdex
    return false
endfunction
 
function UnitEventOnOrder takes nothing returns boolean
    local integer pdex = udg_UDex
    local unit u = GetFilterUnit()
    local integer i = GetUnitUserData(u)
    if i > 0 then
        set udg_UDex = i
        if GetUnitAbilityLevel(u, udg_DetectRemoveAbility) == 0 then
            if not udg_IsUnitRemoved[i] then
                set udg_IsUnitRemoved[i] = true
                set udg_IsUnitAlive[i] = false
                set udg_SummonerOfUnit[i] = null
              
                //For backwards-compatibility:
                set udg_DeathEvent = 0.00
                set udg_DeathEvent = 3.00
                set udg_DeathEvent = 0.00
              
                //Fire deindex event for UDex:
                set udg_UnitIndexEvent = 0.00
                set udg_UnitIndexEvent = 2.00
                set udg_UnitIndexEvent = 0.00
              
                set udg_UDexNext[udg_UDexPrev[i]] = udg_UDexNext[i]
                set udg_UDexPrev[udg_UDexNext[i]] = udg_UDexPrev[i]
              
                // Recycle the index for later use
                set udg_UDexUnits[i] = null
                set udg_UDexPrev[i] = udg_UDexLastRecycled
                set udg_UDexLastRecycled = i
                call UnitEventDestroyGroup(i)
            endif
        elseif not udg_IsUnitAlive[i] then
            if not IsUnitType(u, UNIT_TYPE_DEAD) then
                call UnitEventCheckAfterProxy(i) //modified 22 Oct 2022 to ensure the unit is fully revived before firing the event.
            endif
        elseif IsUnitType(u, UNIT_TYPE_DEAD) then
            if udg_IsUnitNew[i] then
                //This unit was created as a corpse.
                set udg_IsUnitAlive[i] = false
                set udg_DeathEvent = 0.00
                set udg_DeathEvent = 1.00
                set udg_DeathEvent = 0.00
            elseif udg_CargoTransportUnit[i] == null or not IsUnitType(u, UNIT_TYPE_HERO) then
                //The unit may have just started reincarnating.
                call UnitEventCheckAfterProxy(i)
            endif
        elseif GetUnitAbilityLevel(u, udg_DetectTransformAbility) == 0 and not udg_IsUnitTransforming[i] then
            set udg_IsUnitTransforming[i] = true
            call UnitEventCheckAfterProxy(i)  //This block has been updated on 21 July 2017
        endif
        if udg_CargoTransportUnit[i] != null and not udg_IsUnitBeingUnloaded[i] and not IsUnitLoaded(u) or IsUnitType(u, UNIT_TYPE_DEAD) then
            call UnitEventOnUnload()
        endif
        set udg_UDex = pdex
    endif
    set u = null
    return false
endfunction
function UnitEventOnSummon takes nothing returns boolean
    local integer pdex = udg_UDex
    set udg_UDex = GetUnitUserData(GetTriggerUnit())
    if udg_IsUnitNew[udg_UDex] then
        set udg_SummonerOfUnit[udg_UDex] = GetSummoningUnit()
        set udg_UnitIndexEvent = 0.00
        set udg_UnitIndexEvent = 0.50
        set udg_UnitIndexEvent = 0.00
    endif
    set udg_UDex = pdex
    return false
endfunction
function UnitEventOnLoad takes nothing returns boolean
    local integer pdex = udg_UDex
    local integer i = GetUnitUserData(GetTriggerUnit())
    local integer index
    if i != 0 then
        set udg_UDex = i
        if udg_CargoTransportUnit[i] != null then
            call UnitEventOnUnload()
        endif
        //Loaded corpses do not issue an order when unloaded, therefore must
        //use the enter-region event method taken from Jesus4Lyf's Transport.
        if not udg_IsUnitAlive[i] then
            call SetUnitX(udg_UDexUnits[i], udg_WorldMaxX)
            call SetUnitY(udg_UDexUnits[i], udg_WorldMaxY)
        endif
      
        set udg_CargoTransportUnit[i] = GetTransportUnit()
        set index = GetUnitUserData(udg_CargoTransportUnit[i])
        if udg_CargoTransportGroup[index] == null then
            set udg_CargoTransportGroup[index] = CreateGroup()
        endif
        call GroupAddUnit(udg_CargoTransportGroup[index], udg_UDexUnits[i])
        set udg_CargoEvent = 0.00
        set udg_CargoEvent = 1.00
        set udg_CargoEvent = 0.00
        set udg_UDex = pdex
    endif
    return false
endfunction
function UnitEventEnter takes nothing returns boolean
    local integer pdex = udg_UDex
    local integer i = udg_UDexLastRecycled
    local unit u = GetFilterUnit()
    if udg_UnitIndexerEnabled and GetUnitAbilityLevel(u, udg_DetectRemoveAbility) == 0 then
        //Generate a unique integer index for this unit
        if i == 0 then
            set i = udg_UDexMax + 1
            set udg_UDexMax = i
        else
            set udg_UDexLastRecycled = udg_UDexPrev[i]
        endif
        //Link index to unit, unit to index
        set udg_UDexUnits[i] = u
        call SetUnitUserData(u, i)
      
        //For backwards-compatibility, add the unit to a linked list
        set udg_UDexNext[i] = udg_UDexNext[0]
        set udg_UDexPrev[udg_UDexNext[0]] = i
        set udg_UDexNext[0] = i
        set udg_UDexPrev[i] = 0

        set udg_CheckDeathInList[i] = false

        call UnitAddAbility(u, udg_DetectRemoveAbility)
        call UnitMakeAbilityPermanent(u, true, udg_DetectRemoveAbility)
        call UnitAddAbility(u, udg_DetectTransformAbility)
        set udg_UnitTypeOf[i] = GetUnitTypeId(u)
        set udg_IsUnitNew[i] = true
        set udg_IsUnitAlive[i] = true
        set udg_IsUnitRemoved[i] = false
        set udg_IsUnitReincarnating[i] = false
        set udg_IsUnitPreplaced[i] = udg_IsUnitPreplaced[0] //Added 29 May 2017 for Spellbound
        call UnitEventCheckAfterProxy(i)
      
        //Fire index event for UDex
        set udg_UDex = i
        set udg_UnitIndexEvent = 0.00
        set udg_UnitIndexEvent = 1.00
        set udg_UnitIndexEvent = 0.00
    else
        set udg_UDex = GetUnitUserData(u)
        if udg_CargoTransportUnit[udg_UDex] != null and not IsUnitLoaded(u) then
            //The unit was dead, but has re-entered the map.
            call UnitEventOnUnload()
        endif
    endif
    set udg_UDex = pdex
    set u = null
    return false
endfunction
//===========================================================================
function UnitEventInit takes nothing returns nothing
    local integer i = bj_MAX_PLAYER_SLOTS //update to make it work with 1.29
    local player p
    local trigger t = CreateTrigger()
    local trigger load = CreateTrigger()
    local trigger death = CreateTrigger()
    local trigger summon = CreateTrigger()
    local rect r = GetWorldBounds()
    local region re = CreateRegion()
    local boolexpr enterB = Filter(function UnitEventEnter)
    local boolexpr orderB = Filter(function UnitEventOnOrder)
    set udg_WorldMaxX = GetRectMaxX(r)
    set udg_WorldMaxY = GetRectMaxY(r)
    call RegionAddRect(re, r)
    call RemoveRect(r)
    call UnitEventDestroyGroup(0)
    call UnitEventDestroyGroup(1)
  
    set udg_CheckDeathList[0] = 0
    set udg_UnitIndexerEnabled = true
    call TriggerRegisterEnterRegion(CreateTrigger(), re, enterB)
    call TriggerAddCondition(load, Filter(function UnitEventOnLoad))
    call TriggerAddCondition(death, Filter(function UnitEventOnDeath))
    call TriggerAddCondition(summon, Filter(function UnitEventOnSummon))
    loop
        set i = i - 1
        set p = Player(i)
        call SetPlayerAbilityAvailable(p, udg_DetectRemoveAbility, false)
        call SetPlayerAbilityAvailable(p, udg_DetectTransformAbility, false)
        call TriggerRegisterPlayerUnitEvent(summon, p, EVENT_PLAYER_UNIT_SUMMON, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, orderB)
        call TriggerRegisterPlayerUnitEvent(death, p, EVENT_PLAYER_UNIT_DEATH, null)
        call TriggerRegisterPlayerUnitEvent(load, p, EVENT_PLAYER_UNIT_LOADED, null)
        call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, p, enterB)
        exitwhen i == 0
    endloop
    set summon = null
    set death = null
    set load = null
    set re = null
    set enterB = null
    set orderB = null
    set p = null
    set r = null
    set t = null
endfunction
function InitTrig_Unit_Event takes nothing returns nothing
endfunction
It appears not to even fire on resurrection anymore. It fires on death instead.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I've updated the above post now (as well as that of Unit Event itself) to address this. I recall I had to do the same thing a few months back for the "Unit Is Created" event, because the "OnUnitIndexed" event fires before the unit's stats have been fully instantiated (and I guess the OnRevival event had the same issue).
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
I actually had tested the script on this map yesterday.
But now I went to the Unit Event itself, and both the Tauren Chieftain and Archmage's reincarnation work. However, for the units themselves getting revived via Ressurection's spell from Paladin, they refuse to work, there's no message "[unit] has come back to life".
 

Attachments

  • Units reviving.png
    Units reviving.png
    4.1 MB · Views: 20

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
The changes are appreciated. The system works and is nice, despite the known limitations.
All I ask is to remove RegisterPlayerUnitEvent, SpellEffectEvent, Utilities and TimedHandles scripts since they're not used in any shape or form in your reflection system nor by the actual requirements for your system.
Also, as a precaution, you should mention that if one were to use the Defend ability version they would have to add that ability to the units that are able to reflect missiles (although this is already heavily implied).
Summing up, I will assume you will do that, meanwhile I approve.

Approved
 
The changes are appreciated. The system works and is nice, despite the known limitations.
All I ask is to remove RegisterPlayerUnitEvent, SpellEffectEvent, Utilities and TimedHandles scripts since they're not used in any shape or form in your reflection system nor by the actual requirements for your system.
Also, as a precaution, you should mention that if one were to use the Defend ability version they would have to add that ability to the units that are able to reflect missiles (although this is already heavily implied).
Summing up, I will assume you will do that, meanwhile I approve.

Approved
Done
 
Top