Files
CallOfElements/COE_TotemLogic.lua
T
MDGitHubRepo 8d904ad02d Updated pending totem logic
Pending totem logic was only storing the latest pending totem. Refactored it to have a pending totem for each element type so that they do not overwrite when using throwset.
2024-12-14 14:04:16 -05:00

1355 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 the next totem in the active set that is not
yet thrown
-------------------------------------------------------------------]]
function COE_Totem:ThrowSet()
COE:DebugMessage("Method Executing: ThrowSet");
if (COE_Config:GetSaved(COEOPT_ENABLESETS) == 1) then
-- =======================================================================
-- check which totem to throw
-- =======================================================================
local activeset = COE_Config:GetSaved(COEOPT_ACTIVESET);
COE:DebugMessage("ThrowSet activeset value: " .. activeset);
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
if (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);
return;
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);
return;
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};
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: " .. rank);
COE.PendingTotems[totem.Element].Totem = totem;
COE.PendingTotems[totem.Element].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");
Chronos.scheduleByName("COE_Pending", 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