Using Bitfields To Adjust Stat Value Bonuses
First, ensure you have the timers.lua library. That can be found at: [1]
addon_game_mode.lua
Add the following to the top of the script;
require("timers")
In your InitGameMode() function, add these two game event listeners:
ListenToGameEvent( "npc_spawned", Dynamic_Wrap( CAddonGameMode, "OnNPCSpawned" ), self )
ListenToGameEvent( "dota_player_pick_hero", Dynamic_Wrap( CAddonGameMode, "OnPlayerPicked" ), self )
Then add the two functions associated to those listeners.
function CAddonGameMode:OnPlayerPicked( event )
local spawnedUnitIndex = EntIndexToHScript(event.heroindex)
-- Apply timer to update stats
CAddonGameMode:ModifyStatBonuses(spawnedUnitIndex)
end
function CAddonGameMode:OnNPCSpawned( event )
local spawnedUnit = EntIndexToHScript( event.entindex )
if spawnedUnit:IsHero() then
spawnedUnit.strBonus = 0
end
end
The OnPlayerPicked function gets the spawnedUnitIndex and passes it to the ModifyStatBonuses function. This is just to keep the listener clean.
The OnNPCSpawned function is ran for when the player respawns after death. It ensures that the unit spawning is a hero, and then resets the stored value on the hero so that the timer we'll be creating knows to reapply the modifiers.
Next, we'll create the ModifyStatBonuses function.
function CAddonGameMode:ModifyStatBonuses(unit)
local spawnedUnitIndex = unit
print("modifying stat bonuses")
Timers:CreateTimer(DoUniqueString("updateHealth_" .. spawnedUnitIndex:GetPlayerID()), {
endTime = 0.25,
callback = function()
-- ==================================
-- Adjust health based on strength
-- ==================================
-- Get player strength
local strength = spawnedUnitIndex:GetStrength()
--Check if strBonus is stored on hero, if not set it to 0
if spawnedUnitIndex.strBonus == nil then
spawnedUnitIndex.strBonus = 0
end
-- If player strength is different this time around, start the adjustment
if strength ~= spawnedUnitIndex.strBonus then
-- Modifier values
local bitTable = {512,256,128,64,32,16,8,4,2,1}
-- Gets the list of modifiers on the hero and loops through removing and health modifier
local modCount = spawnedUnitIndex:GetModifierCount()
for i = 0, modCount do
for u = 1, #bitTable do
local val = bitTable[u]
if spawnedUnitIndex:GetModifierNameByIndex(i) == "modifier_health_mod_" .. val then
spawnedUnitIndex:RemoveModifierByName("modifier_health_mod_" .. val)
end
end
end
-- Creates temporary item to steal the modifiers from
local healthUpdater = CreateItem("item_health_modifier", nil, nil)
for p=1, #bitTable do
local val = bitTable[p]
local count = math.floor(strength / val)
if count >= 1 then
healthUpdater:ApplyDataDrivenModifier(spawnedUnitIndex, spawnedUnitIndex, "modifier_health_mod_" .. val, {})
strength = strength - val
end
end
-- Cleanup
UTIL_RemoveImmediate(healthUpdater)
healthUpdater = nil
end
-- Updates the stored strength bonus value for next timer cycle
spawnedUnitIndex.strBonus = spawnedUnitIndex:GetStrength()
return 0.25
end
})
end
Basically, what this does is every 0.25 seconds it cycles through checks to see if the strength bonus this time around varies from the strength that is stored on the hero in the spawnedUnitIndex.strBonus variable. That variable is initialized the first run through. If it finds that the strength is different, then it removes all of the bonus modifiers on the hero, then loops through and re-adds the proper amount.
The whole point of the bitfield is to reduce the amount of modifiers required to be removed and re-added. For example, say you have 38 strength. Instead of adding 38 individual +1 modifiers, the bitfield will add 1 +32 modifer, 1 +4 modifier, and 2 +1 modifiers. You'll see where these come from in the next part.
npc_items_custom.txt
This part is where we create a new item. The whole point of this item is to be a dummy that we use to apply modifiers from onto the hero using ApplyDataDrivenModifier in the ModifyStatBonuses() function.
"item_health_modifier"
{
"ID" "2102"
// General
//-------------------------------------------------------------------------------------------------------------
"AbilityBehavior" "DOTA_ABILITY_BEHAVIOR_NO_TARGET | DOTA_ABILITY_BEHAVIOR_IMMEDIATE"
"BaseClass" "item_datadriven"
"Modifiers"
{
"modifier_health_mod_1"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "21"
}
}
"modifier_health_mod_2"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "42"
}
}
"modifier_health_mod_4"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "84"
}
}
"modifier_health_mod_8"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "168"
}
}
"modifier_health_mod_16"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "336"
}
}
"modifier_health_mod_32"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "672"
}
}
"modifier_health_mod_64"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "1344"
}
}
"modifier_health_mod_128"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "2688"
}
}
"modifier_health_mod_256"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "5376"
}
}
"modifier_health_mod_512"
{
"Passive" "1"
"IsHidden" "1"
"Attributes" "MODIFIER_ATTRIBUTE_MULTIPLE"
"Properties"
{
// You may use "modifierProperty" completions within quotes here. Below is an example
"MODIFIER_PROPERTY_HEALTH_BONUS" "10752"
}
}
}
}
Since I won't be going much over multiple counts of the 512 modifier, I didn't find it necessary to go any higher with the modifiers. You can go as high as you'd like though, just keep doubling the value - 1024, 2048, 4096, etc.
Basically, this item has the 10 modifiers that will be used in our bitfield modifier assignment. You can add any properties you want - all I needed was extra health. As you can see, I just added 21 health per strength in each modifier. "modifier_health_mod_1" is just 21, while "modifier_health_mod_512" is 512*21.
This same method can be used for all of the stats. Instead of GetStrength(), you can also use GetPrimaryStatValue() if you want to provide bonuses based on the hero's primary stat. I use that for adding damage (bumping it from Dota's 1 damage per 1 primary stat up to 2.5 damage per 1 primary stat.) GetLevel() would work the same way for applying level-based bonuses.