Files
coa-altoholic/Altoholic/Altoholic.lua
T
florian.berthold 863709e450
release / release (push) Successful in 5s
coa.26: fix Skills frame overlapping the menu (745->615 width)
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.
2026-05-29 23:37:56 +02:00

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