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