• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Main indexers: a snippet for basic cross-compatibility ?

Status
Not open for further replies.
Hello,

I am trying to make a Armor system with a unit indexer as requirement. I need to store the "code armor" value of units whose armor is modified by BlzSetUnitArmor native. Then I use it as input in other pieces of code.

I can't use hashtable no ? Because if the handle ID of a removed unit is reused, I'll see the previous value (not cleaned..). However there are many indexers, mainly:
Do you know a JASS snippet of code to have a basic read-compatibility with all those indexers ?
Just a getter for unit index, and a way to "register" a callback on index/deindex events.

I hope so, otherwise I'll have to write it myself :S
 
You can hook RemoveUnit and use a trigger to catch unit death. Flush the entry when either happens to the unit.

You mean:
- On remove: flush
- On Death: get unit boolean field "decayable". If it is not, then flush.
- On decay: flush
?
Does it handle the case of heroes ? Heroes are revivable even after they decay no ? And a dead hero may be removed 'automatically' when the last reference on it is nulled no ? I wonder how the indexers handle the case. Maybe a periodic check if GetUnitUserData returns 0 or not ?

Have a look at TriggerHappy's thread at "Compatibility With Other Indexers".
Thanks for the tip, I'll check it ASAP! I hope the code is not too complicated for a newbie like me^^
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
You mean:
- On remove: flush
- On Death: get unit boolean field "decayable". If it is not, then flush.
- On decay: flush
?
No he means using a unit event system (like those in the spell section and script sections) which uses the footman defend ability to detect when a unit is removed from the game as it generates an undefend order when the unit is removed.

These are the systems behind unit indexers to know when to recycle unit index values.
 
Last edited:
No he means using a unit event system (like those in the spell section and script sections) which uses the footman defend ability to detect when a unit is removed from the game as it generates an undefend order when the unit is removed.

These are the systems behind unit indexers to know when to recycle unit index values.

Thanks for clearing the missunderstanding! Existing unit event systems are:
- GUI Unit Event (by Bribe, includes GUI Unit Indexer, not-compatible with other indexers)
- Unit Event (by Nestharus, uses Unit Indexer, is it compatible with other indexers that uses GetUnitUserData ?)
- AutoEvents (by grim001, requires AutoIndex, not-compatible with other indexers)
Aren't they ? Did I miss one ?

With that I'm back to my cross-compatibility issue no ?


Edit:
I just realized that my system has a function that temporary adds then removes an ability. Instead of removing it, I could disabling it. So I can differentiate:
- Unit without the ability > new, reset the hasthable value to 0 for this handle id
- Unit with ability > do not reset
No ? The only default would be my hasthable will grow in size (because I never clean the parentKey nor childkey..
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
No ? The only default would be my hasthable will grow in size (because I never clean the parentKey nor childkey..
That can be considered a leak.

As I said, use a unit event system which detects unit removal using the undefend order from the human defend ability. One can use this event for clean up.

Of course one could just use Lua tables with weak keys instead as those automatically garbage collect the mappings when the key is garbage collected. However this requires the map be running in Lua mode.
 
That can be considered a leak.

As I said, use a unit event system which detects unit removal using the undefend order from the human defend ability. One can use this event for clean up.

Of course one could just use Lua tables with weak keys instead as those automatically garbage collect the mappings when the key is garbage collected. However this requires the map be running in Lua mode.

Right about the leak.
Mhhh I don't need all the event system, so maybe just recoding myself the part I need to avoid having many dependencies ?
Do you have any idea of how the Defend ability test works ? When do they check if it is still here or gone ?^^
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
Do you have any idea of how the Defend ability test works ? When do they check if it is still here or gone ?^^
One can look at the source code of the event systems. From what I understand a undefend order is issued (which an immediate order event can detect) when the unit is being removed but before it is actually removed.
 
One can look at the source code of the event systems. From what I understand a undefend order is issued (which an immediate order event can detect) when the unit is being removed but before it is actually removed.

Thanks a lot, with that I can design an armor system with many usefull functions:
- Armor_GetWhite > get white armor
- Armor_GetGreen > get armor bonus
- Armor_GetTotal > get total armor
- ToDamageFactor > transform armor to damage factor
- FromDamageFactor > transform damage factor to armor
- Armor_AdjustToWhite
- Armor_AdjustToTotal

And many others less usefull but that can serve some custom spells :)

Exemple of system code below:
JASS:
library Armor initializer onInit requires Logarithm

globals
    // Values shall be taken from gameplay constants
    public constant real AGI_DEFENSE_BASE = 5.00 // "Hero Attributes - Defense Base Value (before Agility Bonus)"
    public constant real AGI_DEFENSE_BONUS = 0.10 // "Hero Attributes - Defense Bonus per Agility Point"
    public constant real DEFENSE_ARMOR = 0.06 // "Combat - Armor Damage Reduction Multiplier"

    // An ability derivated from Devotion Aura that nullifies what Blizzard considers "base" armor.
    public constant integer NULLIFY_BASE_ARMOR_ABILITY = 'A05K'
    public constant integer NULLIFY_BASE_ARMOR_BUFF = 'B01J'

    // 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.
    public constant integer DETECT_REMOVE_ABILITY = 'A06D'

    // Set to true if you want the getter/setters to work for hidden units.
    // Notes:
    // - Works fine except in one case: during "[ANef] Storm, Earth, And Fire" ability cast.
    public constant boolean ENABLE_UNHIDE = true

    // Set to true to enable last ressort action (if nothing else worked to compute armors).
    // Notes:
    // - No known case for the moment, it's just a safe fallback measure.
    // - Will temporary remove items, and remove all buffs (only aura/passive buffs will be re-activate after, not active ones)
    public constant boolean ENABLE_BONUS_REMOVAL = true

    private hashtable unitData
endglobals

    // ----------------------------------------------------------------------------
    // Concepts:
    // - Total armor = white armor + green armor.
    //    > White armor = base armor + agi (bonuses excluded) armor + code armor (an internal variable).
    //        > Base armor: from unit fields "[def] Combat - Defense base" + "[defUp] Defense Upgrade Bonus" * number of defense upgrades.
    //        > Agi armor (bonus excluded): AGI_DEFENSE_BASE + AGI_DEFENSE_BONUS * agility (bonus excluded).
    //        > Code armor: an internal value, not available with JASS natives...
    //    > Green armor = agi bonuses armor + abilities armor.
    //        > Agility bonuses: AGI_DEFENSE_BASE + AGI_DEFENSE_BONUS * agility bonus.
    //        > Abilities armor: sum of all bonuses provided by item abilities, passive abilities & buffs.
    //
    // Modification:
    // - Code armor is the only one you can edit directly by code.
    // - Base armor can only be modified in Object editor or by adding upgrades.
    // - Agi armor can only be modified by changing hero's agility.
    // - Green armor can only be modified by abilities.
    //
    // Other notes:
    // - Non-hero units do not have agility, and thus agility armor neither.
    // - Dead units have green armor = 0.
    // - Base armor is not a constant!! It can be modified when the unit morphs into another type, or when player learns an upgrade.
    // - Devotion aura with "Data - Percent bonus" = true ONLY considers base armor, and totally ignores agi, code & green armors.




    // ----------------------------------------------------------------------------
    // 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

    // ----------------------------------------------------------------------------
    // ToDamageFactor: 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
    // ----------------------------------------------------------------------------
    public function ToDamageFactor 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

    // ----------------------------------------------------------------------------
    // FromDamageFactor: 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).
    // ----------------------------------------------------------------------------
    public function FromDamageFactor takes real whichDamageFactor returns real
        local real armor = 0.0
        if (whichDamageFactor <= 0.0) then
            // Does not work - infinite armor or healing
            call BJDebugMsg("Armor_FromDamageFactor - Negative or null factor:" + R2S(whichDamageFactor) + ".")
        elseif (whichDamageFactor <= 1.0) then
            // Damage reduction - armor is positive or null
            set armor = ( 1 - whichDamageFactor ) / ( whichDamageFactor * DEFENSE_ARMOR )
        else
            // Damage amplification - armor is negative
            set armor = -1.0 * Logarithm_Logarithm( 1.0 - DEFENSE_ARMOR, 2.0 - whichDamageFactor )
        endif
        return armor
    endfunction

    // ----------------------------------------------------------------------------
    // GetAgi: get armor bonus from agility. Returns 0 for non-hero units.
    // ----------------------------------------------------------------------------
    public function GetAgi 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

    // ----------------------------------------------------------------------------
    // GetCode: get code armor. This is the only armor you can modify directly (with BlzSetUnitArmor primitive).
    // ----------------------------------------------------------------------------
    public function GetCode 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

    // ----------------------------------------------------------------------------
    // GetBase: 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.
    // ----------------------------------------------------------------------------
    public function GetBase takes unit whichUnit returns real
        local real totalArmorBefore = 0.0
        local real totalArmorAfter = 0.0
        local real baseArmor = 0.0
static if ENABLE_BONUS_REMOVAL then
        local integer inventorySize = 0
        local integer tempInventorySlot = 0
        local item array unitItems
endif
        local boolean abilityAdded = false
        local boolean buffAdded = false
        local boolean isDead = false
static if ENABLE_UNHIDE then
        local boolean isHidden = false
endif
        if ( null != whichUnit ) then
static if ENABLE_UNHIDE then
            set isHidden = IsUnitHidden(whichUnit)
            if (isHidden) then
                call ShowUnit(whichUnit, true)
            endif
endif
            // Try to compute base armor using the test ability (that nullifies base armor).
            set totalArmorBefore = BlzGetUnitArmor(whichUnit)
            call UnitAddAbility(whichUnit, NULLIFY_BASE_ARMOR_ABILITY)
            set abilityAdded = ( GetUnitAbilityLevel(whichUnit, NULLIFY_BASE_ARMOR_ABILITY) > 0 )
            set buffAdded = UnitHasBuffBJ(whichUnit, NULLIFY_BASE_ARMOR_BUFF)
            if (abilityAdded and buffAdded) then
                // Test worked, base armor has been nullified properly.
                set totalArmorAfter = BlzGetUnitArmor(whichUnit)
                set baseArmor = totalArmorBefore - totalArmorAfter
            else
                // Test failed
                set isDead = IsUnitDeadBJ(whichUnit)
                if (isDead) then
                    // Case A: unit is dead. Thus its green armor is 0, and base = total - agi - code
                    set baseArmor = BlzGetUnitArmor(whichUnit) - GetAgi(whichUnit, false) - GetCode(whichUnit)
static if ENABLE_BONUS_REMOVAL then
                else
                    // Case Z: last resort is to remove all armor bonuses
                    call BJDebugMsg( "Last resort - Unit " + GetUnitName(whichUnit) )
                    // Temporary remove all items
                    set inventorySize = UnitInventorySizeBJ(whichUnit)
                    set tempInventorySlot = 0
                    loop
                        set tempInventorySlot = tempInventorySlot + 1
                        exitwhen tempInventorySlot > inventorySize
                        set unitItems[tempInventorySlot] = UnitItemInSlotBJ(whichUnit, tempInventorySlot)
                        if ( null != unitItems[tempInventorySlot] ) then
                            call UnitRemoveItemFromSlot(whichUnit, tempInventorySlot)
                        endif
                    endlop
                    // Remove all buffs (unfortunately there is no way to restore the active buffs)
                    call UnitRemoveBuffsEx(whichUnit, true, true, true, true, false, true, true) // Remove all buffs but timed life
                    set armor = BlzGetUnitArmor(whichUnit) - GetAgi(whichUnit, false) - GetCode(whichUnit)
                    // Restore all items
                    set tempInventorySlot = 0
                    loop
                        set tempInventorySlot = tempInventorySlot + 1
                        exitwhen tempInventorySlot > inventorySize
                        if ( null != unitItems[tempInventorySlot] ) then
                            call UnitDropItemSlotBJ( whichUnit, unitItems[tempInventorySlot], tempInventorySlot )
                        endif
                    endlop
endif
                endif
            endif
            if (abilityAdded) then
                call UnitRemoveAbility(whichUnit, NULLIFY_BASE_ARMOR_ABILITY)
            endif
            if (buffAdded) then
                call UnitRemoveBuffBJ(NULLIFY_BASE_ARMOR_BUFF, whichUnit)
            endif
static if ENABLE_UNHIDE then
            if (isHidden) then
                call ShowUnit(whichUnit, false)
            endif
endif
        endif
        return baseArmor
    endfunction

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

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

    // ----------------------------------------------------------------------------
    // GetGreen: get current armor bonus (= number displayed in green in unit status.
    // ----------------------------------------------------------------------------
    public function GetGreen takes unit whichUnit returns real
        return ( GetTotal(whichUnit) - GetWhite(whichUnit) )
    endfunction

    // ----------------------------------------------------------------------------
    // Modify: 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.
    // ----------------------------------------------------------------------------
    public function Modify 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.
    // ----------------------------------------------------------------------------
    public function SetCode takes unit whichUnit, real desiredCodeArmor returns nothing
        local real currentCodeArmor = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            set currentCodeArmor = GetCode(whichUnit)
            set armorDiff = desiredCodeArmor - currentCodeArmor
            call Modify(whichUnit, armorDiff)
        endif
    endfunction

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

    // ----------------------------------------------------------------------------
    // AdjustToWhitePercent: set unit base armor such as white armor changes of the desirated percent.
    // Ex: 1.50 will increase white armor of 50%
    // ----------------------------------------------------------------------------
    public function AdjustToWhitePercent takes unit whichUnit, real desiredWhiteArmorPercent returns nothing
        local real currentWhiteArmor = 0.0
        local real armorDiff = 0.0
        if ( null != whichUnit ) then
            set currentWhiteArmor = GetWhite(whichUnit)
            set armorDiff = currentWhiteArmor * (desiredWhiteArmorPercent - 1.0)
            call Modify(whichUnit, armorDiff)
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // AdjustToTotal: set unit base armor such as total reaches the desired amount.
    // Same as BlzSetUnitArmor.
    // ----------------------------------------------------------------------------
    public function AdjustToTotal takes unit whichUnit, real desiredTotalArmor returns nothing
        if ( null != whichUnit ) then
            call BlzSetUnitArmor(whichUnit, desiredTotalArmor)
        endif
    endfunction

    // ----------------------------------------------------------------------------
    // AdjustToTotalPercent: set unit base armor such as total armor changes of the desirated percent.
    // Ex: 1.50 will increase total armor of 50%
    // ----------------------------------------------------------------------------
    public function AdjustToTotalPercent takes unit whichUnit, real desiredTotalArmorPercent returns nothing
        local real currentTotalArmor = 0.0
        if ( null != whichUnit ) then
            set currentTotalArmor = BlzGetUnitArmor(whichUnit)
            call BlzSetUnitArmor( whichUnit, currentTotalArmor * desiredTotalArmorPercent )
        endif
    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 = GetCode(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: intenally 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.
    // ----------------------------------------------------------------------------
    public 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 function onInit takes nothing returns nothing
        local trigger cleanTrigger

        // Init hashtable
        set unitData = InitHashtable()

        // Init a trigger to detect unit removal (and clean hashtable)
        set cleanTrigger = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( cleanTrigger, EVENT_PLAYER_UNIT_ISSUED_ORDER )
        call TriggerAddAction( cleanTrigger, function onUnitRemoved )
    endfunction

endlibrary
 
Last edited:
Status
Not open for further replies.
Top