From 97e38d5c3df9dfd299e5970dde1f138355c5734a Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Fri, 29 May 2026 01:02:24 +0200 Subject: [PATCH] coa.5: refactor char display into guarded helpers; fix missed sites; restore login scan - Extract AddCharacterTooltipHeader() + SetCharacterRowNameLevel() (Altoholic.lua); nil-guards centralized, callers in AccountSummary/Activity/BagUsage/Skills/tooltip. - Fix sites the manual sweep missed: Skills.lua (row + skill ranks), Keys.lua x3, ShowClassIcons sort (Altoholic.lua:705, getters bypass their own or-0 via the wrapper). - Restore login scan: OnPlayerAlive was ghost-only (fdcb25a) so iLvl never populated; now scans once per session. Removed dated DEBUG leftovers. --- Altoholic/Altoholic.lua | 46 +++++++++++++-------- Altoholic/Altoholic.toc | 2 +- Altoholic/Frames/AccountSummary.lua | 9 +--- Altoholic/Frames/Activity.lua | 7 +--- Altoholic/Frames/BagUsage.lua | 7 +--- Altoholic/Frames/Keys.lua | 6 +-- Altoholic/Frames/Skills.lua | 5 ++- DataStore_Inventory/DataStore_Inventory.lua | 9 ++-- DataStore_Inventory/DataStore_Inventory.toc | 2 +- README.md | 4 ++ 10 files changed, 53 insertions(+), 44 deletions(-) diff --git a/Altoholic/Altoholic.lua b/Altoholic/Altoholic.lua index 14faa05..6538e49 100644 --- a/Altoholic/Altoholic.lua +++ b/Altoholic/Altoholic.lua @@ -249,10 +249,12 @@ end -- *** Event Handlers *** +local hasScannedThisSession local function OnPlayerAlive() - -- print("Altoholic.lua") -- DEBUG 2025 07 21 - if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard - + -- 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 @@ -585,6 +587,22 @@ 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 @@ -679,21 +697,18 @@ function Altoholic:ShowClassIcons() 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), DS:GetCharacterLevel(v)}) + table.insert(CharNameList_sort, {k, v, DS:GetAverageItemLevel(v) or 0, DS:GetCharacterLevel(v) or 0}) end - - -- sort for level first, avg iLevel secondly + table.sort(CharNameList_sort, function(a,b) return b[3]+b[4]*10000 < a[3]+a[4]*10000 end) - -- DEBUG_CHARLIST = CharNameList - -- print("-- altoholic DEBUG READY") - -- #################### - - + + -- for characterName, character in pairs(DS:GetCharacters(realm, account)) do for _,charTbl in ipairs(CharNameList_sort) do local characterName, character = charTbl[1], charTbl[2] @@ -763,10 +778,7 @@ function Altoholic:DrawCharacterTooltip(self, charName) AltoTooltip:SetOwner(self, "ANCHOR_LEFT"); AltoTooltip:ClearLines(); - AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) - - 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) + 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) diff --git a/Altoholic/Altoholic.toc b/Altoholic/Altoholic.toc index 70a230f..a31748b 100644 --- a/Altoholic/Altoholic.toc +++ b/Altoholic/Altoholic.toc @@ -13,7 +13,7 @@ ## Author: Thaoky, Telkar-RG ## X-Edited-By: Exiles (Sub-Net) — florian.berthold@sub-net.at -## Version: 3.3.002b-coa.4 +## Version: 3.3.002b-coa.5 ## X-Category: Inventory, Tradeskill, Mail ## X-Localizations: enUS, frFR, zhCN, zhTW, deDE, koKR, esES, esMX, ruRU ## X-Website: http://wow.curse.com/downloads/wow-addons/details/altoholic.aspx diff --git a/Altoholic/Frames/AccountSummary.lua b/Altoholic/Frames/AccountSummary.lua index 41b378c..96dde04 100644 --- a/Altoholic/Frames/AccountSummary.lua +++ b/Altoholic/Frames/AccountSummary.lua @@ -249,10 +249,7 @@ function ns:Update() _G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."NameNormalText"]:SetWidth(170) - -- CoA: DataStore char-based getters return *no value* for any module that hasn't scanned this char - -- (DataStore.lua: "if not arg1.lastUpdate then return end"). Fresh alts have partial module data, so guard every result. - _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)) + addon:SetCharacterRowNameLevel(entry, i, icon, character) _G[entry..i.."Money"]:SetText(addon:GetMoneyString(DS:GetMoney(character) or 0)) _G[entry..i.."Played"]:SetText(addon:GetTimeString(DS:GetPlayTime(character) or 0)) @@ -344,9 +341,7 @@ function ns:Level_OnEnter(frame) AltoTooltip:ClearLines(); AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT"); - AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) - 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) + addon: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) diff --git a/Altoholic/Frames/Activity.lua b/Altoholic/Frames/Activity.lua index 4cc634d..9ff1899 100644 --- a/Altoholic/Frames/Activity.lua +++ b/Altoholic/Frames/Activity.lua @@ -100,8 +100,7 @@ function ns:Update() _G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."NameNormalText"]:SetWidth(170) - _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)) + addon:SetCharacterRowNameLevel(entry, i, icon, character) local color local num = DS:GetNumMails(character) or 0 @@ -181,9 +180,7 @@ function ns:OnEnter(self) AltoTooltip:ClearLines(); AltoTooltip:SetOwner(self, "ANCHOR_RIGHT"); - AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) - 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) + addon: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) diff --git a/Altoholic/Frames/BagUsage.lua b/Altoholic/Frames/BagUsage.lua index 67ef1ed..6b038fb 100644 --- a/Altoholic/Frames/BagUsage.lua +++ b/Altoholic/Frames/BagUsage.lua @@ -96,8 +96,7 @@ function ns:Update() _G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."NameNormalText"]:SetWidth(170) - _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)) + addon:SetCharacterRowNameLevel(entry, i, icon, character) _G[entry..i.."FreeBags"]:SetText(GREEN .. (DS:GetNumFreeBagSlots(character) or 0)) _G[entry..i.."FreeBank"]:SetText(GREEN .. (DS:GetNumFreeBankSlots(character) or 0)) @@ -182,9 +181,7 @@ function ns:OnEnter(self) AltoTooltip:ClearLines(); AltoTooltip:SetOwner(self, "ANCHOR_RIGHT"); - AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) - 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) + addon:AddCharacterTooltipHeader(character) AltoTooltip:AddLine(" ",1,1,1); local id = self:GetID() diff --git a/Altoholic/Frames/Keys.lua b/Altoholic/Frames/Keys.lua index f2609af..ca50d17 100644 --- a/Altoholic/Frames/Keys.lua +++ b/Altoholic/Frames/Keys.lua @@ -789,7 +789,7 @@ function ns:Item_OnEnter(frame) AltoTooltip:SetOwner(frame, "ANCHOR_LEFT") AltoTooltip:ClearLines() - AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character), nameKey) ) + AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character) or "?", nameKey) ) AltoTooltip:AddLine(" ") local questDone, cr,cg,cb @@ -848,7 +848,7 @@ function ns:Item_OnEnter(frame) else AltoTooltip:SetOwner(frame, "ANCHOR_LEFT") AltoTooltip:ClearLines() - AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character), nameKey) ) + AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character) or "?", nameKey) ) AltoTooltip:AddLine(" ") local questDone, cr,cg,cb @@ -912,7 +912,7 @@ function ns:Item_OnEnter(frame) else AltoTooltip:SetOwner(frame, "ANCHOR_LEFT") AltoTooltip:ClearLines() - AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character), nameKey) ) + AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character) or "?", nameKey) ) AltoTooltip:AddLine(" ") AltoTooltip:AddLine(L["Required reputation"] .. ":",1,1,1) diff --git a/Altoholic/Frames/Skills.lua b/Altoholic/Frames/Skills.lua index 776863e..9dc7197 100644 --- a/Altoholic/Frames/Skills.lua +++ b/Altoholic/Frames/Skills.lua @@ -109,8 +109,7 @@ function ns:Update() _G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."NameNormalText"]:SetWidth(170) - _G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character), DS:GetCharacterClass(character))) - _G[entry..i.."Level"]:SetText(GREEN .. DS:GetCharacterLevel(character)) + addon:SetCharacterRowNameLevel(entry, i, icon, character) -- profession 1 local field = Characters:GetField(line, "spellID1") @@ -251,6 +250,7 @@ function ns:OnEnter(frame) local DS = DataStore local character = DS:GetCharacter(Characters:GetInfo(line)) local curRank, maxRank = DS:GetSkillInfo(character, skillName) + curRank, maxRank = curRank or 0, maxRank or 0 -- CoA: getter returns no value for skills DataStore_Skills hasn't scanned local profession = DS:GetProfession(character, skillName) if (id >= 1) and (id <= 6) then @@ -268,6 +268,7 @@ function ns:OnEnter(frame) skillName = L["Rogue Proficiencies"] local curLock, maxLock = DS:GetSkillInfo(character, L["Lockpicking"]) + curLock, maxLock = curLock or 0, maxLock or 0 -- CoA: guard unscanned lockpicking rank = TEAL .. L["Lockpicking"] .. " " .. curLock .. "/" .. maxLock suggestion = addon:GetSuggestion(L["Lockpicking"], curLock) end diff --git a/DataStore_Inventory/DataStore_Inventory.lua b/DataStore_Inventory/DataStore_Inventory.lua index 6275a8e..16ed098 100644 --- a/DataStore_Inventory/DataStore_Inventory.lua +++ b/DataStore_Inventory/DataStore_Inventory.lua @@ -167,10 +167,13 @@ function ScanInventory() end -- *** Event Handlers *** +local hasScannedThisSession local function OnPlayerAlive() - -- print("DataStore_Inventory.lua") -- DEBUG 2025 07 21 - if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard - + -- CoA: scan once at login. PLAYER_ALIVE also fires on resurrect / Feign-Death cancel where gear + -- is unchanged, so skip those. (The previous "only when ghost" gate also skipped login, so iLvl + -- never populated and UNIT_INVENTORY_CHANGED was the only scan path - see commit fdcb25a.) + if hasScannedThisSession then return end + hasScannedThisSession = true ScanInventory() end diff --git a/DataStore_Inventory/DataStore_Inventory.toc b/DataStore_Inventory/DataStore_Inventory.toc index 46fa63d..a52a982 100644 --- a/DataStore_Inventory/DataStore_Inventory.toc +++ b/DataStore_Inventory/DataStore_Inventory.toc @@ -3,7 +3,7 @@ ## Notes: Stores information about character inventory ## Author: Thaoky (EU-Marécages de Zangar) ## X-Edited-By: Exiles (Sub-Net) -## Version: 3.3.002-coa.2 +## Version: 3.3.002-coa.5 ## Dependencies: DataStore ## OptionalDeps: Ace3 ## SavedVariables: DataStore_InventoryDB diff --git a/README.md b/README.md index a79471b..1f6e95d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ Altoholic: modified development for WotLK Ported for the Ascension CoA (Vol'jin) 3.3.5a client by the Exiles guild. Released as `*-coa.N` tags via Gitea Actions; see `Exiles/coa-altoholic`. +- **3.3.002b-coa.5** — Refactor + completeness pass: + - Extracted the duplicated character header/row blocks into `Altoholic:AddCharacterTooltipHeader()` and `Altoholic:SetCharacterRowNameLevel()` — the nil-guards now live in one place instead of being copy-pasted across frames. + - Fixed crash sites the per-frame sweep had missed: `Skills.lua` (row + skill-rank tooltip), `Keys.lua` (×3 `format` with possibly-nil name), and the latent `ShowClassIcons` sort (`Altoholic.lua` — getters bypass their own `or 0` via the DataStore wrapper). + - Restored login scanning: `OnPlayerAlive` in `Altoholic.lua` + `DataStore_Inventory` was gated to ghost-only (commit fdcb25a), so inventory/iLvl never populated on login. Now scans once per session (still skips resurrect/Feign-Death rescans). Removed dated DEBUG leftovers. - **3.3.002b-coa.4** — Rebranded to the Exiles fork (title `Altoholic (Exiles)`; Thaoky/Telkar-RG still credited as Author). Hardened **all** Altoholic frames against partial alt records: DataStore char-based getters return *no value* for any module that hasn't scanned a char, and the frames assumed full data everywhere. Guarded every `format`/concat/arithmetic/`pairs` site across AccountSummary, Activity, BagUsage, Quests, Reputations, TabCharacters, `DrawCharacterTooltip`, and the recipe tooltip. No DataStore contract change. - **3.3.002b-coa.3** — More partial-record guards in `DataStore_Characters` (own alts seen via guild comm but never fully scanned): - `GetXPRate` — guard nil/zero `XPMax` (crashed AccountSummary; also fixes div-by-zero at max level).