Quantcast
--[[--------------------------------------------------------------------
    Ovale Spell Priority
    Copyright (C) 2012, 2013 Sidoine
    Copyright (C) 2012, 2013 Johnny C. Lam

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License in the LICENSE
    file accompanying this program.
--]]--------------------------------------------------------------------

local _, Ovale = ...

do
	local OvaleCondition = Ovale.OvaleCondition
	local OvaleGUID = Ovale.OvaleGUID

	local floor = math.floor
	local API_GetTime = GetTime
	local API_UnitHealth = UnitHealth
	local API_UnitHealthMax = UnitHealthMax
	local Compare = OvaleCondition.Compare
	local ParseCondition = OvaleCondition.ParseCondition
	local TestValue = OvaleCondition.TestValue

	-- static properties for TimeToDie(), indexed by unit ID
	local lastTTDTime = {}
	local lastTTDHealth = {}
	local lastTTDguid = {}
	local lastTTDdps = {}

	--[[
		Returns:
			Estimated number of seconds before the specified unit reaches zero health
			The current time
			Unit's current health
			Unit's maximum health
	--]]
	local function EstimatedTimeToDie(unitId)
		-- Check for target switch.
		if lastTTDguid[unitId] ~= OvaleGUID:GetGUID(unitId) then
			lastTTDguid[unitId] = OvaleGUID:GetGUID(unitId)
			lastTTDTime[unitId] = nil
			if lastTTDHealth[unitId] then
				wipe(lastTTDHealth[unitId])
			else
				lastTTDHealth[unitId] = {}
			end
			lastTTDdps[unitId] = nil
		end

		local timeToDie
		local health = API_UnitHealth(unitId) or 0
		local maxHealth = API_UnitHealthMax(unitId) or 1
		local currentTime = API_GetTime()

		-- Clamp maxHealth to always be at least 1.
		if maxHealth < health then
			maxHealth = health
		end
		if maxHealth < 1 then
			maxHealth = 1
		end

		if health == 0 then
			timeToDie = 0
		elseif maxHealth <= 5 then
			timeToDie = math.huge
		else
			local now = floor(currentTime)
			if (not lastTTDTime[unitId] or lastTTDTime[unitId] < now) and lastTTDguid[unitId] then
				lastTTDTime[unitId] = now
				local mod10, prevHealth
				for delta = 10, 1, -1 do
					mod10 = (now - delta) % 10
					prevHealth = lastTTDHealth[unitId][mod10]
					if delta == 10 then
						lastTTDHealth[unitId][mod10] = health
					end
					if prevHealth and prevHealth > health then
						lastTTDdps[unitId] = (prevHealth - health) / delta
						Ovale:Logf("prevHealth = %d, health = %d, delta = %d, dps = %d", prevHealth, health, delta, lastTTDdps[unitId])
						break
					end
				end
			end
			local dps = lastTTDdps[unitId]
			if dps and dps > 0 then
				timeToDie = health / dps
			else
				timeToDie = math.huge
			end
		end
		-- Clamp time to die at a finite number.
		if timeToDie == math.huge then
			-- Return time to die in the far-off future (one week).
			timeToDie = 3600 * 24 * 7
		end
		return timeToDie, currentTime, health, maxHealth
	end

	--- Get the current amount of health points of the target.
	-- @name Health
	-- @paramsig number or boolean
	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
	-- @param number Optional. The number to compare against.
	-- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
	--     Defaults to target=player.
	--     Valid values: player, target, focus, pet.
	-- @return The current health.
	-- @return A boolean value for the result of the comparison.
	-- @see Life
	-- @usage
	-- if Health() <10000 Spell(last_stand)
	-- if Health(less 10000) Spell(last_stand)

	local function Health(condition)
		local comparator, limit = condition[1], condition[2]
		local target = ParseCondition(condition)
		local timeToDie, now, health, maxHealth = EstimatedTimeToDie(target)
		if not timeToDie then
			return nil
		elseif timeToDie == 0 then
			return Compare(0, comparator, limit)
		end
		local value, origin, rate = health, now, -1 * health / timeToDie
		local start, ending = now, math.huge
		return TestValue(start, ending, value, origin, rate, comparator, limit)
	end

	OvaleCondition:RegisterCondition("health", false, Health)
	OvaleCondition:RegisterCondition("life", false, Health)

	--- Get the number of health points away from full health of the target.
	-- @name HealthMissing
	-- @paramsig number or boolean
	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
	-- @param number Optional. The number to compare against.
	-- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
	--     Defaults to target=player.
	--     Valid values: player, target, focus, pet.
	-- @return The current missing health.
	-- @return A boolean value for the result of the comparison.
	-- @see LifeMissing
	-- @usage
	-- if HealthMissing() <20000 Item(healthstone)
	-- if HealthMissing(less 20000) Item(healthstone)

	local function HealthMissing(condition)
		local comparator, limit = condition[1], condition[2]
		local target = ParseCondition(condition)
		local timeToDie, now, health, maxHealth = EstimatedTimeToDie(target)
		if not timeToDie or timeToDie == 0 then
			return nil
		end
		local missing = maxHealth - health
		local value, origin, rate = missing, now, health / timeToDie
		local start, ending = now, math.huge
		return TestValue(start, ending, value, origin, rate, comparator, limit)
	end

	OvaleCondition:RegisterCondition("healthmissing", false, Health)
	OvaleCondition:RegisterCondition("lifemissing", false, Health)

	--- Get the current percent level of health of the target.
	-- @name HealthPercent
	-- @paramsig number or boolean
	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
	-- @param number Optional. The number to compare against.
	-- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
	--     Defaults to target=player.
	--     Valid values: player, target, focus, pet.
	-- @return The current health percent.
	-- @return A boolean value for the result of the comparison.
	-- @see LifePercent
	-- @usage
	-- if HealthPercent() <20 Spell(last_stand)
	-- if target.HealthPercent(less 25) Spell(kill_shot)

	local function HealthPercent(condition)
		local comparator, limit = condition[1], condition[2]
		local target = ParseCondition(condition)
		local timeToDie, now, health, maxHealth = EstimatedTimeToDie(target)
		if not timeToDie then
			return nil
		elseif timeToDie == 0 then
			return Compare(0, comparator, limit)
		end
		local healthPercent = health / maxHealth * 100
		local value, origin, rate = healthPercent, now, -1 * healthPercent / timeToDie
		local start, ending = now, math.huge
		return TestValue(start, ending, value, origin, rate, comparator, limit)
	end

	OvaleCondition:RegisterCondition("healthpercent", false, HealthPercent)
	OvaleCondition:RegisterCondition("lifepercent", false, HealthPercent)

	--- Get the amount of health points of the target when it is at full health.
	-- @name MaxHealth
	-- @paramsig number or boolean
	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
	-- @param number Optional. The number to compare against.
	-- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
	--     Defaults to target=player.
	--     Valid values: player, target, focus, pet.
	-- @return The maximum health.
	-- @return A boolean value for the result of the comparison.
	-- @usage
	-- if target.MaxHealth() >10000000 Item(mogu_power_potion)
	-- if target.MaxHealth(more 10000000) Item(mogu_power_potion)

	local function MaxHealth(condition)
		local comparator, limit = condition[1], condition[2]
		local target = ParseCondition(condition)
		local value = API_UnitHealthMax(target)
		return Compare(value, comparator, limit)
	end

	OvaleCondition:RegisterCondition("maxhealth", false, MaxHealth)

	--- Get the estimated number of seconds remaining before the target is dead.
	-- @name TimeToDie
	-- @paramsig number or boolean
	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
	-- @param number Optional. The number to compare against.
	-- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
	--     Defaults to target=player.
	--     Valid values: player, target, focus, pet.
	-- @return The number of seconds.
	-- @return A boolean value for the result of the comparison.
	-- @see DeadIn
	-- @usage
	-- if target.TimeToDie() <2 and ComboPoints() >0 Spell(eviscerate)

	local function TimeToDie(condition)
		local comparator, limit = condition[1], condition[2]
		local target = ParseCondition(condition)
		local timeToDie, now = EstimatedTimeToDie(target)
		local value, origin, rate = timeToDie, now, -1
		local start, ending = now, now + timeToDie
		return TestValue(start, ending, value, origin, rate, comparator, limit)
	end

	OvaleCondition:RegisterCondition("deadin", false, TimeToDie)
	OvaleCondition:RegisterCondition("timetodie", false, TimeToDie)

	--- Get the estimated number of seconds remaining before the target reaches the given percent of max health.
	-- @name TimeToHealthPercent
	-- @paramsig number or boolean
	-- @param percent The percent of maximum health of the target.
	-- @param operator Optional. Comparison operator: less, atMost, equal, atLeast, more.
	-- @param number Optional. The number to compare against.
	-- @param target Optional. Sets the target to check. The target may also be given as a prefix to the condition.
	--     Defaults to target=player.
	--     Valid values: player, target, focus, pet.
	-- @return The number of seconds.
	-- @return A boolean value for the result of the comparison.
	-- @see TimeToDie
	-- @usage
	-- if target.TimeToHealthPercent(25) <15 Item(virmens_bite_potion)

	local function TimeToHealthPercent(condition)
		local percent, comparator, limit = condition[1], condition[2], condition[3]
		local target = ParseCondition(condition)
		local timeToDie, now, health, maxHealth = EstimatedTimeToDie(target)
		local healthPercent = health / maxHealth * 100
		if healthPercent >= percent then
			local t = timeToDie * (healthPercent - percent) / healthPercent
			local value, origin, rate = t, now, -1
			local start, ending = now, now + t
			return TestValue(start, ending, value, origin, rate, comparator, limit)
		end
		return Compare(0, comparator, limit)
	end

	OvaleCondition:RegisterCondition("timetohealthpercent", false, TimeToHealthPercent)
	OvaleCondition:RegisterCondition("timetolifepercent", false, TimeToHealthPercent)
end