--[[ *** 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 " 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. local titleVersion = GetAddOnMetadata(addonName, "Version") or addon.Version AltoholicFrameName:SetText("Altoholic |cFFFFFFFF".. titleVersion .."|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"] 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) 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) 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) 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 "", DS:GetCharacterClass(character) or ""), 1, 1, 1) end function Altoholic:SetCharacterRowNameLevel(entry, i, icon, character) _G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character) or "?", DS:GetCharacterClass(character) or "")) _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) 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) -- CoA: CLASS_ICON_TCOORDS only carries the vanilla 10 + DK on Voljin. -- For the 21 CoA custom classes the lookup is nil; fall back to -- WARRIOR's coords so we render *something* rather than crashing. local tc = CLASS_ICON_TCOORDS[class] or CLASS_ICON_TCOORDS["WARRIOR"] local itemTexture = _G[itemName .. "IconTexture"] itemTexture:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes"); itemTexture:SetTexCoord(tc[1], tc[2], tc[3], tc[4]); 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