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

-- This addon tracks the player's current stance.

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

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

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

local ipairs = ipairs
local pairs = pairs
local substr = string.sub
local tconcat = table.concat
local tinsert = table.insert
local tonumber = tonumber
local tsort = table.sort
local type = type
local wipe = wipe
local API_GetNumShapeshiftForms = GetNumShapeshiftForms
local API_GetShapeshiftForm = GetShapeshiftForm
local API_GetShapeshiftFormInfo = GetShapeshiftFormInfo
local API_GetSpellInfo = GetSpellInfo

-- Register for profiling.
OvaleProfiler:RegisterProfiling(OvaleStance)

local SPELL_NAME_TO_STANCE = {
	-- Death Knight
	[API_GetSpellInfo( 48263)] = "deathknight_blood_presence",
	[API_GetSpellInfo( 48265)] = "deathknight_unholy_presence",
	[API_GetSpellInfo( 48266)] = "deathknight_frost_presence",
	-- Druid
	[API_GetSpellInfo(   768)] = "druid_cat_form",
	[API_GetSpellInfo(   783)] = "druid_travel_form",
	[API_GetSpellInfo(  1066)] = "druid_aquatic_form",
	[API_GetSpellInfo(  5487)] = "druid_bear_form",
	[API_GetSpellInfo( 24858)] = "druid_moonkin_form",
	[API_GetSpellInfo( 33943)] = "druid_flight_form",
	[API_GetSpellInfo( 40120)] = "druid_swift_flight_form",
	[API_GetSpellInfo(171745)] = "druid_claws_of_shirvallah",
	-- Monk
	[API_GetSpellInfo(103985)] = "monk_stance_of_the_fierce_tiger",
	[API_GetSpellInfo(115069)] = "monk_stance_of_the_sturdy_ox",
	[API_GetSpellInfo(115070)] = "monk_stance_of_the_wise_serpent",
	[API_GetSpellInfo(154436)] = "monk_stance_of_the_spirited_crane",
	-- Paladin
	[API_GetSpellInfo( 20154)] = "paladin_seal_of_righteousness",
	[API_GetSpellInfo( 20164)] = "paladin_seal_of_justice",
	[API_GetSpellInfo( 20165)] = "paladin_seal_of_insight",
	[API_GetSpellInfo( 31801)] = "paladin_seal_of_truth",
	[API_GetSpellInfo(105361)] = "paladin_seal_of_command",
	-- Priest
	[API_GetSpellInfo( 15473)] = "priest_shadowform",
	-- Rogue
	[API_GetSpellInfo(  1784)] = "rogue_stealth",
	-- Warlock
	[API_GetSpellInfo(103958)] = "warlock_metamorphosis",
	-- Warrior
	[API_GetSpellInfo(    71)] = "warrior_defensive_stance",
	[API_GetSpellInfo(  2457)] = "warrior_battle_stance",
	[API_GetSpellInfo(156291)] = "warrior_gladiator_stance",
}

-- Table of all valid stance names.
local STANCE_NAME = {}
do
	for _, name in pairs(SPELL_NAME_TO_STANCE) do
		STANCE_NAME[name] = true
	end
end

do
	local debugOptions = {
		stance = {
			name = L["Stances"],
			type = "group",
			args = {
				stance = {
					name = L["Stances"],
					type = "input",
					multiline = 25,
					width = "full",
					get = function(info) return OvaleStance:DebugStances() 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>
-- Whether the stance information is ready for use by other modules.
OvaleStance.ready = false
-- List of available stances, populated by CreateStanceList()
OvaleStance.stanceList = {}
-- Map stance names to stance ID (index on shapeshift/stance bar).
OvaleStance.stanceId = {}
-- Player's current stance.
OvaleStance.stance = nil
-- Table of all valid stance names.
OvaleStance.STANCE_NAME = STANCE_NAME
--</public-static-properties>

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

end

function OvaleStance:OnEnable()
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "UpdateStances")
	self:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
	self:RegisterEvent("UPDATE_SHAPESHIFT_FORMS")
	self:RegisterMessage("Ovale_SpellsChanged", "UpdateStances")
	self:RegisterMessage("Ovale_TalentsChanged", "UpdateStances")
	OvaleData:RegisterRequirement("stance", "RequireStanceHandler", self)
	OvaleState:RegisterState(self, self.statePrototype)
end

function OvaleStance:OnDisable()
	OvaleState:UnregisterState(self)
	OvaleData:UnregisterRequirement("stance")
	self:UnregisterEvent("PLAYER_ALIVE")
	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
	self:UnregisterEvent("UPDATE_SHAPESHIFT_FORM")
	self:UnregisterEvent("UPDATE_SHAPESHIFT_FORMS")
	self:UnregisterMessage("Ovale_SpellsChanged")
	self:UnregisterMessage("Ovale_TalentsChanged")
end

function OvaleStance:PLAYER_TALENT_UPDATE(event)
	-- Clear old stance ID since talent update may overwrite old stance with new one with same ID.
	self.stance = nil
	self:UpdateStances()
end

function OvaleStance:UPDATE_SHAPESHIFT_FORM(event)
	self:ShapeshiftEventHandler()
end

function OvaleStance:UPDATE_SHAPESHIFT_FORMS(event)
	self:ShapeshiftEventHandler()
end

-- Fill OvaleStance.stanceList with stance bar index <-> Ovale stance name mappings.
function OvaleStance:CreateStanceList()
	self:StartProfiling("OvaleStance_CreateStanceList")
	wipe(self.stanceList)
	wipe(self.stanceId)
	local _, name, stanceName
	for i = 1, API_GetNumShapeshiftForms() do
		_, name = API_GetShapeshiftFormInfo(i)
		stanceName = SPELL_NAME_TO_STANCE[name]
		if stanceName then
			self.stanceList[i] = stanceName
			self.stanceId[stanceName] = i
		end
	end
	self:StopProfiling("OvaleStance_CreateStanceList")
end

-- Print out the list of stances in alphabetical order.
do
	local array = {}

	function OvaleStance:DebugStances()
		wipe(array)
		for k, v in pairs(self.stanceList) do
			if self.stance == k then
				tinsert(array, v .. " (active)")
			else
				tinsert(array, v)
			end
		end
		tsort(array)
		return tconcat(array, "\n")
	end
end

-- Return the current stance's name.
function OvaleStance:GetStance()
	return self.stanceList[self.stance]
end

-- Return true if the current stance matches the given name.
-- NOTE: Mirrored in statePrototype below.
function OvaleStance:IsStance(name)
	if name and self.stance then
		if type(name) == "number" then
			return name == self.stance
		else
			return name == OvaleStance.stanceList[self.stance]
		end
	end
	return false
end

function OvaleStance:IsStanceSpell(spellId)
	local name = API_GetSpellInfo(spellId)
	return not not (name and SPELL_NAME_TO_STANCE[name])
end

function OvaleStance:ShapeshiftEventHandler()
	self:StartProfiling("OvaleStance_ShapeshiftEventHandler")
	local newStance = API_GetShapeshiftForm()
	if self.stance ~= newStance then
		self.stance = newStance
		self:SendMessage("Ovale_StanceChanged")
	end
	self:StopProfiling("OvaleStance_ShapeshiftEventHandler")
end

function OvaleStance:UpdateStances()
	self:CreateStanceList()
	self:ShapeshiftEventHandler()
	self.ready = true
end

-- Run-time check that the player is in a certain stance.
-- NOTE: Mirrored in statePrototype below.
function OvaleStance:RequireStanceHandler(spellId, requirement, tokenIterator, target)
	local verified = false
	local stance = tokenIterator()
	if stance then
		local isBang = false
		if substr(stance, 1, 1) == "!" then
			isBang = true
			stance = substr(stance, 2)
		end
		stance = tonumber(stance) or stance
		local isStance = self:IsStance(stance)
		if not isBang and isStance or isBang and not isStance then
			verified = true
		end
		local result = verified and "passed" or "FAILED"
		if isBang then
			self:Log("    Require stance '%s': %s", stance, result)
		else
			self:Log("    Require NOT stance 's': %s", stance, result)
		end
	else
		Ovale:OneTimeMessage("Warning: requirement '%s' is missing a stance argument.", requirement)
	end
	return verified, requirement
end
--</public-static-methods>

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

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

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

--<state-properties>
statePrototype.stance = nil
--</state-properties>

--<public-static-methods>
-- Initialize the state.
function OvaleStance:InitializeState(state)
	state.stance = nil
end

-- Reset the state to the current conditions.
function OvaleStance:ResetState(state)
	self:StartProfiling("OvaleStance_ResetState")
	state.stance = self.stance or 0
	self:StopProfiling("OvaleStance_ResetState")
end

-- Apply the effects of the spell on the player's state, assuming the spellcast completes.
function OvaleStance:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, spellcast)
	self:StartProfiling("OvaleStance_ApplySpellAfterCast")
	local target = OvaleGUID:GetUnitId(targetGUID)
	local stance = state:GetSpellInfoProperty(spellId, "to_stance", target)
	if stance then
		if type(stance) == "string" then
			stance = self.stanceId[stance]
		end
		state.stance = stance
	end
	self:StopProfiling("OvaleStance_ApplySpellAfterCast")
end
--</public-static-methods>

--<state-methods>
-- Mirrored methods.
statePrototype.IsStance = OvaleStance.IsStance
statePrototype.RequireStanceHandler = OvaleStance.RequireStanceHandler
--</state-methods>