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.

726 lines
24 KiB

4 years ago
local ADDON_NAME,Internal = ...
local L = Internal.L
local Settings = Internal.Settings
local UnitClass = UnitClass;
local GetClassColor = C_ClassColor.GetClassColor;
local LOCALIZED_CLASS_NAMES_MALE = LOCALIZED_CLASS_NAMES_MALE;
local MAX_TALENT_TIERS = MAX_TALENT_TIERS;
local LearnTalent = LearnTalent;
local GetTalentInfo = GetTalentInfo;
local GetTalentTierInfo = GetTalentTierInfo;
local GetTalentInfoByID = GetTalentInfoByID
local GetTalentInfoForSpecID = Internal.GetTalentInfoForSpecID;
local GetSpecialization = GetSpecialization;
local GetSpecializationInfo = GetSpecializationInfo;
local GetSpecializationInfoByID = GetSpecializationInfoByID;
local UIDropDownMenu_SetText = UIDropDownMenu_SetText;
local UIDropDownMenu_EnableDropDown = UIDropDownMenu_EnableDropDown;
local UIDropDownMenu_DisableDropDown = UIDropDownMenu_DisableDropDown;
local UIDropDownMenu_SetSelectedValue = UIDropDownMenu_SetSelectedValue;
local format = string.format
local AddSet = Internal.AddSet;
local DeleteSet = Internal.DeleteSet;
local HelpTipBox_Anchor = Internal.HelpTipBox_Anchor;
local HelpTipBox_SetText = Internal.HelpTipBox_SetText;
do -- Filter chat spam
local filters = {
string.gsub(ERR_LEARN_ABILITY_S, "%%s", "(.*)"),
string.gsub(ERR_LEARN_SPELL_S, "%%s", "(.*)"),
string.gsub(ERR_LEARN_PASSIVE_S, "%%s", "(.*)"),
string.gsub(ERR_SPELL_UNLEARNED_S, "%%s", "(.*)"),
}
local function ChatFrame_FilterTalentChanges(self, event, msg, ...)
if Settings.filterChatSpam then
for _,pattern in ipairs(filters) do
if string.match(msg, pattern) then
return true
end
end
end
return false, msg, ...
end
Internal.OnEvent("LOADOUT_CHANGE_START", function ()
ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", ChatFrame_FilterTalentChanges)
end)
Internal.OnEvent("LOADOUT_CHANGE_END", function ()
ChatFrame_RemoveMessageEventFilter("CHAT_MSG_SYSTEM", ChatFrame_FilterTalentChanges)
end)
end
do -- Prevent new spells from flying to the action bar
local WasEventRegistered
Internal.OnEvent("LOADOUT_CHANGE_START", function ()
WasEventRegistered = IconIntroTracker:IsEventRegistered("SPELL_PUSHED_TO_ACTIONBAR")
IconIntroTracker:UnregisterEvent("SPELL_PUSHED_TO_ACTIONBAR")
end)
Internal.OnEvent("LOADOUT_CHANGE_END", function ()
if WasEventRegistered then
IconIntroTracker:RegisterEvent("SPELL_PUSHED_TO_ACTIONBAR")
end
end)
end
-- Make sure talent sets dont have incorrect id, call from GetTalentSet and the UI?
local function FixTalentSet(set)
local temp = {}
local changed = false
for talentID in pairs(set.talents) do
local tier, column = Internal.VerifyTalentForSpec(set.specID, talentID)
if tier == nil or temp[tier] then
set.talents[talentID] = nil
changed = true
else
temp[tier] = talentID
end
end
return changed
end
local function UpdateSetFilters(set)
set.filters = set.filters or {}
local specID = set.specID;
local filters = set.filters
Internal.UpdateRestrictionFilters(set)
filters.spec = specID
if specID then
filters.role, filters.class = select(5, GetSpecializationInfoByID(specID))
else
filters.role, filters.class = nil, nil
end
-- Rebuild character list
filters.character = filters.character or {}
local characters = filters.character
table.wipe(characters)
local class = filters.class
for _,character in Internal.CharacterIterator() do
if class == Internal.GetCharacterInfo(character).class then
characters[#characters+1] = character
end
end
set.filters = filters
return set
end
local function GetTalentSet(id)
if type(id) == "table" then
return id;
else
return BtWLoadoutsSets.talents[id];
end;
end
-- In General, For Player, For Player Spec
local function TalentSetIsValid(set)
set = GetTalentSet(set);
local playerSpecID = GetSpecializationInfo(GetSpecialization());
local playerClass = select(2, UnitClass("player"));
local specClass = select(6, GetSpecializationInfoByID(set.specID));
return true, (playerClass == specClass), (playerSpecID == set.specID)
end
-- Check if the talents in the table talentIDs are selected
local function IsTalentSetActive(set)
for talentID in pairs(set.talents) do
local _, _, _, selected, _, _, _, tier = GetTalentInfoByID(talentID, 1);
local tierAvailable = GetTalentTierInfo(tier, 1)
-- For lower level characters just ignore tiers over their currently available
if tierAvailable and not selected then
return false;
end
end
return true;
end
--[[
Activate a talent set
return complete, dirty
]]
local function ActivateTalentSet(set, state)
local success, complete = true, true;
3 years ago
local canChangeTalents = not Internal.GetRestedTomeBlocker():IsActive()
for talentID in pairs(set.talents) do
local selected, _, _, _, tier = select(4, GetTalentInfoByID(talentID, 1));
local available, currentColumn = GetTalentTierInfo(tier, 1)
if not selected and available then
if canChangeTalents or currentColumn == 0 then
local slotSuccess = LearnTalent(talentID)
success = slotSuccess and success
complete = false
Internal.LogMessage("Switching talent %d to %s (%s)", tier, GetTalentLink(talentID, 1), slotSuccess and "true" or "false")
4 years ago
end
end
end
return complete, false;
end
local function RefreshTalentSet(set)
local talents = set.talents or {}
local specID, specName = GetSpecializationInfo(GetSpecialization());
if specID == set.specID then
wipe(talents)
for tier=1,MAX_TALENT_TIERS do
local _, column = GetTalentTierInfo(tier, 1);
local talentID = GetTalentInfo(tier, column, 1);
if talentID then
talents[talentID] = true;
end
end
end
set.talents = talents
return UpdateSetFilters(set)
end
local function AddTalentSet()
local specIndex = GetSpecialization()
if specIndex == 5 then
specIndex = 1
end
local specID, specName = GetSpecializationInfo(specIndex);
return AddSet("talents", RefreshTalentSet({
specID = specID,
name = format(L["New %s Set"], specName),
useCount = 0,
talents = {},
}))
end
local function TalentSetDelay(set)
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 and 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)
return true
end
end
end
return false
end
--[[
Check what is needed to activate this talent set
return isActive, waitForCooldown, anySelected
]]
local function TalentSetRequirements(set)
local isActive, waitForCooldown, anySelected = true, false, false
for talentID in pairs(set.talents) do
local row = select(8, GetTalentInfoByID(talentID, 1))
local available, column = GetTalentTierInfo(row, 1)
if available then
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
if column ~= 0 then
anySelected = true
end
end
end
end
return isActive, waitForCooldown, anySelected
end
local function GetTalentSetsByName(name)
return Internal.GetSetsByName("talents", name)
end
local function GetTalentSetByName(name)
return Internal.GetSetByName("talents", name, TalentSetIsValid)
end
local function GetTalentSets(id, ...)
if id ~= nil then
return GetTalentSet(id), GetTalentSets(...);
end
end
local function GetTalentSetIfNeeded(id)
if id == nil then
return;
end
local set = Internal.GetTalentSet(id);
if IsTalentSetActive(set) then
return;
end
return set;
end
local talentSetsByTier = {};
local function CombineTalentSets(result, state, ...)
result = result or {};
result.talents = {};
wipe(talentSetsByTier);
for i=1,select('#', ...) do
local set = Internal.GetTalentSet(select(i, ...));
if Internal.AreRestrictionsValidForPlayer(set.restrictions) then
for talentID in pairs(set.talents) do
if result.talents[talentID] == nil then
local tier = select(8, GetTalentInfoByID(talentID, 1));
if (GetTalentTierInfo(tier, 1)) then
if talentSetsByTier[tier] then
result.talents[talentSetsByTier[tier]] = nil;
end
result.talents[talentID] = true;
talentSetsByTier[tier] = talentID;
end
end
end
end
end
if state then
local isActive, waitForCooldown, anySelected = TalentSetRequirements(result)
if not isActive then
3 years ago
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
4 years ago
state.customWait = state.customWait or (waitForCooldown and L["Waiting for talent cooldown"])
end
end
return result;
end
local function DeleteTalentSet(id)
Internal.DeleteSet(BtWLoadoutsSets.talents, 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.talents) do
if setID == id then
table.remove(set.talents, index)
end
end
end
end
local frame = BtWLoadoutsFrame.Talents;
local set = frame.set;
if set.setID == id then
frame.set = nil;-- = select(2,next(BtWLoadoutsSets.talents)) or {};
BtWLoadoutsFrame:Update();
end
end
local function CheckErrors(errorState, set)
set = GetTalentSet(set)
errorState.specID = errorState.specID or set.specID
if errorState.specID ~= set.specID then
return L["Incompatible Specialization"]
end
if not Internal.AreRestrictionsValidFor(set.restrictions, errorState.specID) then
return L["Incompatible Restrictions"]
end
end
Internal.FixTalentSet = FixTalentSet
Internal.GetTalentSet = GetTalentSet
Internal.GetTalentSets = GetTalentSets
Internal.GetTalentSetIfNeeded = GetTalentSetIfNeeded
Internal.GetTalentSetsByName = GetTalentSetsByName
Internal.GetTalentSetByName = GetTalentSetByName
Internal.TalentSetDelay = TalentSetDelay
Internal.AddTalentSet = AddTalentSet
Internal.RefreshTalentSet = RefreshTalentSet
Internal.DeleteTalentSet = DeleteTalentSet
Internal.ActivateTalentSet = ActivateTalentSet
Internal.IsTalentSetActive = IsTalentSetActive
Internal.CombineTalentSets = CombineTalentSets
Internal.GetTalentSets = GetTalentSets
-- Initializes the set dropdown menu for the Loadouts page
local function SetDropDownInit(self, set, index)
Internal.SetDropDownInit(self, set, index, "talents", BtWLoadoutsFrame.Talents)
end
local function CompareSets(a, b)
if not tCompare(a.talents, b.talents, 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
Internal.AddLoadoutSegment({
id = "talents",
name = L["Talents"],
events = "PLAYER_TALENT_UPDATE",
3 years ago
enabled = select(4, GetBuildInfo()) < 100000,
4 years ago
add = AddTalentSet,
get = GetTalentSets,
getByName = GetTalentSetByName,
4 years ago
combine = CombineTalentSets,
isActive = IsTalentSetActive,
activate = ActivateTalentSet,
dropdowninit = SetDropDownInit,
checkerrors = CheckErrors,
export = function (set)
return {
version = 1,
name = set.name,
specID = set.specID,
talents = set.talents,
restrictions = set.restrictions,
}
end,
import = function (source, version, name, ...)
assert(version == 1)
local specID = source.specID or ...
return AddSet("talents", UpdateSetFilters({
specID = specID,
name = name or source.name,
useCount = 0,
talents = source.talents,
restrictions = source.restrictions,
}))
end,
getByValue = function (set)
return Internal.GetSetByValue(BtWLoadoutsSets.talents, set, CompareSets)
end,
verify = function (source, ...)
local specID = source.specID or ...
if not specID or not GetSpecializationInfoByID(specID) then
return false, L["Invalid specialization"]
end
if type(source.talents) ~= "table" then
return false, L["Missing talents"]
end
if source.restrictions ~= nil and type(source.restrictions) ~= "table" then
return false, L["Missing restrictions"]
end
-- @TODO verify talent ids?
return true
end,
})
BtWLoadoutsTalentsMixin = {}
function BtWLoadoutsTalentsMixin:OnLoad()
self.RestrictionsDropDown:SetSupportedTypes("covenant", "race")
self.RestrictionsDropDown:SetScript("OnChange", function ()
self:Update()
end)
self.temp = {}; -- Stores talents for currently unselected specs incase the user switches to them
self.talentIDs = {}
for tier=1,MAX_TALENT_TIERS do
self.talentIDs[tier] = {}
end
end
function BtWLoadoutsTalentsMixin:OnShow()
if not self.initialized then
3 years ago
self.SpecDropDown.includeNone = false;
self.SpecDropDown.includeClass = false;
UIDropDownMenu_SetWidth(self.SpecDropDown, 170);
UIDropDownMenu_JustifyText(self.SpecDropDown, "LEFT");
self.SpecDropDown.GetValue = function ()
if self.set then
return self.set.specID
end
end
self.SpecDropDown.SetValue = function (_, _, arg1)
CloseDropDownMenus();
local set = self.set;
if set then
local temp = self.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 specID = set.specID;
if temp[specID] then
wipe(temp[specID]);
else
temp[specID] = {};
end
for talentID in pairs(set.talents) do
temp[specID][talentID] = true;
end
-- Clear the current talents and copy back the previously selected talents if they exist
specID = arg1;
set.specID = specID;
wipe(set.talents);
if temp[specID] then
for talentID in pairs(temp[specID]) do
set.talents[talentID] = true;
end
end
self:Update()
end
end
4 years ago
self.initialized = true;
end
end
function BtWLoadoutsTalentsMixin:ChangeSet(set)
self.set = set
wipe(self.temp);
self:Update()
end
function BtWLoadoutsTalentsMixin:UpdateSetName(value)
if self.set and self.set.name ~= not value then
self.set.name = value;
self:Update();
end
end
function BtWLoadoutsTalentsMixin:OnButtonClick(button)
CloseDropDownMenus()
if button.isAdd then
BtWLoadoutsHelpTipFlags["TUTORIAL_NEW_SET"] = true;
self.Name:ClearFocus()
self:ChangeSet(AddTalentSet())
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 = DeleteTalentSet,
})
else
StaticPopup_Show("BTWLOADOUTS_DELETESET", set.name, nil, {
set = set,
func = DeleteTalentSet,
})
end
elseif button.isRefresh then
local set = self.set;
RefreshTalentSet(set)
self:Update()
elseif button.isExport then
local set = self.set;
self:GetParent():SetExport(Internal.Export("talents", set.setID))
elseif button.isActivate then
local set = self.set;
if select(6, GetSpecializationInfoByID(set.specID)) == select(2, UnitClass("player")) then
Internal.ActivateProfile({
talents = {set.setID}
});
end
end
end
function BtWLoadoutsTalentsMixin:OnSidebarItemClick(button)
CloseDropDownMenus()
if button.isHeader then
button.collapsed[button.id] = not button.collapsed[button.id]
self:Update()
else
if IsModifiedClick("SHIFT") then
local set = GetTalentSet(button.id);
if select(6, GetSpecializationInfoByID(set.specID)) == select(2, UnitClass("player")) then
Internal.ActivateProfile({
talents = {button.id}
});
end
else
self.Name:ClearFocus();
self:ChangeSet(GetTalentSet(button.id))
end
end
end
function BtWLoadoutsTalentsMixin:OnSidebarItemDoubleClick(button)
CloseDropDownMenus()
if button.isHeader then
return
end
local set = GetTalentSet(button.id);
if select(6, GetSpecializationInfoByID(set.specID)) == select(2, UnitClass("player")) then
Internal.ActivateProfile({
talents = {button.id}
});
end
end
function BtWLoadoutsTalentsMixin:OnSidebarItemDragStart(button)
CloseDropDownMenus()
if button.isHeader then
return
end
local icon = "INV_Misc_QuestionMark";
local set = GetTalentSet(button.id);
local command = format("/btwloadouts activate talents %d", button.id);
if set.specID then
icon = select(4, GetSpecializationInfoByID(set.specID));
end
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 BtWLoadoutsTalentsMixin:Update()
3 years ago
self:GetParent():SetTitle(L["Talents"]);
4 years ago
local sidebar = BtWLoadoutsFrame.Sidebar
sidebar:SetSupportedFilters("spec", "class", "role", "character", "covenant", "race")
sidebar:SetSets(BtWLoadoutsSets.talents)
sidebar:SetCollapsed(BtWLoadoutsCollapsed.talents)
sidebar:SetCategories(BtWLoadoutsCategories.talents)
sidebar:SetFilters(BtWLoadoutsFilters.talents)
sidebar:SetSelected(self.set)
sidebar:Update()
self.set = sidebar:GetSelected()
local set = self.set
local showingNPE = BtWLoadoutsFrame:SetNPEShown(set == nil, L["Talents"], L["Create different talent layouts for the type of content you wish to do. Leave rows blank to skip the tier."])
self:GetParent().ExportButton:SetEnabled(true)
self:GetParent().DeleteButton:SetEnabled(true);
if not showingNPE then
local specID = set.specID
UpdateSetFilters(set)
sidebar:Update()
set.restrictions = set.restrictions or {}
self.RestrictionsDropDown:SetSelections(set.restrictions)
self.RestrictionsDropDown:SetLimitations()
self.RestrictionsButton:SetEnabled(true);
local selected = set.talents;
if not self.Name:HasFocus() then
self.Name:SetText(set.name or "");
end
local _, specName, _, icon, _, classID = GetSpecializationInfoByID(specID);
local className = LOCALIZED_CLASS_NAMES_MALE[classID];
local classColor = GetClassColor(classID);
UIDropDownMenu_SetText(self.SpecDropDown, format("%s: %s", classColor:WrapTextInColorCode(className), specName));
for tier=1,MAX_TALENT_TIERS do
local row = self.talentIDs[tier]
wipe(row)
for column=1,3 do
row[column] = GetTalentInfoForSpecID(specID, tier, column)
end
self.rows[tier]:SetTalents(row);
end
local playerSpecIndex = GetSpecialization()
self:GetParent().RefreshButton:SetEnabled(playerSpecIndex and specID == GetSpecializationInfo(playerSpecIndex))
self:GetParent().ActivateButton:SetEnabled(classID == select(2, UnitClass("player")));
local helpTipBox = self:GetParent().HelpTipBox;
helpTipBox:Hide();
BtWLoadoutsHelpTipFlags["TUTORIAL_CREATE_TALENT_SET"] = true;
else
local specIndex = GetSpecialization()
if not specIndex or specIndex == 5 then
specIndex = 1
end
local specID, specName = GetSpecializationInfo(specIndex);
self.Name:SetText(format(L["New %s Set"], specName));
local _, specName, _, icon, _, classID = GetSpecializationInfoByID(specID);
local className = LOCALIZED_CLASS_NAMES_MALE[classID];
local classColor = GetClassColor(classID);
UIDropDownMenu_SetText(self.SpecDropDown, format("%s: %s", classColor:WrapTextInColorCode(className), specName));
for tier=1,MAX_TALENT_TIERS do
local row = self.talentIDs[tier]
wipe(row)
for column=1,3 do
row[column] = GetTalentInfoForSpecID(specID, tier, column)
end
self.rows[tier]:SetTalents(row);
end
local helpTipBox = self:GetParent().HelpTipBox;
helpTipBox:Hide();
end
end
function BtWLoadoutsTalentsMixin:SetSetByID(setID)
self.set = GetTalentSet(setID)
end