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

[System][vJASS] ArmorUtils v2.0.1

Armor Library
Getters and setters to manipulate a unit armor

What is it ?
Blizzard suxx at handling armor (only possible to get/set total), this system is here to solve that issue, with more setters/getters that are understandable/reliable.
Also their Devotion Aura "Data - Percent Bonus" doesn't work properly...

What id does/does not ?
Does:
  • Get white, green, total armor.
  • Add or set white armor.
  • Get/set the hidden defense bonus set by code.
  • Convert armor amount from/to damage factor.
  • (Educational) Get the amount of defense bonus from agility, or the base+upgrades amount.

Does not:


Explaining armor in W3

A unit armor is made of:
1 - Unit-type base defense ("Combat - Defense Base"/def from the Object Editor in World Editor).
2 - Bonus from Upgrades (= defense upgrades count x Unit-Type "Combat - Defense Upgrade Bonus"/defUp from the Object Editor in World Editor).
3 - Bonus from Agility (= Gameplay Constant "Hero Attributes - Defense Base Value (before Agility Bonus)"/AgiDefenseBase + Hero white Agility x Gameplay Constant "Hero Attributes - Defense Bonus per Agility Point"/AgiDefenseBonus).
4 - Bonus from code (internal, 0 by default, the only thing can be programatically changed. Lost on morphing).
5 - Bonus from abilities/buffs (flat bonus + percent bonus x Defense Base + agility bonus x AgiDefenseBonus)
= Total

In the UI, you can only see the sum of the 4 first values (in white), and the 5th value (in green).

168262-8e60f49268f12902136885992a8d63dd.png

All those values are real internally, and you can use R2I if you want the integer value displayed in the interface.


Mains features (API)

JASS:
// -----------------------------------------------------------------------------
// Getters
// -----------------------------------------------------------------------------
function GetUnitWhiteArmor takes unit whichUnit returns real // Gets white armor (unit-type + upgrades + agility + code) of the unit.
function GetUnitGreenArmor takes unit whichUnit returns real // Gets green armor (abilities/buffs) of the unit.
function GetUnitTotalArmor takes unit whichUnit returns real // Gets total (white + green) armor of the unit. Same as BlzGetUnitArmor(u) or BlzGetUnitRealField(u, UNIT_RF_DEFENSE)

// If you prefer to manipulate the hidden code bonus directly
function GetUnitCodeDefenseBonus takes unit whichUnit returns real // Gets the vaue of the hidden code armor.

// For information
function GetUnitAgiDefenseBonus takes unit whichUnit, boolean includeBonuses returns real // Gets the white armor bonus from Agility (= AgiDefenseBase + Hero white agility x AgiDefenseBonus)
function GetUnitBaseAndUpgradeDefense takes unit whichUnit returns real // Gets the base + upgrade defense (= def + upgrade count * defUp)

// -----------------------------------------------------------------------------
// Setters (beware that the only editable value in real time is the Bonus from code)
// -----------------------------------------------------------------------------
function UnitAddWhiteArmor takes unit whichUnit, real armorDiff returns nothing // Adds an amount to white armor (by changing the hidden code value). Negative value substracts.
function UnitSetWhiteArmor takes unit whichUnit, real desiredWhiteArmor returns nothing // Changes the white armor to the desired value (by changing the hidden code value).
function UnitResetWhiteArmor takes unit whichUnit returns nothing // Resets all previous modifications via this system or calls to BlzSetUnitArmor/BlzSetUnitIntegerField(UNIT_RF_DEFENSE).

// If you prefer to manipulate the hidden code bonus directly
function UnitSetCodeDefenseBonus takes unit whichUnit, real desiredCodeArmor returns nothing // Changes the hidden code value. Is 0 by default.

// -----------------------------------------------------------------------------
// Misc
// -----------------------------------------------------------------------------
function GetUnitTotalArmorFactor takes unit whichUnit returns real // Get the damage factor applied to damage received by this unit (1.0 = no reduction, 0.0<f<1.0 = reduced).
function ArmorConvertAmountToFactor takes real whichArmor returns real // Computes the damage factor matching the total armor value (1.0 = no reduction, 0.0<f<1.0 = reduced).
function ArmorConvertFactorToAmount takes real whichFactor returns real // Computes the total armor matching a damage factor.

Pros:
- Can differenciate white, green and total armors of any unit.
- Works for everything: units, heroes, buildings, cargo units, dead units, hidden units, units with armor change or morph abilities (Avatar, Corporeal/Ethereal Form, Berserk, Spiked Armor, ...)
- More intuitive that Blizzard natives.

Cons:
- Installation/settings needs 1 abilities, and copying 3 gameplay constants values.
- Fires "Unit enter <entire map>" events with a unit of same-type, that has Locust ability, is Hidden, and whose UnitUserData is -1. It's already filtered out from the well-known indexers. It should not impact <playable map> or <user-defined region> events. It's up to you to filter it out of your triggers with this event.
- Working with unit indexers: should be fine for all unit indexers except Grimm001's AutoIndex if you use option "UseUnitUserData = false". In that case you must edit its "UnitFilter" method to filter out the units from this system.

How does it work ?
- Use of Gameplay constants to compute agility armor.
- Creates/deletes a copy of the unit to get base+upgrades armor.
- Hooks BlzSetUnitArmor to get code armor and store it in a hashtable.

Requirements
Logarithm by Vexorian (included in test map). Requires by the 3 functions concerning the damage factor.


How-to install (from the Demo Map)

1. Open the demo map. Copy the "Logarightm" and "Armor Library" folders from Trigger Editor into your map.

2. If you already have a Unit Event system with a Detect Remove Ability, skip this step. Otherwise, copy the ability "Unit Event Ability (Remove Detect)" from Object Editor to your map.

3. Go to the global section of ArmorLib script and set the following values:
- AGI_DEFENSE_BASE - Found in "Advanced" > "Gameplay Constants..." > "Hero Attributes - Defense Base Value (before Agility Bonus)"
- AGI_DEFENSE_BONUS - Found in "Advanced" > "Gameplay Constants..." > "Hero Attributes - Defense Bonus per Agility Point"
- DEFENSE_ARMOR - Found in "Advanced" > "Gameplay Constants..." > "Combat - Armor Damage Reduction Multiplier"
- DETECT_REMOVE_ABILITY: rawcode of the "Unit Event Ability" ability pasted in step 4 or from your Unit Event System.


Change logs

v1.0.0 (27/10/2019) - First release
v1.0.1 (28/11/2019) - Replaced unoptimized calls to IsUnitDeadBJ and UnitHasBuffBJ.
v1.0.2 (10/10/2023)
  • Fixed a mistake/typo in function ArmorToDamageFactor .
  • Many renamings to respect Blizzard naming conventions
    • Renamed GetWhiteArmor to UnitGetWhiteArmor.
    • Renamed GetGreenArmor to GetUnitGreenArmor.
    • Renamed GetTotalArmor to GetUnitTotalArmor.
    • Renamed GetCodeArmor to GetUnitCodeDefenseBonus.
    • Renamed GetAgiArmor to GetUnitAgiDefenseBonus.
    • Renamed GetBaseArmor to GetUnitBaseUpgradeDefense.
    • Renamed ModifyArmor to UnitAddWhiteArmor.
    • Renamed AdjustArmorToWhite to UnitSetWhiteArmor.
    • Renamed SetCodeArmor to UnitSetCodeDefenseBonus.
    • Renamed GetUnitDamageFactor to GetUnitTotalArmorFactor.
    • Renamed ArmorToDamageFactor to ArmorConvertAmountToFactor.
    • Renamed DamageFactorToArmor to ArmorConvertFactorToAmount.
  • Removed AdjustArmorToWhitePercent, AdjustArmorToTotal, AdjustArmorToTotalPercent because they're not really usefull, and can be implemented by the user in a modular system.
v2.0.0 (11/10/2023) - Complete overhaul. Easier to install. Less side effects: doesn't loose buffs anymore, doesn't unhides temporarly hidden units, requires 1 less ability and buff.
v2.0.1 (12/10/2023) - Minor improvment by creating the copy at the edges of the map (skips "unit enters <playable area>" events, and "unit enters <user defined region> in most cases).


Source code
JASS:
/*
ArmorUtils v2.0.1 (12/10/2023) by Ricola3D
    requires Logarithm (by Vexorian) https://www.hiveworkshop.com/threads/graveyard-logarithm-by-vexorian.350941/
    
API:
// -----------------------------------------------------------------------------
// Getters
// -----------------------------------------------------------------------------
function GetUnitWhiteArmor takes unit whichUnit returns real // Gets white armor (unit-type + upgrades + agility + code) of the unit.
function GetUnitGreenArmor takes unit whichUnit returns real // Gets green armor (abilities/buffs) of the unit.
function GetUnitTotalArmor takes unit whichUnit returns real // Gets total (white + green) armor of the unit. Same as BlzGetUnitArmor(u) or BlzGetUnitRealField(u, UNIT_RF_DEFENSE)

// If you prefer to manipulate the hidden code bonus directly
function GetUnitCodeDefenseBonus takes unit whichUnit returns real // Gets the vaue of the hidden code armor.

// For information
function GetUnitAgiDefenseBonus takes unit whichUnit, boolean includeBonuses returns real // Gets the white armor bonus from Agility (= AgiDefenseBase + Hero white agility x AgiDefenseBonus)
function GetUnitBaseUpgradeDefense takes unit whichUnit returns real // Gets the base + upgrade defense (= def + upgrade count * defUp)

// -----------------------------------------------------------------------------
// Setters (beware that the only editable value in real time is the Bonus from code)
// -----------------------------------------------------------------------------
function UnitAddWhiteArmor takes unit whichUnit, real armorDiff returns nothing // Adds an amount to white armor (by changing the hidden code value). Negative value substracts.
function UnitSetWhiteArmor takes unit whichUnit, real desiredWhiteArmor returns nothing // Changes the white armor to the desired value (by changing the hidden code value).
function UnitResetWhiteArmor takes unit whichUnit returns nothing // Resets all previous modifications via this system or calls to BlzSetUnitArmor/BlzSetUnitIntegerField(UNIT_RF_DEFENSE).

// If you prefer to manipulate the hidden code bonus directly
function UnitSetCodeDefenseBonus takes unit whichUnit, real desiredCodeArmor returns nothing // Changes the hidden code value. Is 0 by default.

// -----------------------------------------------------------------------------
// Misc
// -----------------------------------------------------------------------------
function GetUnitTotalArmorFactor takes unit whichUnit returns real // Get the damage factor applied to damage received by this unit (1.0 = no reduction, 0.0<f<1.0 = reduced).
function ArmorConvertAmountToFactor takes real whichArmor returns real // Computes the damage factor matching the total armor value (1.0 = no reduction, 0.0<f<1.0 = reduced).
function ArmorConvertFactorToAmount takes real whichFactor returns real // Computes the total armor matching a damage factor.
*/

native UnitAlive takes unit id returns boolean

library ArmorUtils requires Logarithm

    globals
        // ----------------------------------------------------------------------------
        // BASE SETTINGS
        // ----------------------------------------------------------------------------

        // Values shall be taken from gameplay constants
        constant real DEFENSE_ARMOR = 0.060 // "Combat - Armor Damage Reduction Multiplier"/DefenseArmor
        constant real AGI_DEFENSE_BASE = -2.000 // "Hero Attributes - Defense Base Value (before Agility Bonus)"/AgiDefenseBase
        constant real AGI_DEFENSE_BONUS = 0.300 // "Hero Attributes - Defense Bonus per Agility Point"/AgiDefenseBonus
        
        // System cleaning - Ability to detect unit removal from game (derived from Defend - Adef)
        // If you already have a similar ability from on of the systems below, REUSE IT:
        // - Jesus4Lyf' AIDS: LEAVE_DETECTION_ABILITY
        // - Bribe's GUI Unit Event: DetectRemoveAbility
        // - Nestharus' UnitIndexer or Unit Event: ABILITIES_UNIT_INDEXER
        // - Grim001's AutoIndex or AutoEvents: LeaveDetectAbilityID
        // - TriggerHappy's UnitDex: DETECT_LEAVE_ABILITY
        // - Chopinski's Indexer: ability
        // I prefered just using the ability, and not adding any library as requirement
        private constant integer DETECT_REMOVE_ABILITY = 'A001'
        
        // ----------------------------------------------------------------------------
        // ----------------------------------------------------------------------------

        // Coordinates to create the copy
        private real WorldMaxX
        private real WorldMaxY
        
        // A boolean to decide if we catch or not the unit enters region event
        private integer CatchEnteringUnitType = 0

        // Internal storage for code armor value
        private hashtable Memory
    endglobals

    // ----------------------------------------------------------------------------
    // SaveCode.
    // Notes: since there is no native to access internal "code armor" value from Blizzard. This function stores a copy of it.
    // ----------------------------------------------------------------------------
    private function SaveCode takes unit whichUnit, real codeArmor returns nothing
        if ( null != whichUnit ) then
            // In case there is no Unit Event system, add the remove detect ability manually.
            if ( 0 == GetUnitAbilityLevel(whichUnit, DETECT_REMOVE_ABILITY) ) then
                call UnitAddAbility(whichUnit, DETECT_REMOVE_ABILITY)
                call UnitMakeAbilityPermanent(whichUnit, true, DETECT_REMOVE_ABILITY)
            endif
            call SaveReal( Memory, GetHandleId(whichUnit), StringHash("code_armor"), codeArmor )
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitAgiDefenseBonus: get armor bonus from agility. Returns 0 for non-hero units.
    // ----------------------------------------------------------------------------
    function GetUnitAgiDefenseBonus takes unit whichUnit, boolean includeBonuses returns real
        local real agiArmor = 0.0
        local integer agi = 0
        local boolean isHero = false
        if ( null != whichUnit ) then
            set isHero = IsUnitIdType(GetUnitTypeId(whichUnit), UNIT_TYPE_HERO)
            if (isHero) then
                // Unit is hero, illusion of hero, ...
                set agi = GetHeroAgi(whichUnit, includeBonuses)
                set agiArmor = AGI_DEFENSE_BASE + AGI_DEFENSE_BONUS * I2R(agi)
            endif
        endif
        return agiArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitCodeDefenseBonus: get code armor. This is the only armor you can modify directly (with BlzSetUnitArmor primitive).
    // ----------------------------------------------------------------------------
    function GetUnitCodeDefenseBonus takes unit whichUnit returns real
        local real codeArmor = 0.0
        if ( null != whichUnit ) then
            set codeArmor = LoadReal( Memory, GetHandleId(whichUnit), StringHash("code_armor") )
        endif
        return codeArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitBaseUpgradeDefense: get what Blizzard considers "base" armor.
    // If no upgrades, equal to the number in World Editor > Object Editor > Unit > "[def] Combat - Defense base".
    // Otherwise also includes upgrades count x "[defUp] Defense Upgrade Bonus"
    // Notes:
    // - Meaningless in 99% of cases. Equal to white armor ONLY for non-hero units whose armor have not been modified by code...)
    // - This is the value taken as base by Devotion Aura with "[DataB1] Data - Percent Bonus" set to true.
    // ----------------------------------------------------------------------------
    function GetUnitBaseUpgradeDefense takes unit whichUnit returns real
        local player owner
        local unit copy
        local real copyWhiteArmor
        local real copyBaseUpgradeArmor = 0.
        local boolean interfaceIconVisible
        // We create a new unit of the same type and same owner. It has
        // - the same Combat Base Defense & Defense Upgrade Bonus
        // - the same upgrades (because of owner)
        // - no items (=no bonus from them)
        // - no buffs (even auras need 1s to apply)
        // - internal code defense still equal to 0.
        // So base+upgrade is equal for the original unit and the copy.
        if ( whichUnit != null ) then
            set owner = GetOwningPlayer(whichUnit)
            set CatchEnteringUnitType = GetUnitTypeId(whichUnit)
            set copy = CreateUnit(owner, CatchEnteringUnitType, WorldMaxX, WorldMaxY, 0.) // Create a corpse would be safer for side effects (hero UI, buffs...), but it's impossible if unit death type is "can't raise, doesn't decay"...
            // If the unit is a hero, you can remove the death message
            set copyWhiteArmor = BlzGetUnitArmor(copy) // Copy shouldn't have bonus, so white armor = total armor returned by natives
            set copyBaseUpgradeArmor = copyWhiteArmor - GetUnitAgiDefenseBonus(copy, false) // White = base + upgrades + agi + code. For the copy normally code = 0.
            call SetUnitOwner(copy, Player(PLAYER_NEUTRAL_PASSIVE), true) // If hero, easiest way to avoid the death message/sound and left visible portrait on the left of the screen after killing or removing. Should also avoid interfering with the score.
            call RemoveUnit(copy)
        endif
        return copyBaseUpgradeArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitWhiteArmor: get armor before bonus (= number displayed in white in unit status).
    // ----------------------------------------------------------------------------
    function GetUnitWhiteArmor takes unit whichUnit returns real
        local real whiteArmor = 0.0
        local integer baseAgi = 0
        if ( null != whichUnit ) then
            set whiteArmor = GetUnitBaseUpgradeDefense(whichUnit) + GetUnitAgiDefenseBonus(whichUnit, false) + GetUnitCodeDefenseBonus(whichUnit)
        endif
        return whiteArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetTotal: get total armor. Same as BlzGetUnitArmor.
    // ----------------------------------------------------------------------------
    function GetUnitTotalArmor takes unit whichUnit returns real
        local real armor = 0.0
        if ( null != whichUnit ) then
            set armor = BlzGetUnitArmor(whichUnit)
        endif
        return armor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitGreenArmor: get current armor bonus (= number displayed in green in unit status.
    // ----------------------------------------------------------------------------
    function GetUnitGreenArmor takes unit whichUnit returns real
        return ( GetUnitTotalArmor(whichUnit) - GetUnitWhiteArmor(whichUnit) )
    endfunction

    // ----------------------------------------------------------------------------
    // UnitAddWhiteArmor: apply difference to unit code armor (and thus to white armor). Difference can be positive or negative.
    // Also modifies white armor and total armor of the same difference.
    // ----------------------------------------------------------------------------
    function UnitAddWhiteArmor takes unit whichUnit, real armorDiff returns nothing
        local real currentTotalArmor = 0.0
        if ( null != whichUnit ) then
            set currentTotalArmor = BlzGetUnitArmor(whichUnit)
            call BlzSetUnitArmor(whichUnit, currentTotalArmor + armorDiff )
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // SetCode: set unit code armor to desired value.
    // ----------------------------------------------------------------------------
    function UnitSetCodeDefenseBonus takes unit whichUnit, real desiredCodeArmor returns nothing
        local real currentCodeArmor = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            set currentCodeArmor = GetUnitCodeDefenseBonus(whichUnit)
            set armorDiff = desiredCodeArmor - currentCodeArmor
            call UnitAddWhiteArmor(whichUnit, armorDiff)
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // UnitSetWhiteArmor: set unit code armor such as white armor reaches the desired amount.
    // ----------------------------------------------------------------------------
    function UnitSetWhiteArmor takes unit whichUnit, real desiredWhiteArmor returns nothing
        local real currentWhiteArmor = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            set currentWhiteArmor = GetUnitWhiteArmor(whichUnit)
            set armorDiff = desiredWhiteArmor - currentWhiteArmor
            call UnitAddWhiteArmor(whichUnit, armorDiff)
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // UnitResetWhiteArmor: reset unit white armor to default, i.e. code armor to 0.
    // ----------------------------------------------------------------------------
    function UnitResetWhiteArmor takes unit whichUnit returns nothing
        call UnitSetCodeDefenseBonus(whichUnit, 0.)
    endfunction
    
    // ----------------------------------------------------------------------------
    // ArmorConvertAmountToFactor: get damage modifier corresponding to a armor value.
    // Notes:
    // - "0.02" means damages are reduced by 98%.
    // - "1.25" means damages are amplified by 25%.
    // - For the moment Blizzard displays -71% as min reduction
    // ----------------------------------------------------------------------------
    function ArmorConvertAmountToFactor takes real whichArmor returns real
        local real factor = 0.0
        if (whichArmor >= 0) then
            // Damage reduction - factor is inferior or equal to 1
            set factor = 1 - ( whichArmor * DEFENSE_ARMOR ) / ( 1 + whichArmor * DEFENSE_ARMOR )
        else
            // DamageAmplification - factor is superior to 1
            set factor = 2 - Pow( 1 - DEFENSE_ARMOR , -1 * whichArmor )
        endif
        return factor
    endfunction

    // ----------------------------------------------------------------------------
    // ArmorConvertFactorToAmount: computes the armor necessary to reach this damage factor.
    // Notes:
    // - Does not work for 0 walue (= dodge, nullify damage).
    // - Does not work for negative values (=heal).
    // ----------------------------------------------------------------------------
    function ArmorConvertFactorToAmount takes real whichFactor returns real
        local real armor = 0.0
        if (whichFactor <= 0.0) then
            // Negative factor does not work - infinite armor or healing
        elseif (whichFactor <= 1.0) then
            // Damage reduction - armor is positive or null
            set armor = ( 1 - whichFactor ) / ( whichFactor * DEFENSE_ARMOR )
        else
            // Damage amplification - armor is negative
            set armor = -1.0 * Logarithm( 1.0 - DEFENSE_ARMOR, 2.0 - whichFactor )
        endif
        return armor
    endfunction
    
    // ----------------------------------------------------------------------------
    // GetUnitTotalArmorFactor: get current damage factor taking into account total armor. Does not consider attack type, damage type and armor type!
    // Notes:
    // - If factor is <1, then damage reduction
    // - If factor >1, then damage amplification
    // - If factor ==1, damages unchanged, null armor.
    // - If factor ==0, then error.
    // ----------------------------------------------------------------------------
    function GetUnitTotalArmorFactor takes unit whichUnit returns real
        local real damageFactor = 0.0
        local real totalArmor = 0.0
        if ( null != whichUnit ) then
            set totalArmor = BlzGetUnitArmor(whichUnit)
            set damageFactor = ArmorConvertAmountToFactor(totalArmor)
        endif
        return damageFactor
    endfunction

    // ----------------------------------------------------------------------------
    // onBlzSetUnitArmor: called just before everytime code sets unit armor with "BlzSetUnitArmor" native.
    // Notes: since there is no native to access internal "code armor" value from Blizzard. This function stores a copy of it.
    // ----------------------------------------------------------------------------
    private function onBlzSetUnitArmor takes unit whichUnit, real armorAmount returns nothing
        local real previousCodeArmor = 0.0
        local real nextCodeArmor = 0.0
        local real previousArmorAmount = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            // Detect armor difference
            set previousArmorAmount = BlzGetUnitArmor(whichUnit)
            set armorDiff = armorAmount - previousArmorAmount

            // Get previous code armor (0 if not existing), update it, and save-it back
            set previousCodeArmor = GetUnitCodeDefenseBonus(whichUnit)
            set nextCodeArmor = previousCodeArmor + armorDiff
            call SaveCode(whichUnit, nextCodeArmor)
        endif
    endfunction
    hook BlzSetUnitArmor onBlzSetUnitArmor

    // ----------------------------------------------------------------------------
    // onBlzSetUnitRealField: called just before everytime code sets unit armor with "BlzSetUnitRealField" native for UNIT_RF_DEFENSE field.
    // Notes: internally just calls onBlzSetUnitArmor function.
    // ----------------------------------------------------------------------------
    private function onBlzSetUnitRealField takes unit whichUnit, unitrealfield whichField, real value returns nothing
        if ( UNIT_RF_DEFENSE == whichField ) then
            call onBlzSetUnitArmor(whichUnit, value)
        endif
    endfunction
    hook BlzSetUnitRealField onBlzSetUnitRealField

    private function onUnitEnter takes nothing returns boolean   
        // Unfortunately there is no way to completely remove the unit before other listeners of the "Unit enter region" event are triggered...
        // So let's try to minimize side effects
        // Creating the copy on the edges of the maps already minimize the odds of it being detected by "unit enters region" events (with playable map area, or with user-defined regions).
        local unit u = GetFilterUnit()
        local integer unitType = GetUnitTypeId(u)
        
        if unitType == CatchEnteringUnitType then
            // We only catch once
            set CatchEnteringUnitType = 0
            
            // It's not necessary to have it visible
            call ShowUnit(u, false)
            
            // We could pause it if we want
            //call PauseUnit(u, false)
            
            // Many players filter out the units with Aloc from their code (cause it generally is dummy caster)
            call UnitAddAbility(u, 'Aloc')
            
            // The following indexers will skip units whose UserData is not 0:
            // - Jesus4Lyf's AIDS: https://www.thehelper.net/threads/advanced-indexing-data-storage.116539/
            // - Grim001's AutoIndex: https://wc3modding.info/4542/autoindex/
            // - TriggerHappy's UnitDex: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
            // - Bribe's GUI Unit Indexer: https://www.hiveworkshop.com/threads/gui-unit-indexer-1-4-0-0.197329/
            // - Chopinski's Indexer: https://www.hiveworkshop.com/threads/damage-interface-vjass-lua.324257/
            call SetUnitUserData(u, -1)
            
            // If you use Grimm001's AutoIndex with option "UseUnitUserData = false", I can't do a thing. Because everything is private.
            // But you can use change the AutoIndex' "UnitFilter" function that is made for that.
            
            // If you use Nestharus' UnitIndexer
            static if LIBRARY_UnitIndexer then
                // Trying to set the condition "if (enabled and not p_UnitIndex.exists(indexedUnit))" to false
                p_UnitIndex.create(u) // Should create an index but not fire the event
            endif
        endif
        return false
    endfunction

    // ----------------------------------------------------------------------------
    // onUnitOrdered: called just before a unit is removed from the game. Cleans internal storage.
    // ----------------------------------------------------------------------------
    private function onUnitOrdered takes nothing returns nothing
        local unit whichUnit = GetTriggerUnit()
        local boolean unitBeingRemoved
        static if LIBRARY_UnitIndexer then // If using Nestharus' Unit Indexer
            local p_UnitIndex index
        endif
        // Because unit has DETECT_REMOVE_ABILITY, it will fire a "undefend" event just before being removed from game.
        if ( OrderId2StringBJ(GetIssuedOrderIdBJ()) == "undefend" ) then
            set unitBeingRemoved = ( 0 == GetUnitAbilityLevel(whichUnit, DETECT_REMOVE_ABILITY) )
            if (unitBeingRemoved) then
                call FlushChildHashtable( Memory, GetHandleId(whichUnit) )
                
                static if LIBRARY_UnitIndexer then // If using Nestharus' Unit Indexer
                    set index = GetUnitUserData(whichUnit)
                    call index.destroy()
                endif
            endif
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // onInit: system initializer
    // ----------------------------------------------------------------------------
    private module M
        private static method onInit takes nothing returns nothing
            local integer i = bj_MAX_PLAYER_SLOTS
            local player p
            local rect worldRect = GetWorldBounds()
            local region worldRegion = CreateRegion()
            local trigger cleanTrigger = CreateTrigger() // trigger to detect unit removal (and clean hashtable)
            local trigger unitEnterRegionTrigger = CreateTrigger() // trigger to catch created copies
            
            // Register coordinates for copies creation
            set WorldMaxX = GetRectMaxX(worldRect)
            set WorldMaxY = GetRectMaxY(worldRect)
            
            // First, deactive DETECT_REMOVE_ABILITY for all players (removes the ability effect, hides the button
            loop
                set i = i - 1
                set p = Player(i)
                call SetPlayerAbilityAvailable(p, DETECT_REMOVE_ABILITY, false)
                exitwhen i == 0
            endloop
            
            // Set up the enter trigger (since we are within module initialization, it should execute before most systems)
            call RegionAddRect(worldRegion, worldRect)
            call RemoveRect(worldRect)
            call TriggerRegisterEnterRegion(unitEnterRegionTrigger, worldRegion, Filter(function onUnitEnter))
            
            // Set up the clean trigger
            call TriggerRegisterAnyUnitEventBJ( cleanTrigger, EVENT_PLAYER_UNIT_ISSUED_ORDER )
            call TriggerAddAction( cleanTrigger, function onUnitOrdered )
            
            // Initialize the memory hashtable
            set Memory = InitHashtable() // Init hashtable (avoid adding dependency to an indexer)
        endmethod
    endmodule
    private struct S extends array
        implement M
    endstruct

endlibrary
Previews
Contents

ArmorUtils (Map)

Thanks for the hit-up, Daffa.

Browsing the code, it appears that the script is not fully optimized (about 97% of the code is optimized). The usage of IsUnitDeadBJ, UnitHasBuffBJ, and what appears to be a debug message that has been left uncommented in the system is prevalent in the function GetBaseArmor. Other than that, the system is mighty fine, being very lightweight and flexible towards JASS and GUI users alike.

This is also a decent way to show the capabilities of some more obscure, new unit-related natives.

Approved
 
Level 12
Joined
Feb 27, 2019
Messages
399
Thanks for the hit-up, Daffa.

Browsing the code, it appears that the script is not fully optimized (about 97% of the code is optimized). The usage of IsUnitDeadBJ, UnitHasBuffBJ, and what appears to be a debug message that has been left uncommented in the system is prevalent in the function GetBaseArmor. Other than that, the system is mighty fine, being very lightweight and flexible towards JASS and GUI users alike.

This is also a decent way to show the capabilities of some more obscure, new unit-related natives.

Approved

Thanks a lot for your feedback dude ! I just looked but couldn't find any IsUnitDead and UnitHasBuff natives. Do they exist ? Otherwise is there a replacement snippet that is optimized ? :)
 
Level 18
Joined
Oct 17, 2012
Messages
820
To replace IsUnitDeadBJ, declare this line native UnitAlive takes unit id returns boolean anywhere in a custom script or in the map header and then simply use this native instead.

You can replace UnitHasBuffBJ with its underlying code instead.
 
Last edited:
Level 12
Joined
Feb 27, 2019
Messages
399
To replace IsUnitDeadBJ, declare this line native UnitAlive takes unit id returns boolean anywhere in a custom script or in the map header and then simply use this native instead.

You can replace UnitHasBuffBJ with its underlying code instead.

So if I understand well:
- native UnitAlive takes unit id returns boolean is an undeclared native that is defined in common.ai. Interesting I had no idea some "hidden" natives existed and could be used ^^
- For UnitHasBuffBJ, I should declare a custom native or function with the defintion being the code found in UnitHasBuffBJ ?
 
Level 11
Joined
Jul 4, 2016
Messages
627
Having a strange bug, when creeps attack a unit with this library installed, the unit leave detection ability would show up and replace the unit's q ability.
 
Level 11
Joined
Jul 4, 2016
Messages
627
No worries, I find out the issue, it was because I had two Unit detection abilities and they were conflicting with each other.
 
Level 12
Joined
Feb 27, 2019
Messages
399
You could provide a library that wraps the old functions around the new functions to ease the transition to your current release.
Right.
Anyway I'm still working on this system. The fact than I remove buffs to get the white armor, i.e. temporary buffs are lost. I'm looking for alternate ways to get the white armor. Maybe creating a copy of the unit outside world bounds, hide & kill it. Or anything else that would reduce side effects ^^
 
Last edited:
Level 12
Joined
Feb 27, 2019
Messages
399
I've a another working version. Instead of a (base+upgrade) nullify ability (requires unhidding) & removing all items & all buffs temporary (some buffs are lost), it creates a corpse of same unit-type and destroys it just after.

Pros:
  • It requires 1 less ability and 1 less buff
  • It doesn't require temporary unhiding hidden units.
  • It doesn't remove temporary buffs anymore.
  • It doesn't fire inventory/item events anymore.

Cons:
- It fires "unit enter region"/unit indexed/unit deindexed events because I temporay create corpses to get the base + upgrade armor value. And the corpses don't have "locust" (I know that many authors filter out units with locust because it's often dummy casters).


Does it sound better to you from the viewpoint of map-authors that want minimal side effects ?

...
For know, I don't know it it's possible to not fire those unit enters region/index/deindex events... 😢

JASS:
/*
ArmorUtils v1.0.2 (10/10/2023) by Ricola3D
    requires Logarithm (by Vexorian) https://www.hiveworkshop.com/threads/graveyard-logarithm-by-vexorian.350941/
  
API:
// -----------------------------------------------------------------------------
// Getters
// -----------------------------------------------------------------------------
function GetUnitWhiteArmor takes unit whichUnit returns real // Gets white armor (unit-type + upgrades + agility + code) of the unit.
function GetUnitGreenArmor takes unit whichUnit returns real // Gets green armor (abilities/buffs) of the unit.
function GetUnitTotalArmor takes unit whichUnit returns real // Gets total (white + green) armor of the unit. Same as BlzGetUnitArmor(u) or BlzGetUnitRealField(u, UNIT_RF_DEFENSE)

// If you prefer to manipulate the hidden code bonus directly
function GetUnitCodeDefenseBonus takes unit whichUnit returns real // Gets the vaue of the hidden code armor.

// For information
function GetUnitAgiDefenseBonus takes unit whichUnit, boolean includeBonuses returns real // Gets the white armor bonus from Agility (= AgiDefenseBase + Hero white agility x AgiDefenseBonus)
function GetUnitBaseUpgradeDefense takes unit whichUnit returns real // Gets the base + upgrade defense (= def + upgrade count * defUp)

// -----------------------------------------------------------------------------
// Setters (beware that the only editable value in real time is the Bonus from code)
// -----------------------------------------------------------------------------
function UnitAddWhiteArmor takes unit whichUnit, real armorDiff returns nothing // Adds an amount to white armor (by changing the hidden code value). Negative value substracts.
function UnitSetWhiteArmor takes unit whichUnit, real desiredWhiteArmor returns nothing // Changes the white armor to the desired value (by changing the hidden code value).

// If you prefer to manipulate the hidden code bonus directly
function UnitSetCodeDefenseBonus takes unit whichUnit, real desiredCodeArmor returns nothing // Changes the hidden code value. Is 0 by default.

// -----------------------------------------------------------------------------
// Misc
// -----------------------------------------------------------------------------
function GetUnitTotalArmorFactor takes unit whichUnit returns real // Get the damage factor applied to damage received by this unit (1.0 = no reduction, 0.0<f<1.0 = reduced).
function ArmorConvertAmountToFactor takes real whichArmor returns real // Computes the damage factor matching the total armor value (1.0 = no reduction, 0.0<f<1.0 = reduced).
function ArmorConvertFactorToAmount takes real whichFactor returns real // Computes the total armor matching a damage factor.
*/

native UnitAlive takes unit id returns boolean

library ArmorUtils requires Logarithm

    globals
        // ----------------------------------------------------------------------------
        // BASE SETTINGS
        // ----------------------------------------------------------------------------

        // Values shall be taken from gameplay constants
        constant real DEFENSE_ARMOR = 0.060 // "Combat - Armor Damage Reduction Multiplier"/DefenseArmor
        constant real AGI_DEFENSE_BASE = -2.000 // "Hero Attributes - Defense Base Value (before Agility Bonus)"/AgiDefenseBase
        constant real AGI_DEFENSE_BONUS = 0.300 // "Hero Attributes - Defense Bonus per Agility Point"/AgiDefenseBonus

        // System cleaning - Ability to detect unit removal from game
        // If you already have a Unit Event system (GUI Unit Event by Bribe, Unit Event by Nestharus, or AutoEvents by grim001) you can reuse the same ability.
        private constant integer DETECT_REMOVE_ABILITY = 'A001'
      
        // ----------------------------------------------------------------------------
        // ----------------------------------------------------------------------------

        // Internal storage for code armor value
        private hashtable unitData
    endglobals

    // ----------------------------------------------------------------------------
    // SaveCode.
    // Notes: since there is no native to access internal "code armor" value from Blizzard. This function stores a copy of it.
    // ----------------------------------------------------------------------------
    private function SaveCode takes unit whichUnit, real codeArmor returns nothing
        if ( null != whichUnit ) then
            // In case there is no Unit Event system, add the remove detect ability manually.
            if ( 0 == GetUnitAbilityLevel(whichUnit, DETECT_REMOVE_ABILITY) ) then
                call UnitAddAbility(whichUnit, DETECT_REMOVE_ABILITY)
                call UnitMakeAbilityPermanent(whichUnit, true, DETECT_REMOVE_ABILITY)
            endif
            call SaveReal( unitData, GetHandleId(whichUnit), StringHash("code_armor"), codeArmor )
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitAgiDefenseBonus: get armor bonus from agility. Returns 0 for non-hero units.
    // ----------------------------------------------------------------------------
    function GetUnitAgiDefenseBonus takes unit whichUnit, boolean includeBonuses returns real
        local real agiArmor = 0.0
        local integer agi = 0
        local boolean isHero = false
        if ( null != whichUnit ) then
            set isHero = IsUnitIdType(GetUnitTypeId(whichUnit), UNIT_TYPE_HERO)
            if (isHero) then
                // Unit is hero, illusion of hero, ...
                set agi = GetHeroAgi(whichUnit, includeBonuses)
                set agiArmor = AGI_DEFENSE_BASE + AGI_DEFENSE_BONUS * I2R(agi)
                call DisplayTextToForce(GetPlayersAll(), "agi=" + I2S(agi))
                call DisplayTextToForce(GetPlayersAll(), "agiArmor=" + R2S(agiArmor))
            endif
        endif
        return agiArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitCodeDefenseBonus: get code armor. This is the only armor you can modify directly (with BlzSetUnitArmor primitive).
    // ----------------------------------------------------------------------------
    function GetUnitCodeDefenseBonus takes unit whichUnit returns real
        local real codeArmor = 0.0
        if ( null != whichUnit ) then
            set codeArmor = LoadReal( unitData, GetHandleId(whichUnit), StringHash("code_armor") )
        endif
        return codeArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitBaseUpgradeDefense: get what Blizzard considers "base" armor.
    // If no upgrades, equal to the number in World Editor > Object Editor > Unit > "[def] Combat - Defense base".
    // Otherwise also includes upgrades count x "[defUp] Defense Upgrade Bonus"
    // Notes:
    // - Meaningless in 99% of cases. Equal to white armor ONLY for non-hero units whose armor have not been modified by code...)
    // - This is the value taken as base by Devotion Aura with "[DataB1] Data - Percent Bonus" set to true.
    // ----------------------------------------------------------------------------
    function GetUnitBaseUpgradeDefense takes unit whichUnit returns real
        // We create a corpse of the same unit-type (for Combat Base Defense & Defense Upgrade Bonus) and same owner (for Upgrades, and get its armor
        // Plus corpses don't receive auras, nor have items with bonuses, and have no buff on creation.
        local player owner = GetOwningPlayer(whichUnit)
        local integer unitTypeId = GetUnitTypeId(whichUnit)
        local unit corpse = CreateCorpse(owner, unitTypeId, GetUnitX(whichUnit), GetUnitY(whichUnit), 0.)
        local real corpseTotalArmor = BlzGetUnitArmor(corpse)
        // Corpses don't have bonuses, so total armor = white armor
        local real corpseWhiteArmor = corpseTotalArmor
        // White = base + upgrades + agi + code. For the corpse normally code = 0.
        local real corpseBaseUpgradeArmor = corpseWhiteArmor - GetUnitAgiDefenseBonus(corpse, false)
        call RemoveUnit(corpse)
        // Reminder: corpse base + upgrade == unit base + upgrade
        return corpseBaseUpgradeArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitWhiteArmor: get armor before bonus (= number displayed in white in unit status).
    // ----------------------------------------------------------------------------
    function GetUnitWhiteArmor takes unit whichUnit returns real
        local real whiteArmor = 0.0
        local integer baseAgi = 0
        if ( null != whichUnit ) then
            set whiteArmor = GetUnitBaseUpgradeDefense(whichUnit) + GetUnitAgiDefenseBonus(whichUnit, false) + GetUnitCodeDefenseBonus(whichUnit)
        endif
        return whiteArmor
    endfunction

    // ----------------------------------------------------------------------------
    // GetTotal: get total armor. Same as BlzGetUnitArmor.
    // ----------------------------------------------------------------------------
    function GetUnitTotalArmor takes unit whichUnit returns real
        local real armor = 0.0
        if ( null != whichUnit ) then
            set armor = BlzGetUnitArmor(whichUnit)
        endif
        return armor
    endfunction

    // ----------------------------------------------------------------------------
    // GetUnitGreenArmor: get current armor bonus (= number displayed in green in unit status.
    // ----------------------------------------------------------------------------
    function GetUnitGreenArmor takes unit whichUnit returns real
        return ( GetUnitTotalArmor(whichUnit) - GetUnitWhiteArmor(whichUnit) )
    endfunction

    // ----------------------------------------------------------------------------
    // UnitAddWhiteArmor: apply difference to unit code armor (and thus to white armor). Difference can be positive or negative.
    // Also modifies white armor and total armor of the same difference.
    // ----------------------------------------------------------------------------
    function UnitAddWhiteArmor takes unit whichUnit, real armorDiff returns nothing
        local real currentTotalArmor = 0.0
        if ( null != whichUnit ) then
            set currentTotalArmor = BlzGetUnitArmor(whichUnit)
            call BlzSetUnitArmor(whichUnit, currentTotalArmor + armorDiff )
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // SetCode: set unit code armor to desired value.
    // ----------------------------------------------------------------------------
    function UnitSetCodeDefenseBonus takes unit whichUnit, real desiredCodeArmor returns nothing
        local real currentCodeArmor = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            set currentCodeArmor = GetUnitCodeDefenseBonus(whichUnit)
            set armorDiff = desiredCodeArmor - currentCodeArmor
            call UnitAddWhiteArmor(whichUnit, armorDiff)
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // UnitSetWhiteArmor: set unit base armor such as white armor reaches the desired amount.
    // ----------------------------------------------------------------------------
    function UnitSetWhiteArmor takes unit whichUnit, real desiredWhiteArmor returns nothing
        local real currentWhiteArmor = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            set currentWhiteArmor = GetUnitWhiteArmor(whichUnit)
            set armorDiff = desiredWhiteArmor - currentWhiteArmor
            call UnitAddWhiteArmor(whichUnit, armorDiff)
        endif
    endfunction
  
    // ----------------------------------------------------------------------------
    // ArmorConvertAmountToFactor: get damage modifier corresponding to a armor value.
    // Notes:
    // - "0.02" means damages are reduced by 98%.
    // - "1.25" means damages are amplified by 25%.
    // - For the moment Blizzard displays -71% as min reduction
    // ----------------------------------------------------------------------------
    function ArmorConvertAmountToFactor takes real whichArmor returns real
        local real factor = 0.0
        if (whichArmor >= 0) then
            // Damage reduction - factor is inferior or equal to 1
            set factor = 1 - ( whichArmor * DEFENSE_ARMOR ) / ( 1 + whichArmor * DEFENSE_ARMOR )
        else
            // DamageAmplification - factor is superior to 1
            set factor = 2 - Pow( 1 - DEFENSE_ARMOR , -1 * whichArmor )
        endif
        return factor
    endfunction

    // ----------------------------------------------------------------------------
    // ArmorConvertFactorToAmount: computes the armor necessary to reach this damage factor.
    // Notes:
    // - Does not work for 0 walue (= dodge, nullify damage).
    // - Does not work for negative values (=heal).
    // ----------------------------------------------------------------------------
    function ArmorConvertFactorToAmount takes real whichFactor returns real
        local real armor = 0.0
        if (whichFactor <= 0.0) then
            // Negative factor does not work - infinite armor or healing
        elseif (whichFactor <= 1.0) then
            // Damage reduction - armor is positive or null
            set armor = ( 1 - whichFactor ) / ( whichFactor * DEFENSE_ARMOR )
        else
            // Damage amplification - armor is negative
            set armor = -1.0 * Logarithm( 1.0 - DEFENSE_ARMOR, 2.0 - whichFactor )
        endif
        return armor
    endfunction
  
    // ----------------------------------------------------------------------------
    // GetUnitTotalArmorFactor: get current damage factor taking into account total armor. Does not consider attack type, damage type and armor type!
    // Notes:
    // - If factor is <1, then damage reduction
    // - If factor >1, then damage amplification
    // - If factor ==1, damages unchanged, null armor.
    // - If factor ==0, then error.
    // ----------------------------------------------------------------------------
    function GetUnitTotalArmorFactor takes unit whichUnit returns real
        local real damageFactor = 0.0
        local real totalArmor = 0.0
        if ( null != whichUnit ) then
            set totalArmor = BlzGetUnitArmor(whichUnit)
            set damageFactor = ArmorConvertAmountToFactor(totalArmor)
        endif
        return damageFactor
    endfunction

    // ----------------------------------------------------------------------------
    // onBlzSetUnitArmor: called just before everytime code sets unit armor with "BlzSetUnitArmor" native.
    // Notes: since there is no native to access internal "code armor" value from Blizzard. This function stores a copy of it.
    // ----------------------------------------------------------------------------
    private function onBlzSetUnitArmor takes unit whichUnit, real armorAmount returns nothing
        local real previousCodeArmor = 0.0
        local real nextCodeArmor = 0.0
        local real previousArmorAmount = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            // Detect armor difference
            set previousArmorAmount = BlzGetUnitArmor(whichUnit)
            set armorDiff = armorAmount - previousArmorAmount

            // Get previous code armor (0 if not existing), update it, and save-it back
            set previousCodeArmor = GetUnitCodeDefenseBonus(whichUnit)
            set nextCodeArmor = previousCodeArmor + armorDiff
            call SaveCode(whichUnit, nextCodeArmor)
        endif
    endfunction
    hook BlzSetUnitArmor onBlzSetUnitArmor

    // ----------------------------------------------------------------------------
    // onBlzSetUnitRealField: called just before everytime code sets unit armor with "BlzSetUnitRealField" native for UNIT_RF_DEFENSE field.
    // Notes: internally just calls onBlzSetUnitArmor function.
    // ----------------------------------------------------------------------------
    private function onBlzSetUnitRealField takes unit whichUnit, unitrealfield whichField, real value returns nothing
        if ( UNIT_RF_DEFENSE == whichField ) then
            call onBlzSetUnitArmor(whichUnit, value)
        endif
    endfunction
    hook BlzSetUnitRealField onBlzSetUnitRealField

    // ----------------------------------------------------------------------------
    // onUnitRemoved: called just before a unit is removed from the game. Cleans internal storage.
    // ----------------------------------------------------------------------------
    private function onUnitRemoved takes nothing returns nothing
        local unit whichUnit = GetTriggerUnit()
        local boolean unitBeingRemoved
        // Because unit has DETECT_REMOVE_ABILITY, it will fire a "undefend" event just before being removed from game.
        if ( OrderId2StringBJ(GetIssuedOrderIdBJ()) == "undefend" ) then
            set unitBeingRemoved = ( 0 == GetUnitAbilityLevel(whichUnit, DETECT_REMOVE_ABILITY) )
            if (unitBeingRemoved) then
                call FlushChildHashtable( unitData, GetHandleId(whichUnit) )
            endif
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // onInit: system initializer
    // ----------------------------------------------------------------------------
    private module M
        private static method onInit takes nothing returns nothing
            local integer i = bj_MAX_PLAYER_SLOTS
            local player p
            local trigger cleanTrigger =  CreateTrigger() // trigger to detect unit removal (and clean hashtable)
            loop
                set i = i - 1
                set p = Player(i)
                call SetPlayerAbilityAvailable(p, DETECT_REMOVE_ABILITY, false)
                exitwhen i == 0
            endloop
            call TriggerRegisterAnyUnitEventBJ( cleanTrigger, EVENT_PLAYER_UNIT_ISSUED_ORDER )
            call TriggerAddAction( cleanTrigger, function onUnitRemoved )
            set unitData = InitHashtable() // Init hashtable (avoid adding dependency to an indexer)
        endmethod
    endmodule
    private struct S extends array
        implement M
    endstruct

endlibrary
 
Level 12
Joined
Feb 27, 2019
Messages
399
Okay, the v2.0.0 is out.
It temporary creates copies of the unit (with Locust ability and Hidden, UnitUserData = -1) and thus fires "Unit enters region" events. But I added code to nullify the impact on indexers.


Also I wonder if, to be more respectfull of Blizzard' naming conventions, I shouldn't rename such system in UnitArmorUtils. Furthermore if I need/create other utils for attacks, hp, mana to distinguish base from bonus. And thus allow the future creation of systems/spells with bonus in percentage of armor/damage/hp/mana... :D
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Okay, the v2.0.0 is out.
It temporary creates copies of the unit (with Locust ability and Hidden, UnitUserData = -1) and thus fires "Unit enters region" events. But I added code to nullify the impact on indexers.


Also I wonder if, to be more respectfull of Blizzard' naming conventions, I shouldn't rename such system in UnitArmorUtils. Furthermore if I need/create other utils for attacks, hp, mana to distinguish base from bonus. And thus allow the future creation of systems/spells with bonus in percentage of armor/damage/hp/mana... :D
nah, I don't call it UnitDamageEngine/UnitAttackIndexer, you don't need to call this UnitArmorUtils.
 
Level 12
Joined
Feb 27, 2019
Messages
399
v2.0.1: creates the copy unit on the edge of the map, to avoid firing "unit enters <playable map>" and most <unit enters <user-defined region>" events.

I added a trigger in the demo to show that it doens't fire "unit enters <playable area>.

@Bribe do you think it would be usefull to separate the unit creation into another code snippet like "CreateUnitSilent" so other authors can use in their own systems ?

Future works:
  • Solve a displeasing result where the code sometimes returns "-0.00" instead of "0.00".
  • Save the base+upgrade armor, and clean/recompute/smart recompute on listening "unit finishes research" event and hooking SetPlayerTechResearched.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
v2.0.1: creates the copy unit on the edge of the map, to avoid firing "unit enters <playable map>" and most <unit enters <user-defined region>" events.

I added a trigger in the demo to show that it doens't fire "unit enters <playable area>.

@Bribe do you think it would be usefull to separate the unit creation into another code snippet like "CreateUnitSilent" so other authors can use in their own systems ?

Future works:
  • Solve a displeasing result where the code sometimes returns "-0.00" instead of "0.00".
  • Save the base+upgrade armor, and clean/recompute/smart recompute on listening "unit finishes research" event and hooking SetPlayerTechResearched.
No, most unit indexers have a filter condition to prevent certain units from being indexed.
 
Level 12
Joined
Feb 27, 2019
Messages
399
No, most unit indexers have a filter condition to prevent certain units from being indexed.

After some testings I just realized the game behaves strangely.

I've made the following a map with a function that does :
1 - a call to "CreateUnit"
2 - a call to "Change unit owner to <neutral passive>".
3 - a call to remove this unit from game.

And for logging, it has the following:
  • A log before & after of each functions above.
  • Triggers for event "unit enters <entire map>" created from within each type of initializer (scope, library, struct, module, global GUI trigger, GUI map initialization).
  • A trigger for event "player property <food used> becomes > -1.0".
  • A trigger for event "unit changes of owner".

And the result is WTF !! Here is the sequence of what happens:
1697114463035.png


The order of "unit enters <entire map>" triggers is totally reversed comparing to initialization order. Plus all of them except the GUI trigger execute during the "CreateUnit()" call, not after. And (just why ???) the GUI trigger doesn't executed as soon as the creation is done, but at the end of the current function/trigger.

And accessorly, the "food used" is also changed during the call too.

What suxx for me is: your GUI Unit Indexer/GUI Unit Event executes first, so I cannot set UnitUserData to -1 before to prevent it from indexing, I must use your "set UnitIndexerEnabled = false" And I don't think I can do "static if" for GUI systems no ? ^^
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
What problem are you trying to solve? What does the custom value matter? If you do not want indexer events to trigger, why not? Also, the "OnUnitCreated" event will clear up the whole synchronous thing that you're seeing with logic happening during the thread where you created the unit.

The reversal of the registration is due to the way Blizzard's engine prioritizes real variable event execution sequence.
 
Level 12
Joined
Feb 27, 2019
Messages
399
What problem are you trying to solve? What does the custom value matter? If you do not want indexer events to trigger, why not? Also, the "OnUnitCreated" event will clear up the whole synchronous thing that you're seeing with logic happening during the thread where you created the unit.

The reversal of the registration is due to the way Blizzard's engine prioritizes real variable event execution sequence.
What problem are you trying to solve?
-> Just reducing event pollution/side effects (because I created units not of type Dummy, nor with 'Aloc' ability in their editor data. I've 0 control on the editor data used as patron to create the unit).

What does the custom value matter?
-> When I checked all the unit indexers code, I saw that almost all of them did index the entering units only if UnitUserData == 0. So it sounded as a good way to prevent my temporary units from being indexed.

What's the "OnUnitCreated" event you're talking about ? It's something from the Lua version of your Unit Event system ? I don't understand what you mean by "it will clear up the whole synchronous thing". Maybe because I never learnt/used Lua scripting langage :S
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I added OnUnitCreated recently. JASS-based maps use UnitIndexEvent becomes Equal to 1.50.

With that event, you can ensure the function that called CreateUnit has concluded or hit a TriggerSleepAction beforehand.

With the usual OnUnitIndexed type event, it all runs before the CreateUnit function call has even returned the unit.
 
Level 12
Joined
Feb 27, 2019
Messages
399
I added OnUnitCreated recently. JASS-based maps use UnitIndexEvent becomes Equal to 1.50.

With that event, you can ensure the function that called CreateUnit has concluded or hit a TriggerSleepAction beforehand.

With the usual OnUnitIndexed type event, it all runs before the CreateUnit function call has even returned the unit.

Gosh Blizzard really coded it like shit. If I sumarize it:
First, "unit enters region" events are fired in reverse order of registration.

Depending of the function you use to register to the event, the call time differs:
  • "TriggerRegisterEnterRegion": executes before the unit is fully created.
  • "TriggerRegisterEnterRectSimple"/"TriggerRegisterEnterRegionSimple": executes after the unit is fully created.

I understand that in "GUI Unit Indexer", you just used the "TriggerRegisterEnterRegion" to create a "UnitIndexEvent = 1.00" event. And in your GUI Unit Event/Lua Unit Event, you used "TriggerRegisterEnterRegion" + a timer to create both "UnitIndexEvent = 1.00" & "UnitIndexEvent = 1.50" events ?

So if I want to skip both events, I must set udg_UnitIndexerEnabled = false before creation, and reset it after a 0s timer right ?


----------
Also a question @Bribe without any link to my issue: were you aware that you could use "TriggerRegisterEnterRectSimple" or "TriggerRegisterEnterRegionSimple" instead of a timer in your unit event system ? Or was it just simpler to code it with a timer ?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I used the timer because there was already a timer being used to handle that sort of logic, but yes part of me wanted to use the condition rather than the boolexpr.

udg_UnitIndexerEnabled = false before creation, and reset it after a 0s timer right

Without the 0s timer. Just set it to true right after the unit is created.
 
Level 12
Joined
Feb 27, 2019
Messages
399
I used the timer because there was already a timer being used to handle that sort of logic, but yes part of me wanted to use the condition rather than the boolexpr.



Without the 0s timer. Just set it to true right after the unit is created.
oh right my bad, since you used a starttimer, i can reset it right after. ty :)
 
Top