--[[ CALL OF ELEMENTS The All-In-One Shaman Addon by Wyverex (2006) Totem Module / Logical ]] if (not COE_Totem) then COE_Totem = {}; end --[[ ---------------------------------------------------------------- stores the original functions hooked by SetupTimerHooks() -------------------------------------------------------------------]] COE_Totem["TimerHooks"] = {}; --[[ ============================================================================================= L O G I C ================================================================================================]] --[[ ---------------------------------------------------------------- METHOD: COE_Totem:Rescan PURPOSE: Scans the totems and totem sets -------------------------------------------------------------------]] function COE_Totem:Rescan() COE:ScanTotems(); COE:InitTotemSets(); -- Reset all active totems -- ------------------------ COE_Totem:ResetTimers(); COE.ActiveTotems.Earth = nil; COE.ActiveTotems.Fire = nil; COE.ActiveTotems.Water = nil; COE.ActiveTotems.Air = nil; COE_Totem:UpdateAllFrames(); COETotemFrame.Reconfigure = true; end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ThrowTotem PURPOSE: Throws the totem with the specified named id -------------------------------------------------------------------]] function COE_Totem:ThrowTotem(element, id) COE:DebugMessage( "COE_Totem:ThrowTotem element and id: " .. element .. " " .. id); if (not COE.Initialized) then return; end -- get associated button -- ---------------------- local button = getglobal("COETotem" .. element .. id); if (button == nil) then COE:Message("Invalid Totem"); return; end local totem = button.totem; if (totem == nil) then COE:Message(COESTR_NOTOTEM); return; end -- throw totem -- ------------ CastSpellByName(totem.SpellName); end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:RankModifierDown PURPOSE: Returns if the rank 1 modifier is currently pressed -------------------------------------------------------------------]] function COE_Totem:RankModifierDown() local overridekey = COE_Config:GetSaved(COEOPT_OVERRIDERANK); -- check if the override key is down -- ---------------------------------- if (overridekey == COEMODE_OVERRIDESHIFT and IsShiftKeyDown()) then return true; elseif (overridekey == COEMODE_OVERRIDEALT and IsAltKeyDown()) then return true; elseif (overridekey == COEMODE_OVERRIDECTRL and IsControlKeyDown()) then return true; end return false; end --[[ ---------------------------------------------------------------- METHOD: COESched_AdviseTotem PURPOSE: If the totem warning is still active, a notification is displayed and the function rescheduled -------------------------------------------------------------------]] function COESched_AdviseTotem(totem, msg) if (COE_Config:GetSaved(COEOPT_ADVISOR) == 1 and totem.Warn) then -- issue warning -- -------------- COE:Notification(string.format(COESTR_CLEANSINGTOTEM, msg), COECOL_TOTEMCLEANSING); -- reschedule -- ----------- Chronos.scheduleByName("COEAdvise" .. msg, COE.AdvisorWarningInterval, COESched_AdviseTotem, totem, msg); end end --[[ ---------------------------------------------------------------- METHOD: COESched_RunAdvisor PURPOSE: Scans the party/raid for debuffs that are curable by totems and shows a notification if it finds such -------------------------------------------------------------------]] function COESched_RunAdvisor() -- advisor still activated? -- ------------------------- if (COE_Config:GetSaved(COEOPT_ADVISOR) == 0) then return; end local warnPoison, warnDisease, warnTremor = false, false, false; local poison, disease, tremor; -- scan player first -- ------------------ poison, disease, tremor = COE_Totem:ScanTargetForDebuff("player"); warnPoison = warnPoison or poison; warnDisease = warnDisease or disease; warnTremor = warnTremor or tremor; -- scan party -- ----------- local i; for i = 1, GetNumPartyMembers() do if (warnPoison and warnDisease and warnTremor) then break end -- scan party member -- ------------------ poison, disease, tremor = COE_Totem:ScanTargetForDebuff("party" .. i); warnPoison = warnPoison or poison; warnDisease = warnDisease or disease; warnTremor = warnTremor or tremor; -- scan party member's pet -- ------------------------ if (UnitExists("partypet" .. i)) then poison, disease, tremor = COE_Totem:ScanTargetForDebuff( "partypet" .. i); warnPoison = warnPoison or poison; warnDisease = warnDisease or disease; warnTremor = warnTremor or tremor; end end -- issue warnings if necessary -- ---------------------------- if (warnTremor and COE.CleansingTotems.Tremor.Totem and (COE.ActiveTotems["Earth"] ~= COE.CleansingTotems.Tremor.Totem or not COE_Totem:IsTimerActive(COE.ActiveTotems["Earth"]))) then if (not COE.CleansingTotems.Tremor.Warn) then COE.CleansingTotems.Tremor.Warn = true; COESched_AdviseTotem(COE.CleansingTotems.Tremor, COESTR_TOTEMTREMOR); end else COE.CleansingTotems.Tremor.Warn = false; end if (warnDisease and COE.CleansingTotems.Disease.Totem and (COE.ActiveTotems["Water"] ~= COE.CleansingTotems.Disease.Totem or not COE_Totem:IsTimerActive(COE.ActiveTotems["Water"]))) then if (not COE.CleansingTotems.Disease.Warn) then COE.CleansingTotems.Disease.Warn = true; COESched_AdviseTotem(COE.CleansingTotems.Disease, COESTR_TOTEMDISEASE); end else COE.CleansingTotems.Disease.Warn = false; end if (warnPoison and COE.CleansingTotems.Poison.Totem and (COE.ActiveTotems["Water"] ~= COE.CleansingTotems.Poison.Totem or not COE_Totem:IsTimerActive(COE.ActiveTotems["Water"]))) then if (not COE.CleansingTotems.Poison.Warn) then COE.CleansingTotems.Poison.Warn = true; COESched_AdviseTotem(COE.CleansingTotems.Poison, COESTR_TOTEMPOISON); end else COE.CleansingTotems.Poison.Warn = false; end -- reschedule -- ----------- Chronos.scheduleByName("COEAdvise", COE.AdvisorInterval, COESched_RunAdvisor); end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ScanTargetForDebuff PURPOSE: Scans the specified target for debuffs that are curable by totems -------------------------------------------------------------------]] function COE_Totem:ScanTargetForDebuff(target) local slot = 1; local poison, disease, tremor = false, false, false; -- iterate over all possible 16 debuff slots -- ------------------------------------------ COETotemTTTextLeft1:SetText(""); COETotemTTTextRight1:SetText(""); COETotemTT:SetUnitDebuff(target, slot); local debufftype = COETotemTTTextRight1:GetText(); local debuffname = COETotemTTTextLeft1:GetText(); while (debuffname and slot <= 16 and not (poison and disease and tremor)) do if (debufftype) then if (debufftype == COESTR_POISON) then -- it's a poison debuff -- --------------------- poison = true; elseif (debufftype == COESTR_DISEASE) then -- it's a disease debuff -- --------------------- disease = true; else -- check if it's a debuff curable -- by the tremor totem -- ------------------------------- local debuff; for debuff in COESTR_TREMOR do if (debuffname == COESTR_TREMOR[debuff]) then tremor = true; break end end end end slot = slot + 1; COETotemTTTextLeft1:SetText(""); COETotemTTTextRight1:SetText(""); COETotemTT:SetUnitDebuff(target, slot); debufftype = COETotemTTTextRight1:GetText(); debuffname = COETotemTTTextLeft1:GetText(); end return poison, disease, tremor; end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ThrowAdvisedTotem PURPOSE: If there is an advised totem waiting to be thrown is is done here -------------------------------------------------------------------]] function COE_Totem:ThrowAdvisedTotem() if (COE.CleansingTotems.Tremor.Warn and (COE.ActiveTotems.Earth ~= COE.CleansingTotems.Tremor.Totem or not COE_Totem:IsTimerActive(COE.ActiveTotems["Earth"]))) then CastSpellByName(COE.CleansingTotems.Tremor.Totem.SpellName); elseif (COE.CleansingTotems.Disease.Warn and (COE.ActiveTotems.Water ~= COE.CleansingTotems.Disease.Totem or not COE_Totem:IsTimerActive(COE.ActiveTotems["Water"]))) then CastSpellByName(COE.CleansingTotems.Disease.Totem.SpellName); elseif (COE.CleansingTotems.Poison.Warn and (COE.ActiveTotems.Water ~= COE.CleansingTotems.Poison.Totem or not COE_Totem:IsTimerActive(COE.ActiveTotems["Water"]))) then CastSpellByName(COE.CleansingTotems.Poison.Totem.SpellName); end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:IsAdvisedTotem PURPOSE: Returns if the specified totem is an advised totem -------------------------------------------------------------------]] function COE_Totem:IsAdvisedTotem(totem) if ((COE.CleansingTotems.Tremor.Warn and totem == COE.CleansingTotems.Tremor.Totem) or (COE.CleansingTotems.Disease.Warn and totem == COE.CleansingTotems.Disease.Totem) or (COE.CleansingTotems.Poison.Warn and totem == COE.CleansingTotems.Poison.Totem)) then return true; else return false; end end --[[ ============================================================================================= T O T E M - S E T S ================================================================================================]] --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SwitchNamedSet PURPOSE: If there exists a custom set with the name of the playertarget and the target is hostile, switch to this set -------------------------------------------------------------------]] function COE_Totem:SwitchNamedSet() if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 1) then if (UnitIsEnemy("player", "target")) then -- iterate over all custom sets -- ----------------------------- local i; for i = 1, table.getn(COE_CustomTotemSets) do if (COE_CustomTotemSets[i].Name == UnitName("target")) then COE_Totem:SwitchToSet(COE_CustomTotemSets[i].Name); return true; end end end end return false; end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SwitchPVPSet PURPOSE: If the playertarget is a hostile player, activate the class totem set, else switch back to the last active set -------------------------------------------------------------------]] function COE_Totem:SwitchPVPSet() if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 1 and COE_Config:GetSaved(COEOPT_AUTOSWITCH) == 1) then if (UnitIsEnemy("player", "target")) then if (UnitIsPlayer("target")) then -- switch to pvp set -- ------------------ local class = UnitClass("target"); COE_Totem:SwitchToSet("[PVP] " .. class); elseif (UnitExists("target")) then -- targeted a mob. switch back to the last set -- -------------------------------------------- if (COE.LastActiveSet ~= nil) then COE_Totem:SwitchToSet(COE.LastActiveSet); else COE_Totem:SwitchToSet(COEUI_DEFAULTSET); end end end end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SwitchToNextSet PURPOSE: If a pvp set is activated, the default set is activated Otherwise the next custom set is activated -------------------------------------------------------------------]] function COE_Totem:SwitchToNextSet() if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 1) then local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET); if (activeset < COESET_DEFAULT) then -- pvp set is active -- switch to default set -- ---------------------- COE_Totem:SwitchToSet(COEUI_DEFAULTSET); elseif (activeset == COE.TotemSetCount) then -- last custom set is active or none available -- switch to default set -- -------------------------------------------- COE_Totem:SwitchToSet(COEUI_DEFAULTSET); elseif (activeset == COESET_DEFAULT) then -- default set is active -- switch to first custom set -- --------------------------- COE_Totem:SwitchToSet(COE_CustomTotemSets[1].Name); else -- switch to next custom set -- -------------------------- COE_Totem:SwitchToSet( COE_CustomTotemSets[activeset - COESET_DEFAULT + 1].Name); end end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SwitchToPriorSet PURPOSE: If a pvp set is activated, the default set is activated Otherwise the next custom set is activated -------------------------------------------------------------------]] function COE_Totem:SwitchToPriorSet() if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 1) then local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET); if (activeset < COESET_DEFAULT) then -- pvp set is active -- switch to default set -- ---------------------- COE_Totem:SwitchToSet(COEUI_DEFAULTSET); elseif (activeset == COESET_DEFAULT) then -- default set is active -- switch to last custom set -- --------------------------- COE_Totem:SwitchToSet(COE_CustomTotemSets[COE.TotemSetCount - COESET_DEFAULT].Name); elseif (activeset == COESET_DEFAULT + 1) then -- first custom set is active -- switch to default set -- --------------------------- COE_Totem:SwitchToSet(COEUI_DEFAULTSET); else -- switch to prior custom set -- --------------------------- COE_Totem:SwitchToSet( COE_CustomTotemSets[activeset - COESET_DEFAULT - 1].Name); end end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SwitchToSet PURPOSE: Activates the specified totem set -------------------------------------------------------------------]] function COE_Totem:SwitchToSet(name) if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 0) then return; end local pvpset = false; -- check for pvp sets -- ------------------- local i, set; for i = 1, table.getn(COE_SavedTotemSets) do if (COE_SavedTotemSets[i].Name == name) then -- set found -- ---------- set = i; pvpset = i ~= COESET_DEFAULT; break end end -- check for custom sets -- ---------------------- if (not set) then for i = 1, table.getn(COE_CustomTotemSets) do if (COE_CustomTotemSets[i].Name == name) then -- set found -- ---------- set = COESET_DEFAULT + i; break end end end if (not set) then return; end -- activate set if not already active -- chech this to stop notification spamming in a duel -- --------------------------------------------------- if (COE_Config:GetSaved(COEOPT_ACTIVESET) ~= set) then COE_Config:ActivateSet(set); if (not pvpset) then COE.LastActiveSet = name; end -- reset set cycle -- ---------------- COE_Totem:ResetSetCycle(); -- notify player -- -------------- COE:Message(string.format(COESTR_SWITCHTOSET, name)); end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ThrowSet PURPOSE: Throws every totem in the active set that is not yet thrown and is ready. Each element has its own GCD category on the server, so the whole set goes out on one press -------------------------------------------------------------------]] function COE_Totem:ThrowSet() COE:DebugMessage("Method Executing: ThrowSet"); if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 1) then -- ======================================================================= -- check which totems to throw -- ======================================================================= local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET); COE:DebugMessage("ThrowSet activeset value: " .. activeset); local now = GetTime(); for k = 1, 4 do COE:DebugMessage("ThrowSet k value: " .. k); local element = COE:LocalizedElement( COE.TotemSets[activeset].CastOrder[k]); COE:DebugMessage("ThrowSet element value: " .. element); local totem = COE.TotemSets[activeset][element]; COE:DebugMessage("ThrowSet COE.SetCycle[element] value: " .. tostring(COE.SetCycle[element])); if (COE.SetCycle[element] == false and totem) then local thrown = COE.SetThrown[element]; if (thrown and now - thrown < COE.SetThrowGuard) then -- cast sent moments ago and not yet confirmed -- -------------------------------------------- COE:DebugMessage("ThrowSet skipping unconfirmed: " .. element); elseif (totem.isTrinket) then if (totem.TrinketSlot) then -- first check if the trinket is already usable -- --------------------------------------------- local start, duration = GetInventoryItemCooldown("player", totem.TrinketSlot); if (start == 0 and duration == 0) then UseInventoryItem(totem.TrinketSlot); COE.SetThrown[element] = now; end end else COE:DebugMessage("ThrowSet casting totem: " .. element); -- first check if the totem is already usable -- ------------------------------------------- local start, duration = GetSpellCooldown( totem.Ranks[totem.MaxRank] .SpellID, BOOKTYPE_SPELL); COE:DebugMessage("ThrowSet spell cooldown duration: " .. duration); if (start == 0 and duration == 0) then COE:DebugMessage( "ThrowSet casting spell: " .. COE.TotemSets[activeset][element].SpellName); CastSpellByName(COE.TotemSets[activeset][element] .SpellName); -- COE_Totem:SetPendingTotem(totem, totem.MaxRank); COE.SetThrown[element] = now; end end end end end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ResetSetCycle PURPOSE: Marks all totems of the current set as not yet thrown -------------------------------------------------------------------]] function COE_Totem:ResetSetCycle() COE:Message(COESTR_RESTARTINGSET); COE.SetCycle = {Earth = false, Fire = false, Water = false, Air = false}; COE.SetThrown = {}; end --[[ ============================================================================================= T I M E R - L O G I C ================================================================================================]] --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SetPendingTotem PURPOSE: Sets up or clears a pending totem The pending totem's timer is activated on the next successful SPELLCAST_STOP or removed if it times out first PURPOSE: Sets up or clears a pending totem. The pending totem's timer is activated when the spell is identified as cast. If the spell is not identified as cast within 1 seconds, then the pending status is removed. -------------------------------------------------------------------]] function COE_Totem:SetPendingTotem(totem, rank) COE:DebugMessage("Method Executing: SetPendingTotem"); if (totem) then -- Set PendingTotems for the given totem element type COE:DebugMessage("SetPendingTotem PendingTotems being set: " .. totem.SpellName .. " with rank: " .. tostring(rank)); local pending = COE.PendingTotems[totem.Element]; if (not pending) then -- no known element: cast without a timer instead of erroring -- ------------------------------------------------------------ return; end pending.Totem = totem; pending.Rank = rank; -- COE.PendingTotems[totem.Element].TimeoutStartMS = GetTime(); -- get the current lag - Not used but saving if needed -- -------------------- -- local _, _, lag = GetNetStats(); -- local timeout = lag / 1000 + COE.TotemPending.Timeout; local timeoutSec = 1; -- use lag + 0.5 seconds as pending timeout -- ----------------------------------------- COE:DebugMessage("SetPendingTotem Setting pending timeout to: " .. timeoutSec .. " seconds"); -- one timeout per element: a set throw has several pending at once Chronos.scheduleByName("COE_Pending" .. totem.Element, timeoutSec, COESched_CheckPendingTotem, totem.Element); end end --[[ ---------------------------------------------------------------- METHOD: COESched_CheckPendingTotem PURPOSE: Clears the pending totem if it is still active PURPOSE: Clears the pending totem if it is still pending. -------------------------------------------------------------------]] function COESched_CheckPendingTotem(element) COE:DebugMessage("Executing Method: COESched_CheckPendingTotem"); if (element) then COE:DebugMessage("COESched_CheckPendingTotem for element: " .. element); COE_Totem:ClearPendingTotem(COE.PendingTotems[element]); end end --[[ ---------------------------------------------------------------- METHOD: ClearPendingTotem PURPOSE: Clears the pending totem. -------------------------------------------------------------------]] function COE_Totem:ClearPendingTotem(pendingTotem) COE:DebugMessage("Executing Method: ClearPendingTotem"); if (pendingTotem) then pendingTotem.Totem = nil; pendingTotem.Rank = nil; end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:GetTotemFromText PURPOSE: Identifies a totem from the text value passed in. Can return a nil totem. -------------------------------------------------------------------]] function COE_Totem:GetTotemFromText(text) local totem; COE:DebugMessage("GetTotemFromText for text value: " .. text); -- find the totem for k = 1, COE.TotemCount do if (string.find(text, COE.TotemData[k].SpellName)) then -- use existing totem object -- -------------------------- totem = COE.TotemData[k]; COE:DebugMessage("GetTotemFromText found totem: " .. totem.SpellName); break end end return totem; end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ActivateTotem PURPOSE: Activates the totem timer and deactivates a still active timer of the same element -------------------------------------------------------------------]] function COE_Totem:ActivateTotem(totem) COE:DebugMessage("Method Executing: ActivateTotem with totem parameter " .. totem.SpellName); local pendingTotemObj = COE.PendingTotems[totem.Element]; if (pendingTotemObj and pendingTotemObj.Totem) then COE:DebugMessage("ActivateTotem identified pending totem : " .. pendingTotemObj.Totem.SpellName); -- deactivate still active totem of the same element -- -------------------------------------------------- local active = COE.ActiveTotems[totem.Element]; if (active) then COE_Totem:DeactivateTimer(active); end local pendingTotem = pendingTotemObj.Totem; local pendingTotemRank = pendingTotemObj.Rank; -- activate timer -- --------------- Chronos.startTimer("COE" .. totem.SpellName); totem.CurDuration = pendingTotem.Ranks[pendingTotemRank].Duration; totem.CurHealth = pendingTotem.Ranks[pendingTotemRank].Health; -- activate cooldown timer if this totem has a cooldown -- ----------------------------------------------------- if (pendingTotem.Ranks[pendingTotemRank].Cooldown > 0) then totem.CurCooldown = pendingTotem.Ranks[pendingTotemRank].Cooldown; Chronos.startTimer("COECooldown" .. totem.SpellName); Chronos.scheduleByName("COECooldownSwitch" .. totem.SpellName, totem.CurCooldown, COESched_CooldownEnd, totem); end -- activate warning timers -- ------------------------ if (totem.CurDuration >= 10) then Chronos.scheduleByName("COEWarn10" .. totem.SpellName, totem.CurDuration - 10, COESched_ExpirationWarning, 10, totem); end Chronos.scheduleByName("COEWarn5" .. totem.SpellName, totem.CurDuration - 5, COESched_ExpirationWarning, 5, totem); Chronos.scheduleByName("COEWarn0" .. totem.SpellName, totem.CurDuration, COESched_ExpirationWarning, 0, totem); -- mark totem as active -- --------------------- COE.ActiveTotems[pendingTotem.Element] = totem; totem.isActive = true; -- set totem in timer frame -- ------------------------- if (COE_Config:GetSaved(COEOPT_TIMERFRAME) == 1) then getglobal("COETimer" .. pendingTotem.Element).totem = totem; COETimerFrame:Show(); end -- mark totem as thrown if in current set -- --------------------------------------- local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET); if (COE.TotemSets[activeset][totem.Element] and COE.TotemSets[activeset][totem.Element].SpellName == totem.SpellName) then COE:DebugMessage("ActivateTotem Element " .. totem.Element .. " in current cycle: SET"); COE.SetCycle[totem.Element] = true; end -- clear pending totem -- -------------------- COE_Totem:ClearPendingTotem(pendingTotemObj); -- if in timers only mode, reconfigure the totem bar -- -------------------------------------------------- if (COE_Config:GetSaved(COEOPT_DISPLAYMODE) == COEMODE_TIMERSONLY) then COE_Totem:Invalidate(getglobal("COE" .. totem.Element .. "Frame"), true, true, true); COETotemFrame.Reconfigure = true; else -- otherwise just invalidate the dynamic buttons -- ---------------------------------------------- COE_Totem:Invalidate(getglobal("COE" .. totem.Element .. "Frame"), false, false, true); end end end --[[ ---------------------------------------------------------------- DEPRECATED: Use ActivateTotem METHOD: COE_Totem:ActivatePendingTotem PURPOSE: Activates the pending totem timer and deactivates a still active timer of the same element -------------------------------------------------------------------]] function COE_Totem:ActivatePendingTotem(totem) COE:DebugMessage( "Method Executing: ActivatePendingTotem with totem parameter " .. totem.SpellName); -- deactivate still active totem of the same element -- -------------------------------------------------- COE:DebugMessage( "ActivatePendingTotem - identifying active totems of type" .. COE.TotemPending.Totem.Element); local active = COE.ActiveTotems[COE.TotemPending.Totem.Element]; if (active) then COE_Totem:DeactivateTimer(active); end -- activate timer -- --------------- Chronos.startTimer("COE" .. totem.SpellName); totem.CurDuration = COE.TotemPending.Totem.Ranks[COE.TotemPending.UseRank] .Duration; totem.CurHealth = COE.TotemPending.Totem.Ranks[COE.TotemPending.UseRank] .Health; -- activate cooldown timer if this totem has a cooldown -- ----------------------------------------------------- if (COE.TotemPending.Totem.Ranks[COE.TotemPending.UseRank].Cooldown > 0) then totem.CurCooldown = COE.TotemPending.Totem.Ranks[COE.TotemPending .UseRank].Cooldown; Chronos.startTimer("COECooldown" .. totem.SpellName); Chronos.scheduleByName("COECooldownSwitch" .. totem.SpellName, totem.CurCooldown, COESched_CooldownEnd, totem); end -- activate warning timers -- ------------------------ if (totem.CurDuration >= 10) then Chronos.scheduleByName("COEWarn10" .. totem.SpellName, totem.CurDuration - 10, COESched_ExpirationWarning, 10, totem); end Chronos.scheduleByName("COEWarn5" .. totem.SpellName, totem.CurDuration - 5, COESched_ExpirationWarning, 5, totem); Chronos.scheduleByName("COEWarn0" .. totem.SpellName, totem.CurDuration, COESched_ExpirationWarning, 0, totem); -- mark totem as active -- --------------------- COE.ActiveTotems[COE.TotemPending.Totem.Element] = totem; totem.isActive = true; -- set totem in timer frame -- ------------------------- if (COE_Config:GetSaved(COEOPT_TIMERFRAME) == 1) then getglobal("COETimer" .. COE.TotemPending.Totem.Element).totem = totem; COETimerFrame:Show(); end -- mark totem as thrown if in current set -- --------------------------------------- local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET); if (COE.TotemSets[activeset][totem.Element] and COE.TotemSets[activeset][totem.Element].SpellName == totem.SpellName) then COE:DebugMessage("Element " .. totem.Element .. " in current cycle: SET"); COE.SetCycle[totem.Element] = true; end -- clear pending totem -- -------------------- COE_Totem:SetPendingTotem(nil); -- if in timers only mode, reconfigure the totem bar -- -------------------------------------------------- if (COE_Config:GetSaved(COEOPT_DISPLAYMODE) == COEMODE_TIMERSONLY) then COE_Totem:Invalidate(getglobal("COE" .. totem.Element .. "Frame"), true, true, true); COETotemFrame.Reconfigure = true; else -- otherwise just invalidate the dynamic buttons -- ---------------------------------------------- COE_Totem:Invalidate(getglobal("COE" .. totem.Element .. "Frame"), false, false, true); end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:DeactivateTimer PURPOSE: Deactivates an active totem timer -------------------------------------------------------------------]] function COE_Totem:DeactivateTimer(totem) if (totem and totem.isActive) then COE:DebugMessage("Deactivating totem: " .. totem.SpellName); -- end timers -- ----------- Chronos.endTimer("COE" .. totem.SpellName); Chronos.unscheduleByName("COEWarn10" .. totem.SpellName); Chronos.unscheduleByName("COEWarn5" .. totem.SpellName); Chronos.unscheduleByName("COEWarn0" .. totem.SpellName); -- reset totem data -- ------------------ totem.CurDuration = 0; totem.CurHealth = 0; totem.isActive = false; -- unset totem in timerframe -- -------------------------- local timer = getglobal("COETimer" .. totem.Element); timer.totem = nil; COETimerFrame:StopMovingOrSizing(); -- mark totem as unthrown if in current set -- ----------------------------------------- local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET); if (COE.TotemSets[activeset][totem.Element] and COE.TotemSets[activeset][totem.Element].SpellName == totem.SpellName) then COE:DebugMessage("DeactivateTimer Element " .. totem.Element .. " in current cycle: UNSET"); COE.SetCycle[totem.Element] = false; end -- invalidate dynamic buttons -- --------------------------- COE_Totem:Invalidate(getglobal("COE" .. totem.Element .. "Frame"), false, false, true); end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:IsTimerActive PURPOSE: Returns if the specified totem timer is active -------------------------------------------------------------------]] function COE_Totem:IsTimerActive(totem) if (not totem) then return false; end return (totem.CurDuration > 0) and (totem.CurDuration - Chronos.getTimer("COE" .. totem.SpellName) > 0); end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:GetTimeLeft PURPOSE: Returns the amount of seconds left on the totem timer -------------------------------------------------------------------]] function COE_Totem:GetTimeLeft(totem) if (totem.CurDuration == 0) then return 0; else return totem.CurDuration - Chronos.getTimer("COE" .. totem.SpellName); end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:GetCooldownLeft PURPOSE: Returns the amount of seconds left on the totem cooldown -------------------------------------------------------------------]] function COE_Totem:GetCooldownLeft(totem) if (totem.CurCooldown == 0) then return 0; else return totem.CurCooldown - Chronos.getTimer("COECooldown" .. totem.SpellName); end end --[[ ---------------------------------------------------------------- METHOD: COESched_ExpirationWarning PURPOSE: Issues an expiration warning for the specified totem -------------------------------------------------------------------]] function COESched_ExpirationWarning(timemark, totem) COE:DebugMessage( "Method Executing: COESched_ExpirationWarning with totem parameter " .. totem.SpellName); if (COE_Config:GetSaved(COEOPT_ENABLETOTEMBAR) == 1 and COE_Config:GetSaved(COEOPT_ENABLETIMERS) == 1 and COE_Config:GetSaved(COEOPT_TIMERNOTIFICATIONS) == 1) then local text; if (totem.isTrinket) then text = COESTR_TRINKET_TOTEM; else text = totem.SpellName; end if (timemark > 0) then COE:Notification(string.format(COESTR_TOTEMWARNING, text, timemark), COECOL_TOTEMWARNING); else COE:Notification(string.format(COESTR_TOTEMEXPIRED, text), COECOL_TOTEMDESTROYED); COE_Totem:DeactivateTimer(totem); end end end --[[ ---------------------------------------------------------------- METHOD: COESched_CooldownEnd PURPOSE: Stops the cooldown timer of the totem when the cooldown has ended -------------------------------------------------------------------]] function COESched_CooldownEnd(totem) Chronos.endTimer("COECooldown" .. totem.SpellName); totem.CurCooldown = 0; end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:TotemDamage PURPOSE: Assigns damage to the totem being hit and triggers a notification if the totem is destroyed -------------------------------------------------------------------]] function COE_Totem:TotemDamage() COE:DebugMessage("Method Executing: TotemDamage"); local num, regex, i; for num, regex in COESTR_TOTEMDAMAGE do local match = {string.gfind(arg1, regex)()}; if (table.getn(match) >= 1) then -- find totem -- ----------- for i = 1, COE.TotemCount do if (COE.TotemData[i].SpellName == match[1]) then -- subtract damage -- ---------------- COE.TotemData[i].CurHealth = COE.TotemData[i].CurHealth - tonumber(match[2]); -- totem destroyed? -- ----------------- if (COE_Config:GetSaved(COEOPT_ENABLETIMERS) == 1) then if (COE.TotemData[i].CurHealth <= 0) then COE:DebugMessage(match[1] .. " destroyed"); if (COE_Config:GetSaved(COEOPT_TIMERNOTIFICATIONS) == 1) then COE:Notification(string.format( COESTR_TOTEMDESTROYED, COE.TotemData[i].SpellName), COECOL_TOTEMDESTROYED); end COE_Totem:DeactivateTimer(COE.TotemData[i]); else COE:DebugMessage( match[1] .. " took " .. match[2] .. " damage. " .. COE.TotemData[i].CurHealth .. " health left"); end end break end end break end end end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:ResetTimers PURPOSE: Resets all active timers to zero -------------------------------------------------------------------]] function COE_Totem:ResetTimers() COE:DebugMessage("Method Executing: COE_Totem:ResetTimers"); local k; for k = 1, 4 do if (COE.ActiveTotems[COE_Element[k]]) then COE_Totem:DeactivateTimer(COE.ActiveTotems[COE_Element[k]]); end end end --[[ ============================================================================================= T I M E R - H O O K S ================================================================================================]] --[[ ---------------------------------------------------------------- METHOD: COE_Totem:SetupTimerHooks PURPOSE: Stores the CastSpell, CastSpellByName, UseAction and UseInventoryItem functions and replaces them with a call to the corresponding hook functions and the original function afterwards -------------------------------------------------------------------]] function COE_Totem:SetupTimerHooks() COE:DebugMessage("Method Executing: SetupTimerHooks"); COE_Totem.TimerHooks["UseAction"] = UseAction; UseAction = function(id, book, onself) COE_Totem:HookUseAction(id, book); COE_Totem.TimerHooks["UseAction"](id, book, onself); end; COE_Totem.TimerHooks["CastSpell"] = CastSpell; CastSpell = function(id, book) COE_Totem:HookCastSpell(id, book); COE_Totem.TimerHooks["CastSpell"](id, book); end; COE_Totem.TimerHooks["CastSpellByName"] = CastSpellByName; CastSpellByName = function(SpellName) COE_Totem:HookCastSpellByName(SpellName); COE_Totem.TimerHooks["CastSpellByName"](SpellName); end; COE_Totem.TimerHooks["UseInventoryItem"] = UseInventoryItem; UseInventoryItem = function(slotid) COE_Totem:HookUseInventoryItem(slotid); COE_Totem.TimerHooks["UseInventoryItem"](slotid); end; end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:HookUseAction PURPOSE: Setup the totem timer if the action triggered a totem -------------------------------------------------------------------]] function COE_Totem:HookUseAction(id, book) COE:DebugMessage("Method Executing: HookUseAction"); -- use only when timers are enabled -- --------------------------------- if (COE_Config:GetSaved(COEOPT_ENABLETIMERS) == 0) then return; end -- get action tooltip -- ------------------- COETotemTT:SetAction(id); -- check if this triggered a totem -- -------------------------------- local text = COETotemTTTextLeft1:GetText(); local i, rank, ranktext, trinket; -- check if this triggered the trinket -- ------------------------------------ trinket = text == COESTR_TRINKET_TOOLTIP; for i = 1, COE.TotemCount do if ((trinket and COE.TotemData[i].isTrinket) or COE.TotemData[i].SpellName == text) then if (COE.TotemData[i].isTrinket) then rank = 0; else -- get rank information -- Since WOW doesn't seem to clear the rank information -- when setting a tooltip for an action that doesn't -- have such, we need to test if the rank info is -- actually shown first -- ----------------------------------------------------- if (COETotemTTTextRight1:IsShown()) then ranktext = COETotemTTTextRight1:GetText(); end if (ranktext) then _, _, rank = string.find(ranktext, COESTR_TOTEMRANK); rank = tonumber(rank); else rank = COE.TotemData[i].MaxRank; end end -- set pending totem -- ------------------ COE_Totem:SetPendingTotem(COE.TotemData[i], rank); return; end end -- no totem spell. remove pending totem if set -- -------------------------------------------- COE_Totem:SetPendingTotem(nil); end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:HookCastSpell PURPOSE: Setup the totem timer if the spell triggered a totem -------------------------------------------------------------------]] function COE_Totem:HookCastSpell(id, book) COE:DebugMessage("Method Executing: HookCastSpell"); -- use only when timers are enabled -- --------------------------------- if (COE_Config:GetSaved(COEOPT_ENABLETIMERS) == 0) then return; end -- get the associated totem object -- -------------------------------- local i, k, totem; for i = 1, COE.TotemCount do for k = 1, COE.TotemData[i].MaxRank do if (COE.TotemData[i].Ranks[k].SpellID == id) then -- totem found. mark it as pending -- -------------------------------- COE_Totem:SetPendingTotem(COE.TotemData[i], k); return; end end end -- no totem spell. remove pending totem if set -- -------------------------------------------- COE_Totem:SetPendingTotem(nil); end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:HookCastSpellByName PURPOSE: Setup the totem timer if the spell triggered a totem -------------------------------------------------------------------]] function COE_Totem:HookCastSpellByName(SpellName) COE:DebugMessage("Method Executing: HookCastSpellByName"); -- use only when timers are enabled -- --------------------------------- if (COE_Config:GetSaved(COEOPT_ENABLETIMERS) == 0) then return; end -- extract rank information -- ------------------------- local _, _, rank = string.find(SpellName, COESTR_CASTBYNAME); local spell = string.gsub(SpellName, COESTR_CASTBYNAME, ""); -- get the associated totem object -- -------------------------------- local i, k, totem; for i = 1, COE.TotemCount do if (COE.TotemData[i].SpellName == spell) then if (rank) then rank = tonumber(rank); else rank = COE.TotemData[i].MaxRank; end -- totem found. mark it as pending -- -------------------------------- COE_Totem:SetPendingTotem(COE.TotemData[i], rank); return; end end -- no totem spell. remove pending totem if set -- -------------------------------------------- COE_Totem:SetPendingTotem(nil); end --[[ ---------------------------------------------------------------- METHOD: COE_Totem:HookUseInventoryItem PURPOSE: Setup the totem timer if the item triggered a totem -------------------------------------------------------------------]] function COE_Totem:HookUseInventoryItem(slotid) COE:DebugMessage("Method Executing: HookUseInventoryItem"); -- use only when timers are enabled -- --------------------------------- if (COE_Config:GetSaved(COEOPT_ENABLETIMERS) == 0) then return; end -- only use for trinket slots -- --------------------------- if (slotid ~= GetInventorySlotInfo("Trinket0Slot") and slotid ~= GetInventorySlotInfo("Trinket1Slot")) then return; end -- iterate through out totems and find the trinket totem -- ------------------------------------------------------ local i; for i = 1, COE.TotemCount do if (COE.TotemData[i].isTrinket) then -- does the totem have the same slot id? -- -------------------------------------- if (COE.TotemData[i].TrinketSlot == slotid) then -- our trinket totem was cast -- --------------------------- COE_Totem:SetPendingTotem(COE.TotemData[i], 0); return; end end end -- no totem spell. remove pending totem if set -- -------------------------------------------- COE_Totem:SetPendingTotem(nil); end