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.
1531 lines
43 KiB
1531 lines
43 KiB
local ADDON_NAME,Internal = ...
|
|
local L = Internal.L
|
|
|
|
local External = {}
|
|
_G[ADDON_NAME] = External
|
|
|
|
local HelpTipBox_Anchor = Internal.HelpTipBox_Anchor;
|
|
local HelpTipBox_SetText = Internal.HelpTipBox_SetText;
|
|
|
|
local IsResting = IsResting;
|
|
local UnitAura = UnitAura;
|
|
local UnitClass = UnitClass;
|
|
local UnitLevel = UnitLevel;
|
|
local UnitCastingInfo = UnitCastingInfo;
|
|
local GetClassColor = C_ClassColor.GetClassColor;
|
|
local LOCALIZED_CLASS_NAMES_MALE = LOCALIZED_CLASS_NAMES_MALE;
|
|
|
|
local GetItemCount = GetItemCount;
|
|
local GetItemInfo = GetItemInfo;
|
|
|
|
local SetSpecialization = SetSpecialization;
|
|
local GetSpecialization = GetSpecialization;
|
|
local GetNumSpecializations = GetNumSpecializations;
|
|
local GetSpecializationInfo = GetSpecializationInfo;
|
|
local GetSpecializationInfoByID = GetSpecializationInfoByID;
|
|
|
|
local StaticPopup_Show = StaticPopup_Show;
|
|
local StaticPopup_Hide = StaticPopup_Hide;
|
|
local StaticPopup_Visible = StaticPopup_Visible;
|
|
|
|
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 GetCharacterInfo = Internal.GetCharacterInfo;
|
|
local GetCharacterSlug = Internal.GetCharacterSlug
|
|
|
|
local loadoutSegments = {}
|
|
local loadoutSegmentsByID = {}
|
|
local loadoutSegmentsUIOrder = {}
|
|
|
|
-- We need to add a small delay after switching specs before changing other things because Blizzard is
|
|
-- still changing things after the cast is finished
|
|
local specChangeInfo = {
|
|
spellId = 200749, -- 200749 is the changing spec spell id
|
|
endTime = nil,
|
|
castGUID = nil,
|
|
}
|
|
local function IsChangingSpec(verifyCastGUID)
|
|
if not specChangeInfo.endTime then
|
|
return false
|
|
end
|
|
|
|
if specChangeInfo.endTime + .5 < GetTime() then
|
|
specChangeInfo.endTime = nil
|
|
specChangeInfo.castGUID = nil
|
|
|
|
return false
|
|
end
|
|
|
|
if verifyCastGUID ~= nil and specChangeInfo.castGUID ~= verifyCastGUID then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
-- Activating a set can take multiple passes, things maybe delayed
|
|
-- by switching spec or waiting for the player to use a tome
|
|
local target = {};
|
|
local targetstate = {};
|
|
|
|
-- Handles events during loadout changing
|
|
local eventHandler = CreateFrame("Frame");
|
|
eventHandler:Hide();
|
|
|
|
local function HideLoadoutPopups()
|
|
StaticPopup_Hide("BTWLOADOUTS_PARTIAL")
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEM")
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEMPARTIAL")
|
|
end
|
|
local uiErrorTracking
|
|
local function CancelActivateProfile()
|
|
C_Timer.After(1, function ()
|
|
UIErrorsFrame:Clear()
|
|
if uiErrorTracking then
|
|
UIErrorsFrame:RegisterEvent("UI_ERROR_MESSAGE")
|
|
end
|
|
uiErrorTracking = nil
|
|
end)
|
|
|
|
wipe(target);
|
|
wipe(targetstate);
|
|
HideLoadoutPopups()
|
|
eventHandler:UnregisterAllEvents();
|
|
eventHandler:Hide();
|
|
Internal.Call("LOADOUT_CHANGE_END")
|
|
Internal.LogMessage("--- END ---")
|
|
end
|
|
Internal.CancelActivateProfile = CancelActivateProfile;
|
|
local function ContinuePartialActivateProfile()
|
|
target.dirty = true
|
|
target.allowPartial = true
|
|
Internal.LogMessage("Allow partial activation")
|
|
end
|
|
Internal.ContinuePartialActivateProfile = ContinuePartialActivateProfile;
|
|
local function ContinueIgnoreItemActivateProfile()
|
|
target.dirty = true
|
|
target.ignoreItem = true
|
|
Internal.LogMessage("Ignore using items for activation")
|
|
end
|
|
Internal.ContinueIgnoreItemActivateProfile = ContinueIgnoreItemActivateProfile;
|
|
local function ContinueIgnoreChainsActivateProfile()
|
|
target.dirty = true
|
|
target.ignoreJailersChains = true
|
|
end
|
|
Internal.ContinueIgnoreChainsActivateProfile = ContinueIgnoreChainsActivateProfile;
|
|
|
|
local errorState = {} -- Reusable state for checking loadouts for errors
|
|
local function LoadoutHasErrors(set)
|
|
wipe(errorState)
|
|
|
|
errorState.specID = set.specID
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
if segment.enabled and set[segment.id] then
|
|
for index,subsetID in ipairs(set[segment.id]) do
|
|
if segment.checkerrors then
|
|
local error = segment.checkerrors(errorState, subsetID)
|
|
if error then
|
|
return true, errorState.specID
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false, errorState.specID
|
|
end
|
|
local function GetLoadoutErrors(errors, set)
|
|
wipe(errorState)
|
|
|
|
errorState.specID = set.specID
|
|
local hasError = false
|
|
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
if segment.enabled and set[segment.id] then
|
|
local segmenterrors = errors[segment.id]
|
|
if not segmenterrors then
|
|
segmenterrors = {}
|
|
errors[segment.id] = segmenterrors
|
|
else
|
|
wipe(errors[segment.id])
|
|
end
|
|
|
|
for index,subsetID in ipairs(set[segment.id]) do
|
|
if segment.checkerrors then
|
|
local error = segment.checkerrors(errorState, subsetID)
|
|
if error then
|
|
hasError = true
|
|
errors[segment.id][index] = error
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return hasError, errors, errorState.specID
|
|
end
|
|
-- Checks of a loadout is activatable
|
|
local function IsLoadoutActivatable(set)
|
|
local specID = set.specID
|
|
|
|
if specID and not Internal.CanSwitchToSpecialization(specID) then
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local function UpdateSetFilters(set)
|
|
local specID = set.specID
|
|
|
|
local filters = set.filters or {}
|
|
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
|
|
if type(filters.character) ~= "table" then
|
|
filters.character = {}
|
|
end
|
|
local characters = filters.character
|
|
wipe(characters)
|
|
if type(set.character) == "table" and next(set.character) ~= nil then
|
|
for character in pairs(set.character) do
|
|
characters[#characters+1] = character
|
|
end
|
|
else
|
|
local class = filters.class
|
|
for _,character in Internal.CharacterIterator() do
|
|
if class == Internal.GetCharacterInfo(character).class or specID == nil then
|
|
characters[#characters+1] = character
|
|
end
|
|
end
|
|
end
|
|
|
|
filters.disabled = set.disabled ~= true and 0 or 1
|
|
|
|
set.filters = filters
|
|
|
|
return set
|
|
end
|
|
local function AddProfile()
|
|
local set = {
|
|
name = L["New Loadout"],
|
|
version = 2,
|
|
useCount = 0,
|
|
}
|
|
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
set[segment.id] = {}
|
|
end
|
|
|
|
return AddSet("profiles", UpdateSetFilters(set))
|
|
end
|
|
local function GetProfile(id)
|
|
return BtWLoadoutsSets.profiles[id];
|
|
end
|
|
local function GetProfileByName(name)
|
|
for _,set in pairs(BtWLoadoutsSets.profiles) do
|
|
if type(set) == "table" and set.name:lower():trim() == name:lower():trim() then
|
|
return set;
|
|
end
|
|
end
|
|
end
|
|
Internal.GetProfileByName = GetProfileByName
|
|
local function DeleteProfile(id)
|
|
do
|
|
local set = type(id) == "table" and id or GetProfile(id);
|
|
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
local ids = set[segment.id]
|
|
if ids then
|
|
for _,id in ipairs(ids) do
|
|
local subSet = segment.get(id)
|
|
subSet.useCount = (subSet.useCount or 1) - 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Disconnect conditions for the deleted loadout
|
|
for _,superset in pairs(BtWLoadoutsSets.conditions) do
|
|
if type(superset) == "table" and superset.profileSet == set.setID then
|
|
Internal.RemoveConditionFromMap(superset);
|
|
|
|
superset.profileSet = nil;
|
|
end
|
|
end
|
|
end
|
|
DeleteSet(BtWLoadoutsSets.profiles, id);
|
|
|
|
local frame = BtWLoadoutsFrame.Loadouts;
|
|
local set = frame.set;
|
|
if set == id or set.setID == id then
|
|
frame.set = nil;--select(2,next(BtWLoadoutsSets.profiles)) or {};
|
|
BtWLoadoutsFrame:Update();
|
|
end
|
|
end
|
|
local function ActivateProfile(profile)
|
|
local hasErrors, specID = LoadoutHasErrors(profile)
|
|
if not hasErrors and not IsLoadoutActivatable(profile) then
|
|
--@TODO display an error
|
|
return;
|
|
end
|
|
|
|
target.name = profile.name
|
|
target.state = targetstate
|
|
|
|
if specID then
|
|
target.specID = specID or profile.specID;
|
|
end
|
|
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
local id = segment.id
|
|
if segment.enabled and profile[id] and #profile[id] > 0 then
|
|
target[id] = target[id] or {};
|
|
for _,setID in ipairs(profile[id]) do
|
|
target[id][#target[id]+1] = setID;
|
|
end
|
|
end
|
|
end
|
|
|
|
if not target.active then
|
|
Internal.Call("LOADOUT_CHANGE_START")
|
|
Internal.ClearLog()
|
|
Internal.LogMessage("--- START ---")
|
|
Internal.LogMessage(format("%s: %s", (select(2, GetAddOnInfo(ADDON_NAME))), (GetAddOnMetadata(ADDON_NAME, "Version"))))
|
|
else
|
|
Internal.Call("LOADOUT_CHANGE_APPEND")
|
|
Internal.LogMessage("--- APPEND ---")
|
|
end
|
|
|
|
target.active = true
|
|
target.dirty = true;
|
|
eventHandler:RegisterEvent("GET_ITEM_INFO_RECEIVED");
|
|
eventHandler:RegisterEvent("PLAYER_REGEN_DISABLED");
|
|
eventHandler:RegisterEvent("PLAYER_REGEN_ENABLED");
|
|
eventHandler:RegisterEvent("PLAYER_UPDATE_RESTING");
|
|
eventHandler:RegisterUnitEvent("UNIT_AURA", "player");
|
|
eventHandler:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED");
|
|
eventHandler:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED");
|
|
eventHandler:RegisterEvent("ZONE_CHANGED");
|
|
eventHandler:RegisterEvent("ZONE_CHANGED_INDOORS");
|
|
eventHandler:RegisterEvent("ITEM_UNLOCKED");
|
|
eventHandler:RegisterEvent("SPELL_UPDATE_COOLDOWN");
|
|
eventHandler:RegisterEvent("PLAYER_STOPPED_MOVING");
|
|
eventHandler:RegisterEvent("PLAYER_TALENT_UPDATE");
|
|
eventHandler:RegisterEvent("PLAYER_LEARN_TALENT_FAILED");
|
|
eventHandler:RegisterEvent("PLAYER_PVP_TALENT_UPDATE");
|
|
eventHandler:RegisterEvent("PLAYER_LEARN_PVP_TALENT_FAILED");
|
|
eventHandler:RegisterEvent("BAG_UPDATE_DELAYED");
|
|
if C_Covenants then -- Skip for pre-Shadowlands
|
|
eventHandler:RegisterEvent("SOULBIND_ACTIVATED");
|
|
end
|
|
eventHandler:RegisterUnitEvent("UNIT_SPELLCAST_START", "player");
|
|
eventHandler:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", "player");
|
|
eventHandler:Show();
|
|
end
|
|
local IsProfileActive, AddWipeCacheEvents
|
|
do
|
|
local state = {}
|
|
local temp = {}
|
|
local function IsActive(set)
|
|
if set.specID and UnitLevel("player") >= 10 then
|
|
local playerSpecID = GetSpecializationInfo(GetSpecialization());
|
|
if set.specID ~= playerSpecID then
|
|
return false;
|
|
end
|
|
end
|
|
|
|
wipe(state)
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
local ids = set[segment.id]
|
|
if segment.enabled and ids and #ids > 0 then
|
|
wipe(temp);
|
|
|
|
segment.combine(temp, state, segment.get(unpack(ids)));
|
|
if not segment.isActive(temp) then
|
|
return false;
|
|
end
|
|
end
|
|
end
|
|
|
|
return true;
|
|
end
|
|
local activeLoadoutCache = setmetatable({}, {
|
|
__index = function(self, key)
|
|
if type(key) == "number" then
|
|
local result = self[GetProfile(key)]
|
|
self[key] = result
|
|
return result
|
|
elseif type(key) == "table" then
|
|
local result = IsActive(key)
|
|
self[key] = result
|
|
return result
|
|
end
|
|
end,
|
|
});
|
|
function IsProfileActive(set)
|
|
return activeLoadoutCache[set]
|
|
end
|
|
local wipeEventHandler = CreateFrame("Frame");
|
|
wipeEventHandler:Hide();
|
|
wipeEventHandler:SetScript("OnEvent", function ()
|
|
wipe(activeLoadoutCache);
|
|
end);
|
|
|
|
function AddWipeCacheEvents(...)
|
|
for i=1,select('#', ...) do
|
|
local event = select(i, ...)
|
|
wipeEventHandler:RegisterEvent(event)
|
|
end
|
|
end
|
|
end
|
|
local function IsLoadoutEnabled(set)
|
|
if set.disabled then
|
|
return false
|
|
end
|
|
|
|
if set.character ~= nil and next(set.character) ~= nil then
|
|
local character = Internal.GetCharacterSlug()
|
|
return set.character[character] ~= nil
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function Internal.ImportLoadout(source, version, name)
|
|
assert(version == 2)
|
|
|
|
local set = {
|
|
name = name,
|
|
version = 2,
|
|
useCount = 0,
|
|
specID = source.specID
|
|
}
|
|
|
|
for _,segment in Internal.EnumerateLoadoutSegments() do
|
|
if source[segment.id] then
|
|
set[segment.id] = {unpack(source[segment.id])}
|
|
else
|
|
set[segment.id] = {}
|
|
end
|
|
end
|
|
|
|
return AddSet("profiles", UpdateSetFilters(set))
|
|
end
|
|
|
|
local function GetActiveProfiles()
|
|
if target.active then
|
|
if target.name then
|
|
return format(L["Changing to %s..."], target.name)
|
|
else
|
|
return L["Changing..."]
|
|
end
|
|
end
|
|
|
|
local activeProfiles = {}
|
|
for _,profile in pairs(BtWLoadoutsSets.profiles) do
|
|
if type(profile) == "table" and IsLoadoutEnabled(profile) and IsProfileActive(profile) then
|
|
activeProfiles[#activeProfiles+1] = profile.name
|
|
end
|
|
end
|
|
if #activeProfiles == 0 then
|
|
return nil
|
|
end
|
|
|
|
table.sort(activeProfiles)
|
|
return table.concat(activeProfiles, "/");
|
|
end
|
|
local combinedSets = {}
|
|
local blockers = {}
|
|
local blockerTemp = {}
|
|
local function ContinueActivateProfile()
|
|
local set = target
|
|
local state = target.state
|
|
set.dirty = false
|
|
|
|
if Internal.CheckTimeout() then
|
|
Internal.LogMessage("--- TIMEOUT ---")
|
|
CancelActivateProfile()
|
|
return
|
|
end
|
|
|
|
Internal.LogNewPass()
|
|
|
|
Internal.SetWaitReason() -- Clear wait reason
|
|
Internal.UpdateLauncher(GetActiveProfiles());
|
|
|
|
if IsChangingSpec() then
|
|
Internal.SetWaitReason(L["Waiting for specialization change"])
|
|
HideLoadoutPopups()
|
|
return;
|
|
end
|
|
|
|
wipe(state)
|
|
wipe(blockers)
|
|
|
|
local segments = 0
|
|
|
|
local specID = set.specID;
|
|
local playerSpecID = GetSpecializationInfo(GetSpecialization())
|
|
if specID ~= nil and specID ~= playerSpecID then
|
|
-- Need to change spec
|
|
|
|
segments = segments + 1
|
|
blockers[Internal.GetTaxiBlocker()] = 1
|
|
blockers[Internal.GetFlyingBlocker()] = 1
|
|
blockers[Internal.GetMovingBlocker()] = 1
|
|
blockers[Internal.GetCombatBlocker()] = 1
|
|
blockers[Internal.GetMythicPlusBlocker()] = 1
|
|
blockers[Internal.GetJailersChainBlocker()] = 1
|
|
end
|
|
|
|
state.ignoreItem = target.ignoreItem
|
|
state.allowPartial = target.allowPartial
|
|
|
|
wipe(combinedSets)
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
if segment.enabled and target[segment.id] then
|
|
wipe(blockerTemp)
|
|
state.blockers = blockerTemp
|
|
|
|
combinedSets[segment.id] = segment.combine(combinedSets[segment.id], state, segment.get(unpack(target[segment.id])))
|
|
|
|
segments = segments + 1
|
|
for blocker,complete in pairs(blockerTemp) do
|
|
blockers[blocker] = (blockers[blocker] or 0) + (complete and 1 or 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
for blocker,count in pairs(blockers) do
|
|
if count == segments and blocker:IsActive(state) and not blocker:UsableItem() then
|
|
if blocker:ShouldWait(state) then
|
|
Internal.SetWaitReason(blocker:GetWaitReasonMessage())
|
|
else
|
|
CancelActivateProfile()
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
if not target.allowPartial then
|
|
local supportPartialMessages = {}
|
|
local supportPartial = false
|
|
for blocker,count in pairs(blockers) do
|
|
if count ~= segments and blocker:IsActive(state) and not blocker:UsableItem() then
|
|
if blocker:ShouldWait(state) then
|
|
Internal.SetWaitReason(blocker:GetWaitReasonMessage())
|
|
end
|
|
|
|
local message = blocker:PopupMessagePartial()
|
|
supportPartialMessages[#supportPartialMessages+1] = message
|
|
supportPartial = true
|
|
end
|
|
end
|
|
if supportPartial then
|
|
if #supportPartialMessages > 0 then
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEMPARTIAL")
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEM")
|
|
if not StaticPopup_Visible("BTWLOADOUTS_PARTIAL") then
|
|
if #supportPartialMessages > 1 then
|
|
StaticPopup_Show("BTWLOADOUTS_PARTIAL", L["Your loadout cannot be completely activated"]);
|
|
else
|
|
StaticPopup_Show("BTWLOADOUTS_PARTIAL", supportPartialMessages[1]);
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
if specID ~= nil and specID ~= playerSpecID and UnitLevel("player") >= 10 then
|
|
for specIndex=1,GetNumSpecializations() do
|
|
if GetSpecializationInfo(specIndex) == specID then
|
|
Internal.LogMessage("Switching specialization to %s", (select(2, GetSpecializationInfo(specIndex))))
|
|
Internal.SetWaitReason(L["Waiting for specialization change"])
|
|
SetSpecialization(specIndex);
|
|
target.dirty = false;
|
|
return;
|
|
end
|
|
end
|
|
end
|
|
|
|
if state.customWait then
|
|
Internal.SetWaitReason(state.customWait)
|
|
HideLoadoutPopups()
|
|
return
|
|
end
|
|
|
|
if not target.ignoreItem then
|
|
for blocker,count in pairs(blockers) do
|
|
if blocker:IsActive(state) and blocker:UsableItem() and blocker:ShouldWait(state) then
|
|
Internal.SetWaitReason(blocker:GetWaitReasonMessage())
|
|
|
|
local itemID, name, link, quality, icon = blocker:UsableItem();
|
|
|
|
StaticPopup_Hide("BTWLOADOUTS_PARTIAL")
|
|
|
|
if count == segments then
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEMPARTIAL")
|
|
|
|
local message, item = blocker:PopupMessage()
|
|
if name ~= nil and not StaticPopup_Visible("BTWLOADOUTS_NEEDITEM") then
|
|
local r, g, b = GetItemQualityColor(quality or 2);
|
|
StaticPopup_Show("BTWLOADOUTS_NEEDITEM", message, item, {["texture"] = icon, ["name"] = name, ["color"] = {r, g, b, 1}, ["link"] = link, ["count"] = 1});
|
|
elseif itemID == nil then
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEM")
|
|
end
|
|
else
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEM")
|
|
|
|
local message, item = blocker:PopupMessagePartial()
|
|
if name ~= nil and not StaticPopup_Visible("BTWLOADOUTS_NEEDITEMPARTIAL") then
|
|
local r, g, b = GetItemQualityColor(quality or 2);
|
|
StaticPopup_Show("BTWLOADOUTS_NEEDITEMPARTIAL", message, item, {["texture"] = icon, ["name"] = name, ["color"] = {r, g, b, 1}, ["link"] = link, ["count"] = 1});
|
|
elseif itemID == nil then
|
|
StaticPopup_Hide("BTWLOADOUTS_NEEDITEMPARTIAL")
|
|
end
|
|
end
|
|
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
HideLoadoutPopups()
|
|
|
|
if uiErrorTracking == nil then
|
|
uiErrorTracking = UIErrorsFrame:IsEventRegistered("UI_ERROR_MESSAGE")
|
|
UIErrorsFrame:UnregisterEvent("UI_ERROR_MESSAGE")
|
|
end
|
|
|
|
local complete = true;
|
|
for _,segment in ipairs(loadoutSegments) do
|
|
if segment.enabled and combinedSets[segment.id] then
|
|
local segmentComplete, segmentDirty = segment.activate(combinedSets[segment.id], state)
|
|
if not segmentComplete then
|
|
complete = false
|
|
end
|
|
if segmentDirty then
|
|
set.dirty = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Done
|
|
if complete then
|
|
CancelActivateProfile();
|
|
end
|
|
|
|
Internal.UpdateLauncher(GetActiveProfiles());
|
|
end
|
|
|
|
function Internal.DirtyAfter(timer)
|
|
C_Timer.After(timer, function()
|
|
target.dirty = true;
|
|
end);
|
|
end
|
|
|
|
eventHandler:SetScript("OnEvent", function (self, event, ...)
|
|
self[event](self, ...);
|
|
end);
|
|
function eventHandler:GET_ITEM_INFO_RECEIVED()
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_REGEN_DISABLED()
|
|
HideLoadoutPopups()
|
|
end
|
|
function eventHandler:PLAYER_REGEN_ENABLED()
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_UPDATE_RESTING()
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_STOPPED_MOVING()
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:UNIT_AURA()
|
|
C_Timer.After(1, function()
|
|
target.dirty = true;
|
|
end);
|
|
end
|
|
function eventHandler:PLAYER_SPECIALIZATION_CHANGED(...)
|
|
-- Added delay just to be safe
|
|
C_Timer.After(1, function()
|
|
target.dirty = true;
|
|
end);
|
|
end
|
|
function eventHandler:ACTIVE_TALENT_GROUP_CHANGED(...)
|
|
end
|
|
function eventHandler:ZONE_CHANGED(...)
|
|
target.dirty = true;
|
|
end
|
|
eventHandler.ZONE_CHANGED_INDOORS = eventHandler.ZONE_CHANGED;
|
|
function eventHandler:ITEM_UNLOCKED(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_TALENT_UPDATE(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_LEARN_TALENT_FAILED(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_PVP_TALENT_UPDATE(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:PLAYER_LEARN_PVP_TALENT_FAILED(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:BAG_UPDATE_DELAYED(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:SOULBIND_ACTIVATED(...)
|
|
target.dirty = true;
|
|
end
|
|
function eventHandler:SPELL_UPDATE_COOLDOWN()
|
|
-- Added delay because it didnt seem to always trigger correctly
|
|
C_Timer.After(1, function()
|
|
target.dirty = true;
|
|
end);
|
|
end
|
|
function eventHandler:UNIT_SPELLCAST_START()
|
|
local endTime, _, castGUID, _, spellId = select(5, UnitCastingInfo("player"))
|
|
if spellId == specChangeInfo.spellId then
|
|
specChangeInfo.endTime = endTime / 1000
|
|
specChangeInfo.castGUID = castGUID
|
|
end
|
|
end
|
|
function eventHandler:UNIT_SPELLCAST_INTERRUPTED(_, castGUID, spellId, ...)
|
|
if spellId == specChangeInfo.spellId and IsChangingSpec(castGUID) then
|
|
CancelActivateProfile();
|
|
Internal.UpdateLauncher(GetActiveProfiles());
|
|
end
|
|
end
|
|
|
|
eventHandler:SetScript("OnUpdate", function (self)
|
|
if target.dirty then
|
|
ContinueActivateProfile();
|
|
end
|
|
end)
|
|
|
|
-- [[ Internal API ]]
|
|
-- Loadouts are split into segments, ... @TODO
|
|
do
|
|
local function MustBeBefore(a, b)
|
|
if b.after then
|
|
for after in string.gmatch(b.after, "[^,]+") do
|
|
if after == a.id then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
function Internal.AddLoadoutSegment(details)
|
|
details.enabled = details.enabled == nil and true or details.enabled
|
|
|
|
if (details.import or details.export or details.verify or details.getByValue) and not (details.import and details.export and details.verify and details.getByValue) then
|
|
error(L["Segments that support import/export must define import, export, verify, and getByValue functions"])
|
|
end
|
|
|
|
loadoutSegmentsUIOrder[#loadoutSegmentsUIOrder+1] = details
|
|
loadoutSegmentsByID[details.id] = details
|
|
|
|
if details.events then
|
|
AddWipeCacheEvents(strsplit(",", details.events))
|
|
end
|
|
|
|
for index,segment in ipairs(loadoutSegments) do
|
|
if MustBeBefore(details, segment) then
|
|
table.insert(loadoutSegments, index, details)
|
|
return
|
|
end
|
|
end
|
|
loadoutSegments[#loadoutSegments+1] = details
|
|
end
|
|
function Internal.EnumerateLoadoutSegments()
|
|
return function (tbl, index)
|
|
repeat
|
|
index = index + 1
|
|
until not tbl[index] or tbl[index].enabled
|
|
if tbl[index] then
|
|
return index, tbl[index]
|
|
end
|
|
end, loadoutSegmentsUIOrder, 0
|
|
end
|
|
function Internal.GetLoadoutSegment(id)
|
|
return loadoutSegmentsByID[id]
|
|
end
|
|
function Internal.SetLoadoutSegmentEnabled(id, value)
|
|
loadoutSegmentsByID[id].enabled = value and true or false
|
|
end
|
|
function Internal.GetLoadoutSegmentEnabled(id)
|
|
return loadoutSegmentsByID[id].enabled and true or false
|
|
end
|
|
end
|
|
function Internal.IsActivatingLoadout()
|
|
return target.active
|
|
end
|
|
function Internal.SetWaitReason(reason)
|
|
if reason == nil then
|
|
target.timeout = target.timeout or (GetTime() + 10) -- Set a timeout
|
|
else
|
|
target.timeout = nil
|
|
end
|
|
|
|
target.currentWaitReason = reason
|
|
end
|
|
function Internal.CheckTimeout()
|
|
if not target.timeout then
|
|
return false
|
|
end
|
|
|
|
return target.timeout < GetTime()
|
|
end
|
|
function Internal.GetWaitReason()
|
|
return target.currentWaitReason
|
|
end
|
|
|
|
Internal.GetProfile = GetProfile
|
|
Internal.GetActiveProfiles = GetActiveProfiles
|
|
Internal.ActivateProfile = ActivateProfile
|
|
Internal.LoadoutHasErrors = LoadoutHasErrors
|
|
Internal.GetLoadoutErrors = GetLoadoutErrors
|
|
Internal.IsLoadoutActivatable = IsLoadoutActivatable
|
|
Internal.IsProfileActive = IsProfileActive
|
|
Internal.AddProfile = AddProfile
|
|
Internal.DeleteProfile = DeleteProfile
|
|
Internal.IsLoadoutEnabled = IsLoadoutEnabled
|
|
|
|
-- [[ External API ]]
|
|
-- Return: id, name, specID, character
|
|
function External.GetLoadoutInfo(id)
|
|
local set = GetProfile(id)
|
|
if not set then
|
|
return
|
|
end
|
|
|
|
return set.setID, set.name, set.specID, set.character
|
|
end
|
|
function External.IsLoadoutActive(id)
|
|
local set = GetProfile(id)
|
|
if not set then
|
|
return
|
|
end
|
|
|
|
return IsProfileActive(set)
|
|
end
|
|
do
|
|
local loadouts = {}
|
|
-- Get a list of all loadouts
|
|
-- Return: id, ...
|
|
function External.GetLoadouts()
|
|
wipe(loadouts);
|
|
for id,set in pairs(BtWLoadoutsSets.profiles) do
|
|
if type(set) == "table" then
|
|
loadouts[#loadouts+1] = id
|
|
end
|
|
end
|
|
return unpack(loadouts);
|
|
end
|
|
-- Get a list of all currently active loadouts
|
|
-- Return: id, ...
|
|
function External.GetActiveLoadouts()
|
|
wipe(loadouts);
|
|
for id,set in pairs(BtWLoadoutsSets.profiles) do
|
|
if type(set) == "table" and IsProfileActive(set) then
|
|
loadouts[#loadouts+1] = id
|
|
end
|
|
end
|
|
return unpack(loadouts);
|
|
end
|
|
-- Get a list of all loadouts valid for the current character
|
|
-- Return: id, ...
|
|
function External.GetCharacterLoadouts()
|
|
wipe(loadouts);
|
|
for id,set in pairs(BtWLoadoutsSets.profiles) do
|
|
if type(set) == "table" and IsLoadoutActivatable(set) then
|
|
loadouts[#loadouts+1] = id
|
|
end
|
|
end
|
|
return unpack(loadouts);
|
|
end
|
|
end
|
|
function External.ActivateLoadout(id)
|
|
local loadout
|
|
if type(id) == "number" then
|
|
loadout = GetProfile(id)
|
|
else
|
|
loadout = GetProfileByName(id)
|
|
end
|
|
if loadout == nil then
|
|
error(L["Unknown loadout " .. tostring(id)])
|
|
end
|
|
|
|
ActivateProfile(loadout)
|
|
end
|
|
|
|
BtWLoadoutsSetsScrollListItemMixin = {}
|
|
function BtWLoadoutsSetsScrollListItemMixin:OnLoad()
|
|
self:RegisterForDrag("LeftButton");
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:Set(item)
|
|
self.item = item
|
|
|
|
local button = self
|
|
if item and not item.ignore then
|
|
button.type = item.type
|
|
button.isAdd = item.isAdd
|
|
button.isHeader = item.isHeader
|
|
|
|
button.name = item.name
|
|
button.error = item.error
|
|
button.ErrorBorder:SetShown(item.error ~= nil)
|
|
button.ErrorOverlay:SetShown(item.error ~= nil)
|
|
|
|
if item.isSeparator then
|
|
button:Hide()
|
|
else
|
|
button.index = item.index
|
|
button.id = item.id
|
|
|
|
button:SetEnabled(not item.isHeader)
|
|
if item.isHeader then
|
|
button.Name:SetPoint("LEFT", 0, 0)
|
|
button.Name:SetTextColor(0.75, 0.61, 0)
|
|
|
|
-- if item.isEmpty then
|
|
button.ExpandedIcon:Hide()
|
|
button.CollapsedIcon:Hide()
|
|
-- elseif item.isCollapsed then
|
|
-- button.ExpandedIcon:Hide()
|
|
-- button.CollapsedIcon:Show()
|
|
-- else
|
|
-- button.ExpandedIcon:Show()
|
|
-- button.CollapsedIcon:Hide()
|
|
-- end
|
|
|
|
button.AddButton:Show()
|
|
|
|
button.MoveButton:SetEnabled(false)
|
|
button.MoveButton:Hide()
|
|
|
|
button.RemoveButton:SetEnabled(false)
|
|
button.RemoveButton:Hide()
|
|
elseif item.isAdd then
|
|
button.Name:SetPoint("LEFT", 15, 0)
|
|
button.Name:SetTextColor(0.973, 0.937, 0.580)
|
|
|
|
button.AddButton:Hide()
|
|
|
|
button.MoveButton:SetEnabled(false)
|
|
button.MoveButton:Hide()
|
|
|
|
button.RemoveButton:SetEnabled(false)
|
|
button.RemoveButton:Hide()
|
|
|
|
button.ExpandedIcon:Hide()
|
|
button.CollapsedIcon:Hide()
|
|
else
|
|
button.Name:SetPoint("LEFT", 15, 0)
|
|
button.Name:SetTextColor(1, 1, 1)
|
|
|
|
button.AddButton:Hide()
|
|
|
|
button.MoveButton:SetEnabled(true)
|
|
button.MoveButton:Hide()
|
|
|
|
button.RemoveButton:SetEnabled(true)
|
|
button.RemoveButton:Hide()
|
|
|
|
-- button.AddButton:Hide()
|
|
-- button.RemoveButton:Show()
|
|
-- button.MoveDownButton:Show()
|
|
-- button.MoveUpButton:Show()
|
|
|
|
-- button.MoveUpButton:SetEnabled(not item.first)
|
|
-- button.MoveDownButton:SetEnabled(not item.last)
|
|
|
|
button.ExpandedIcon:Hide()
|
|
button.CollapsedIcon:Hide()
|
|
end
|
|
|
|
button.Name:SetText(item.name)
|
|
|
|
button:Show();
|
|
end
|
|
else
|
|
button:Hide();
|
|
end
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:OnClick()
|
|
if self.isHeader then
|
|
local frame = self:GetParent():GetParent():GetParent()
|
|
frame.Collapsed[self.type] = not frame.Collapsed[self.type]
|
|
frame:Update()
|
|
elseif self.isAdd then
|
|
self:Add(self)
|
|
else
|
|
local DropDown = self:GetParent():GetParent().DropDown
|
|
local index = self.index
|
|
local segment = self.type
|
|
|
|
local tab = BtWLoadoutsFrame.Loadouts
|
|
|
|
Internal.GetLoadoutSegment(segment).dropdowninit(DropDown, tab.set, index)
|
|
|
|
-- DropDown:SetSelected(tab.set[segment][index])
|
|
-- DropDown:SetSets(BtWLoadoutsSets[segment])
|
|
-- DropDown:SetCategories(BtWLoadoutsCategories[segment])
|
|
-- DropDown:OnChange(function (newSetID)
|
|
-- local previousSetID = tab.set[segment][index]
|
|
-- print(previousSetID, newSetID)
|
|
-- if set.talents[index] then
|
|
-- local subset = Internal.GetTalentSet(set.talents[index]);
|
|
-- subset.useCount = (subset.useCount or 1) - 1;
|
|
-- end
|
|
-- end)
|
|
|
|
-- UIDropDownMenu_SetInitializeFunction(DropDown, function (self, level, menuList)
|
|
-- return Internal.GetLoadoutSegment(segment).dropdowninit(self, level, menuList, index)
|
|
-- end)
|
|
|
|
ToggleDropDownMenu(nil, nil, DropDown, self, 0, 0)
|
|
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
|
|
end
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:OnEnter()
|
|
local scrollChild = self:GetParent()
|
|
local currentDrag = scrollChild.currentDrag
|
|
if currentDrag and currentDrag ~= self then
|
|
if self.isHeader or self.isAdd or self.type ~= currentDrag.type then
|
|
return
|
|
end
|
|
|
|
local frame = scrollChild:GetParent():GetParent()
|
|
local a, b = self.item, currentDrag.item
|
|
|
|
-- Flip the set ids within the loadout and flip their indexes too
|
|
frame.set[a.type][a.index], frame.set[a.type][b.index] = frame.set[a.type][b.index], frame.set[a.type][a.index]
|
|
a.index, b.index = b.index, a.index
|
|
|
|
-- Update the buttons with the flipped sets
|
|
self:Set(b)
|
|
currentDrag:Set(a)
|
|
|
|
self:GetParent().currentDrag = self
|
|
|
|
return
|
|
end
|
|
|
|
self.MoveButton:SetShown(self.MoveButton:IsEnabled())
|
|
self.RemoveButton:SetShown(self.RemoveButton:IsEnabled())
|
|
|
|
if self.error then
|
|
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
|
|
GameTooltip:SetText(self.name, 1, 1, 1)
|
|
GameTooltip:AddLine(format("\n|cffff0000%s|r", self.error))
|
|
GameTooltip:Show()
|
|
end
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:OnLeave()
|
|
if not MouseIsOver(self) then
|
|
self.MoveButton:Hide()
|
|
self.RemoveButton:Hide()
|
|
end
|
|
|
|
GameTooltip:Hide()
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:StartDrag()
|
|
if self.isHeader or self.isAdd then
|
|
return
|
|
end
|
|
|
|
self:GetParent().currentDrag = self
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:Add(button)
|
|
local DropDown = self:GetParent():GetParent().DropDown
|
|
|
|
local segment = self.type
|
|
local tab = BtWLoadoutsFrame.Loadouts
|
|
|
|
Internal.GetLoadoutSegment(segment).dropdowninit(DropDown, tab.set)
|
|
|
|
ToggleDropDownMenu(nil, nil, DropDown, button, 0, 0)
|
|
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:Remove()
|
|
local tab = self:GetParent():GetParent():GetParent()
|
|
local set = tab.set
|
|
|
|
local index = self.index
|
|
assert(type(set[self.type]) == "table" and index ~= nil and index >= 1 and index <= #set[self.type])
|
|
table.remove(set[self.type], index);
|
|
|
|
tab:Update()
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:MoveUp()
|
|
local tab = self:GetParent():GetParent():GetParent()
|
|
local set = tab.set
|
|
|
|
local index = self.index
|
|
assert(type(set[self.type]) == "table" and index > 1 and index <= #set[self.type])
|
|
set[self.type][index-1], set[self.type][index] = set[self.type][index], set[self.type][index-1]
|
|
|
|
tab:Update()
|
|
end
|
|
function BtWLoadoutsSetsScrollListItemMixin:MoveDown()
|
|
local tab = self:GetParent():GetParent():GetParent()
|
|
local set = tab.set
|
|
|
|
local index = self.index
|
|
assert(type(set[self.type]) == "table" and index >= 1 and index < #set[self.type])
|
|
set[self.type][index+1], set[self.type][index] = set[self.type][index], set[self.type][index+1]
|
|
|
|
tab:Update()
|
|
end
|
|
|
|
local function SetsScrollFrameUpdate(self)
|
|
self:GetScrollChild().currentDrag = nil -- Clear current drag
|
|
|
|
local buttons = self.buttons
|
|
local items = self.items
|
|
local offset = HybridScrollFrame_GetOffset(self)
|
|
|
|
if not buttons then
|
|
return
|
|
end
|
|
|
|
local totalHeight, displayedHeight = #items * (buttons[1]:GetHeight() + 1), self:GetHeight()
|
|
local hasScrollBar = totalHeight > displayedHeight
|
|
|
|
for i,button in ipairs(buttons) do
|
|
button:SetWidth(hasScrollBar and 530 or 540)
|
|
|
|
local item = items[i+offset]
|
|
button:Set(item)
|
|
end
|
|
HybridScrollFrame_Update(self, totalHeight, displayedHeight)
|
|
end
|
|
local function AddItem(items, index)
|
|
local item = items[index] or {}
|
|
items[index] = item
|
|
|
|
wipe(item)
|
|
|
|
return item, index + 1
|
|
end
|
|
local function BuildSubSetItems(type, header, getcallback, sets, items, index, isCollapsed, errors)
|
|
local item
|
|
|
|
do
|
|
item, index = AddItem(items, index)
|
|
|
|
item.name = header
|
|
item.type = type
|
|
item.isCollapsed = isCollapsed
|
|
item.isHeader = true
|
|
-- item.isEmpty = subset == nil
|
|
end
|
|
|
|
if not isCollapsed then
|
|
if sets and #sets > 0 then
|
|
for i,setID in ipairs(sets) do
|
|
local subset = getcallback(setID)
|
|
item, index = AddItem(items, index)
|
|
|
|
if subset.character then
|
|
local characterInfo = Internal.GetCharacterInfo(subset.character);
|
|
if characterInfo then
|
|
item.name = format("%s |cFFD5D5D5(%s - %s)|r", subset.name, characterInfo.name, characterInfo.realm);
|
|
else
|
|
item.name = format("%s |cFFD5D5D5(%s)|r", subset.name, subset.character);
|
|
end
|
|
else
|
|
item.name = subset.name ~= "" and subset.name or L["Unnamed"];
|
|
end
|
|
|
|
item.error = errors[i]
|
|
item.type = type
|
|
item.index = i
|
|
item.id = subset.setID
|
|
item.first = i == 1
|
|
item.last = i == #sets
|
|
end
|
|
else
|
|
item, index = AddItem(items, index)
|
|
|
|
item.type = type
|
|
item.name = L["Add"]
|
|
item.isAdd = true
|
|
end
|
|
end
|
|
|
|
return index
|
|
end
|
|
local function AddSeparator(items, index)
|
|
-- item, index = AddItem(items, index)
|
|
-- item.isSeparator = true
|
|
return index
|
|
end
|
|
local function BuildSetItems(set, items, collapsed, errors)
|
|
local index = 1
|
|
|
|
for i,segment in Internal.EnumerateLoadoutSegments() do
|
|
if i ~= 1 then
|
|
index = AddSeparator(items, index)
|
|
end
|
|
index = BuildSubSetItems(segment.id, segment.name, segment.get, set[segment.id], items, index, collapsed[segment.id], errors[segment.id])
|
|
end
|
|
|
|
while items[index] do
|
|
table.remove(items, index)
|
|
end
|
|
|
|
return items
|
|
end
|
|
|
|
local function shallowcopy(tbl)
|
|
local result = {}
|
|
for k,v in pairs(tbl) do
|
|
result[k] = v
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Stores errors for currently viewed set
|
|
local errors = {}
|
|
|
|
BtWLoadoutsLoadoutsMixin = {}
|
|
function BtWLoadoutsLoadoutsMixin:OnLoad()
|
|
self:RegisterEvent("GLOBAL_MOUSE_UP")
|
|
|
|
HybridScrollFrame_CreateButtons(self.SetsScroll, "BtWLoadoutsSetsScrollListItemTemplate", 4, -3, "TOPLEFT", "TOPLEFT", 0, -1, "TOP", "BOTTOM");
|
|
self.SetsScroll.update = SetsScrollFrameUpdate;
|
|
|
|
self.temp = {} -- Stores character restrictions for unselected specs
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:OnEvent()
|
|
if self.SetsScroll:GetScrollChild().currentDrag ~= nil then
|
|
self:GetParent():Update()
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:OnShow()
|
|
if not self.initialized then
|
|
self.SpecDropDown.includeNone = true;
|
|
UIDropDownMenu_SetWidth(self.SpecDropDown, 175);
|
|
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 classFile = set.specID and select(6, GetSpecializationInfoByID(set.specID))
|
|
self.temp[classFile or "NONE"] = set.character
|
|
|
|
set.specID = arg1;
|
|
|
|
classFile = set.specID and select(6, GetSpecializationInfoByID(set.specID))
|
|
set.character = self.temp[classFile or "NONE"] or shallowcopy(set.character)
|
|
|
|
self:Update()
|
|
end
|
|
end
|
|
|
|
self.CharacterDropDown.GetValue = function (self)
|
|
local frame = self:GetParent()
|
|
|
|
if frame.set == nil then
|
|
return {} -- Needs a table since its a multi select
|
|
end
|
|
|
|
if type(frame.set.character) ~= "table" then
|
|
frame.set.character = {}
|
|
end
|
|
|
|
return frame.set.character
|
|
end
|
|
self.CharacterDropDown.SetValue = function (self, button, arg1, arg2, checked)
|
|
local frame = self:GetParent()
|
|
if frame.set then
|
|
if arg1 == nil then
|
|
wipe(frame.set.character)
|
|
elseif frame.set.character[arg1] then
|
|
frame.set.character[arg1] = nil
|
|
else
|
|
frame.set.character[arg1] = true
|
|
end
|
|
|
|
BtWLoadoutsFrame:Update()
|
|
end
|
|
end
|
|
UIDropDownMenu_SetWidth(self.CharacterDropDown, 175);
|
|
UIDropDownMenu_JustifyText(self.CharacterDropDown, "LEFT");
|
|
|
|
self.initialized = true;
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:ChangeSet(set)
|
|
self.set = set
|
|
wipe(self.temp);
|
|
self:Update()
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:UpdateSetEnabled(value)
|
|
if self.set and self.set.disabled ~= not value then
|
|
self.set.disabled = not value;
|
|
self:Update();
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:UpdateSetName(value)
|
|
if self.set and self.set.name ~= not value then
|
|
self.set.name = value;
|
|
BtWLoadoutsHelpTipFlags["TUTORIAL_RENAME_SET"] = true
|
|
self:Update();
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:OnButtonClick(button)
|
|
CloseDropDownMenus()
|
|
if button.isAdd then
|
|
BtWLoadoutsHelpTipFlags["TUTORIAL_NEW_SET"] = true;
|
|
|
|
self.Name:ClearFocus()
|
|
self:ChangeSet(AddProfile())
|
|
|
|
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 = DeleteProfile,
|
|
})
|
|
else
|
|
StaticPopup_Show("BTWLOADOUTS_DELETESET", set.name, nil, {
|
|
set = set,
|
|
func = DeleteProfile,
|
|
})
|
|
end
|
|
elseif button.isRefresh then
|
|
-- Do nothing
|
|
elseif button.isExport then
|
|
local set = self.set;
|
|
self:GetParent():SetExport(External.Export(set.setID))
|
|
elseif button.isActivate then
|
|
BtWLoadoutsHelpTipFlags["TUTORIAL_ACTIVATE_SET"] = true
|
|
|
|
ActivateProfile(self.set)
|
|
self:Update()
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:OnSidebarItemClick(button)
|
|
CloseDropDownMenus()
|
|
if button.isHeader then
|
|
button.collapsed[button.id] = not button.collapsed[button.id]
|
|
self:Update()
|
|
else
|
|
if IsModifiedClick("SHIFT") then
|
|
ActivateProfile(GetProfile(button.id));
|
|
else
|
|
self.Name:ClearFocus();
|
|
self:ChangeSet(GetProfile(button.id));
|
|
end
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:OnSidebarItemDoubleClick(button)
|
|
CloseDropDownMenus()
|
|
if button.isHeader then
|
|
return
|
|
end
|
|
|
|
ActivateProfile(GetProfile(button.id));
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:OnSidebarItemDragStart(button)
|
|
CloseDropDownMenus()
|
|
if button.isHeader then
|
|
return
|
|
end
|
|
|
|
local icon = "INV_Misc_QuestionMark";
|
|
local set = GetProfile(button.id);
|
|
local command = format("/btwloadouts activate loadout %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 BtWLoadoutsLoadoutsMixin:Update()
|
|
self:GetParent():SetTitle(L["Loadouts"]);
|
|
local sidebar = BtWLoadoutsFrame.Sidebar
|
|
|
|
sidebar:SetSupportedFilters("spec", "class", "role", "character", "disabled")
|
|
sidebar:SetSets(BtWLoadoutsSets.profiles)
|
|
sidebar:SetCollapsed(BtWLoadoutsCollapsed.profiles)
|
|
sidebar:SetCategories(BtWLoadoutsCategories.profiles)
|
|
sidebar:SetFilters(BtWLoadoutsFilters.profiles)
|
|
sidebar:SetSelected(self.set)
|
|
|
|
sidebar:Update()
|
|
self.set = sidebar:GetSelected()
|
|
local set = self.set
|
|
|
|
local showingNPE = BtWLoadoutsFrame:SetNPEShown(set == nil, L["Loadouts"], L["Add sets of one or more types (Talents, Soulbinds, etc.) to your loadouts to swap them together."])
|
|
|
|
self:GetParent().ExportButton:SetEnabled(true)
|
|
self:GetParent().RefreshButton:SetEnabled(false)
|
|
self:GetParent().DeleteButton:SetEnabled(true)
|
|
|
|
self.Collapsed = self.Collapsed or {}
|
|
|
|
if not showingNPE then
|
|
local hasErrors, errors, specID = GetLoadoutErrors(errors, set)
|
|
if type(specID) == "number" and set.specID == nil then
|
|
set.specID = specID
|
|
end
|
|
|
|
specID = set.specID;
|
|
|
|
UpdateSetFilters(set)
|
|
sidebar:Update()
|
|
|
|
local classFile = specID and select(6, GetSpecializationInfoByID(specID))
|
|
if specID == nil or specID == 0 then
|
|
UIDropDownMenu_SetText(self.SpecDropDown, L["None"]);
|
|
else
|
|
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));
|
|
end
|
|
|
|
if classFile and type(set.character) == "table" and not set.character["inherit"] then
|
|
-- Filter out any characters that are not valid for the selected spec
|
|
for character in pairs(set.character) do
|
|
local characterData = Internal.GetCharacterInfo(character)
|
|
if not characterData or characterData.class ~= classFile then
|
|
set.character[character] = nil
|
|
end
|
|
end
|
|
end
|
|
self.CharacterDropDown:SetClass(classFile)
|
|
self.CharacterDropDown:UpdateName()
|
|
|
|
self.Enabled:SetChecked(not set.disabled);
|
|
|
|
self.SetsScroll.items = BuildSetItems(set, self.SetsScroll.items or {}, self.Collapsed, errors)
|
|
SetsScrollFrameUpdate(self.SetsScroll)
|
|
|
|
if not self.Name:HasFocus() then
|
|
self.Name:SetText(set.name or "");
|
|
end
|
|
|
|
self:GetParent().ActivateButton:SetEnabled(classFile == select(2, UnitClass("player")));
|
|
|
|
-- Tutorial stuff
|
|
local helpTipBox = self:GetParent().HelpTipBox;
|
|
if not BtWLoadoutsHelpTipFlags["TUTORIAL_RENAME_SET"] then
|
|
helpTipBox.closeFlag = "TUTORIAL_RENAME_SET";
|
|
|
|
HelpTipBox_Anchor(helpTipBox, "TOP", self.Name);
|
|
|
|
helpTipBox:Show();
|
|
HelpTipBox_SetText(helpTipBox, L["Change the name of your new loadout."]);
|
|
elseif not BtWLoadoutsHelpTipFlags["TUTORIAL_CREATE_TALENT_SET"] then
|
|
helpTipBox.closeFlag = "TUTORIAL_CREATE_TALENT_SET";
|
|
|
|
HelpTipBox_Anchor(helpTipBox, "TOP", self.SetsScroll);
|
|
|
|
helpTipBox:Show();
|
|
HelpTipBox_SetText(helpTipBox, L["Create a talent set for your new loadout."]);
|
|
elseif not BtWLoadoutsHelpTipFlags["TUTORIAL_ACTIVATE_SET"] then
|
|
helpTipBox.closeFlag = "TUTORIAL_ACTIVATE_SET";
|
|
|
|
HelpTipBox_Anchor(helpTipBox, "TOP", self:GetParent().ActivateButton);
|
|
|
|
helpTipBox:Show();
|
|
HelpTipBox_SetText(helpTipBox, L["Activate your loadout."]);
|
|
else
|
|
helpTipBox.closeFlag = nil;
|
|
helpTipBox:Hide();
|
|
end
|
|
else
|
|
self.Name:SetText(L["New Loadout"]);
|
|
|
|
self.SetsScroll.items = BuildSetItems({}, self.SetsScroll.items or {}, self.Collapsed, {})
|
|
SetsScrollFrameUpdate(self.SetsScroll)
|
|
|
|
self.Enabled:SetChecked(true)
|
|
|
|
UIDropDownMenu_SetText(self.SpecDropDown, L["None"])
|
|
|
|
-- Tutorial stuff
|
|
local helpTipBox = self:GetParent().HelpTipBox;
|
|
if not BtWLoadoutsHelpTipFlags["TUTORIAL_NEW_SET"] then
|
|
helpTipBox.closeFlag = "TUTORIAL_NEW_SET";
|
|
|
|
HelpTipBox_Anchor(helpTipBox, "BOTTOM", self:GetParent().NPE.AddButton);
|
|
|
|
helpTipBox:Show();
|
|
HelpTipBox_SetText(helpTipBox, L["To begin, create a new set."]);
|
|
else
|
|
helpTipBox.closeFlag = nil;
|
|
helpTipBox:Hide();
|
|
end
|
|
end
|
|
end
|
|
function BtWLoadoutsLoadoutsMixin:SetSetByID(setID)
|
|
self.set = GetProfile(setID)
|
|
end
|