Using Bitfields To Adjust Stat Value Bonuses

From Valve Developer Community
Jump to: navigation, search

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.