local _, AltManager = ...;
_G["AltManager"] = AltManager;
local Dialog = LibStub("LibDialog-1.0")
local sizey = 450;
local instances_y_add = 45;
local xoffset = 0;
local yoffset = 40;
local addon = "AltManager";
local per_alt_x = 120;
local ilvl_text_size = 8;
local remove_button_size = 12;
local min_x_size = 300;
local min_level = 70;
local name_label = "" -- Name
local mythic_keystone_label = "Keystone"
local mythic_plus_label = "Mythic+ Rating"
local worldboss_label = "World Boss"
local aiding_the_accord_label = "Aiding the Accord"
local conquest_label = "Conquest"
local conquest_earned_label = "Conquest Earned"
local gold_label = "Gold"
local supplies_label = "Dragon Isles Supplies"
local elemental_overflow_label = "Elemental Overflow"
local storm_sigil_label = "Storm Sigil"
local bloody_token_label = "Bloody Tokens"
local honor_label = "Honor"
local valor_label = "Valor"
local timewarped_badges_label = "Timewarped Badge"
local function GetCurrencyAmount(id)
local info = C_CurrencyInfo.GetCurrencyInfo(id)
return info.quantity;
end
-- if Blizzard keeps supporting old api, get the IDs from
-- C_ChallengeMode.GetMapTable() and names from C_ChallengeMode.GetMapUIInfo(id)
local dungeons = {
-- MoP
[2] = "TJS",
-- WoD
[165] = "SBG",
-- [166] = "GD",
-- [169] = "ID",
-- Legion
[200] = "HOV",
[210] = "COS",
-- [227] = "LOWR",
-- [234] = "UPPR",
-- BFA
-- [244] = "AD",
-- [245] = "FH",
-- [246] = "TD",
-- [247] = "ML",
-- [248] = "WCM",
-- [249] = "KR",
-- [250] = "Seth",
-- [251] = "UR",
-- [252] = "SotS",
-- [353] = "SoB",
-- [369] = "YARD",
-- [370] = "SHOP",
-- Shadowlands
-- [375] = "MoTS",
-- [376] = "NW",
-- [377] = "DOS",
-- [378] = "HoA",
-- [379] = "PF",
-- [380] = "SD",
-- [381] = "SoA",
-- [382] = "ToP",
-- [391] = "STRT",
-- [392] = "GMBT",
-- Dragonflight
[399] = "RLP",
[400] = "NO",
[401] = "AV",
[402] = "AA",
};
SLASH_ALTMANAGER1 = "/mam";
SLASH_ALTMANAGER2 = "/alts";
local function spairs(t, order)
local keys = {}
for k in pairs(t) do keys[#keys+1] = k end
if order then
table.sort(keys, function(a,b) return order(t, a, b) end)
else
table.sort(keys)
end
local i = 0
return function()
i = i + 1
if keys[i] then
return keys[i], t[keys[i]]
end
end
end
local function true_numel(t)
local c = 0
for k, v in pairs(t) do c = c + 1 end
return c
end
function SlashCmdList.ALTMANAGER(cmd, editbox)
local rqst, arg = strsplit(' ', cmd)
if rqst == "help" then
print("Alt Manager help:")
print(" \"/mam or /alts\" to open main addon window.")
print(" \"/alts purge\" to remove all stored data.")
print(" \"/alts remove name\" to remove characters by name.")
elseif rqst == "purge" then
AltManager:Purge();
elseif rqst == "remove" then
AltManager:RemoveCharactersByName(arg)
else
AltManager:ShowInterface();
end
end
do
local main_frame = CreateFrame("frame", "AltManagerFrame", UIParent);
AltManager.main_frame = main_frame;
main_frame:SetFrameStrata("MEDIUM");
main_frame.background = main_frame:CreateTexture(nil, "BACKGROUND");
main_frame.background:SetAllPoints();
main_frame.background:SetDrawLayer("ARTWORK", 1);
main_frame.background:SetColorTexture(0, 0, 0, 0.5);
-- Set frame position
main_frame:ClearAllPoints();
main_frame:SetPoint("CENTER", UIParent, "CENTER", xoffset, yoffset);
main_frame:RegisterEvent("ADDON_LOADED");
main_frame:RegisterEvent("PLAYER_LOGIN");
main_frame:RegisterEvent("PLAYER_LOGOUT");
main_frame:RegisterEvent("QUEST_TURNED_IN");
main_frame:RegisterEvent("BAG_UPDATE_DELAYED");
main_frame:RegisterEvent("ARTIFACT_XP_UPDATE");
main_frame:RegisterEvent("CHAT_MSG_CURRENCY");
main_frame:RegisterEvent("CURRENCY_DISPLAY_UPDATE");
main_frame:RegisterEvent("PLAYER_LEAVING_WORLD");
main_frame:SetScript("OnEvent", function(self, ...)
local event, loaded = ...;
if event == "ADDON_LOADED" then
if addon == loaded then
AltManager:OnLoad();
end
end
if event == "PLAYER_LOGIN" then
AltManager:OnLogin();
end
if event == "PLAYER_LEAVING_WORLD" or event == "ARTIFACT_XP_UPDATE" then
local data = AltManager:CollectData(false);
AltManager:StoreData(data);
end
if (event == "BAG_UPDATE_DELAYED" or event == "QUEST_TURNED_IN" or event == "CHAT_MSG_CURRENCY" or event == "CURRENCY_DISPLAY_UPDATE") and AltManager.addon_loaded then
local data = AltManager:CollectData(false);
AltManager:StoreData(data);
end
end)
main_frame:EnableKeyboard(true);
main_frame:SetScript("OnKeyDown", function(self, key) if key == "ESCAPE" then main_frame:SetPropagateKeyboardInput(false); else main_frame:SetPropagateKeyboardInput(true); end end )
main_frame:SetScript("OnKeyUp", function(self, key) if key == "ESCAPE" then AltManager:HideInterface() end end);
-- Show Frame
main_frame:Hide();
end
function AltManager:InitDB()
local t = {};
t.alts = 0;
t.data = {};
return t;
end
function AltManager:CalculateXSizeNoGuidCheck()
local alts = MethodAltManagerDB.alts;
return max((alts + 1) * per_alt_x, min_x_size)
end
function AltManager:CalculateXSize()
return self:CalculateXSizeNoGuidCheck()
end
-- because of guid...
function AltManager:OnLogin()
self:ValidateReset();
self:StoreData(self:CollectData());
self.main_frame:SetSize(self:CalculateXSize(), sizey);
self.main_frame.background:SetAllPoints();
-- Create menus
AltManager:CreateContent();
AltManager:MakeTopBottomTextures(self.main_frame);
AltManager:MakeBorder(self.main_frame, 5);
end
function AltManager:PurgeDbShadowlands()
if MethodAltManagerDB == nil or MethodAltManagerDB.data == nil then return end
local remove = {}
for alt_guid, alt_data in spairs(MethodAltManagerDB.data, function(t, a, b) return t[a].ilevel > t[b].ilevel end) do
if alt_data.charlevel == nil or alt_data.charlevel < min_level then -- poor heuristic to remove old max level chars
table.insert(remove, alt_guid)
end
end
for k, v in pairs(remove) do
-- don't need to redraw, this is don on load
MethodAltManagerDB.alts = MethodAltManagerDB.alts - 1;
MethodAltManagerDB.data[v] = nil
end
end
function AltManager:OnLoad()
self.main_frame:UnregisterEvent("ADDON_LOADED");
MethodAltManagerDB = MethodAltManagerDB or self:InitDB();
self:PurgeDbShadowlands();
if MethodAltManagerDB.alts ~= true_numel(MethodAltManagerDB.data) then
print("Altcount inconsistent, using", true_numel(MethodAltManagerDB.data))
MethodAltManagerDB.alts = true_numel(MethodAltManagerDB.data)
end
self.addon_loaded = true
C_MythicPlus.RequestRewards();
C_MythicPlus.RequestCurrentAffixes();
C_MythicPlus.RequestMapInfo();
end
function AltManager:CreateFontFrame(parent, x_size, height, relative_to, y_offset, label, justify)
local f = CreateFrame("Button", nil, parent);
f:SetSize(x_size, height);
f:SetNormalFontObject("GameFontHighlightSmall")
f:SetText(label)
f:SetPoint("TOPLEFT", relative_to, "TOPLEFT", 0, y_offset);
f:GetFontString():SetJustifyH(justify);
f:GetFontString():SetJustifyV("CENTER");
f:SetPushedTextOffset(0, 0);
f:GetFontString():SetWidth(120)
f:GetFontString():SetHeight(20)
return f;
end
function AltManager:Keyset()
local keyset = {}
if MethodAltManagerDB and MethodAltManagerDB.data then
for k in pairs(MethodAltManagerDB.data) do
table.insert(keyset, k)
end
end
return keyset
end
function AltManager:ValidateReset()
local db = MethodAltManagerDB
if not db then return end;
if not db.data then return end;
local keyset = {}
for k in pairs(db.data) do
table.insert(keyset, k)
end
for alt = 1, db.alts do
local expiry = db.data[keyset[alt]].expires or 0;
local char_table = db.data[keyset[alt]];
if time() > expiry then
-- reset this alt
char_table.dungeon = "Unknown";
char_table.level = "?";
char_table.run_history = nil;
char_table.expires = self:GetNextWeeklyResetTime();
char_table.worldboss = false;
char_table.aiding_the_accord = false;
char_table.incarnates_normal = 0;
char_table.incarnates_heroic = 0;
char_table.incarnates_mythic = 0;
end
end
end
function AltManager:Purge()
MethodAltManagerDB = self:InitDB();
end
function AltManager:RemoveCharactersByName(name)
local db = MethodAltManagerDB;
local indices = {};
for guid, data in pairs(db.data) do
if db.data[guid].name == name then
indices[#indices+1] = guid
end
end
db.alts = db.alts - #indices;
for i = 1,#indices do
db.data[indices[i]] = nil
end
print("Found " .. (#indices) .. " characters by the name of " .. name)
print("Please reload ui to update the displayed info.")
-- things wont be redrawn
end
function AltManager:RemoveCharacterByGuid(index, skip_confirmation)
local db = MethodAltManagerDB;
if db.data[index] == nil then return end
local delete = function()
if db.data[index] == nil then return end
db.alts = db.alts - 1;
db.data[index] = nil
self.main_frame:SetSize(self:CalculateXSizeNoGuidCheck(), sizey);
if self.main_frame.alt_columns ~= nil then
-- Hide the last col
-- find the correct frame to hide
local count = #self.main_frame.alt_columns
for j = 0,count-1 do
if self.main_frame.alt_columns[count-j]:IsShown() then
self.main_frame.alt_columns[count-j]:Hide()
-- also for instances
if self.instances_unroll ~= nil and self.instances_unroll.alt_columns ~= nil and self.instances_unroll.alt_columns[count-j] ~= nil then
self.instances_unroll.alt_columns[count-j]:Hide()
end
break
end
end
-- and hide the remove button
if self.main_frame.remove_buttons ~= nil and self.main_frame.remove_buttons[index] ~= nil then
self.main_frame.remove_buttons[index]:Hide()
end
end
self:UpdateStrings()
-- it's not simple to update the instances text with current design, so hide it and let the click do update
if self.instances_unroll ~= nil and self.instances_unroll.state == "open" then
self:CloseInstancesUnroll()
self.instances_unroll.state = "closed";
end
end
if skip_confirmation == nil then
local name = db.data[index].name
Dialog:Register("AltManagerRemoveCharacterDialog", {
text = "Are you sure you want to remove " .. name .. " from the list?",
width = 500,
on_show = function(self, data)
end,
buttons = {
{ text = "Delete",
on_click = delete},
{ text = "Cancel", }
},
show_while_dead = true,
hide_on_escape = true,
})
if Dialog:ActiveDialog("AltManagerRemoveCharacterDialog") then
Dialog:Dismiss("AltManagerRemoveCharacterDialog")
end
Dialog:Spawn("AltManagerRemoveCharacterDialog", {string = string})
else
delete();
end
end
function AltManager:StoreData(data)
if not self.addon_loaded then
return
end
-- This can happen shortly after logging in, the game doesn't know the characters guid yet
if not data or not data.guid then
return
end
if UnitLevel('player') < min_level then return end;
local db = MethodAltManagerDB;
local guid = data.guid;
db.data = db.data or {};
local update = false;
for k, v in pairs(db.data) do
if k == guid then
update = true;
end
end
if not update then
db.data[guid] = data;
db.alts = db.alts + 1;
else
local lvl = db.data[guid].artifact_level;
data.artifact_level = data.artifact_level or lvl;
db.data[guid] = data;
end
end
function AltManager:CollectData(do_artifact)
if UnitLevel('player') < min_level then return end;
-- this is an awful hack that will probably have some unforeseen consequences,
-- but Blizzard fucked something up with systems on logout, so let's see how it
-- goes.
_, i = GetAverageItemLevel()
if i == 0 then return end;
-- fix this when i'm not on a laptop at work
do_artifact = false
local name = UnitName('player')
local _, class = UnitClass('player')
local dungeon = nil;
local expire = nil;
local level = nil;
local highest_mplus = 0;
local guid = UnitGUID('player');
local mine_old = nil
if MethodAltManagerDB and MethodAltManagerDB.data then
mine_old = MethodAltManagerDB.data[guid];
end
-- C_MythicPlus.RequestRewards();
C_MythicPlus.RequestCurrentAffixes();
C_MythicPlus.RequestMapInfo();
for k,v in pairs(dungeons) do
-- request info in advance
C_MythicPlus.RequestMapInfo(k);
end
local maps = C_ChallengeMode.GetMapTable();
for i = 1, #maps do
C_ChallengeMode.RequestLeaders(maps[i]);
end
local run_history = C_MythicPlus.GetRunHistory(false, true);
-- find keystone
local keystone_found = false;
for bag=BACKPACK_CONTAINER, NUM_TOTAL_EQUIPPED_BAG_SLOTS do
for slot=1, C_Container.GetContainerNumSlots(bag) do
local containerInfo = C_Container.GetContainerItemInfo(bag, slot)
if containerInfo ~= nil then
local slotItemID = containerInfo.itemID
local slotLink = containerInfo.hyperlink
if slotItemID == 180653 then
local itemString = slotLink:match("|Hkeystone:([0-9:]+)|h(%b[])|h")
local info = { strsplit(":", itemString) }
dungeon = tonumber(info[2])
if not dungeon then dungeon = nil end
level = tonumber(info[3])
if not level then level = nil end
keystone_found = true;
end
end
end
end
if not keystone_found then
dungeon = "Unknown";
level = "?"
end
local saves = GetNumSavedInstances();
local normal_difficulty = 14
local heroic_difficulty = 15
local mythic_difficulty = 16
for i = 1, saves do
local raid_name, _, reset, difficulty, _, _, _, _, _, _, _, killed_bosses = GetSavedInstanceInfo(i);
-- Vault of the Incarnates IDs = {2119, 2120, 2121, 2122, 2123, 2124, 2125, 2126}
if raid_name == C_Map.GetMapInfo(2119).name and reset > 0 then
if difficulty == normal_difficulty then Incarnates_Normal = killed_bosses end
if difficulty == heroic_difficulty then Incarnates_Heroic = killed_bosses end
if difficulty == mythic_difficulty then Incarnates_Mythic = killed_bosses end
end
end
local world_boss_quests = {
[69927] = "Bazual",
[69928] = "Liskanoth",
[69929] = "Strunraan",
[69930] = "Basrikron"
}
local worldboss = nil
for k,v in pairs(world_boss_quests)do
if C_QuestLog.IsQuestFlaggedCompleted(k) then
worldboss = v
end
end
local aiding_the_accord_quests = {
[72374] = "Yes",
[70750] = "Yes",
[72068] = "Yes",
[72373] = "Yes"
}
local aiding_the_accord = nil
for k,v in pairs(aiding_the_accord_quests)do
if C_QuestLog.IsQuestFlaggedCompleted(k) then
aiding_the_accord = v
end
end
-- this is how the official pvp ui does it, so if its wrong.. sue me
local currencyInfo = C_CurrencyInfo.GetCurrencyInfo(Constants.CurrencyConsts.CONQUEST_CURRENCY_ID);
local maxProgress = currencyInfo.maxQuantity;
local conquest_earned = math.min(currencyInfo.totalEarned, maxProgress);
local conquest_total = currencyInfo.quantity
local _, ilevel = GetAverageItemLevel();
local gold = GetMoneyString(GetMoney(), true)
local supplies = GetCurrencyAmount(2003);
local elemental_overflow = GetCurrencyAmount(2118);
local storm_sigil = GetCurrencyAmount(2122);
local bloody_token = GetCurrencyAmount(2123);
local honor_points = GetCurrencyAmount(1792);
local valor_points = GetCurrencyAmount(1191);
local timewarped_badges = GetCurrencyAmount(1166);
local mplus_data = C_PlayerInfo.GetPlayerMythicPlusRatingSummary('player')
local mplus_score = mplus_data.currentSeasonScore
local char_table = {}
char_table.guid = UnitGUID('player');
char_table.name = name;
char_table.class = class;
char_table.ilevel = ilevel;
char_table.charlevel = UnitLevel('player')
char_table.dungeon = dungeon;
char_table.level = level;
char_table.run_history = run_history;
char_table.worldboss = worldboss;
char_table.aiding_the_accord = aiding_the_accord;
char_table.conquest_earned = conquest_earned;
char_table.conquest_total = conquest_total;
char_table.mplus_score = mplus_score
char_table.gold = gold;
char_table.supplies = supplies;
char_table.elemental_overflow = elemental_overflow;
char_table.storm_sigil = storm_sigil;
char_table.bloody_token = bloody_token;
char_table.honor_points = honor_points;
char_table.valor_points = valor_points;
char_table.timewarped_badges = timewarped_badges;
char_table.incarnates_normal = Incarnates_Normal;
char_table.incarnates_heroic = Incarnates_Heroic;
char_table.incarnates_mythic = Incarnates_Mythic;
char_table.expires = self:GetNextWeeklyResetTime();
char_table.data_obtained = time();
char_table.time_until_reset = C_DateAndTime.GetSecondsUntilDailyReset();
return char_table;
end
function AltManager:UpdateStrings()
local font_height = 20;
local db = MethodAltManagerDB;
local keyset = {}
for k in pairs(db.data) do
table.insert(keyset, k)
end
self.main_frame.alt_columns = self.main_frame.alt_columns or {};
local alt = 0
for alt_guid, alt_data in spairs(db.data, function(t, a, b) return t[a].ilevel > t[b].ilevel end) do
alt = alt + 1
-- create the frame to which all the fontstrings anchor
local anchor_frame = self.main_frame.alt_columns[alt] or CreateFrame("Button", nil, self.main_frame);
if not self.main_frame.alt_columns[alt] then
self.main_frame.alt_columns[alt] = anchor_frame;
self.main_frame.alt_columns[alt].guid = alt_guid
anchor_frame:SetPoint("TOPLEFT", self.main_frame, "TOPLEFT", per_alt_x * alt, -1);
end
anchor_frame:SetSize(per_alt_x, sizey);
-- init table for fontstring storage
self.main_frame.alt_columns[alt].label_columns = self.main_frame.alt_columns[alt].label_columns or {};
local label_columns = self.main_frame.alt_columns[alt].label_columns;
-- create / fill fontstrings
local i = 1;
for column_iden, column in spairs(self.columns_table, function(t, a, b) return t[a].order < t[b].order end) do
-- only display data with values
if type(column.data) == "function" then
local current_row = label_columns[i] or self:CreateFontFrame(anchor_frame, per_alt_x, column.font_height or font_height, anchor_frame, -(i - 1) * font_height, column.data(alt_data), "CENTER");
-- insert it into storage if just created
if not self.main_frame.alt_columns[alt].label_columns[i] then
self.main_frame.alt_columns[alt].label_columns[i] = current_row;
end
if column.color then
local color = column.color(alt_data)
current_row:GetFontString():SetTextColor(color.r, color.g, color.b, 1);
end
current_row:SetText(column.data(alt_data))
if column.font then
current_row:GetFontString():SetFont(column.font, ilvl_text_size)
else
--current_row:GetFontString():SetFont("Fonts\\FRIZQT__.TTF", 14)
end
if column.justify then
current_row:GetFontString():SetJustifyV(column.justify);
end
if column.remove_button ~= nil then
self.main_frame.remove_buttons = self.main_frame.remove_buttons or {}
local extra = self.main_frame.remove_buttons[alt_data.guid] or column.remove_button(alt_data)
if self.main_frame.remove_buttons[alt_data.guid] == nil then
self.main_frame.remove_buttons[alt_data.guid] = extra
end
extra:SetParent(current_row)
extra:SetPoint("TOPRIGHT", current_row, "TOPRIGHT", -18, 2 );
extra:SetPoint("BOTTOMRIGHT", current_row, "TOPRIGHT", -18, -remove_button_size + 2);
extra:SetFrameLevel(current_row:GetFrameLevel() + 1)
extra:Show();
end
end
i = i + 1
end
end
end
function AltManager:UpdateInstanceStrings(my_rows, font_height)
self.instances_unroll.alt_columns = self.instances_unroll.alt_columns or {};
local alt = 0
local db = MethodAltManagerDB;
for alt_guid, alt_data in spairs(db.data, function(t, a, b) return t[a].ilevel > t[b].ilevel end) do
alt = alt + 1
-- create the frame to which all the fontstrings anchor
local anchor_frame = self.instances_unroll.alt_columns[alt] or CreateFrame("Button", nil, self.main_frame.alt_columns[alt]);
if not self.instances_unroll.alt_columns[alt] then
self.instances_unroll.alt_columns[alt] = anchor_frame;
end
anchor_frame:SetPoint("TOPLEFT", self.instances_unroll.unroll_frame, "TOPLEFT", per_alt_x * alt, -1);
anchor_frame:SetSize(per_alt_x, instances_y_add);
-- init table for fontstring storage
self.instances_unroll.alt_columns[alt].label_columns = self.instances_unroll.alt_columns[alt].label_columns or {};
local label_columns = self.instances_unroll.alt_columns[alt].label_columns;
-- create / fill fontstrings
local i = 1;
for column_iden, column in spairs(my_rows, function(t, a, b) return t[a].order < t[b].order end) do
local current_row = label_columns[i] or self:CreateFontFrame(anchor_frame, per_alt_x, column.font_height or font_height, anchor_frame, -(i - 1) * font_height, column.data(alt_data), "CENTER");
-- insert it into storage if just created
if not self.instances_unroll.alt_columns[alt].label_columns[i] then
self.instances_unroll.alt_columns[alt].label_columns[i] = current_row;
end
current_row:SetText(column.data(alt_data)) -- fills data
i = i + 1
end
-- hotfix visibility
if anchor_frame:GetParent():IsShown() then anchor_frame:Show() else anchor_frame:Hide() end
end
end
function AltManager:OpenInstancesUnroll(my_rows, button)
-- do unroll
self.instances_unroll.unroll_frame = self.instances_unroll.unroll_frame or CreateFrame("Button", nil, self.main_frame);
self.instances_unroll.unroll_frame:SetSize(per_alt_x, instances_y_add);
self.instances_unroll.unroll_frame:SetPoint("TOPLEFT", self.main_frame, "TOPLEFT", 4, self.main_frame.lowest_point - 10);
self.instances_unroll.unroll_frame:Show();
local font_height = 20;
-- create the rows for the unroll
if not self.instances_unroll.labels then
self.instances_unroll.labels = {};
local i = 1
for row_iden, row in spairs(my_rows, function(t, a, b) return t[a].order < t[b].order end) do
if row.label then
local label_row = self:CreateFontFrame(self.instances_unroll.unroll_frame, per_alt_x, font_height, self.instances_unroll.unroll_frame, -(i-1)*font_height, row.label..":", "RIGHT");
table.insert(self.instances_unroll.labels, label_row)
end
i = i + 1
end
end
-- populate it for alts
self:UpdateInstanceStrings(my_rows, font_height)
-- fixup the background
self.main_frame:SetSize(self:CalculateXSizeNoGuidCheck(), sizey + instances_y_add);
self.main_frame.background:SetAllPoints();
end
function AltManager:CloseInstancesUnroll()
-- do rollup
self.main_frame:SetSize(self:CalculateXSizeNoGuidCheck(), sizey);
self.main_frame.background:SetAllPoints();
self.instances_unroll.unroll_frame:Hide();
for k, v in pairs(self.instances_unroll.alt_columns) do
v:Hide()
end
end
function AltManager:ProduceRelevantMythics(run_history)
-- find thresholds
local weekly_info = C_WeeklyRewards.GetActivities(Enum.WeeklyRewardChestThresholdType.MythicPlus);
table.sort(run_history, function(left, right) return left.level > right.level; end);
local thresholds = {}
local max_threshold = 0
for i = 1 , #weekly_info do
thresholds[weekly_info[i].threshold] = true;
if weekly_info[i].threshold > max_threshold then
max_threshold = weekly_info[i].threshold;
end
end
return run_history, thresholds, max_threshold
end
function AltManager:MythicRunHistoryString(alt_data, vault_slot)
if alt_data.run_history == nil or alt_data.run_history == 0 or next(alt_data.run_history) == nil then
return "-"
end
local sorted_history = AltManager:ProduceRelevantMythics(alt_data.run_history);
local first = true;
local result = " ";
if vault_slot == 1 then
if #sorted_history < 1 then return "-" end
for run = 1, math.min(1, #sorted_history) do
result = result .. "|cFF00FF00" .. tostring(sorted_history[run].level) .. "|r "
end
return result
end
if vault_slot == 2 then
if #sorted_history < 2 then return "-" end
if #sorted_history == 2 then
for run = 2, math.min(4, #sorted_history) do
result = result .. tostring(sorted_history[run].level) .. " "
end
else
for run = 2, math.min(4, #sorted_history) - 1 do
result = result .. tostring(sorted_history[run].level) .. " "
end
if math.min(4, #sorted_history) == 4 then
result = result .. "|cFF00FF00" .. tostring(sorted_history[math.min(4, #sorted_history)].level) .. "|r "
elseif math.min(4, #sorted_history) > 2 then
result = result .. tostring(sorted_history[math.min(4, #sorted_history)].level) .. " "
end
end
return result
end
if vault_slot == 3 then
if #sorted_history < 5 then return "-" end
if #sorted_history == 5 then
for run = 5, math.min(8, #sorted_history) do
result = result .. tostring(sorted_history[run].level) .. " "
end
else
for run = 5, math.min(8, #sorted_history) - 1 do
result = result .. tostring(sorted_history[run].level) .. " "
end
if math.min(8, #sorted_history) == 8 then
result = result .. "|cFF00FF00" .. tostring(sorted_history[math.min(8, #sorted_history)].level) .. "|r "
elseif math.min(8, #sorted_history) > 5 then
result = result .. tostring(sorted_history[math.min(8, #sorted_history)].level) .. " "
end
end
return result
end
return result
end
function AltManager:CreateContent()
-- Close button
self.main_frame.closeButton = CreateFrame("Button", "CloseButton", self.main_frame, "UIPanelCloseButton");
self.main_frame.closeButton:ClearAllPoints()
self.main_frame.closeButton:SetPoint("BOTTOMRIGHT", self.main_frame, "TOPRIGHT", -10, -2);
self.main_frame.closeButton:SetScript("OnClick", function() AltManager:HideInterface(); end);
--self.main_frame.closeButton:SetSize(32, h);
local column_table = {
name = {
order = 1,
label = name_label,
data = function(alt_data) return alt_data.name end,
color = function(alt_data) return RAID_CLASS_COLORS[alt_data.class] end,
},
ilevel = {
order = 2,
data = function(alt_data) return string.format("%.2f", alt_data.ilevel or 0) end, -- , alt_data.neck_level or 0
justify = "TOP",
font = "Fonts\\FRIZQT__.TTF",
remove_button = function(alt_data) return self:CreateRemoveButton(function() AltManager:RemoveCharacterByGuid(alt_data.guid) end) end
},
gold = {
order = 3,
justify = "TOP",
font = "Fonts\\FRIZQT__.TTF",
data = function(alt_data) return tostring(alt_data.gold or "0") end,
},
mplus = {
order = 4,
label = "Slot 1",
data = function(alt_data) return self:MythicRunHistoryString(alt_data,1) end,
},
mplus2 = {
order = 4.1,
label = "Vault Slot 2",
data = function(alt_data) return self:MythicRunHistoryString(alt_data,2) end,
},
mplus3 = {
order = 4.2,
label = "Slot 3",
data = function(alt_data) return self:MythicRunHistoryString(alt_data,3) end,
},
keystone = {
order = 4.3,
label = mythic_keystone_label,
data = function(alt_data) return (dungeons[alt_data.dungeon] or alt_data.dungeon) .. " +" .. tostring(alt_data.level); end,
},
mplus_score = {
order = 4.4,
label = mythic_plus_label,
data = function(alt_data) return tostring(alt_data.mplus_score or "0") end,
},
fake_just_for_offset = {
order = 5,
label = "",
data = function(alt_data) return " " end,
},
valor_points = {
order = 6,
label = valor_label,
data = function(alt_data) return tostring(alt_data.valor_points or "?") end,
},
supplies = {
order = 6.1,
label = supplies_label,
data = function(alt_data) return tostring(alt_data.supplies or "?") end,
},
elemental_overflow = {
order = 6.2,
label = elemental_overflow_label,
data = function(alt_data) return tostring(alt_data.elemental_overflow or "?") end,
},
storm_sigil = {
order = 6.3,
label = storm_sigil_label,
data = function(alt_data) return tostring(alt_data.storm_sigil or "?") end,
},
bloody_token = {
order = 6.4,
label = bloody_token_label,
data = function(alt_data) return tostring(alt_data.bloody_token or "?") end,
},
timewarped_badges = {
order = 6.5,
label = timewarped_badges_label,
data = function(alt_data) return tostring(alt_data.timewarped_badges or "?") end,
},
fake_just_for_offset_2 = {
order = 7,
label = "",
data = function(alt_data) return " " end,
},
aiding_the_accord = {
order = 8,
label = aiding_the_accord_label,
data = function(alt_data) return alt_data.aiding_the_accord or "No" end,
},
worldbosses = {
order = 9,
label = worldboss_label,
data = function(alt_data) return alt_data.worldboss and (alt_data.worldboss .. " killed") or "-" end,
},
honor_points = {
order = 10,
label = honor_label,
data = function(alt_data) return tostring(alt_data.honor_points or "?") end,
},
conquest_pts = {
order = 11,
label = conquest_label,
data = function(alt_data) return (alt_data.conquest_total and tostring(alt_data.conquest_total) or "0") end,
},
conquest_cap = {
order = 12,
label = conquest_earned_label,
data = function(alt_data) return (alt_data.conquest_earned and (tostring(alt_data.conquest_earned) .. " / " .. C_CurrencyInfo.GetCurrencyInfo(Constants.CurrencyConsts.CONQUEST_CURRENCY_ID).maxQuantity) or "?") end, -- .. "/" .. "500"
},
dummy_line = {
order = 13,
label = " ",
data = function(alt_data) return " " end,
},
raid_unroll = {
order = 14,
data = "unroll",
name = "Instances >>",
unroll_function = function(button, my_rows)
self.instances_unroll = self.instances_unroll or {};
self.instances_unroll.state = self.instances_unroll.state or "closed";
if self.instances_unroll.state == "closed" then
self:OpenInstancesUnroll(my_rows)
-- update ui
button:SetText("Instances <<");
self.instances_unroll.state = "open";
else
self:CloseInstancesUnroll()
-- update ui
button:SetText("Instances >>");
self.instances_unroll.state = "closed";
end
end,
rows = {
uldir = {
order = 4,
label = "Vault of the Incarnates",
data = function(alt_data) return self:MakeRaidString(alt_data.incarnates_normal, alt_data.incarnates_heroic, alt_data.incarnates_mythic) end
},
}
}
}
self.columns_table = column_table;
-- create labels and unrolls
local font_height = 20;
local label_column = self.main_frame.label_column or CreateFrame("Button", nil, self.main_frame);
if not self.main_frame.label_column then self.main_frame.label_column = label_column; end
label_column:SetSize(per_alt_x, sizey);
label_column:SetPoint("TOPLEFT", self.main_frame, "TOPLEFT", 4, -1);
local i = 1;
for row_iden, row in spairs(self.columns_table, function(t, a, b) return t[a].order < t[b].order end) do
if row.label then
local label_row = self:CreateFontFrame(self.main_frame, per_alt_x, font_height, label_column, -(i-1)*font_height, row.label~="" and row.label..":" or " ", "RIGHT");
self.main_frame.lowest_point = -(i-1)*font_height;
end
if row.data == "unroll" then
-- create a button that will unroll it
local unroll_button = CreateFrame("Button", "UnrollButton", self.main_frame, "UIPanelButtonTemplate");
unroll_button:SetText(row.name);
--unroll_button:SetFrameStrata("HIGH");
unroll_button:SetFrameLevel(self.main_frame:GetFrameLevel() + 2)
unroll_button:SetSize(unroll_button:GetTextWidth() + 20, 25);
unroll_button:SetPoint("BOTTOMRIGHT", self.main_frame, "TOPLEFT", 4 + per_alt_x, -(i-1)*font_height-10);
unroll_button:SetScript("OnClick", function() row.unroll_function(unroll_button, row.rows) end);
self.main_frame.lowest_point = -(i-1)*font_height-10;
end
i = i + 1
end
end
function AltManager:MakeRaidString(normal, heroic, mythic)
if not normal then normal = 0 end
if not heroic then heroic = 0 end
if not mythic then mythic = 0 end
local string = ""
if mythic > 0 then string = string .. tostring(mythic) .. "M" end
if heroic > 0 and mythic > 0 then string = string .. "-" end
if heroic > 0 then string = string .. tostring(heroic) .. "H" end
if normal > 0 and (mythic > 0 or heroic > 0) then string = string .. "-" end
if normal > 0 then string = string .. tostring(normal) .. "N" end
return string == "" and "-" or string
end
function AltManager:HideInterface()
self.main_frame:Hide();
end
function AltManager:ShowInterface()
self.main_frame:Show();
self:StoreData(self:CollectData())
self:UpdateStrings();
end
function AltManager:CreateRemoveButton(func)
local frame = CreateFrame("Button", nil, nil)
frame:ClearAllPoints()
frame:SetScript("OnClick", function() func() end);
self:MakeRemoveTexture(frame)
frame:SetWidth(remove_button_size)
return frame
end
function AltManager:MakeRemoveTexture(frame)
if frame.remove_tex == nil then
frame.remove_tex = frame:CreateTexture(nil, "BACKGROUND")
frame.remove_tex:SetTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up")
frame.remove_tex:SetAllPoints()
frame.remove_tex:Show();
end
return frame
end
function AltManager:MakeTopBottomTextures(frame)
if frame.bottomPanel == nil then
frame.bottomPanel = frame:CreateTexture(nil);
end
if frame.topPanel == nil then
frame.topPanel = CreateFrame("Frame", "AltManagerTopPanel", frame);
frame.topPanelTex = frame.topPanel:CreateTexture(nil, "BACKGROUND");
local logo = frame.topPanel:CreateTexture("logo","ARTWORK")
logo:SetPoint("TOPLEFT")
logo:SetTexture("Interface\\AddOns\\AltManager\\Media\\AltManager64")
--frame.topPanelTex:ClearAllPoints();
frame.topPanelTex:SetAllPoints();
--frame.topPanelTex:SetSize(frame:GetWidth(), 30);
frame.topPanelTex:SetDrawLayer("ARTWORK", -5);
frame.topPanelTex:SetColorTexture(0, 0, 0, 0.7);
frame.topPanelString = frame.topPanel:CreateFontString("OVERLAY");
frame.topPanelString:SetFont("Fonts\\Morpheus.TTF", 20)
frame.topPanelString:SetTextColor(1, 1, 1, 1);
frame.topPanelString:SetJustifyH("CENTER")
frame.topPanelString:SetJustifyV("MIDDLE")
frame.topPanelString:SetWidth(260)
frame.topPanelString:SetHeight(20)
frame.topPanelString:SetText("Xtra Thicc Alt Manager");
frame.topPanelString:ClearAllPoints();
frame.topPanelString:SetPoint("CENTER", frame.topPanel, "CENTER", 0, 0);
frame.topPanelString:Show();
end
frame.bottomPanel:SetColorTexture(0, 0, 0, 0.7);
frame.bottomPanel:ClearAllPoints();
frame.bottomPanel:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 0, 0);
frame.bottomPanel:SetPoint("TOPRIGHT", frame, "BOTTOMRIGHT", 0, 0);
frame.bottomPanel:SetSize(frame:GetWidth(), 30);
frame.bottomPanel:SetDrawLayer("ARTWORK", 7);
frame.topPanel:ClearAllPoints();
frame.topPanel:SetSize(frame:GetWidth(), 30);
frame.topPanel:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 0);
frame.topPanel:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 0, 0);
frame:SetMovable(true);
frame.topPanel:EnableMouse(true);
frame.topPanel:RegisterForDrag("LeftButton");
frame.topPanel:SetScript("OnDragStart", function(self,button)
frame:SetMovable(true);
frame:StartMoving();
end);
frame.topPanel:SetScript("OnDragStop", function(self,button)
frame:StopMovingOrSizing();
frame:SetMovable(false);
end);
end
function AltManager:MakeBorderPart(frame, x, y, xoff, yoff, part)
if part == nil then
part = frame:CreateTexture(nil);
end
part:SetTexture(0, 0, 0, 1);
part:ClearAllPoints();
part:SetPoint("TOPLEFT", frame, "TOPLEFT", xoff, yoff);
part:SetSize(x, y);
part:SetDrawLayer("ARTWORK", 7);
return part;
end
function AltManager:MakeBorder(frame, size)
if size == 0 then
return;
end
frame.borderTop = self:MakeBorderPart(frame, frame:GetWidth(), size, 0, 0, frame.borderTop); -- top
frame.borderLeft = self:MakeBorderPart(frame, size, frame:GetHeight(), 0, 0, frame.borderLeft); -- left
frame.borderBottom = self:MakeBorderPart(frame, frame:GetWidth(), size, 0, -frame:GetHeight() + size, frame.borderBottom); -- bottom
frame.borderRight = self:MakeBorderPart(frame, size, frame:GetHeight(), frame:GetWidth() - size, 0, frame.borderRight); -- right
end
-- shamelessly stolen from saved instances
function AltManager:GetNextWeeklyResetTime()
if not self.resetDays then
local region = self:GetRegion()
if not region then return nil end
self.resetDays = {}
self.resetDays.DLHoffset = 0
if region == "US" then
self.resetDays["2"] = true -- tuesday
-- ensure oceanic servers over the dateline still reset on tues UTC (wed 1/2 AM server)
self.resetDays.DLHoffset = -3
elseif region == "EU" then
self.resetDays["3"] = true -- wednesday
elseif region == "CN" or region == "KR" or region == "TW" then -- XXX: codes unconfirmed
self.resetDays["4"] = true -- thursday
else
self.resetDays["2"] = true -- tuesday?
end
end
local offset = (self:GetServerOffset() + self.resetDays.DLHoffset) * 3600
local nightlyReset = self:GetNextDailyResetTime()
if not nightlyReset then return nil end
while not self.resetDays[date("%w",nightlyReset+offset)] do
nightlyReset = nightlyReset + 24 * 3600
end
return nightlyReset
end
function AltManager:GetNextDailyResetTime()
local resettime = GetQuestResetTime()
if not resettime or resettime <= 0 or -- ticket 43: can fail during startup
-- also right after a daylight savings rollover, when it returns negative values >.<
resettime > 24*3600+30 then -- can also be wrong near reset in an instance
return nil
end
if false then -- this should no longer be a problem after the 7.0 reset time changes
-- ticket 177/191: GetQuestResetTime() is wrong for Oceanic+Brazilian characters in PST instances
local serverHour, serverMinute = GetGameTime()
local serverResetTime = (serverHour*3600 + serverMinute*60 + resettime) % 86400 -- GetGameTime of the reported reset
local diff = serverResetTime - 10800 -- how far from 3AM server
if math.abs(diff) > 3.5*3600 -- more than 3.5 hours - ignore TZ differences of US continental servers
and self:GetRegion() == "US" then
local diffhours = math.floor((diff + 1800)/3600)
resettime = resettime - diffhours*3600
if resettime < -900 then -- reset already passed, next reset
resettime = resettime + 86400
elseif resettime > 86400+900 then
resettime = resettime - 86400
end
end
end
return time() + resettime
end
function AltManager:GetServerOffset()
local serverDay = C_DateAndTime.GetCurrentCalendarTime().weekday - 1 -- 1-based starts on Sun
local localDay = tonumber(date("%w")) -- 0-based starts on Sun
local serverHour, serverMinute = GetGameTime()
local localHour, localMinute = tonumber(date("%H")), tonumber(date("%M"))
if serverDay == (localDay + 1)%7 then -- server is a day ahead
serverHour = serverHour + 24
elseif localDay == (serverDay + 1)%7 then -- local is a day ahead
localHour = localHour + 24
end
local server = serverHour + serverMinute / 60
local localT = localHour + localMinute / 60
local offset = floor((server - localT) * 2 + 0.5) / 2
return offset
end
function AltManager:GetRegion()
if not self.region then
local reg
reg = GetCVar("portal")
if reg == "public-test" then -- PTR uses US region resets, despite the misleading realm name suffix
reg = "US"
end
if not reg or #reg ~= 2 then
local gcr = GetCurrentRegion()
reg = gcr and ({ "US", "KR", "EU", "TW", "CN" })[gcr]
end
if not reg or #reg ~= 2 then
reg = (GetCVar("realmList") or ""):match("^(%a+)%.")
end
if not reg or #reg ~= 2 then -- other test realms?
reg = (GetRealmName() or ""):match("%((%a%a)%)")
end
reg = reg and reg:upper()
if reg and #reg == 2 then
self.region = reg
end
end
return self.region
end
function AltManager:GetWoWDate()
local hour = tonumber(date("%H"));
local day = C_DateAndTime.GetCurrentCalendarTime().weekday;
return day, hour;
end
function AltManager:TimeString(length)
if length == 0 then
return "Now";
end
if length < 3600 then
return string.format("%d mins", length / 60);
end
if length < 86400 then
return string.format("%d hrs %d mins", length / 3600, (length % 3600) / 60);
end
return string.format("%d days %d hrs", length / 86400, (length % 86400) / 3600);
end