--[[-------------------------------------------------------------------- Ovale Spell Priority Copyright (C) 2013, 2014 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. --]]-------------------------------------------------------------------- --[[ This addon tracks Eclipse energy information on druids. --]] local _, Ovale = ... local OvaleEclipse = Ovale:NewModule("OvaleEclipse", "AceEvent-3.0") Ovale.OvaleEclipse = OvaleEclipse --<private-static-properties> -- Profiling set-up. local Profiler = Ovale.Profiler local profiler = nil do local group = OvaleEclipse:GetName() Profiler:RegisterProfilingGroup(group) profiler = Profiler:GetProfilingGroup(group) end -- Forward declarations for module dependencies. local OvaleAura = nil local OvaleData = nil local OvaleFuture = nil local OvaleSpellBook = nil local OvaleState = nil local floor = math.floor local API_GetEclipseDirection = GetEclipseDirection local API_UnitClass = UnitClass local API_UnitGUID = UnitGUID local API_UnitPower = UnitPower local SPELL_POWER_ECLIPSE = SPELL_POWER_ECLIPSE local OVALE_ECLIPSE_DEBUG = "eclipse" -- Player's GUID. local self_guid = nil -- Player's class. local _, self_class = API_UnitClass("player") -- Table of functions to update spellcast information to register with OvaleFuture. local self_updateSpellcastInfo = {} local LUNAR_ECLIPSE = ECLIPSE_BAR_LUNAR_BUFF_ID local SOLAR_ECLIPSE = ECLIPSE_BAR_SOLAR_BUFF_ID -- Nature's Grace: You gain 15% spell haste for 15 seconds each time you trigger an Eclipse. local NATURES_GRACE = 16886 local CELESTIAL_ALIGNMENT = 112071 local DREAM_OF_CENARIUS = 145151 local DREAM_OF_CENARIUS_TALENT = 17 local EUPHORIA = 81062 local MOONKIN_FORM = 24858 local STARFALL = 48505 --</private-static-properties> --<public-static-properties> -- Direction that the eclipse status is moving: -1 = "lunar", 0 = "none", 1 = "solar". OvaleEclipse.eclipse = 0 OvaleEclipse.eclipseDirection = 0 --<public-static-properties> --<private-static-methods> -- Manage Eclipsed damage multiplier information. local function GetDamageMultiplier(spellId, snapshot, auraObject) auraObject = auraObject or OvaleAura local damageMultiplier = 1 local si = OvaleData.spellInfo[spellId] if si and (si.arcane == 1 or si.nature == 1) then -- Update the damage multiplier for Moonkin Form 10% bonus to Arcane and Nature damage. local moonkinForm = auraObject:GetAura("player", MOONKIN_FORM, "HELPFUL", true) if auraObject:IsActiveAura(moonkinForm) then damageMultiplier = damageMultiplier * 1.1 end -- Update the damage multiplier for Eclipse bonus to Arcane and Nature damage. local aura if si.arcane == 1 then aura = auraObject:GetAura("player", LUNAR_ECLIPSE, "HELPFUL", true) end if not auraObject:IsActiveAura(aura) and si.nature == 1 then aura = auraObject:GetAura("player", SOLAR_ECLIPSE, "HELPFUL", true) end if not auraObject:IsActiveAura(aura) then aura = auraObject:GetAura("player", CELESTIAL_ALIGNMENT, "HELPFUL", true) end if auraObject:IsActiveAura(aura) then local eclipseEffect = aura.value1 local masteryEffect = snapshot.masteryEffect or 0 eclipseEffect = eclipseEffect - floor(masteryEffect) + masteryEffect damageMultiplier = damageMultiplier * (1 + eclipseEffect / 100) end end return damageMultiplier end do self_updateSpellcastInfo.GetDamageMultiplier = GetDamageMultiplier end --</private-static-methods> --<public-static-methods> function OvaleEclipse:OnInitialize() -- Resolve module dependencies. OvaleAura = Ovale.OvaleAura OvaleData = Ovale.OvaleData OvaleFuture = Ovale.OvaleFuture OvaleSpellBook = Ovale.OvaleSpellBook OvaleState = Ovale.OvaleState end function OvaleEclipse:OnEnable() if self_class == "DRUID" then self_guid = API_UnitGUID("player") self:RegisterMessage("Ovale_SpecializationChanged") end end function OvaleEclipse:OnDisable() if self_class == "DRUID" then self:UnregisterMessage("Ovale_SpecializationChanged") end end function OvaleEclipse:Ovale_SpecializationChanged(event, specialization, previousSpecialization) if specialization == "balance" then self:Update() self:RegisterEvent("ECLIPSE_DIRECTION_CHANGE", "UpdateEclipseDirection") self:RegisterEvent("UNIT_POWER") self:RegisterEvent("UNIT_POWER_FREQUENT", "UNIT_POWER") self:RegisterMessage("Ovale_StanceChanged", "Update") self:RegisterMessage("Ovale_AuraAdded") OvaleState:RegisterState(self, self.statePrototype) OvaleFuture:RegisterSpellcastInfo(self_updateSpellcastInfo) else OvaleState:UnregisterState(self) OvaleFuture:UnregisterSpellcastInfo(self_updateSpellcastInfo) self:UnregisterEvent("ECLIPSE_DIRECTION_CHANGE") self:UnregisterEvent("UNIT_POWER") self:UnregisterEvent("UNIT_POWER_FREQUENT") self:UnregisterMessage("Ovale_AuraAdded") self:UnregisterMessage("Ovale_StanceChanged") end end function OvaleEclipse:UNIT_POWER(event, unitId, powerToken) if unitId == "player" and powerToken == "ECLIPSE" then self:Update() end end function OvaleEclipse:Ovale_AuraAdded(event, timestamp, guid, spellId, caster) if guid == self_guid then if spellId == LUNAR_ECLIPSE or spellId == SOLAR_ECLIPSE then self:UpdateEclipseDirection() end end end function OvaleEclipse:Update() self:UpdateEclipse() self:UpdateEclipseDirection() end function OvaleEclipse:UpdateEclipse() profiler.Start("OvaleEclipse_UpdateEclipse") self.eclipse = API_UnitPower("player", SPELL_POWER_ECLIPSE) profiler.Stop("OvaleEclipse_UpdateEclipse") end function OvaleEclipse:UpdateEclipseDirection() profiler.Start("OvaleEclipse_UpdateEclipseDirection") local direction = API_GetEclipseDirection() if direction == "moon" then self.eclipseDirection = -1 elseif direction == "sun" then self.eclipseDirection = 1 else -- if direction == "none" then if self.eclipse < 0 then self.eclipseDirection = -1 elseif self.eclipse > 0 then self.eclipseDirection = 1 else -- if self.eclipse == 0 then self.eclipseDirection = 0 end end profiler.Stop("OvaleEclipse_UpdateEclipseDirection") end --</public-static-methods> --[[---------------------------------------------------------------------------- State machine for simulator. AFTER: OvalePower --]]---------------------------------------------------------------------------- --<public-static-properties> OvaleEclipse.statePrototype = {} --</public-static-properties> --<private-static-properties> local statePrototype = OvaleEclipse.statePrototype --</private-static-properties> --<state-properties> -- Direction in which the Eclipse bar is moving. statePrototype.eclipseDirection = nil --</state-properties> --<public-static-methods> -- Initialize the state. function OvaleEclipse:InitializeState(state) state.eclipseDirection = 0 end -- Reset the state to the current conditions. function OvaleEclipse:ResetState(state) profiler.Start("OvaleEclipse_ResetState") state.eclipseDirection = self.eclipseDirection profiler.Stop("OvaleEclipse_ResetState") end -- Apply the effects of the spell at the start of the spellcast. function OvaleEclipse:ApplySpellStartCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast) profiler.Start("OvaleEclipse_ApplySpellStartCast") -- Channeled spells cost resources at the start of the channel. if isChanneled then state:ApplyEclipseEnergy(spellId, startCast, spellcast.snapshot) end profiler.Stop("OvaleEclipse_ApplySpellStartCast") end -- Apply the effects of the spell on the player's state, assuming the spellcast completes. function OvaleEclipse:ApplySpellAfterCast(state, spellId, targetGUID, startCast, endCast, nextCast, isChanneled, nocd, spellcast) profiler.Start("OvaleEclipse_ApplySpellAfterCast") -- Instant or cast-time spells cost resources at the end of the spellcast. if not isChanneled then state:ApplyEclipseEnergy(spellId, endCast, spellcast.snapshot) end profiler.Stop("OvaleEclipse_ApplySpellAfterCast") end --</public-static-methods> --<state-methods> -- Update the state of the simulator for the eclipse energy gained by casting the given spell. statePrototype.ApplyEclipseEnergy = function(state, spellId, atTime, snapshot) profiler.Start("OvaleEclipse_ApplyEclipseEnergy") if spellId == CELESTIAL_ALIGNMENT then local aura = state:AddAuraToGUID(self_guid, spellId, self_guid, "HELPFUL", atTime, atTime + 15, snapshot) aura.value1 = state:EclipseBonusDamage(atTime, snapshot) -- Celestial Alignment grants the spell effects of both Lunar and Solar Eclipse and -- also resets the total Eclipse energy to zero. state:AddEclipse(LUNAR_ECLIPSE, atTime, snapshot) state:AddEclipse(SOLAR_ECLIPSE, atTime, snapshot) state.eclipse = 0 -- Remove any current Eclipse state. state:RemoveEclipse(LUNAR_ECLIPSE, atTime) state:RemoveEclipse(SOLAR_ECLIPSE, atTime) else local si = OvaleData.spellInfo[spellId] if si and si.eclipse then local power = state.eclipse local direction = state.eclipseDirection local energy = state:EclipseEnergy(spellId) -- Celestial Alignment prevents gaining Eclipse energy during its duration. local aura = state:GetAura("player", CELESTIAL_ALIGNMENT, "HELPFUL", true) if state:IsActiveAura(aura) then energy = 0 end -- Only adjust the total Eclipse energy if the spell adds Eclipse energy in the current direction. if (direction <= 0 and energy < 0) or (direction >= 0 and energy > 0) then Ovale:Logf("[%s] Eclipse %d -> %d", OVALE_ECLIPSE_DEBUG, power, power + energy) power = power + energy -- Crossing zero energy removes the corresponding Eclipse state. if direction < 0 and power <= 0 then state:RemoveEclipse(SOLAR_ECLIPSE, atTime) elseif direction > 0 and power >= 0 then state:RemoveEclipse(LUNAR_ECLIPSE, atTime) end -- Clamp Eclipse energy to min/max values and note that an Eclipse state will be reached. if power <= -100 then power = -100 direction = 1 state:AddEclipse(LUNAR_ECLIPSE, atTime, snapshot) elseif power >= 100 then power = 100 direction = -1 state:AddEclipse(SOLAR_ECLIPSE, atTime, snapshot) end end state.eclipse = power state.eclipseDirection = direction end end profiler.Stop("OvaleEclipse_ApplyEclipseEnergy") end statePrototype.EclipseEnergy = function(state, spellId) local eclipseEnergy = 0 local si = OvaleData.spellInfo[spellId] if si and si.eclipse then local energy = si.eclipse --[[ eclipse = 0 means that the spell generates no Eclipse energy. eclipse < 0 means that the spell generates Lunar energy. eclipse > 0 means that the spell generates Solar energy. --]] if energy ~= "0" then -- If there is no Eclipse direction yet, then start moving in the direction generated -- by the energy of the spellcast. local direction = state.eclipseDirection if direction == 0 then direction = (energy < 0) and -1 or 1 end -- If "eclipsedir" is set, then the spell adds energy in the current direction. if si.eclipsedir then energy = energy * direction end -- Euphoria: While not in an Eclipse state, your spells generate double the normal -- amount of Solar or Lunar energy. if OvaleSpellBook:IsKnownSpell(EUPHORIA) then local lunar = state:GetAura("player", LUNAR_ECLIPSE, "HELPFUL", true) local solar = state:GetAura("player", SOLAR_ECLIPSE, "HELPFUL", true) if not state:IsActiveAura(lunar) and not state:IsActiveAura(solar) then energy = energy * 2 end end eclipseEnergy = energy end end return eclipseEnergy end statePrototype.EclipseBonusDamage = function(state, atTime, snapshot) -- Base Eclipse bonus (percent) to damage. local bonus = 15 -- Add in mastery bonus to Eclipse damage. bonus = bonus + snapshot.masteryEffect -- Add in bonus from Dream of Cenarius. if OvaleSpellBook:GetTalentPoints(DREAM_OF_CENARIUS_TALENT) > 0 then local aura = state:GetAura("player", DREAM_OF_CENARIUS, "HELPFUL", true) if state:IsActiveAura(aura, atTime) then bonus = bonus + 25 end end return bonus end statePrototype.AddEclipse = function(state, eclipseId, atTime, snapshot) if eclipseId == LUNAR_ECLIPSE or eclipseId == SOLAR_ECLIPSE then local eclipseName = (eclipseId == LUNAR_ECLIPSE) and "Lunar" or "Solar" Ovale:Logf("[%s] Adding %s Eclipse (%d) at %f", OVALE_ECLIPSE_DEBUG, eclipseName, eclipseId, atTime) local aura = state:AddAuraToGUID(self_guid, eclipseId, self_guid, "HELPFUL", atTime, math.huge, snapshot) -- Set the value of the Eclipse aura to the Eclipse's bonus damage. aura.value1 = state:EclipseBonusDamage(atTime, snapshot) -- Reaching Eclipse state grants Nature's Grace. state:AddAuraToGUID(self_guid, NATURES_GRACE, self_guid, "HELPFUL", atTime, atTime + 15, snapshot) -- Reaching Lunar Eclipse resets the cooldown of Starfall. if eclipseId == LUNAR_ECLIPSE then state:ResetSpellCooldown(STARFALL, atTime) end end end statePrototype.RemoveEclipse = function(state, eclipseId, atTime) if eclipseId == LUNAR_ECLIPSE or eclipseId == SOLAR_ECLIPSE then state:RemoveAuraOnGUID(self_guid, eclipseId, "HELPFUL", true, atTime) end end --</state-methods>