863709e450
release / release (push) Successful in 5s
Root cause of the 'all over the place' Skills layout: the Skills content frame was 745 wide (widened at coa.9 for extra columns) vs 615 for every other Summary view. Both anchor TOPRIGHT, so the extra 130px pushed the Skills frame's LEFT edge over the nav menu -> profession names rendered on top of the menu. Restored to 615 so the left edge clears the menu like AccountSummary; the two-column name/rank list now sits in the content area.
885 lines
28 KiB
Lua
885 lines
28 KiB
Lua
--[[ *** Altoholic ***
|
|
Written by : Thaoky, EU-Marécages de Zangar
|
|
--]]
|
|
|
|
local addonName = ...
|
|
local addon = _G[addonName]
|
|
|
|
local L = LibStub("AceLocale-3.0"):GetLocale(addonName)
|
|
local DS
|
|
|
|
local WHITE = "|cFFFFFFFF"
|
|
local RED = "|cFFFF0000"
|
|
local GREEN = "|cFF00FF00"
|
|
local YELLOW = "|cFFFFFF00"
|
|
local ORANGE = "|cFFFF7F00"
|
|
local TEAL = "|cFF00FF9A"
|
|
local GOLD = "|cFFFFD700"
|
|
|
|
local THIS_ACCOUNT = "Default"
|
|
|
|
Altoholic.ClassInfo = {
|
|
["MAGE"] = "|cFF69CCF0",
|
|
["WARRIOR"] = "|cFFC79C6E",
|
|
["HUNTER"] = "|cFFABD473",
|
|
["ROGUE"] = "|cFFFFF569",
|
|
["WARLOCK"] = "|cFF9482CA",
|
|
["DRUID"] = "|cFFFF7D0A",
|
|
["SHAMAN"] = "|cFF2459FF",
|
|
["PALADIN"] = "|cFFF58CBA",
|
|
["PRIEST"] = WHITE,
|
|
["DEATHKNIGHT"] = "|cFFC41F3B"
|
|
}
|
|
|
|
local function InitLocalization()
|
|
-- this function's purpose is to initialize the text attribute of widgets created in XML.
|
|
-- in versions prior to 3.1.003, they were initialized through global constants named XML_ALTO_???
|
|
-- the strings stayed in memory for no reason, and could not be included in the automated localization offered by curse, hence the change of approach.
|
|
|
|
AltoholicMinimapButton.tooltip = format("%s\n%s\n%s", addonName, WHITE..L["Left-click to |cFF00FF00open"], WHITE..L["Right-click to |cFF00FF00drag"] )
|
|
|
|
AltoAccountSharing_InfoButton.tooltip = format("%s|r\n%s\n%s\n\n%s",
|
|
WHITE..L["Account Name"],
|
|
L["Enter an account name that will be\nused for |cFF00FF00display|r purposes only."],
|
|
L["This name can be anything you like,\nit does |cFF00FF00NOT|r have to be the real account name."],
|
|
L["This field |cFF00FF00cannot|r be left empty."])
|
|
|
|
AltoholicTabSummary_Options.tooltip = format("%s:|r %s", WHITE..GAMEOPTIONS_MENU, addonName)
|
|
AltoholicTabSummary_OptionsDataStore.tooltip = format("%s:|r %s", WHITE..GAMEOPTIONS_MENU, "DataStore")
|
|
|
|
AltoholicFrameTab1:SetText(L["Summary"])
|
|
AltoholicFrameTab2:SetText(L["Characters"])
|
|
AltoholicTabSummaryMenuItem1:SetText(L["Account Summary"])
|
|
AltoholicTabSummaryMenuItem2:SetText(L["Bag Usage"])
|
|
AltoholicTabSummaryMenuItem4:SetText(L["Activity"])
|
|
AltoholicTabSummaryMenuItem5:SetText(L["Guild Members"])
|
|
AltoholicTabSummaryMenuItem6:SetText(L["Guild Skills"])
|
|
AltoholicTabSummaryMenuItem7:SetText(L["Guild Bank Tabs"])
|
|
AltoholicTabSummaryMenuItem8:SetText(L["Calendar"])
|
|
AltoholicTabSummary_RequestSharing:SetText(L["Account Sharing"])
|
|
|
|
AltoholicTabCharactersText1:SetText(L["Realm"])
|
|
AltoholicTabCharactersText2:SetText(L["Character"])
|
|
|
|
AltoholicTabSearch_Sort1:SetText(L["Item / Location"])
|
|
AltoholicTabSearch_Sort2:SetText(L["Character"])
|
|
AltoholicTabSearch_Sort3:SetText(L["Realm"])
|
|
AltoholicTabSearchSlot:SetText(L["Equipment Slot"])
|
|
AltoholicTabSearchLocation:SetText(L["Location"])
|
|
|
|
AltoholicFramePetsText1:SetText(L["View"])
|
|
AltoholicFrameReputationsText1:SetText(L["View"])
|
|
AltoholicFrameCurrenciesText1:SetText(L["View"])
|
|
|
|
AltoholicTabGuildBank_HideInTooltipText:SetText(L["Hide this guild in the tooltip"])
|
|
|
|
AltoAccountSharingName:SetText(L["Account Name"])
|
|
AltoAccountSharingText1:SetText(L["Send account sharing request to:"])
|
|
AltoAccountSharingText2:SetText(ORANGE.."Available Content")
|
|
AltoAccountSharingText3:SetText(ORANGE.."Size")
|
|
AltoAccountSharingText4:SetText(ORANGE.."Date")
|
|
AltoAccountSharing_UseNameText:SetText(L["Character"])
|
|
|
|
AltoholicFrameTotals:SetText(L["Totals"])
|
|
AltoholicFrameSearchLabel:SetText(L["Search Containers"])
|
|
AltoholicFrame_ResetButton:SetText(L["Reset"])
|
|
|
|
-- nil strings to save memory, since they are not used later on.
|
|
L["Summary"] = nil
|
|
L["Characters"] = nil
|
|
L["Account Summary"] = nil
|
|
L["Bag Usage"] = nil
|
|
L["Activity"] = nil
|
|
L["Guild Skills"] = nil
|
|
L["Guild Bank Tabs"] = nil
|
|
|
|
L["View"] = nil
|
|
L["Hide this guild in the tooltip"] = nil
|
|
L["Search Containers"] = nil
|
|
L["Equipment Slot"] = nil
|
|
L["Location"] = nil
|
|
L["Reset"] = nil
|
|
L["Send account sharing request to:"] = nil
|
|
L["Left-click to |cFF00FF00open"] = nil
|
|
L["Right-click to |cFF00FF00drag"] = nil
|
|
L["Enter an account name that will be\nused for |cFF00FF00display|r purposes only."] = nil
|
|
L["This name can be anything you like,\nit does |cFF00FF00NOT|r have to be the real account name."] = nil
|
|
L["This field |cFF00FF00cannot|r be left empty."] = nil
|
|
|
|
if GetLocale() == "deDE" then
|
|
-- This is a global string from wow, for some reason the original is causing problem. DO NOT copy this line in localization files
|
|
ITEM_MOD_SPELL_POWER = "Erh\195\182ht die Zaubermacht um %d.";
|
|
end
|
|
end
|
|
|
|
local function BuildUnsafeItemList()
|
|
-- This method will clean the unsafe item list currently in the DB.
|
|
-- In the previous game session, the list has been populated with items id's that were originally unsafe and for which a query was sent to the server.
|
|
-- In this session, a getiteminfo on these id's will keep returning a nil if the item is really unsafe, so this method will get rid of the id's that are now valid.
|
|
local TmpUnsafe = {} -- create a temporary table with confirmed unsafe id's
|
|
local unsafeItems = Altoholic.db.global.unsafeItems
|
|
|
|
for _, itemID in pairs(unsafeItems) do
|
|
local itemName = GetItemInfo(itemID)
|
|
if not itemName then -- if the item is really unsafe .. save it
|
|
table.insert(TmpUnsafe, itemID)
|
|
end
|
|
end
|
|
|
|
wipe(unsafeItems) -- clear the DB table
|
|
|
|
for _, itemID in pairs(TmpUnsafe) do
|
|
table.insert(unsafeItems, itemID) -- save the confirmed unsafe ids back in the db
|
|
end
|
|
end
|
|
|
|
-- *** DB functions ***
|
|
local currentAlt
|
|
local currentRealm
|
|
local currentAccount
|
|
|
|
function addon:GetCharacterTable(name, realm, account)
|
|
-- Usage:
|
|
-- local c = addon:GetCharacterTable(char, realm, account)
|
|
-- all 3 parameters default to current player, realm or account
|
|
-- use this for features that have to work regardless of an alt's location (any realm, any account)
|
|
local key = format("%s.%s.%s", account or currentAccount, realm or currentRealm, name or currentAlt)
|
|
return addon.db.global.Characters[key]
|
|
end
|
|
|
|
function addon:GetCharacterTableByLine(line)
|
|
-- shortcut to get the right character table based on the line number in the info table.
|
|
return addon:GetCharacterTable( addon.Characters:GetInfo(line) )
|
|
end
|
|
|
|
function addon:GetCurrentCharacter()
|
|
return currentAlt, currentRealm, currentAccount
|
|
end
|
|
|
|
function addon:SetCurrentCharacter(name, realm, account)
|
|
currentAlt = name
|
|
if realm then
|
|
addon:SetCurrentRealm(realm)
|
|
end
|
|
if account then
|
|
addon:SetCurrentAccount(account)
|
|
end
|
|
end
|
|
|
|
function addon:GetCurrentRealm()
|
|
return currentRealm, currentAccount
|
|
end
|
|
|
|
function addon:SetCurrentRealm(name)
|
|
currentRealm = name
|
|
end
|
|
|
|
function addon:GetCurrentAccount()
|
|
return currentAccount
|
|
end
|
|
|
|
function addon:SetCurrentAccount(name)
|
|
currentAccount = name
|
|
end
|
|
|
|
function addon:GetGuild(name, realm, account)
|
|
name = name or GetGuildInfo("player")
|
|
if not name then return end
|
|
|
|
realm = realm or GetRealmName()
|
|
account = account or THIS_ACCOUNT
|
|
|
|
local key = format("%s.%s.%s", account, realm, name)
|
|
return addon.db.global.Guilds[key]
|
|
end
|
|
|
|
function Altoholic:GetGuildMembers(guild)
|
|
assert(type(guild) == "table")
|
|
return guild.members
|
|
end
|
|
|
|
function Altoholic:SetLastAccountSharingInfo(name, realm, account)
|
|
local sharing = Altoholic.db.global.Sharing.Domains[format("%s.%s", account, realm)]
|
|
sharing.lastSharingTimestamp = time()
|
|
sharing.lastUpdatedWith = name
|
|
end
|
|
|
|
function Altoholic:GetLastAccountSharingInfo(realm, account)
|
|
local sharing = Altoholic.db.global.Sharing.Domains[format("%s.%s", account, realm)]
|
|
|
|
if sharing then
|
|
return date("%m/%d/%Y %H:%M", sharing.lastSharingTimestamp), sharing.lastUpdatedWith
|
|
end
|
|
end
|
|
|
|
|
|
-- *** Scanning functions ***
|
|
local function ScanFriends()
|
|
local c = addon.ThisCharacter
|
|
wipe(c.Friends)
|
|
|
|
for i = 1, GetNumFriends() do
|
|
local name = GetFriendInfo(i);
|
|
table.insert(c.Friends, name)
|
|
end
|
|
end
|
|
|
|
local function ScanSavedInstances()
|
|
local c = addon.ThisCharacter
|
|
|
|
wipe(c.SavedInstance)
|
|
|
|
for i=1, GetNumSavedInstances() do
|
|
local instanceName, instanceID, instanceReset, difficulty, _, extended, _, isRaid, maxPlayers, difficultyName = GetSavedInstanceInfo(i)
|
|
|
|
if instanceReset > 0 then -- in 3.2, instances with reset = 0 are also listed (to support raid extensions)
|
|
extended = extended and 0 or 1
|
|
isRaid = isRaid and 0 or 1
|
|
|
|
if difficulty > 1 then
|
|
instanceName = format("%s %s", instanceName, difficultyName)
|
|
end
|
|
|
|
local key = instanceName.. "|" .. instanceID
|
|
c.SavedInstance[key] = format("%s|%s|%s|%s", instanceReset, time(), extended, isRaid )
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- *** Event Handlers ***
|
|
local hasScannedThisSession
|
|
local function OnPlayerAlive()
|
|
-- CoA: scan once at login (see DataStore_Inventory / commit fdcb25a). FRIENDLIST_UPDATE also
|
|
-- keeps the friends list fresh; this just guarantees an initial scan without rescanning on res.
|
|
if hasScannedThisSession then return end
|
|
hasScannedThisSession = true
|
|
ScanFriends()
|
|
end
|
|
|
|
local function OnPlayerLogout()
|
|
local t = {}
|
|
for i = 1, 10 do
|
|
t[i] = strchar(64 + random(26))
|
|
end
|
|
|
|
local y = (tonumber(date("%Y")) - 2000) + 64
|
|
local m = tonumber(date("%m")) + 64
|
|
local d = date("%d")
|
|
local h = tonumber(date("%H")) + 64
|
|
local M = date("%M")
|
|
local S = date("%S")
|
|
local x = t[1]..S..t[3]..t[4]..strchar(m)..t[7]..M..t[2]..t[6]..t[8]..d..t[9]..strchar(h)..t[5]..t[1]..strchar(y)..t[4]
|
|
|
|
addon.Options:Set("Lola", x)
|
|
end
|
|
|
|
local function OnRaidInstanceWelcome()
|
|
RequestRaidInfo()
|
|
end
|
|
|
|
local function OnChatMsgSystem(event, arg)
|
|
if arg then
|
|
if tostring(arg1) == INSTANCE_SAVED then
|
|
RequestRaidInfo()
|
|
end
|
|
end
|
|
end
|
|
|
|
local trackedItems = {
|
|
[39878] = 590400, -- Mysterious Egg, 6 days 20 hours
|
|
[44717] = 590400, -- Disgusting Jar, 6 days 20 hours
|
|
}
|
|
|
|
local lootMsg = gsub(LOOT_ITEM_SELF, "%%s", "(.+)")
|
|
|
|
local function OnChatMsgLoot(event, arg)
|
|
local _, _, link = strfind(arg, lootMsg)
|
|
if not link then return end
|
|
|
|
local id = addon:GetIDFromLink(link)
|
|
id = tonumber(id)
|
|
if not id then return end
|
|
|
|
for itemID, duration in pairs(trackedItems) do
|
|
if itemID == id then
|
|
local name = GetItemInfo(itemID)
|
|
if name then
|
|
local c = addon.ThisCharacter
|
|
table.insert(c.Timers, name .."|" .. time() .. "|" .. duration)
|
|
addon.Calendar.Events:BuildList()
|
|
addon.Tabs.Summary:Refresh()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function addon:OnEnable()
|
|
DS = DataStore
|
|
|
|
InitLocalization()
|
|
addon.Options:Init()
|
|
addon.Tasks:Init()
|
|
addon.Profiler:Init()
|
|
addon:InitTooltip()
|
|
|
|
addon:RegisterEvent("PLAYER_ALIVE", OnPlayerAlive)
|
|
addon:RegisterEvent("PLAYER_LOGOUT", OnPlayerLogout)
|
|
addon:RegisterEvent("UPDATE_INSTANCE_INFO", ScanSavedInstances)
|
|
addon:RegisterEvent("RAID_INSTANCE_WELCOME", OnRaidInstanceWelcome)
|
|
|
|
addon:RegisterEvent("AUCTION_HOUSE_SHOW", addon.AuctionHouse.OnShow)
|
|
addon:RegisterEvent("PLAYER_TALENT_UPDATE", addon.Talents.OnUpdate);
|
|
|
|
-- CoA: just "Altoholic <version>" in the title bar (Exiles branding + author credit live in the .toc).
|
|
-- Read the live .toc Version so it tracks each -coa.N release without editing this string.
|
|
-- CoA: use a Lua constant, not GetAddOnMetadata — TOC metadata is cached at game launch
|
|
-- and does NOT refresh on /reload, so the .toc version looked stale ("still .18"). A Lua
|
|
-- constant re-evaluates on every /reload, giving a truthful loaded-code version. Bump with the .toc.
|
|
AltoholicFrameName:SetText("Altoholic |cFFFFFFFF3.3.002b-coa.26|r")
|
|
|
|
local realm = GetRealmName()
|
|
local player = UnitName("player")
|
|
local key = format("%s.%s.%s", THIS_ACCOUNT, realm, player)
|
|
addon.ThisCharacter = addon.db.global.Characters[key]
|
|
|
|
addon:SetCurrentCharacter(player, realm, THIS_ACCOUNT)
|
|
|
|
addon.Tabs.Summary:Init()
|
|
addon.Containers:Init()
|
|
addon.Search:Init()
|
|
addon.Currencies:Init()
|
|
|
|
-- do not move this one into the frame's OnLoad
|
|
UIDropDownMenu_Initialize(AltoholicFrameEquipmentRightClickMenu, Equipment_RightClickMenu_OnLoad, "MENU");
|
|
|
|
_G["AltoholicFrameClassesItem10"]:SetPoint("BOTTOMRIGHT", "AltoholicFrameClasses", "BOTTOMRIGHT", -15, 0);
|
|
for j=9, 1, -1 do
|
|
_G["AltoholicFrameClassesItem" .. j]:SetPoint("BOTTOMRIGHT", "AltoholicFrameClassesItem" .. (j + 1), "BOTTOMLEFT", -5, 0);
|
|
end
|
|
|
|
addon.Options:RestoreToUI()
|
|
|
|
if Altoholic.Options:Get("ShowMinimap") == 1 then
|
|
addon:MoveMinimapIcon()
|
|
AltoholicMinimapButton:Show();
|
|
else
|
|
AltoholicMinimapButton:Hide();
|
|
end
|
|
|
|
addon:RegisterEvent("BAG_UPDATE", addon.Containers.OnBagUpdate)
|
|
addon:RegisterEvent("FRIENDLIST_UPDATE", ScanFriends);
|
|
addon:RegisterEvent("CHAT_MSG_SYSTEM", OnChatMsgSystem);
|
|
addon:RegisterEvent("CHAT_MSG_LOOT", OnChatMsgLoot)
|
|
addon:RegisterEvent("UNIT_PET", addon.Pets.OnChange);
|
|
|
|
if IsInGuild() then
|
|
addon:RegisterEvent("GUILD_ROSTER_UPDATE", addon.Guild.Members.OnRosterUpdate);
|
|
end
|
|
|
|
BuildUnsafeItemList()
|
|
|
|
-- create an empty frame to manage the timer via its Onupdate
|
|
addon.TimerFrame = CreateFrame("Frame", "AltoholicTimerFrame", UIParent)
|
|
local f = addon.TimerFrame
|
|
|
|
f:SetWidth(1)
|
|
f:SetHeight(1)
|
|
f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 1, 1)
|
|
f:SetScript("OnUpdate", function(addon, elapsed) Altoholic.Tasks:OnUpdate(elapsed) end)
|
|
f:Show()
|
|
end
|
|
|
|
function addon:OnDisable()
|
|
end
|
|
|
|
function addon:ToggleUI()
|
|
if (AltoholicFrame:IsVisible()) then
|
|
AltoholicFrame:Hide();
|
|
else
|
|
AltoholicFrame:Show();
|
|
end
|
|
end
|
|
|
|
function addon:OnShow()
|
|
SetPortraitTexture(AltoholicFramePortrait, "player");
|
|
|
|
-- CoA: apply the saved UI scale on open (upstream only applied it after the Options
|
|
-- tab was visited, so the window opened un-scaled). Default is 1.0 — scaling is opt-in
|
|
-- via Options; a true larger layout is tracked separately.
|
|
local O = addon.db.global.options
|
|
if O and O.UIScale then
|
|
AltoholicFrame:SetScale(O.UIScale)
|
|
end
|
|
|
|
addon.Characters:BuildList()
|
|
addon.Characters:BuildView()
|
|
|
|
if not addon.Tabs.current then
|
|
addon.Tabs.current = 1
|
|
addon.Tabs.Summary:MenuItem_OnClick(1)
|
|
elseif addon.Tabs.current == 1 then
|
|
addon.Tabs.Summary:Refresh()
|
|
end
|
|
end
|
|
|
|
|
|
-- *** Utility functions ***
|
|
function Altoholic:ScrollFrameUpdate(desc)
|
|
assert(type(desc) == "table") -- desc is the table that contains a standardized description of the scrollframe
|
|
|
|
local frame = desc.Frame
|
|
local entry = frame.."Entry"
|
|
|
|
-- hide all lines and set their id to 0, the update function is responsible for showing and setting id's of valid lines
|
|
for i = 1, desc.NumLines do
|
|
_G[ entry..i ]:SetID(0)
|
|
_G[ entry..i ]:Hide()
|
|
end
|
|
|
|
local offset = FauxScrollFrame_GetOffset( _G[ frame.."ScrollFrame" ] )
|
|
-- call the update handler
|
|
desc:Update(offset, entry, desc)
|
|
|
|
local last = (desc:GetSize() < desc.NumLines) and desc.NumLines or desc:GetSize()
|
|
FauxScrollFrame_Update( _G[ frame.."ScrollFrame" ], last, desc.NumLines, desc.LineHeight);
|
|
end
|
|
|
|
function Altoholic:ClearScrollFrame(name, entry, lines, height)
|
|
for i=1, lines do -- Hides all entries of the scrollframe, and updates it accordingly
|
|
_G[ entry..i ]:Hide()
|
|
end
|
|
FauxScrollFrame_Update( name, lines, lines, height);
|
|
end
|
|
|
|
function addon:Item_OnEnter(frame)
|
|
if not frame.id then return end
|
|
|
|
GameTooltip:SetOwner(frame, "ANCHOR_LEFT");
|
|
frame.link = frame.link or select(2, GetItemInfo(frame.id) )
|
|
|
|
if frame.link then
|
|
GameTooltip:SetHyperlink(frame.link);
|
|
else
|
|
-- GameTooltip:AddLine(L["Unknown link, please relog this character"],1,1,1);
|
|
GameTooltip:SetHyperlink("item:"..frame.id..":0:0:0:0:0:0:0") -- this line queries the server for an unknown id
|
|
GameTooltip:ClearLines(); -- don't leave residual info in the tooltip after the server query
|
|
end
|
|
GameTooltip:Show();
|
|
end
|
|
|
|
function addon:Item_OnClick(frame, button)
|
|
if not frame.id then return end
|
|
|
|
if not frame.link then
|
|
frame.link = select(2, GetItemInfo(frame.id) )
|
|
end
|
|
if not frame.link then return end -- still not valid ? exit
|
|
|
|
if ( button == "LeftButton" ) and ( IsControlKeyDown() ) then
|
|
DressUpItemLink(frame.link);
|
|
elseif ( button == "LeftButton" ) and ( IsShiftKeyDown() ) then
|
|
local chat = ChatEdit_GetLastActiveWindow()
|
|
|
|
if chat:IsShown() then
|
|
chat:Insert(frame.link);
|
|
else
|
|
AltoholicFrame_SearchEditBox:SetText(GetItemInfo(frame.link))
|
|
end
|
|
end
|
|
end
|
|
|
|
function addon:SetItemButtonTexture(button, texture, width, height)
|
|
-- wrapper for SetItemButtonTexture from ItemButtonTemplate.lua
|
|
width = width or 36
|
|
height = height or 36
|
|
|
|
local itemTexture = _G[button.."IconTexture"]
|
|
if not itemTexture then return end -- CoA: guard buttons that don't exist / lack an IconTexture region (e.g. iterating more professions than there are _ProfN buttons)
|
|
|
|
itemTexture:SetWidth(width);
|
|
itemTexture:SetHeight(height);
|
|
itemTexture:SetAllPoints(_G[button]);
|
|
|
|
SetItemButtonTexture(_G[button], texture)
|
|
end
|
|
|
|
function addon:TextureToFontstring(name, height, width)
|
|
return format("|T%s:%s:%s|t", name, height, width)
|
|
end
|
|
|
|
function addon:TextureToFontstring2(name, height, width, insetLeft, insetRight, insetTop, insetBottom)
|
|
local w = width + insetLeft + insetRight
|
|
local h = height + insetTop + insetBottom
|
|
local coordx2 = width + insetLeft
|
|
local coordy2 = height + insetTop
|
|
|
|
-- |TTexturePath:size1:size2:xoffset:yoffset:dimx:dimy:coordx1:coordx2:coordy1:coordy2|t
|
|
return format("|T%s:%s:%s:0:0:%s:%s:%s:%s:%s:%s|t", name, height, width, w, h, insetLeft, coordx2, insetTop, coordy2)
|
|
end
|
|
|
|
function addon:TextureToFontstringCut(name, heightOrig, widthOrig, insetLeft, insetRight, insetTop, insetBottom)
|
|
local h = heightOrig - insetTop - insetBottom
|
|
local w = widthOrig - insetLeft - insetRight
|
|
|
|
local coordy1 = insetTop
|
|
local coordy2 = h - insetBottom
|
|
|
|
local coordx1 = insetLeft
|
|
local coordx2 = w - insetRight
|
|
|
|
-- |TTexturePath:height:width:xoffset:yoffset:dimx:dimy:coordx1:coordx2:coordy1:coordy2|t
|
|
return format("|T%s:%s:%s:0:0:%s:%s:%s:%s:%s:%s|t", name, h, w, widthOrig, heightOrig, coordx1, coordx2, coordy1, coordy2)
|
|
end
|
|
|
|
function addon:GetSpellIcon(spellID)
|
|
if spellID == 13614 then -- Herbalism
|
|
spellID = 2383 -- Find Herbs
|
|
end
|
|
return select(3, GetSpellInfo(spellID))
|
|
end
|
|
|
|
function addon:GetIDFromLink(link)
|
|
if link then
|
|
return tonumber(link:match("item:(%d+)"))
|
|
end
|
|
end
|
|
|
|
function addon:GetSpellIDFromRecipeLink(link)
|
|
-- returns nil if recipe id is not in the DB, returns the spellID otherwise
|
|
local recipeID = addon:GetIDFromLink(link)
|
|
return addon.RecipeDB[recipeID]
|
|
end
|
|
|
|
function addon:GetMoneyString(copper, color, noTexture)
|
|
copper = copper or 0 -- CoA: callers may pass a no-value DS getter result
|
|
color = color or "|cFFFFD700"
|
|
|
|
local gold = floor( copper / 10000 );
|
|
copper = mod(copper, 10000)
|
|
local silver = floor( copper / 100 );
|
|
copper = mod(copper, 100)
|
|
|
|
if noTexture then -- use noTexture for places where the texture does not fit too well, ex: tooltips
|
|
copper = format("%s%s%s%s", color, copper, "|cFFEDA55F", COPPER_AMOUNT_SYMBOL)
|
|
silver = format("%s%s%s%s", color, silver, "|cFFC7C7CF", SILVER_AMOUNT_SYMBOL)
|
|
gold = format("%s%s%s%s", color, gold, "|cFFFFD700", GOLD_AMOUNT_SYMBOL)
|
|
else
|
|
copper = color..format(COPPER_AMOUNT_TEXTURE, copper, 13, 13)
|
|
silver = color..format(SILVER_AMOUNT_TEXTURE, silver, 13, 13)
|
|
gold = color..format(GOLD_AMOUNT_TEXTURE, gold, 13, 13)
|
|
end
|
|
return format("%s %s %s", gold, silver, copper)
|
|
end
|
|
|
|
function addon:GetTimeString(seconds)
|
|
seconds = seconds or 0 -- CoA: callers may pass a no-value DS getter result
|
|
local days = floor(seconds / 86400); -- TotalTime is expressed in seconds
|
|
seconds = mod(seconds, 86400)
|
|
local hours = floor(seconds / 3600);
|
|
seconds = mod(seconds, 3600)
|
|
local minutes = floor(seconds / 60);
|
|
seconds = mod(seconds, 60)
|
|
|
|
return format("%s|rd %s|rh %s|rm", WHITE..days, WHITE..hours, WHITE..minutes)
|
|
end
|
|
|
|
function addon:GetFactionColour(faction)
|
|
if faction == "Alliance" then
|
|
return "|cFF2459FF"
|
|
else
|
|
return RED
|
|
end
|
|
end
|
|
|
|
function Altoholic:GetClassColor(class)
|
|
return Altoholic.ClassInfo[class] or WHITE
|
|
end
|
|
|
|
function addon:GetDelayInDays(delay)
|
|
return floor((time() - delay) / 86400)
|
|
end
|
|
|
|
-- CoA: shared, nil-safe character display helpers.
|
|
-- DataStore char-based getters return *no value* for any module that hasn't
|
|
-- scanned a given character (DataStore.lua: "if not arg1.lastUpdate then return end").
|
|
-- Fresh alts have partial per-module data, so every field is guarded here once
|
|
-- instead of being copy-pasted (and missed) across the frames.
|
|
function Altoholic:AddCharacterTooltipHeader(character)
|
|
local locClass, engClass = DS:GetCharacterClass(character)
|
|
local className = Altoholic:GetCoAClassName(engClass) or locClass or "" -- CoA: current class name (PROPHET->Venomancer, …)
|
|
AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character) or "?", DS:GetColoredCharacterFaction(character) or "")
|
|
AltoTooltip:AddLine(format("%s %s |r%s %s", L["Level"],
|
|
GREEN..(DS:GetCharacterLevel(character) or 0), DS:GetCharacterRace(character) or "", className), 1, 1, 1)
|
|
end
|
|
|
|
function Altoholic:SetCharacterRowNameLevel(entry, i, icon, character)
|
|
local locClass, engClass = DS:GetCharacterClass(character)
|
|
local className = Altoholic:GetCoAClassName(engClass) or locClass or "" -- CoA: current class name
|
|
_G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character) or "?", className))
|
|
_G[entry..i.."Level"]:SetText(GREEN .. (DS:GetCharacterLevel(character) or 0))
|
|
end
|
|
|
|
function Altoholic:FormatDelay(timeStamp)
|
|
-- timeStamp = value when time() was last called for a given variable (ex: last time the mailbox was checked)
|
|
if not timeStamp then
|
|
return YELLOW .. NEVER
|
|
end
|
|
|
|
if timeStamp == 0 then
|
|
return YELLOW .. "N/A"
|
|
end
|
|
|
|
local seconds = (time() - timeStamp)
|
|
|
|
-- 86400 seconds per day
|
|
-- assuming 30 days / month = 2.592.000 seconds
|
|
-- assuming 365 days / year = 31.536.000 seconds
|
|
-- in the absence of possibility to track real dates, these approximations will have to do the trick, as it's not possible at this point to determine the number of days in a month, or in a year.
|
|
|
|
local year = floor(seconds / 31536000);
|
|
seconds = mod(seconds, 31536000)
|
|
|
|
local month = floor(seconds / 2592000);
|
|
seconds = mod(seconds, 2592000)
|
|
|
|
local day = floor(seconds / 86400);
|
|
seconds = mod(seconds, 86400)
|
|
|
|
local hour = floor(seconds / 3600);
|
|
seconds = mod(seconds, 3600)
|
|
|
|
-- note: RecentTimeDate is not a direct API function, it's in UIParent.lua
|
|
return RecentTimeDate(year, month, day, hour)
|
|
end
|
|
|
|
function addon:GetRestedXP(character)
|
|
local rate = DS:GetRestXPRate(character) or 0 -- CoA: getter returns no value for unscanned/partial chars
|
|
|
|
local coeff = 1
|
|
if addon.Options:Get("RestXPMode") == 1 then
|
|
coeff = 1.5
|
|
end
|
|
rate = rate * coeff
|
|
|
|
-- second return value = the actual percentage of rest xp, as a numeric value (1 to 100, not 150)
|
|
local color = GREEN
|
|
if rate >= (100 * coeff) then
|
|
rate = 100 * coeff
|
|
else
|
|
if rate < (30 * coeff) then
|
|
color = RED
|
|
elseif rate < (60 * coeff) then
|
|
color = YELLOW
|
|
end
|
|
end
|
|
return format("%s%d", color, rate).."%", rate
|
|
end
|
|
|
|
function addon:GetSuggestion(index, level)
|
|
if addon.Suggestions[index] then
|
|
for _, v in pairs( addon.Suggestions[index] ) do
|
|
if level < v[1] then -- the suggestions are sorted by level, so whenever we're below, return the text
|
|
return v[2]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Altoholic:UpdateSlider(name, text, field)
|
|
local s = _G[name]
|
|
_G[name .. "Text"]:SetText(text .. " (" .. s:GetValue() ..")");
|
|
|
|
if not Altoholic.db then return end
|
|
local a = Altoholic.db.global
|
|
if a == nil then return end
|
|
|
|
a.options[field] = s:GetValue()
|
|
self:MoveMinimapIcon()
|
|
end
|
|
|
|
function Altoholic:ShowWidgetTooltip(frame)
|
|
if not frame.tooltip then return end
|
|
|
|
AltoTooltip:SetOwner(frame, "ANCHOR_LEFT");
|
|
AltoTooltip:ClearLines();
|
|
AltoTooltip:AddLine(frame.tooltip)
|
|
AltoTooltip:Show();
|
|
end
|
|
|
|
function Altoholic:ShowClassIcons()
|
|
local entry = "AltoholicFrameClassesItem"
|
|
local i = 1
|
|
|
|
local realm, account = Altoholic:GetCurrentRealm()
|
|
|
|
|
|
-- Sort characters by level first, then average item level. The getters yield no value
|
|
-- for alts whose Characters/Inventory module hasn't scanned them, so default [3]/[4] to 0.
|
|
local CharNameList = DS:GetCharacters(realm, account)
|
|
local CharNameList_sort = {}
|
|
|
|
for k,v in pairs(CharNameList) do
|
|
table.insert(CharNameList_sort, {k, v, DS:GetAverageItemLevel(v) or 0, DS:GetCharacterLevel(v) or 0})
|
|
end
|
|
|
|
table.sort(CharNameList_sort, function(a,b) return b[3]+b[4]*10000 < a[3]+a[4]*10000 end)
|
|
|
|
|
|
-- for characterName, character in pairs(DS:GetCharacters(realm, account)) do
|
|
for _,charTbl in ipairs(CharNameList_sort) do
|
|
local characterName, character = charTbl[1], charTbl[2]
|
|
local itemName = entry .. i;
|
|
local itemButton = _G[itemName];
|
|
itemButton:SetScript("OnEnter", function(self)
|
|
Altoholic:DrawCharacterTooltip(self, self.CharName)
|
|
end)
|
|
itemButton:SetScript("OnLeave", function(self)
|
|
AltoTooltip:Hide()
|
|
end)
|
|
|
|
local _, class = DS:GetCharacterClass(character)
|
|
local itemTexture = _G[itemName .. "IconTexture"]
|
|
-- CoA: CLASS_ICON_TCOORDS only carries the vanilla 10 + DK on Voljin,
|
|
-- so the 21 CoA custom classes have no entry. GetCoAClassIcon (defined
|
|
-- in CoAClassColors.lua) returns the realm-authoritative atlas + coords
|
|
-- for any CoA-playable class (incl. vanilla 10 + DK); it returns nil for
|
|
-- an unknown/unscanned (no-value) token, in which case we keep the stock
|
|
-- CLASS_ICON_TCOORDS path, defaulting to WARRIOR rather than crashing.
|
|
local coaTex, l, r, t, b = Altoholic:GetCoAClassIcon(class)
|
|
if coaTex then
|
|
itemTexture:SetTexture(coaTex);
|
|
itemTexture:SetTexCoord(l, r, t, b);
|
|
else
|
|
local tc = CLASS_ICON_TCOORDS[class] or CLASS_ICON_TCOORDS["WARRIOR"]
|
|
itemTexture:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes");
|
|
itemTexture:SetTexCoord(tc[1], tc[2], tc[3], tc[4]);
|
|
end
|
|
itemTexture:SetWidth(36);
|
|
itemTexture:SetHeight(36);
|
|
itemTexture:SetAllPoints(itemButton);
|
|
|
|
Altoholic:CreateButtonBorder(itemButton)
|
|
|
|
if DS:GetCharacterFaction(character) == "Alliance" then
|
|
itemButton.border:SetVertexColor(0.1, 0.25, 1, 0.5)
|
|
else
|
|
itemButton.border:SetVertexColor(1, 0, 0, 0.5)
|
|
end
|
|
itemButton.border:Show()
|
|
|
|
itemButton.CharName = characterName
|
|
itemButton:Show()
|
|
|
|
i = i + 1
|
|
if i > 10 then -- users of Symbolic Links might have more than 10 columns, prevent it
|
|
break
|
|
end
|
|
end
|
|
|
|
while i <= 10 do
|
|
_G[ entry .. i ]:Hide()
|
|
_G[ entry .. i ].CharName = nil
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
function addon:CreateButtonBorder(frame)
|
|
if frame.border then return end
|
|
|
|
local border = frame:CreateTexture(nil, "OVERLAY")
|
|
border:SetWidth(67);
|
|
border:SetHeight(67)
|
|
border:SetPoint("CENTER", frame)
|
|
border:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
|
|
border:SetBlendMode("ADD")
|
|
border:Hide()
|
|
|
|
frame.border = border
|
|
end
|
|
|
|
function Altoholic:DrawCharacterTooltip(self, charName)
|
|
local realm, account = Altoholic:GetCurrentRealm()
|
|
local character = DS:GetCharacter(charName, realm, account)
|
|
|
|
AltoTooltip:SetOwner(self, "ANCHOR_LEFT");
|
|
AltoTooltip:ClearLines();
|
|
Altoholic:AddCharacterTooltipHeader(character)
|
|
|
|
local zone, subZone = DS:GetLocation(character)
|
|
AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..(zone or "?"), GOLD..(subZone or "")),1,1,1)
|
|
|
|
local restXP = DS:GetRestXP(character)
|
|
if restXP and restXP > 0 then
|
|
AltoTooltip:AddLine(format("%s: %s", L["Rest XP"], GREEN..restXP),1,1,1)
|
|
end
|
|
|
|
AltoTooltip:AddLine("Average iLevel: " .. GREEN .. format("%.1f", DS:GetAverageItemLevel(character) or 0),1,1,1);
|
|
|
|
if IsAddOnLoaded("DataStore_Achievements") then
|
|
if (DS:GetNumCompletedAchievements(character) or 0) > 0 then
|
|
AltoTooltip:AddLine(ACHIEVEMENTS_COMPLETED ..": " .. GREEN .. DS:GetNumCompletedAchievements(character) .. "/"..(DS:GetNumAchievements(character) or 0))
|
|
AltoTooltip:AddLine(ACHIEVEMENT_TITLE ..": " .. GREEN .. (DS:GetNumAchievementPoints(character) or 0))
|
|
end
|
|
end
|
|
|
|
AltoTooltip:Show();
|
|
end
|
|
|
|
function addon:SetMsgBoxHandler(func, arg1, arg2)
|
|
local msg = AltoMsgBox
|
|
|
|
msg.ButtonHandler = func
|
|
msg.arg1 = arg1
|
|
msg.arg2 = arg2
|
|
end
|
|
|
|
function addon:MsgBox_OnClick(button)
|
|
-- until I have time to check all the places where msgbox is used, keep "button" as 1 for yes, and nil for no
|
|
-- also, change the handler to work with ...
|
|
local msg = AltoMsgBox
|
|
|
|
if msg.ButtonHandler then
|
|
msg:ButtonHandler(button, msg.arg1, msg.arg2)
|
|
msg.ButtonHandler = nil -- prevent subsequent calls from coming back here
|
|
msg.arg1 = nil
|
|
msg.arg2 = nil
|
|
else
|
|
addon:Print("MessageBox Handler not defined")
|
|
end
|
|
msg:Hide();
|
|
msg:SetHeight(100)
|
|
AltoMsgBox_Text:SetHeight(28)
|
|
end
|
|
|
|
-- ** Unsafe Items **
|
|
function addon:SaveUnsafeItem(itemID)
|
|
if not addon:IsItemUnsafe(itemID) then -- if the item is not a known unsafe item, save it in the db
|
|
table.insert(Altoholic.db.global.unsafeItems, itemID)
|
|
end
|
|
end
|
|
|
|
function addon:IsItemUnsafe(itemID)
|
|
for _, v in pairs(Altoholic.db.global.unsafeItems) do -- browse current realm's unsafe item list
|
|
if v == itemID then -- if the itemID passed as parameter is a known unsafe item .. return true to skip it
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- *** Hooks ***
|
|
local Orig_ChatEdit_InsertLink = ChatEdit_InsertLink
|
|
|
|
function ChatEdit_InsertLink(text, ...)
|
|
if text and AltoholicFrame_SearchEditBox:IsVisible() then
|
|
if not DataStore_Crafts:IsTradeSkillWindowOpen() then
|
|
AltoholicFrame_SearchEditBox:Insert(GetItemInfo(text))
|
|
return true
|
|
end
|
|
end
|
|
return Orig_ChatEdit_InsertLink(text, ...)
|
|
end
|