Quantcast
--[[--------------------------------------------------------------------
    Copyright (C) 2012 Sidoine De Wispelaere.
    Copyright (C) 2012, 2013, 2014 Johnny C. Lam.
    See the file LICENSE.txt for copying permission.
--]]--------------------------------------------------------------------

local OVALE, Ovale = ...
local OvalePower = Ovale:NewModule("OvalePower", "AceEvent-3.0")
Ovale.OvalePower = OvalePower

--<private-static-properties>
local L = Ovale.L
local OvaleDebug = Ovale.OvaleDebug
local OvaleProfiler = Ovale.OvaleProfiler

-- Forward declarations for module dependencies.
local OvaleAura = nil
local OvaleFuture = nil
local OvaleGUID = nil
local OvaleData = nil
local OvaleState = nil

local ceil = math.ceil
local format = string.format
local gsub = string.gsub
local pairs = pairs
local strmatch = string.match
local tconcat = table.concat
local tonumber = tonumber
local tostring = tostring
local wipe = wipe
local API_CreateFrame = CreateFrame
local API_GetPowerRegen = GetPowerRegen
local API_UnitPower = UnitPower
local API_UnitPowerMax = UnitPowerMax
local API_UnitPowerType = UnitPowerType
local INFINITY = math.huge

-- Register for debugging messages.
OvaleDebug:RegisterDebugging(OvalePower)
-- Register for profiling.
OvaleProfiler:RegisterProfiling(OvalePower)

-- Table of functions to update spellcast information to register with OvaleFuture.
local self_updateSpellcastInfo = {}
-- List of resources that have finishers that we need to save to the spellcast.
local self_SpellcastInfoPowerTypes = { "chi", "holy" }

-- Frame for resolving strings.
local self_button = nil
-- Frame for tooltip-scanning.
local self_tooltip = nil
-- Table of Lua patterns for matching spell costs in tooltips.
local self_costPatterns = {}

-- Map suffix for buff parameters to multiplier for spell cost reduction.
local BUFF_PERCENT_REDUCTION = {
	["_less15"] = 0.15,
	["_less50"] = 0.50,
	["_less75"] = 0.75,
	["_half"] = 0.5,
}

do
	local debugOptions = {
		power = {
			name = L["Power"],
			type = "group",
			args = {
				power = {
					name = L["Power"],
					type = "input",
					multiline = 25,
					width = "full",
					get = function(info) return OvaleState.state:DebugPower() end,
				},
			},
		},
	}
	-- Insert debug options into OvaleDebug.
	for k, v in pairs(debugOptions) do
		OvaleDebug.options.args[k] = v
	end
end
--</private-static-properties>

--<public-static-properties>
-- Player's current power type (key for POWER table).
OvalePower.powerType = nil
-- Player's current power; power[powerType] = number.
OvalePower.power = {}
-- Player's current max power; maxPower[powerType] = number.
OvalePower.maxPower = {}
-- Player's current power regeneration rate for the active power type.
OvalePower.activeRegen = 0
OvalePower.inactiveRegen = 0

OvalePower.POWER_INFO =
{
	alternate = { id = SPELL_POWER_ALTERNATE_POWER, token = "ALTERNATE_RESOURCE_TEXT", mini = 0 },
	burningembers = { id = SPELL_POWER_BURNING_EMBERS, token = "BURNING_EMBERS", mini = 0, segments = true, costString = BURNING_EMBERS_COST },
	chi = { id = SPELL_POWER_CHI, token = "CHI", mini = 0, costString = CHI_COST },
	demonicfury = { id = SPELL_POWER_DEMONIC_FURY, token = "DEMONIC_FURY", mini = 0, costString = DEMONIC_FURY_COST },
	energy = { id = SPELL_POWER_ENERGY, token = "ENERGY", mini = 0, costString = ENERGY_COST },
	focus = { id = SPELL_POWER_FOCUS, token = "FOCUS", mini = 0, costString = FOCUS_COST },
	holy = { id = SPELL_POWER_HOLY_POWER, token = "HOLY_POWER", mini = 0, costString = HOLY_POWER_COST },
	mana = { id = SPELL_POWER_MANA, token = "MANA", mini = 0, costString = MANA_COST },
	rage = { id = SPELL_POWER_RAGE, token = "RAGE", mini = 0, costString = RAGE_COST },
	runicpower = { id = SPELL_POWER_RUNIC_POWER, token = "RUNIC_POWER", mini = 0, costString = RUNIC_POWER_COST },
	shadoworbs = { id = SPELL_POWER_SHADOW_ORBS, token = "SHADOW_ORBS", mini = 0 },
	shards = { id = SPELL_POWER_SOUL_SHARDS, token = "SOUL_SHARDS", mini = 0, costString = SOUL_SHARDS_COST },
}

-- Power types that can regenerate/pool over time with no actions.
OvalePower.PRIMARY_POWER = {
	energy = true,
	focus = true,
	mana = true,
}

OvalePower.POWER_TYPE = {}
do
	for powerType, v in pairs(OvalePower.POWER_INFO) do
		OvalePower.POWER_TYPE[v.id] = powerType
		OvalePower.POWER_TYPE[v.token] = powerType
	end
end

-- POOLED_RESOURCE[class] = powerType
OvalePower.POOLED_RESOURCE = {
	["DRUID"] = "energy",
	["HUNTER"] = "focus",
	["ROGUE"] = "energy"
}
--</public-static-properties>

--<private-static-methods>
-- Manage spellcast[power] information.
local function SaveToSpellcast(spellcast)
	local spellId = spellcast.spellId
	if spellId then
		local si = OvaleData.spellInfo[spellId]
		for _, powerType in pairs(self_SpellcastInfoPowerTypes) do
			if si[powerType] == "finisher" then
				-- Get the maximum cost of the finisher.
				local maxCostParam = "max_" .. powerType
				local maxCost = si[maxCostParam] or 1
				local target = OvaleGUID:GetUnitId(spellcast.target)
				local cost = OvaleData:GetSpellInfoProperty(spellId, powerType, target)
				if cost == "finisher" then
					-- This finisher costs up to maxCost resources.
					local power = OvalePower.power[powerType]
					if power > maxCost then
						cost = maxCost
					else
						cost = power
					end
				elseif cost == 0 then
					-- If this is a finisher that costs no resources, then treat it as using the maximum cost.
					cost = maxCost
				end
				-- Save the cost to the spellcast table.
				spellcast[powerType] = cost
			end
		end
	end
end

local function UpdateFromSpellcast(dest, spellcast)
	for _, powerType in pairs(self_SpellcastInfoPowerTypes) do
		if spellcast[powerType] then
			dest[powerType] = spellcast[powerType]
		end
	end
end

do
	self_updateSpellcastInfo.SaveToSpellcast = SaveToSpellcast
	self_updateSpellcastInfo.UpdateFromSpellcast = UpdateFromSpellcast
end
--</private-static-methods>

--<public-static-methods>
function OvalePower:OnInitialize()
	-- Resolve module dependencies.
	OvaleAura = Ovale.OvaleAura
	OvaleData = Ovale.OvaleData
	OvaleFuture = Ovale.OvaleFuture
	OvaleGUID = Ovale.OvaleGUID
	OvaleState = Ovale.OvaleState

	-- Create the tooltip used for scanning.
	self_tooltip = API_CreateFrame("GameTooltip", "OvalePower_ScanningTooltip", nil, "GameTooltipTemplate")
	self_tooltip:SetOwner(UIParent, "ANCHOR_NONE")

	-- Populate the table of patterns to match spell costs in tooltips.
	self_button = API_CreateFrame("Button")
	for powerType, powerInfo in pairs(self.POWER_INFO) do
		local costString = powerInfo.costString
		if costString then
			for i = 1, 3 do
				-- Resolve the string then extract it again.
				self_button:SetFormattedText(format(costString, i))
				local text = self_button:GetText()
				local pattern = gsub(text, tostring(i), "(%%d)")
				self_costPatterns[pattern] = powerType
			end
		end
	end
end

function OvalePower:OnEnable()
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "EventHandler")
	self:RegisterEvent("PLAYER_LEVEL_UP", "EventHandler")
	self:RegisterEvent("UNIT_DISPLAYPOWER")
	self:RegisterEvent("UNIT_LEVEL")
	self:RegisterEvent("UNIT_MAXPOWER")
	self:RegisterEvent("UNIT_POWER")
	self:RegisterEvent("UNIT_POWER_FREQUENT", "UNIT_POWER")
	self:RegisterEvent("UNIT_RANGEDDAMAGE")
	self:RegisterEvent("UNIT_SPELL_HASTE", "UNIT_RANGEDDAMAGE")
	self:RegisterMessage("Ovale_StanceChanged", "EventHandler")
	self:RegisterMessage("Ovale_TalentsChanged", "EventHandler")
	for powerType in pairs(self.POWER_INFO) do
		OvaleData:RegisterRequirement(powerType, "RequirePowerHandler", self)
	end
	OvaleState:RegisterState(self, self.statePrototype)
	OvaleFuture:RegisterSpellcastInfo(self_updateSpellcastInfo)
end

function OvalePower:OnDisable()
	OvaleState:UnregisterState(self)
	OvaleFuture:UnregisterSpellcastInfo(self_updateSpellcastInfo)
	for powerType in pairs(self.POWER_INFO) do
		OvaleData:UnregisterRequirement(powerType)
	end
	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
	self:UnregisterEvent("PLAYER_LEVEL_UP")
	self:UnregisterEvent("UNIT_DISPLAYPOWER")
	self:UnregisterEvent("UNIT_LEVEL")
	self:UnregisterEvent("UNIT_MAXPOWER")
	self:UnregisterEvent("UNIT_POWER")
	self:UnregisterEvent("UNIT_POWER_FREQUENT")
	self:UnregisterEvent("UNIT_RANGEDDAMAGE")
	self:UnregisterEvent("UNIT_SPELL_HASTE")
	self:UnregisterMessage("Ovale_StanceChanged")
	self:UnregisterMessage("Ovale_TalentsChanged")
end

function OvalePower:EventHandler(event)
	self:UpdatePowerType(event)
	self:UpdateMaxPower(event)
	self:UpdatePower(event)
	self:UpdatePowerRegen(event)
end

function OvalePower:UNIT_DISPLAYPOWER(event, unitId)
	if unitId == "player" then
		self:UpdatePowerType(event)
		self:UpdatePowerRegen(event)
	end
end

function OvalePower:UNIT_LEVEL(event, unitId)
	if unitId == "player" then
		self:EventHandler(event)
	end
end

function OvalePower:UNIT_MAXPOWER(event, unitId, powerToken)
	if unitId == "player" then
		local powerType = self.POWER_TYPE[powerToken]
		if powerType then
			self:UpdateMaxPower(event, powerType)
		end
	end
end

function OvalePower:UNIT_POWER(event, unitId, powerToken)
	if unitId == "player" then
		local powerType = self.POWER_TYPE[powerToken]
		if powerType then
			self:UpdatePower(event, powerType)
		end
	end
end

function OvalePower:UNIT_RANGEDDAMAGE(event, unitId)
	if unitId == "player" then
		self:UpdatePowerRegen(event)
	end
end

function OvalePower:UpdateMaxPower(event, powerType)
	self:StartProfiling("OvalePower_UpdateMaxPower")
	if powerType then
		local powerInfo = self.POWER_INFO[powerType]
		self.maxPower[powerType] = API_UnitPowerMax("player", powerInfo.id, powerInfo.segments)
	else
		for powerType, powerInfo in pairs(self.POWER_INFO) do
			self.maxPower[powerType] = API_UnitPowerMax("player", powerInfo.id, powerInfo.segments)
		end
	end
	self:StopProfiling("OvalePower_UpdateMaxPower")
end

function OvalePower:UpdatePower(event, powerType)
	self:StartProfiling("OvalePower_UpdatePower")
	if powerType then
		local powerInfo = self.POWER_INFO[powerType]
		local oldPower = self.power[powerType]
		local power = API_UnitPower("player", powerInfo.id, powerInfo.segments)
		self.power[powerType] = power
		self:Debug(true, "%s: %d -> %d (%s).", event, oldPower, power, powerType)
	else
		for powerType, powerInfo in pairs(self.POWER_INFO) do
			local oldPower = self.power[powerType]
			local power = API_UnitPower("player", powerInfo.id, powerInfo.segments)
			self.power[powerType] = power
			self:Debug(true, "%s: %d -> %d (%s).", event, oldPower, power, powerType)
		end
	end
	self:StopProfiling("OvalePower_UpdatePower")
end

function OvalePower:UpdatePowerRegen(event)
	self:StartProfiling("OvalePower_UpdatePowerRegen")
	self.inactiveRegen, self.activeRegen = API_GetPowerRegen()
	self:StopProfiling("OvalePower_UpdatePowerRegen")
end

function OvalePower:UpdatePowerType(event)
	self:StartProfiling("OvalePower_UpdatePowerType")
	local currentType, currentToken = API_UnitPowerType("player")
	self.powerType = self.POWER_TYPE[currentType]
	self:StopProfiling("OvalePower_UpdatePowerType")
end

function OvalePower:GetSpellCost(spellId, powerType)
	self:StartProfiling("OvalePower_GetSpellCost")
	self_tooltip:SetSpellByID(spellId)
	local spellCost, spellPowerType
	for i = 2, self_tooltip:NumLines() do
		local line = _G["OvalePower_ScanningTooltipTextLeft" .. i]
		local text = line:GetText()
		if text then
			for pattern, pt in pairs(self_costPatterns) do
				if not powerType or pt == powerType then
					local cost = strmatch(text, pattern)
					if cost then
						spellCost = tonumber(cost)
						spellPowerType = pt
						break
					end
				end
			end
			if spellCost and spellPowerType then
				break
			end
		end
	end
	self:StopProfiling("OvalePower_GetSpellCost")
	return spellCost, spellPowerType
end

-- Get power cost of the spell.
-- NOTE: Mirrored in statePrototype below.
function OvalePower:PowerCost(spellId, powerType, target, maximumCost)
	OvalePower:StartProfiling("OvalePower_PowerCost")
	local buffParam = "buff_" .. powerType
	local spellCost = 0
	local si = OvaleData.spellInfo[spellId]
	if si and si[powerType] then
		--[[
			cost == 0 means the that spell uses no resources.
			cost > 0 means that the spell costs resources.
			cost < 0 means that the spell generates resources.
			cost == "finisher" means that the spell uses all of the resources (zeroes it out).
		--]]
		local cost
		if self.GetSpellInfoProperty then
			cost = self.GetSpellInfoProperty(self, spellId, powerType, target)
		else
			cost = OvaleData:GetSpellInfoProperty(spellId, powerType, target)
		end
		if cost == "finisher" then
			-- This spell is a finisher so compute the cost based on the amount of resources consumed.
			cost = (self.power and self.power[powerType]) or self[powerType] or 0
			-- Clamp cost between values defined by min_<powerType> and max_<powerType>.
			local minCostParam = "min_" .. powerType
			local maxCostParam = "max_" .. powerType
			local minCost = si[minCostParam] or 1
			local maxCost = si[maxCostParam]
			if cost < minCost then
				cost = minCost
			end
			if maxCost and cost > maxCost then
				cost = maxCost
			end
		else
			--[[
				Add extra resource generated by presence of a buff.
				"buff_<powerType>" is the spell ID of the buff that causes extra resources to be generated or used.
				"buff_<powerType>_amount" is the amount of extra resources generated or used, defaulting to -1
					(one extra resource generated).
			--]]
			local buffExtraParam = buffParam
			local buffAmountParam = buffParam .. "_amount"
			local buffExtra = si[buffExtraParam]
			if buffExtra then
				local aura, isActiveAura
				if self.GetAura then
					aura = self.GetAura(self, "player", buffExtra, nil, true)
					isActiveAura = self.IsActiveAura(self, aura)
				else
					aura = OvaleAura:GetAura("player", buffExtra, nil, true)
					isActiveAura = OvaleAura:IsActiveAura(aura)
				end
				if isActiveAura then
					local buffAmount = si[buffAmountParam] or -1
					-- Check if this aura has a stacking effect.
					local siAura = OvaleData.spellInfo[buffExtra]
					if siAura and siAura.stacking == 1 then
						buffAmount = buffAmount * aura.stacks
					end
					cost = cost + buffAmount
					self:Log("Spell ID '%d' had %f %s added from aura ID '%d'.", spellId, buffAmount, powerType, aura.spellId)
				end
			end
		end
		--[[
			Compute any multiplier to the resource cost of the spell due to the presence of a buff.
			"buff_<powerType>_less<N>" is the spell Id of the buff that reduces the resource cost by N percent.
		--]]
		local multiplier = 1
		for suffix, reduction in pairs(BUFF_PERCENT_REDUCTION) do
			local buffPercentReduction = si[buffParam .. suffix]
			if buffPercentReduction then
				local aura, isActiveAura
				if self.GetAura then
					aura = self.GetAura(self, "player", buffPercentReduction)
					isActiveAura = self.IsActiveAura(self, aura)
				else
					aura = OvaleAura:GetAura("player", buffPercentReduction)
					isActiveAura = OvaleAura:IsActiveAura(aura)
				end
				if isActiveAura then
					-- Check if this aura has a stacking effect.
					local siAura = OvaleData.spellInfo[buffPercentReduction]
					if siAura and siAura.stacking then
						reduction = reduction * aura.stacks
						-- Clamp to a maximum of 100% reduction.
						if reduction > 1 then
							reduction = 1
						end
					end
					multiplier = multiplier * (1 - reduction)
				end
			end
		end
		--[[
			Apply any percent reductions to cost after fixed reductions are applied.
			This seems to be a consistent Blizzard rule for spell costs so that you
			never end up with a negative spell cost.
		--]]
		if cost > 0 then
			cost = cost * multiplier
		end
		--[[
			Some abilities use "up to" N additional resources if available, e.g., Ferocious Bite.
			Document this with "extra_<powerType>=N" in SpellInfo().
			Add these additional resources to the cost after checking if the spell is resource-free for the base cost.
		--]]
		local extraPowerParam = "extra_" .. powerType
		local extraPower
		if self.GetSpellInfoProperty then
			extraPower = self.GetSpellInfoProperty(self, spellId, extraPowerParam, target)
		else
			extraPower = OvaleData:GetSpellInfoProperty(spellId, extraPowerParam, target)
		end
		if extraPower then
			if not maximumCost then
				-- Clamp the extra power to the remaining power.
				local power = (self.power and self.power[powerType]) or self[powerType] or 0
				power = power > cost and power - cost or 0
				if extraPower >= power then
					extraPower = power
				end
			end
			-- Apply any percent reductions to the extra resource cost.
			if extraPower > 0 then
				extraPower = extraPower * multiplier
				self:Log("Spell ID '%d' will use %d extra %s.", spellId, extraPower, powerType)
			end
			cost = cost + extraPower
		end
		-- Round up to whole number of resources.
		spellCost = ceil(cost)
	else
		-- Determine cost using information from the spell tooltip if there is no SpellInfo() for the spell's cost.
		local cost = OvalePower:GetSpellCost(spellId, powerType)
		if cost then
			spellCost = cost
		end
	end
	OvalePower:StopProfiling("OvalePower_PowerCost")
	return spellCost
end

-- Run-time check that the player has enough power.
-- NOTE: Mirrored in statePrototype below.
function OvalePower:RequirePowerHandler(spellId, requirement, tokenIterator, target)
	local verified = false
	local cost = tokenIterator()
	if cost then
		local powerType = requirement
		cost = self:PowerCost(spellId, powerType, target)
		if cost > 0 then
			local power = (self.power and self.power[powerType]) or self[powerType] or 0
			if power >= cost then
				result = "passed"
				verified = true
			end
		else
			verified = true
		end
		if cost > 0 then
			local result = verified and "passed" or "FAILED"
			self:Log("    Require %f %s: %s", cost, powerType, result)
		end
	else
		Ovale:OneTimeMessage("Warning: requirement '%s' is missing a cost argument.", requirement)
	end
	return verified, requirement
end

function OvalePower:DebugPower()
	self:Print("Power type: %s", self.powerType)
	for powerType, v in pairs(self.power) do
		self:Print("Power (%s): %d / %d", powerType, v, self.maxPower[powerType])
	end
	self:Print("Active regen: %f", self.activeRegen)
	self:Print("Inactive regen: %f", self.inactiveRegen)
end
--</public-static-methods>

--[[----------------------------------------------------------------------------
	State machine for simulator.
--]]----------------------------------------------------------------------------

--<public-static-properties>
OvalePower.statePrototype = {}
--</public-static-properties>

--<private-static-properties>
local statePrototype = OvalePower.statePrototype
--</private-static-properties>

--<state-properties>
--[[
	This block is here for compiler.pl to know that these properties are added to the state machine.

	statePrototype.alternate = nil
	statePrototype.burningembers = nil
	statePrototype.chi = nil
	statePrototype.demonicfury = nil
	statePrototype.energy = nil
	statePrototype.focus = nil
	statePrototype.holy = nil
	statePrototype.mana = nil
	statePrototype.rage = nil
	statePrototype.runicpower = nil
	statePrototype.shadoworbs = nil
	statePrototype.shards = nil
--]]

-- powerRate[powerType] = regen rate
statePrototype.powerRate = nil
--</state-properties>

--<public-static-methods>
-- Initialize the state.
function OvalePower:InitializeState(state)
	for powerType in pairs(self.POWER_INFO) do
		state[powerType] = 0
	end
	state.powerRate = {}
end

-- Reset the state to the current conditions.
function OvalePower:ResetState(state)
	self:StartProfiling("OvalePower_ResetState")
	-- Power levels for each resource.
	for powerType in pairs(self.POWER_INFO) do
		state[powerType] = self.power[powerType] or 0
	end
	-- Clear power regeneration rates for each resource.
	for powerType in pairs(self.POWER_INFO) do
		state.powerRate[powerType] = 0
	end
	-- Set power regeneration for current resource.
	if OvaleFuture.inCombat then
		state.powerRate[self.powerType] = self.activeRegen
	else
		state.powerRate[self.powerType] = self.inactiveRegen
	end
	self:StopProfiling("OvalePower_ResetState")
end

-- Release state resources prior to removing from the simulator.
function OvalePower:CleanState(state)
	for powerType in pairs(self.POWER_INFO) do
		state[powerType] = nil
	end
	for k in pairs(state.powerRate) do
		state.powerRate[k] = nil
	end
end

-- Apply the effects of the spell at the start of the spellcast.
function OvalePower:ApplySpellStartCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, spellcast)
	self:StartProfiling("OvalePower_ApplySpellStartCast")
	-- Channeled spells cost resources at the start of the channel.
	if isChanneled then
		if state.inCombat then
			state.powerRate[self.powerType] = self.activeRegen
		end
		state:ApplyPowerCost(spellId, targetGUID, startCast, endCast, nextCast, isChanneled, spellcast)
	end
	self:StopProfiling("OvalePower_ApplySpellStartCast")
end

-- Apply the effects of the spell on the player's state, assuming the spellcast completes.
function OvalePower:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, spellcast)
	self:StartProfiling("OvalePower_ApplySpellAfterCast")
	-- Instant or cast-time spells cost resources at the end of the spellcast.
	if not isChanneled then
		if state.inCombat then
			state.powerRate[self.powerType] = self.activeRegen
		end
		state:ApplyPowerCost(spellId, targetGUID, startCast, endCast, nextCast, isChanneled, spellcast)
	end
	self:StopProfiling("OvalePower_ApplySpellAfterCast")
end
--</public-static-methods>

--<state-methods>
-- Update the state of the simulator for the power cost of the given spell.
statePrototype.ApplyPowerCost = function(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, spellcast)
	OvalePower:StartProfiling("OvalePower_state_ApplyPowerCost")
	local target = OvaleGUID:GetUnitId(targetGUID)
	local si = OvaleData.spellInfo[spellId]

	-- Update power using information from the spell tooltip if there is no SpellInfo() for the spell's cost.
	do
		local cost, powerType = OvalePower:GetSpellCost(spellId)
		if cost and powerType and state[powerType] and not (si and si[powerType]) then
			state[powerType] = state[powerType] - cost
		end
	end

	if si then
		-- Update power state.
		for powerType, powerInfo in pairs(OvalePower.POWER_INFO) do
			local cost = state:PowerCost(spellId, powerType, target)
			local power = state[powerType] or 0
			if cost then
				power = power - cost
				-- Add any power regenerated or consumed during the cast time of a non-channeled spell.
				if not isChanneled then
					local powerRate = state.powerRate[powerType]
					local gain = powerRate * (nextCast - state.currentTime)
					power = power + gain
				end
				-- Clamp power to lower and upper limits.
				local mini = powerInfo.mini or 0
				local maxi = powerInfo.maxi or OvalePower.maxPower[powerType]
				if mini and power < mini then
					power = mini
				end
				if maxi and power > maxi then
					power = maxi
				end
				state[powerType] = power
			end
		end
	end
	OvalePower:StopProfiling("OvalePower_state_ApplyPowerCost")
end

-- Return the number of seconds before enough of the given power type is available for the spell.
-- If not powerType is given, the the pooled resource for that class is used.
statePrototype.TimeToPower = function(state, spellId, target, powerType)
	local seconds = 0
	powerType = powerType or OvalePower.POOLED_RESOURCE[state.class]
	if powerType then
		local power = state[powerType]
		local powerRate = state.powerRate[powerType]
		local cost = state:PowerCost(spellId, powerType, target)
		if power < cost then
			if powerRate > 0 then
				seconds = (cost - power) / powerRate
			else
				seconds = INFINITY
			end
		end
	end
	return seconds
end

-- Return the amount of the given resource needed to cast the given spell.
-- Mirrored methods.
statePrototype.PowerCost = OvalePower.PowerCost
statePrototype.RequirePowerHandler = OvalePower.RequirePowerHandler

-- Print out the levels of each power type in the current state.
do
	local output = {}

	statePrototype.DebugPower = function(state)
		wipe(output)
		for powerType in pairs(OvalePower.POWER_INFO) do
			output[#output + 1] = Ovale:MakeString("%s = %d", powerType, state[powerType])
		end
		return tconcat(output, "\n")
	end
end
--</state-methods>