You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

738 lines
22 KiB

local ADDON_NAME,Internal = ...
local L = Internal.L
local GetMilestoneEssence = C_AzeriteEssence.GetMilestoneEssence;
local HelpTipBox_Anchor = Internal.HelpTipBox_Anchor;
local HelpTipBox_SetText = Internal.HelpTipBox_SetText;
local AddSet = Internal.AddSet
local format = string.format
local function CompareSets(a, b)
if not tCompare(a.essences, b.essences, 10) then
return false
end
if type(a.restrictions) ~= type(b.restrictions) and not tCompare(a.restrictions, b.restrictions, 10) then
return false
end
return true
end
local function UpdateSetFilters(set)
set.filters = set.filters or {}
Internal.UpdateRestrictionFilters(set)
local filters = set.filters
filters.role = set.role
-- Rebuild character list
filters.character = filters.character or {}
local characters = filters.character
table.wipe(characters)
local role = filters.role
for _,character in Internal.CharacterIterator() do
if Internal.IsClassRoleValid(Internal.GetCharacterInfo(character).class, role) then
characters[#characters+1] = character
end
end
set.filters = filters
return set
end
local function GetEssenceSet(id)
if type(id) == "table" then
return id;
else
return BtWLoadoutsSets.essences[id];
end
end
local IsQuestFlaggedCompleted = C_QuestLog and C_QuestLog.IsQuestFlaggedCompleted or IsQuestFlaggedCompleted
local function CanActivateEssences()
return IsQuestFlaggedCompleted(55618) -- The Heart Forge quest
end
-- returns isValid and isValidForPlayer
local function EssenceSetIsValid(set)
local set = GetEssenceSet(set);
return true, Internal.IsClassRoleValid(select(2, UnitClass("player")), set.role)
end
local function IsEssenceSetActive(set)
if CanActivateEssences() then
for milestoneID,essenceID in pairs(set.essences) do
local info = C_AzeriteEssence.GetMilestoneInfo(milestoneID);
if info and (info.unlocked or info.canUnlock) and C_AzeriteEssence.GetMilestoneEssence(milestoneID) ~= essenceID then
return false;
end
end
end
return true;
end
local function ActivateEssenceSet(set, state)
local success, complete = true, true;
if (not state or (not state.ignoreItem and not state.allowPartial)) and state.heartEquipped or GetInventoryItemID("player", INVSLOT_NECK) == 158075 then
for milestoneID,essenceID in pairs(set.essences) do
local info = C_AzeriteEssence.GetEssenceInfo(essenceID)
local essenceName, essenceRank = info.name, info.rank
if info and info.valid and info.unlocked then
local info = C_AzeriteEssence.GetMilestoneInfo(milestoneID);
if info.canUnlock then
C_AzeriteEssence.UnlockMilestone(milestoneID);
complete = false;
end
if info.unlocked and C_AzeriteEssence.GetMilestoneEssence(milestoneID) ~= essenceID then
C_AzeriteEssence.ActivateEssence(essenceID, milestoneID);
complete = false;
Internal.LogMessage("Switching essence %d to %s", milestoneID, C_AzeriteEssence.GetEssenceHyperlink(essenceID, essenceRank or 4))
end
end
end
end
return complete, not complete;
end
local function RefreshEssenceSet(set)
local essences = set.essences or {}
wipe(essences)
essences[115] = GetMilestoneEssence(115);
essences[116] = GetMilestoneEssence(116);
essences[117] = GetMilestoneEssence(117);
essences[119] = GetMilestoneEssence(119);
set.essences = essences
return UpdateSetFilters(set)
end
local function AddEssenceSet()
local role = select(5,GetSpecializationInfo(GetSpecialization()));
return AddSet("essences", RefreshEssenceSet({
role = role,
name = format(L["New %s Set"], _G[role]);
useCount = 0,
essences = {},
}))
end
local function GetEssenceSetsByName(name)
return Internal.GetSetsByName("essences", name)
end
local function GetEssenceSetByName(name)
return Internal.GetSetByName("essences", name, EssenceSetIsValid)
end
local function GetEssenceSets(id, ...)
if id ~= nil then
return BtWLoadoutsSets.essences[id], Internal.GetEssenceSets(...);
end
end
local function GetEssenceSetIfNeeded(id)
if id == nil then
return;
end
local set = Internal.GetEssenceSet(id);
if IsEssenceSetActive(set) then
return;
end
return set;
end
--[[
Check what is needed to activate this talent set
return isActive, waitForCooldown
]]
local function EssenceSetRequirements(set)
local isActive, waitForCooldown = true, false
for milestoneID,essenceID in pairs(set.essences) do
if essenceID ~= C_AzeriteEssence.GetMilestoneEssence(milestoneID) then
isActive = false
local spellID = C_AzeriteEssence.GetMilestoneSpell(milestoneID)
if spellID then
spellID = FindSpellOverrideByID(spellID)
local start, duration = GetSpellCooldown(spellID)
if start ~= 0 then -- Milestone spell on cooldown, we need to wait before switching
Internal.DirtyAfter((start + duration) - GetTime() + 1)
waitForCooldown = true
break
end
end
end
end
-- for talentID in pairs(set.talents) do
-- local row = select(8, GetTalentInfoByID(talentID, 1))
-- local column = select(2, GetTalentTierInfo(row, 1))
-- local selectedTalentID, _, _, _, _, spellID = GetTalentInfo(row, column, 1)
-- if selectedTalentID ~= talentID then
-- isActive = false
-- if spellID then
-- spellID = FindSpellOverrideByID(spellID)
-- local start, duration = GetSpellCooldown(spellID)
-- if start ~= 0 then -- Talent spell on cooldown, we need to wait before switching
-- Internal.DirtyAfter((start + duration) - GetTime() + 1)
-- waitForCooldown = true
-- break -- We dont actually need to check anything more
-- end
-- end
-- end
-- end
return isActive, waitForCooldown
end
local function CombineEssenceSets(result, state, ...)
result = result or {};
result.essences = {};
if CanActivateEssences() and (not state or state.heartEquipped) then -- Check if essences have been unlocked and we will have the heart equipped
for i=1,select('#', ...) do
local set = select(i, ...);
if Internal.AreRestrictionsValidForPlayer(set.restrictions) then
for milestoneID, essenceID in pairs(set.essences) do
result.essences[milestoneID] = essenceID;
end
end
end
if state then
if result.essences[115] == nil then
state.conflictAndStrife = GetMilestoneEssence(115) == 32; -- Conflict is equipped
else
state.conflictAndStrife = result.essences[115] == 32
end
local isActive, waitForCooldown = EssenceSetRequirements(result)
if not isActive then
if state.blockers then
state.blockers[Internal.GetRestedTomeBlocker()] = true
state.blockers[Internal.GetCombatBlocker()] = true
state.blockers[Internal.GetMythicPlusBlocker()] = true
state.blockers[Internal.GetJailersChainBlocker()] = true
end
state.customWait = state.customWait or (waitForCooldown and L["Waiting for essence cooldown"])
end
end
end
return result;
end
local function DeleteEssenceSet(id)
Internal.DeleteSet(BtWLoadoutsSets.essences, id);
if type(id) == "table" then
id = id.setID;
end
for _,set in pairs(BtWLoadoutsSets.profiles) do
if type(set) == "table" then
for index,setID in ipairs(set.essences) do
if setID == id then
table.remove(set.essences, index)
end
end
end
end
local frame = BtWLoadoutsFrame.Essences;
local set = frame.set;
if set.setID == id then
frame.set = nil;-- = select(2,next(BtWLoadoutsSets.essences)) or {};
BtWLoadoutsFrame:Update();
end
end
local function EssenceSetDelay(set)
for milestoneID,essenceID in pairs(set.essences) do
local spellID = C_AzeriteEssence.GetMilestoneSpell(milestoneID)
if spellID and essenceID ~= C_AzeriteEssence.GetMilestoneEssence(milestoneID) then
spellID = FindSpellOverrideByID(spellID)
local start, duration = GetSpellCooldown(spellID)
if start ~= 0 then -- Milestone spell on cooldown, we need to wait before switching
Internal.DirtyAfter((start + duration) - GetTime() + 1)
return true
end
end
end
return false
end
local function CheckErrors(errorState, set)
set = GetEssenceSet(set)
if errorState.specID then
errorState.role, errorState.class = select(5, GetSpecializationInfoByID(errorState.specID))
end
errorState.role = errorState.role or set.role
if errorState.role ~= set.role then
return L["Incompatible Role"]
end
if not Internal.AreRestrictionsValidFor(set.restrictions, errorState.specID) then
return L["Incompatible Restrictions"]
end
end
Internal.GetEssenceSets = GetEssenceSets
Internal.GetEssenceSetIfNeeded = GetEssenceSetIfNeeded
Internal.CanActivateEssences = CanActivateEssences
Internal.GetEssenceSet = GetEssenceSet
Internal.GetEssenceSetsByName = GetEssenceSetsByName
Internal.GetEssenceSetByName = GetEssenceSetByName
Internal.EssenceSetDelay = EssenceSetDelay
Internal.AddEssenceSet = AddEssenceSet
Internal.RefreshEssenceSet = RefreshEssenceSet
Internal.DeleteEssenceSet = DeleteEssenceSet
Internal.ActivateEssenceSet = ActivateEssenceSet
Internal.IsEssenceSetActive = IsEssenceSetActive
Internal.CombineEssenceSets = CombineEssenceSets
Internal.GetEssenceSets = GetEssenceSets
-- Initializes the set dropdown menu for the Loadouts page
local function SetDropDownInit(self, set, index)
Internal.SetDropDownInit(self, set, index, "essences", BtWLoadoutsFrame.Essences)
end
Internal.AddLoadoutSegment({
id = "essences",
name = L["Essences"],
after = "equipment",
events = "AZERITE_ESSENCE_UPDATE",
add = AddEssenceSet,
get = GetEssenceSets,
getByName = GetEssenceSetByName,
combine = CombineEssenceSets,
isActive = IsEssenceSetActive,
activate = ActivateEssenceSet,
dropdowninit = SetDropDownInit,
checkerrors = CheckErrors,
export = function (set)
return {
version = 1,
name = set.name,
role = set.role,
essences = set.essences,
restrictions = set.restrictions,
}
end,
import = function (source, version, name, ...)
assert(version == 1)
local role = source.role or ...
return Internal.AddSet("essences", UpdateSetFilters({
role = role,
name = name or source.name,
useCount = 0,
essences = source.essences,
restrictions = source.restrictions,
}))
end,
getByValue = function (set)
return Internal.GetSetByValue(BtWLoadoutsSets.essences, set, CompareSets)
end,
verify = function (source, ...)
local role = source.role or ...
if not role or type(_G[role]) ~= "string" then
return false, L["Invalid role"]
end
if source.restrictions ~= nil and type(source.restrictions) ~= "table" then
return false, L["Missing restrictions"]
end
-- @TODO verify essence ids?
return true
end,
})
BtWLoadoutsAzeriteMilestoneSlotMixin = {};
function BtWLoadoutsAzeriteMilestoneSlotMixin:OnLoad()
self.EmptyGlow.Anim:Play();
end
function BtWLoadoutsAzeriteMilestoneSlotMixin:OnEnter()
if self.id then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
GameTooltip:SetAzeriteEssence(self.id, 4);
SharedTooltip_SetBackdropStyle(GameTooltip, GAME_TOOLTIP_BACKDROP_STYLE_AZERITE_ITEM);
end
if self:GetParent().pending then
SetCursor("interface/cursor/cast.blp");
end
end
function BtWLoadoutsAzeriteMilestoneSlotMixin:OnLeave()
GameTooltip_Hide();
end
function BtWLoadoutsAzeriteMilestoneSlotMixin:OnClick()
local essences = self:GetParent();
local selected = essences.set.essences;
local pendingEssenceID = essences.pending;
if pendingEssenceID then
for milestoneID,essenceID in pairs(selected) do
if essenceID == pendingEssenceID then
selected[milestoneID] = nil;
end
end
selected[self.milestoneID] = pendingEssenceID;
essences.pending = nil;
SetCursor(nil);
else
selected[self.milestoneID] = nil;
end
BtWLoadoutsFrame:Update();
end
BtWLoadoutsAzeriteEssenceButtonMixin = {};
function BtWLoadoutsAzeriteEssenceButtonMixin:OnClick()
SetCursor("interface/cursor/cast.blp");
BtWLoadoutsFrame.Essences.pending = self.id;
BtWLoadoutsFrame:Update();
end
function BtWLoadoutsAzeriteEssenceButtonMixin:OnEnter()
if self.id then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
GameTooltip:SetAzeriteEssence(self.id, 4);
end
if BtWLoadoutsFrame.Essences.pending then
SetCursor("interface/cursor/cast.blp");
end
end
local function RoleDropDown_OnClick(self, arg1, arg2, checked)
local tab = BtWLoadoutsFrame.Essences;
CloseDropDownMenus();
local set = tab.set;
local temp = tab.temp;
-- @TODO: If we always access talents by set.talents then we can just swap tables in and out of
-- the temp table instead of copying the talentIDs around
-- We are going to copy the currently selected talents for the currently selected spec into
-- a temporary table incase the user switches specs back
local role = set.role;
if temp[role] then
wipe(temp[role]);
else
temp[role] = {};
end
for milestoneID, essenceID in pairs(set.essences) do
temp[role][milestoneID] = essenceID;
end
-- Clear the current talents and copy back the previously selected talents if they exist
role = arg1;
set.role = role;
wipe(set.essences);
if temp[role] then
for milestoneID, essenceID in pairs(temp[role]) do
set.essences[milestoneID] = essenceID;
end
end
BtWLoadoutsFrame:Update();
end
local function RoleDropDownInit(self, level, menuList)
local info = UIDropDownMenu_CreateInfo();
local set = self:GetParent().set;
local selected = set and set.role;
if (level or 1) == 1 then
for _,role in Internal.Roles() do
info.text = _G[role];
info.arg1 = role;
info.func = RoleDropDown_OnClick;
info.checked = selected == role;
UIDropDownMenu_AddButton(info, level);
end
end
end
local EssenceScrollFrameUpdate;
do
local MAX_ESSENCES = 14;
function EssenceScrollFrameUpdate(self)
local pending = self:GetParent().pending;
local set = self:GetParent().set;
local buttons = self.buttons;
local role = set and set.role or select(5, GetSpecializationInfo(GetSpecialization()));
local selected = set and set.essences;
local offset = HybridScrollFrame_GetOffset(self);
for i,item in ipairs(buttons) do
local index = offset + i;
local essence = Internal.GetEssenceInfoForRole(role, index);
if essence then
item.id = essence.ID;
item.Name:SetText(essence.name);
item.Icon:SetTexture(essence.icon);
if selected then
item.ActivatedMarkerMain:SetShown(selected[115] == essence.ID);
item.ActivatedMarkerPassive:SetShown((selected[116] == essence.ID) or (selected[117] == essence.ID));
else
item.ActivatedMarkerMain:SetShown(false);
item.ActivatedMarkerPassive:SetShown(false);
end
item.PendingGlow:SetShown(pending == essence.ID);
item:Show();
else
item:Hide();
end
end
local totalHeight = MAX_ESSENCES * (41 + 1) + 3 * 2;
HybridScrollFrame_Update(self, totalHeight, self:GetHeight());
end
end
BtWLoadoutsEssencesMixin = {}
function BtWLoadoutsEssencesMixin:OnLoad()
self.RestrictionsDropDown:SetSupportedTypes("spec", "race")
self.RestrictionsDropDown:SetScript("OnChange", function ()
self:Update()
end)
self.temp = {}; -- Stores talents for currently unselected specs incase the user switches to them
self.pending = nil;
end
function BtWLoadoutsEssencesMixin:OnShow()
if not self.initialized then
UIDropDownMenu_SetWidth(self.RoleDropDown, 170);
UIDropDownMenu_Initialize(self.RoleDropDown, RoleDropDownInit);
UIDropDownMenu_JustifyText(self.RoleDropDown, "LEFT");
self.Slots = {
[115] = self.MajorSlot,
[116] = self.MinorSlot1,
[117] = self.MinorSlot2,
[119] = self.MinorSlot3,
};
HybridScrollFrame_CreateButtons(self.EssenceList, "BtWLoadoutsAzeriteEssenceButtonTemplate", 4, -3, "TOPLEFT", "TOPLEFT", 0, -1, "TOP", "BOTTOM");
self.EssenceList.update = EssenceScrollFrameUpdate;
self.initialized = true
end
end
function BtWLoadoutsEssencesMixin:ChangeSet(set)
self.set = set
wipe(self.temp);
self:Update()
end
function BtWLoadoutsEssencesMixin:UpdateSetName(value)
if self.set and self.set.name ~= not value then
self.set.name = value;
self:Update();
end
end
function BtWLoadoutsEssencesMixin:OnButtonClick(button)
CloseDropDownMenus()
if button.isAdd then
self.Name:ClearFocus();
self:ChangeSet(AddEssenceSet())
C_Timer.After(0, function ()
self.Name:HighlightText();
self.Name:SetFocus();
end)
elseif button.isDelete then
local set = self.set;
if set.useCount > 0 then
StaticPopup_Show("BTWLOADOUTS_DELETEINUSESET", set.name, nil, {
set = set,
func = DeleteEssenceSet,
});
else
StaticPopup_Show("BTWLOADOUTS_DELETESET", set.name, nil, {
set = set,
func = DeleteEssenceSet,
});
end
elseif button.isRefresh then
local set = self.set;
RefreshEssenceSet(set)
self:Update()
elseif button.isExport then
local set = self.set;
self:GetParent():SetExport(Internal.Export("essences", set.setID))
elseif button.isActivate then
Internal.ActivateProfile({
essences = {self.set.setID}
});
end
end
function BtWLoadoutsEssencesMixin:OnSidebarItemClick(button)
CloseDropDownMenus()
if button.isHeader then
button.collapsed[button.id] = not button.collapsed[button.id]
self:Update()
else
if IsModifiedClick("SHIFT") then
Internal.ActivateProfile({
essences = {button.id}
});
else
self.Name:ClearFocus();
self:ChangeSet(GetEssenceSet(button.id))
end
end
end
function BtWLoadoutsEssencesMixin:OnSidebarItemDoubleClick(button)
CloseDropDownMenus()
if button.isHeader then
return
end
Internal.ActivateProfile({
essences = {button.id}
});
end
function BtWLoadoutsEssencesMixin:OnSidebarItemDragStart(button)
CloseDropDownMenus()
if button.isHeader then
return
end
local icon = "INV_Misc_QuestionMark";
local set = GetEssenceSet(button.id);
local command = format("/btwloadouts activate essences %d", button.id);
if command then
local macroId;
local numMacros = GetNumMacros();
for i=1,numMacros do
if GetMacroBody(i):trim() == command then
macroId = i;
break;
end
end
if not macroId then
if numMacros == MAX_ACCOUNT_MACROS then
print(L["Cannot create any more macros"]);
return;
end
if InCombatLockdown() then
print(L["Cannot create macros while in combat"]);
return;
end
macroId = CreateMacro(set.name, icon, command, false);
else
-- Rename the macro while not in combat
if not InCombatLockdown() then
icon = select(2,GetMacroInfo(macroId))
EditMacro(macroId, set.name, icon, command)
end
end
if macroId then
PickupMacro(macroId);
end
end
end
function BtWLoadoutsEssencesMixin:Update()
self:GetParent():SetTitle(L["Essences"]);
local sidebar = BtWLoadoutsFrame.Sidebar
sidebar:SetSupportedFilters("role", "character", "spec", "class", "race")
sidebar:SetSets(BtWLoadoutsSets.essences)
sidebar:SetCollapsed(BtWLoadoutsCollapsed.essences)
sidebar:SetCategories(BtWLoadoutsCategories.essences)
sidebar:SetFilters(BtWLoadoutsFilters.essences)
sidebar:SetSelected(self.set)
sidebar:Update()
self.set = sidebar:GetSelected()
local set = self.set
local showingNPE = BtWLoadoutsFrame:SetNPEShown(set == nil, L["Essences"], L["Create sets for the Battle for Azeroth artifact neck."])
self:GetParent().ExportButton:SetEnabled(true)
self:GetParent().DeleteButton:SetEnabled(true);
if not showingNPE then
UpdateSetFilters(set)
sidebar:Update()
set.restrictions = set.restrictions or {}
self.RestrictionsDropDown:SetSelections(set.restrictions)
self.RestrictionsDropDown:SetLimitations("role", set.role)
self.RestrictionsButton:SetEnabled(true);
local role = set.role;
UIDropDownMenu_SetText(self.RoleDropDown, _G[role]);
if not self.Name:HasFocus() then
self.Name:SetText(set.name or "");
end
for milestoneID,item in pairs(self.Slots) do
local essenceID = set.essences[milestoneID];
item.milestoneID = milestoneID;
if essenceID then
local info = Internal.GetEssenceInfoByID(essenceID);
item.id = essenceID;
item.Icon:Show();
item.Icon:SetTexture(info.icon);
item.EmptyGlow:Hide();
item.EmptyIcon:Hide();
else
item.id = nil;
item.Icon:Hide();
item.EmptyGlow:Show();
item.EmptyIcon:Show();
end
end
local playerSpecIndex = GetSpecialization()
self:GetParent().RefreshButton:SetEnabled(playerSpecIndex and role == select(5, GetSpecializationInfo(playerSpecIndex)))
self:GetParent().ActivateButton:SetEnabled(role == select(5, GetSpecializationInfo(GetSpecialization())));
local helpTipBox = self:GetParent().HelpTipBox;
helpTipBox:Hide();
else
local role = select(5, GetSpecializationInfo(GetSpecialization()))
UIDropDownMenu_SetText(self.RoleDropDown, _G[role]);
self.Name:SetText(format(L["New %s Set"], _G[role]));
for milestoneID,item in pairs(self.Slots) do
item.milestoneID = milestoneID;
item.id = nil;
item.Icon:Hide();
item.EmptyGlow:Hide();
item.EmptyIcon:Show();
end
local helpTipBox = self:GetParent().HelpTipBox;
helpTipBox:Hide();
end
EssenceScrollFrameUpdate(self.EssenceList);
end
function BtWLoadoutsEssencesMixin:SetEnabled(value)
BtWLoadoutsTabFrame_SetEnabled(self, value)
end
function BtWLoadoutsEssencesMixin:SetSetByID(setID)
self.set = GetEssenceSet(setID)
end