Files
CallOfElements/COE_TotemLogic.lua
T
shaga 72ab7c8aa5 Throw the whole totem set on one press
OctoWoW gives each totem element its own global cooldown category
(Fire 105, Earth 106, Water 107, Air 108), so one totem per element can
be cast at the same time. ThrowSet still stopped after the first totem
it cast, as it had to when all totems shared one global cooldown, so
"/coe throwset" dropped one totem per key press.

- ThrowSet casts every ready, not yet thrown totem of the active set.
- A second press within 1 second skips totems whose cast is not yet
  confirmed, so a double press does not drop them again.
  "/coe restartset" clears this.
- Each element gets its own pending timeout. They shared one name, so
  casting several at once replaced the earlier timeouts.
2026-10-15 07:05:15 +00:00

1374 lines
48 KiB
Lua

--[[
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