Compare commits

...

10 Commits

Author SHA1 Message Date
florian.berthold 78e50e9f5c coa.25: clean two-column Skills layout
release / release (push) Successful in 5s
Skills vertical list now uses a proper two-column row: indented [icon] profession name
in the Name cell, rank/max color-coded in its own Level column to the right (was all
crammed into one text cell). Character headers span the row with name + (class).
2026-05-29 22:58:45 +02:00
florian.berthold 14dd30e9a9 coa.24: revert char-view profession grid to clean 2-slot original
release / release (push) Successful in 4s
The relocated Prof1-8 block (coa.20) was jumbled below the dropdowns in the narrow
char-view panel. Restored Prof1/Prof2 in the original bottom row (Cooking/First Aid/Prof1/Prof2);
removed Prof3-8. Full profession list stays on the Skills tab. No crash (loop stops at Prof2).
2026-05-29 22:50:04 +02:00
florian.berthold 996a11dd0a coa.23: correct CoA class names (PROPHET->Venomancer etc.) + fix Skills rank/max
release / release (push) Successful in 5s
- CoA renamed classes but UnitClass returns old tokens; added a token->name map
  (CoAClassColors.lua, from coa-omen) applied in the Skills header + the shared
  AddCharacterTooltipHeader/SetCharacterRowNameLevel helpers (fixes class names everywhere).
- Skills vertical list now shows rank/max (precompute carries maxRank; was showing /0).
2026-05-29 22:27:53 +02:00
florian.berthold d1616b4354 coa.22: Skills view as a vertical list (character header + one row per profession)
release / release (push) Successful in 6s
Rewrote the Skills tab from the cramped multi-column per-character grid to a vertical
list: each character is a header row, followed by one row per known primary profession
(incl Woodcutting/Woodworking) + secondary skills (Cooking/First Aid/Fishing) + Riding,
icon + name + rank/max, top to bottom. Simplified the column headers to match.
2026-05-29 22:04:19 +02:00
florian.berthold 3663d44cd4 coa.21: title from Lua constant (updates on /reload, unlike cached TOC metadata)
release / release (push) Successful in 5s
GetAddOnMetadata Version is cached at game launch and not refreshed by /reload, so the
title showed a stale version after reloads ('still .18'). Use a Lua constant that
re-evaluates each /reload as a truthful loaded-code indicator.
2026-05-29 20:42:03 +02:00
florian.berthold d915f6b844 coa.20: restore bigger window + relocate char-view professions
release / release (push) Successful in 5s
- Re-applied the window enlargement (832x555, more rows) - user accepts the AuctionFrame
  art seam in exchange for the bigger frame.
- Char-view professions (Prof1-8) moved out of the cramped bottom Cooking/FirstAid row
  into the open middle-left gap (2 rows of 4) so they stop overlapping/overflowing.
2026-05-29 20:30:29 +02:00
florian.berthold d440c62a73 coa.19: revert the window enlargement (restore original 832x447 frame)
release / release (push) Successful in 5s
The coa.12 enlargement couldn't extend WoW's fixed AuctionFrame art cleanly -> fragmented
background + broken scrollbar. Reverted to the original clean frame (14 rows, intact art,
working scrollbar). All crash fixes, Skills professions cap, login-scan, class icons, and
the char-view profession wrap are preserved (they live in different lines/files).
2026-05-29 20:23:40 +02:00
florian.berthold 219e749046 coa.18: guard DataStore_Achievements nil criteria quantity
release / release (push) Successful in 5s
GetAchievementCriteriaInfo returns nil reqQuantity/quantity for some CoA achievements;
'reqQuantity > 1' crashed. Guarded both.
2026-05-29 20:05:03 +02:00
florian.berthold 0a56cbe560 coa.17: comprehensive partial-data hardening + DataStore_Characters login scan + Skills strip cap
release / release (push) Successful in 5s
- Hardening sweep across DataStore_* (softened crash-asserts in Talents/Containers/Quests
  to graceful nil) + Altoholic frames (guarded remaining getter results).
- DataStore_Characters: scan on login (was ghost-gated -> name/level/class never populated;
  the core 'no character data' cause).
- Skills tab: cap inline professions at 6 (+N) so the strip stops overflowing into Cooking.
2026-05-29 19:53:03 +02:00
florian.berthold b8d619c3bb coa.16: Talents view no longer crashes on CoA custom classes
release / release (push) Successful in 5s
DataStore_Talents._GetTreeReference degrades to nil (was assert) when a custom class
(MONK, etc.) has no/partial talent reference data; Talents.lua skips background render
when GetTreeInfo returns no bg.
2026-05-29 19:36:11 +02:00
19 changed files with 208 additions and 497 deletions
+10 -4
View File
@@ -335,8 +335,10 @@ function addon:OnEnable()
-- 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.
local titleVersion = GetAddOnMetadata(addonName, "Version") or addon.Version
AltoholicFrameName:SetText("Altoholic |cFFFFFFFF".. titleVersion .."|r")
-- 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.25|r")
local realm = GetRealmName()
local player = UnitName("player")
@@ -606,13 +608,17 @@ 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 "", DS:GetCharacterClass(character) or ""), 1, 1, 1)
GREEN..(DS:GetCharacterLevel(character) or 0), DS:GetCharacterRace(character) or "", className), 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 ""))
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
+1 -1
View File
@@ -13,7 +13,7 @@
## Author: Thaoky, Telkar-RG
## X-Edited-By: Exiles (Sub-Net) — florian.berthold@sub-net.at
## Version: 3.3.002b-coa.15
## Version: 3.3.002b-coa.25
## 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
+1
View File
@@ -98,6 +98,7 @@ local function AddRealm(AccountName, RealmName)
professions[#professions + 1] = {
name = p.name,
rank = p.rank or 0,
maxRank = p.maxRank or 0, -- CoA: needed for the "rank/max" display in the vertical Skills list
spellID = DataStore:GetProfessionSpellID(p.name),
}
end
+20
View File
@@ -115,3 +115,23 @@ function Alto:GetCoAClassIcon(token)
if not tc then return end
return COA_CLASS_ICON_TEXTURE, tc[1], tc[2], tc[3], tc[4]
end
-- CoA renamed its classes, but UnitClass()/DataStore still return the OLD tokens
-- (PROPHET, MONK, …). Map them to the current display names. Source: coa-omen/README-CoA.md.
-- Tokens not listed here keep their normal localized name (returns nil).
local COA_CLASS_NAMES = {
HERO = "Hero", BARBARIAN = "Barbarian", WITCHDOCTOR = "Witch Doctor",
DEMONHUNTER = "Felsworn", WITCHHUNTER = "Witch Hunter", STORMBRINGER = "Stormbringer",
FLESHWARDEN = "Knight of Xoroth", GUARDIAN = "Guardian", MONK = "Templar",
SONOFARUGAL = "Bloodmage", RANGER = "Ranger", CHRONOMANCER = "Chronomancer",
NECROMANCER = "Necromancer", PYROMANCER = "Pyromancer", CULTIST = "Cultist",
STARCALLER = "Starcaller", SUNCLERIC = "Sun Cleric", TINKER = "Tinker",
PROPHET = "Venomancer", REAPER = "Reaper", WILDWALKER = "Primalist",
SPIRITMAGE = "Runemaster",
}
-- Current CoA display name for a class token, or nil if unmapped (caller falls back).
function Alto:GetCoAClassName(token)
if type(token) ~= "string" then return end
return COA_CLASS_NAMES[token]
end
+9 -9
View File
@@ -118,15 +118,15 @@ function ns:Update()
_G[entry..i.."BankSlotsNormalText"]:SetText(L["Bank not visited yet"])
else
_G[entry..i.."BankSlotsNormalText"]:SetText(format("%s/%s|r/%s|r/%s|r/%s|r/%s|r/%s|r/%s |r(%s|r)",
DS:GetContainerSize(character, 100),
WHITE .. DS:GetContainerSize(character, 5),
WHITE .. DS:GetContainerSize(character, 6),
WHITE .. DS:GetContainerSize(character, 7),
WHITE .. DS:GetContainerSize(character, 8),
WHITE .. DS:GetContainerSize(character, 9),
WHITE .. DS:GetContainerSize(character, 10),
WHITE .. DS:GetContainerSize(character, 11),
CYAN .. DS:GetNumBankSlots(character)))
DS:GetContainerSize(character, 100) or 0, -- CoA: empty/unscanned bank bags return nil size
WHITE .. (DS:GetContainerSize(character, 5) or 0),
WHITE .. (DS:GetContainerSize(character, 6) or 0),
WHITE .. (DS:GetContainerSize(character, 7) or 0),
WHITE .. (DS:GetContainerSize(character, 8) or 0),
WHITE .. (DS:GetContainerSize(character, 9) or 0),
WHITE .. (DS:GetContainerSize(character, 10) or 0),
WHITE .. (DS:GetContainerSize(character, 11) or 0),
CYAN .. (DS:GetNumBankSlots(character) or 0)))
end
elseif (lineType == INFO_TOTAL_LINE) then
_G[entry..i.."Collapse"]:Hide()
+3 -3
View File
@@ -177,8 +177,8 @@ local function UpdateSpread()
local slotID = bagIndices[line].from - 3 + j
local itemID, itemLink, itemCount = DS:GetSlotInfo(container, slotID)
if (slotID <= containerSize) then
if (slotID <= (containerSize or 0)) then -- CoA: containerSize nil for unscanned bag on partial-data alt
if itemID then
Altoholic:SetItemButtonTexture(itemName, GetItemIcon(itemID));
@@ -278,7 +278,7 @@ local function UpdateAllInOne()
local container = DS:GetContainer(character, containerID)
local _, _, containerSize = DS:GetContainerInfo(character, containerID)
for slotID = 1, containerSize do
for slotID = 1, (containerSize or 0) do -- CoA: containerSize nil for unscanned bag on partial-data alt
local itemID, itemLink, itemCount = DS:GetSlotInfo(container, slotID)
if itemID then
currentSlotIndex = currentSlotIndex + 1
+3 -3
View File
@@ -81,9 +81,9 @@ local SecondaryLevelSort = {-- sort functions for the alts
end
end,
["level"] = function(a, b)
local levelA = select(4, DataStore:GetGuildMemberInfo(a))
local levelB = select(4, DataStore:GetGuildMemberInfo(b))
local levelA = select(4, DataStore:GetGuildMemberInfo(a)) or 0 -- CoA: nil level on partial guild data crashed table.sort
local levelB = select(4, DataStore:GetGuildMemberInfo(b)) or 0
if viewSortOrder then
return levelA < levelB
else
+9 -7
View File
@@ -25,9 +25,9 @@ local PrimaryLevelSort = { -- sort functions for the mains
end
end,
["level"] = function(a, b)
local levelA = select(4, DataStore:GetGuildMemberInfo(a.name))
local levelB = select(4, DataStore:GetGuildMemberInfo(b.name))
local levelA = select(4, DataStore:GetGuildMemberInfo(a.name)) or 0 -- CoA: nil level on partial guild data crashed table.sort
local levelB = select(4, DataStore:GetGuildMemberInfo(b.name)) or 0
if viewSortOrder then
return levelA < levelB
else
@@ -76,9 +76,9 @@ local SecondaryLevelSort = {-- sort functions for the alts
end
end,
["level"] = function(a, b)
local levelA = select(4, DataStore:GetGuildMemberInfo(a))
local levelB = select(4, DataStore:GetGuildMemberInfo(b))
local levelA = select(4, DataStore:GetGuildMemberInfo(a)) or 0 -- CoA: nil level on partial guild data crashed table.sort
local levelB = select(4, DataStore:GetGuildMemberInfo(b)) or 0
if viewSortOrder then
return levelA < levelB
else
@@ -199,6 +199,7 @@ local function DisplayProfessionLink(frameName, member, index)
local icon = addon:TextureToFontstring(addon:GetSpellIcon(tonumber(spellID)), 18, 18) .. " "
if link then
local curRank, maxRank = DataStore:GetProfessionInfo(link)
curRank, maxRank = curRank or 0, maxRank or 0 -- CoA: GetProfessionInfo returns nil if the link doesn't match the trade pattern
local ts = addon.TradeSkills
text:SetText(icon .. ts:GetColor(curRank) .. curRank .. "/" .. maxRank)
else
@@ -350,7 +351,8 @@ function ns:OnEnter(self)
if not spellID or not link then return end
local curRank, maxRank = DataStore:GetProfessionInfo(link)
curRank, maxRank = curRank or 0, maxRank or 0 -- CoA: nil ranks when link doesn't match trade pattern; guard concat below
AltoTooltip:ClearLines();
AltoTooltip:SetOwner(self, "ANCHOR_RIGHT");
+97 -381
View File
@@ -28,407 +28,122 @@ local Characters = addon.Characters
local size = 22
local inset = 2
function ns:Update()
local VisibleLines = 20
local frame = "AltoholicFrameSkills"
local entry = frame.."Entry"
local DS = DataStore
local offset = FauxScrollFrame_GetOffset( _G[ frame.."ScrollFrame" ] );
local DisplayedCount = 0
local VisibleCount = 0
local DrawRealm
local i=1
for _, line in pairs(Characters:GetView()) do
local lineType = Characters:GetLineType(line)
if (offset > 0) or (DisplayedCount >= VisibleLines) then -- if the line will not be visible
if lineType == INFO_REALM_LINE then -- then keep track of counters
if Characters:GetField(line, "isCollapsed") == false then
DrawRealm = true
else
DrawRealm = false
-- CoA: vertical list. For each visible character a header row, then one row per known
-- profession / secondary skill (icon + name + rank/max), top to bottom (like the other detail views).
local SECONDARY = { 2550, 3273, 7733 } -- Cooking, First Aid, Fishing (spell id -> icon + GetSpellInfo name)
local items = {}
for _, viewLine in pairs(Characters:GetView()) do
if Characters:GetLineType(viewLine) == INFO_CHARACTER_LINE then
local character = DS:GetCharacter( Characters:GetInfo(viewLine) )
items[#items + 1] = { kind = "header", viewLine = viewLine, character = character }
local profs = Characters:GetField(viewLine, "professions")
if profs then
for _, p in ipairs(profs) do
items[#items + 1] = { kind = "skill", viewLine = viewLine, character = character,
spellID = p.spellID, name = p.name, rank = p.rank or 0, maxRank = p.maxRank or 0 }
end
VisibleCount = VisibleCount + 1
offset = offset - 1 -- no further control, nevermind if it goes negative
elseif DrawRealm then
VisibleCount = VisibleCount + 1
offset = offset - 1 -- no further control, nevermind if it goes negative
end
else -- line will be displayed
if lineType == INFO_REALM_LINE then
local _, realm, account = Characters:GetInfo(line)
if Characters:GetField(line, "isCollapsed") == false then
_G[ entry..i.."Collapse" ]:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-Up");
DrawRealm = true
else
_G[ entry..i.."Collapse" ]:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-Up");
DrawRealm = false
for _, sid in ipairs(SECONDARY) do
local sName = GetSpellInfo(sid)
if sName then
local cur, max = DS:GetSkillInfo(character, sName)
if cur and cur > 0 then
items[#items + 1] = { kind = "skill", viewLine = viewLine, character = character,
spellID = sid, name = sName, rank = cur, maxRank = max or 0 }
end
end
_G[entry..i.."Collapse"]:Show()
_G[entry..i.."Name"]:SetWidth(300)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 25, 0)
_G[entry..i.."NameNormalText"]:SetWidth(300)
if account == "Default" then -- saved as default, display as localized.
_G[entry..i.."NameNormalText"]:SetText(format("%s (%s".. L["Account"]..": %s%s|r)", realm, WHITE, GREEN, L["Default"]))
else
local last = addon:GetLastAccountSharingInfo(realm, account)
_G[entry..i.."NameNormalText"]:SetText(format("%s (%s".. L["Account"]..": %s%s %s%s|r)", realm, WHITE, GREEN, account, YELLOW, last or ""))
end
_G[entry..i.."Level"]:SetText("")
_G[entry..i.."Skill1NormalText"]:SetText("")
_G[entry..i.."CookingNormalText"]:SetText("")
_G[entry..i.."FirstAidNormalText"]:SetText("")
_G[entry..i.."FishingNormalText"]:SetText("")
_G[entry..i.."RidingNormalText"]:SetText("")
_G[ entry..i ]:SetID(line)
_G[ entry..i ]:Show()
i = i + 1
VisibleCount = VisibleCount + 1
DisplayedCount = DisplayedCount + 1
elseif DrawRealm then
if (lineType == INFO_CHARACTER_LINE) then
local character = DS:GetCharacter( Characters:GetInfo(line) )
local icon
if DS:GetCharacterFaction(character) == "Alliance" then
-- icon = addon:TextureToFontstring(ICON_FACTION_ALLIANCE, size, size) .. " "
icon = addon:TextureToFontstring2(ICON_FACTION_ALLIANCE, size, size, inset, inset, inset, inset) .. " "
else
-- icon = addon:TextureToFontstring(ICON_FACTION_HORDE, size, size) .. " "
icon = addon:TextureToFontstring2(ICON_FACTION_HORDE, size, size, inset, inset, inset, inset) .. " "
end
_G[entry..i.."Collapse"]:Hide()
_G[entry..i.."Name"]:SetWidth(170)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0)
_G[entry..i.."NameNormalText"]:SetWidth(170)
addon:SetCharacterRowNameLevel(entry, i, icon, character)
-- CoA: render ALL primary professions the character knows into the
-- single wide Professions cell, as a row of icon+rank segments.
-- The list is precomputed in Characters.lua (field "professions")
-- and may be empty (unscanned char) -> cell renders blank. Every
-- value is guarded with "or 0" before GetColor/concat.
local professions = Characters:GetField(line, "professions")
local profText = ""
if professions then
for _, p in ipairs(professions) do
local rank = p.rank or 0
local profIcon = ""
if p.spellID then
profIcon = addon:TextureToFontstring2(addon:GetSpellIcon(p.spellID), size, size, inset, inset, inset, inset) .. " "
end
profText = profText .. profIcon .. ns:GetColor(rank) .. rank .. "|r "
end
end
_G[entry..i.."Skill1NormalText"]:SetText(profText)
local field
-- cooking
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(2550), size, size) .. " "
icon = addon:TextureToFontstring2(addon:GetSpellIcon(2550), size, size, inset, inset, inset, inset) .. " "
field = Characters:GetField(line, "cooking") or 0
_G[entry..i.."CookingNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- first aid
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(3273), size, size) .. " "
icon = addon:TextureToFontstring2(addon:GetSpellIcon(3273), size, size, inset, inset, inset, inset) .. " "
field = Characters:GetField(line, "firstaid") or 0
_G[entry..i.."FirstAidNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- fishing
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(7733), size, size) .. " "
icon = addon:TextureToFontstring2(addon:GetSpellIcon(7733), size, size, inset, inset, inset, inset) .. " "
field = Characters:GetField(line, "fishing") or 0
_G[entry..i.."FishingNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- riding
field = Characters:GetField(line, "riding") or 0
if field >= 300 then
-- icon = addon:TextureToFontstring("Interface\\Icons\\Ability_Mount_Gryphon_01", size, size) .. " "
icon = addon:TextureToFontstring2("Interface\\Icons\\ability_mount_drake_bronze", size, size, inset, inset, inset, inset)
for _,spId in pairs({63956, 63963, 60024, 72808, 72807, 63796, 40192, 69395, 60021, 59976, 64927, 67336, 65439, 49193, 71810, 44317, 44744, 58615, 37015, 3363, 32345}) do
-- IsPetKnown(character, companionType, spellID)
if DS:IsPetKnown(character, "MOUNT", spId) then
icon = addon:TextureToFontstring2("Interface\\Icons\\inv_misc_enggizmos_03", size, size, inset, inset, inset, inset)
break
end
end
elseif field >= 225 then
icon = addon:TextureToFontstring2("Interface\\Icons\\ability_mount_goldengryphon", size, size, inset, inset, inset, inset)
elseif field >= 150 then
icon = addon:TextureToFontstring2("Interface\\Icons\\ability_mount_charger", size, size, inset, inset, inset, inset)
elseif field >= 75 then
icon = addon:TextureToFontstring2("Interface\\Icons\\spell_nature_swiftness", size, size, inset, inset, inset, inset)
else
icon = addon:TextureToFontstring2("Interface\\Icons\\inv_boots_03", size, size, inset, inset, inset, inset)
end
if DS:IsSpellKnown(character, 54197) then -- KNOWS [Cold Weather Flying]
-- Interface\Icons\Spell_Frost_FrostShock
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(54197), size, size) .. " "
-- TextureToFontstringHalfSquare(name, side, inset, isRight)
-- icon = addon:TextureToFontstringHalfSquare(addon:GetSpellIcon(54197), size, inset, true) .. " "
-- icon = icon .. addon:TextureToFontstringHalfSquare(addon:GetSpellIcon(54197), size, inset, 0.7, 1.0) .. " "
-- TextureToFontstringCut(name, heightOrig, widthOrig, insetLeft, insetRight, insetTop, insetBottom)
-- local sTemp = size+2*inset
-- icon = icon .. addon:TextureToFontstringCut(addon:GetSpellIcon(54197), sTemp, sTemp, floor(0.7*sTemp), floor(0.1*sTemp), inset, inset) .. " "
icon = addon:TextureToFontstring(addon:GetSpellIcon(54197), size, size) .. " " .. icon .. " "
else -- DOES NOT KNOW [Cold Weather Flying]
icon = format("|T%s:%s:%s:0:0:%s:%s:%s:%s:%s:%s|t", addon:GetSpellIcon(54197), size, size, size, size, 0, 0, 0, 0) .. " " .. icon .. " "
end
_G[entry..i.."RidingNormalText"]:SetText(icon .. ns:GetColor(field, 300) .. field)
elseif (lineType == INFO_TOTAL_LINE) then
_G[entry..i.."Collapse"]:Hide()
_G[entry..i.."Name"]:SetWidth(200)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 15, 0)
_G[entry..i.."NameNormalText"]:SetWidth(200)
_G[entry..i.."NameNormalText"]:SetText(L["Totals"])
_G[entry..i.."Level"]:SetText(Characters:GetField(line, "level"))
_G[entry..i.."Skill1NormalText"]:SetText("")
_G[entry..i.."CookingNormalText"]:SetText("")
_G[entry..i.."FirstAidNormalText"]:SetText("")
_G[entry..i.."FishingNormalText"]:SetText("")
_G[entry..i.."RidingNormalText"]:SetText("")
end
_G[ entry..i ]:SetID(line)
_G[ entry..i ]:Show()
i = i + 1
VisibleCount = VisibleCount + 1
DisplayedCount = DisplayedCount + 1
end
local riding = Characters:GetField(viewLine, "riding") or 0
if riding > 0 then
items[#items + 1] = { kind = "skill", viewLine = viewLine, character = character,
spellID = 33388, name = (L and L["Riding"]) or "Riding", rank = riding, maxRank = 300 }
end
end
end
while i <= VisibleLines do
_G[ entry..i ]:SetID(0)
_G[ entry..i ]:Hide()
i = i + 1
end
FauxScrollFrame_Update( _G[ frame.."ScrollFrame" ], VisibleCount, VisibleLines, 18);
end
local offset = FauxScrollFrame_GetOffset( _G[ frame.."ScrollFrame" ] )
for i = 1, VisibleLines do
local e = entry..i
local btn = _G[e]
local item = items[i + offset]
if item then
_G[e.."Collapse"]:Hide()
_G[e.."Skill1NormalText"]:SetText("")
_G[e.."CookingNormalText"]:SetText("")
_G[e.."FirstAidNormalText"]:SetText("")
_G[e.."FishingNormalText"]:SetText("")
_G[e.."RidingNormalText"]:SetText("")
if item.kind == "header" then
-- character header: colored name + (class) across the row, no rank column
_G[e.."Name"]:SetPoint("TOPLEFT", 15, 0)
_G[e.."Name"]:SetWidth(420)
_G[e.."NameNormalText"]:SetWidth(420)
local locClass, engClass = DS:GetCharacterClass(item.character)
local className = Altoholic:GetCoAClassName(engClass) or locClass or "" -- CoA: PROPHET->Venomancer, MONK->Templar, …
_G[e.."NameNormalText"]:SetText( (DS:GetColoredCharacterName(item.character) or "?") .. " " .. WHITE .. "(" .. className .. ")" )
_G[e.."Level"]:SetText("")
else
-- profession row: [icon] name in the Name cell (indented), rank/max in its own column
local iconEsc = ""
if item.spellID then
iconEsc = addon:TextureToFontstring2(addon:GetSpellIcon(item.spellID), size, size, inset, inset, inset, inset) .. " "
end
_G[e.."Name"]:SetPoint("TOPLEFT", 38, 0)
_G[e.."Name"]:SetWidth(200)
_G[e.."NameNormalText"]:SetWidth(200)
_G[e.."NameNormalText"]:SetText( iconEsc .. WHITE .. item.name )
local cap = (item.maxRank > 0) and item.maxRank or 450
_G[e.."Level"]:SetPoint("TOPLEFT", 250, 0)
_G[e.."Level"]:SetWidth(120)
_G[e.."Level"]:SetJustifyH("LEFT")
_G[e.."Level"]:SetText( ns:GetColor(item.rank, cap) .. item.rank .. " / " .. item.maxRank )
end
btn.coaItem = item
btn:SetID(item.viewLine or 0)
btn:Show()
else
btn.coaItem = nil
btn:SetID(0)
btn:Hide()
end
end
FauxScrollFrame_Update( _G[ frame.."ScrollFrame" ], #items, VisibleLines, 18 )
end
function ns:OnEnter(frame)
local line = frame:GetParent():GetID()
local lineType = Characters:GetLineType(line)
if lineType ~= INFO_CHARACTER_LINE then
return
end
local id = frame:GetID()
local skillName, rank, suggestion
local DS = DataStore
local character = DS:GetCharacter(Characters:GetInfo(line))
-- CoA: id 1 is now the combined "Professions" cell -> list every known primary
-- profession with rank/max and recipe counts in a single tooltip.
if id == 1 then
local professions = Characters:GetField(line, "professions")
AltoTooltip:ClearLines()
AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT")
AltoTooltip:AddLine(L["Professions"] or "Professions", 1, 1, 1)
if not professions or #professions == 0 then
AltoTooltip:AddLine(L["No data"])
AltoTooltip:Show()
return
end
for _, p in ipairs(professions) do
local pName = p.name
local curRank, maxRank = DS:GetSkillInfo(character, pName)
curRank, maxRank = curRank or 0, maxRank or 0
local rankText = ns:GetColor(curRank) .. curRank .. "/" .. maxRank
local recipeText = ""
local prof = DS:GetProfession(character, pName)
if prof and DS:GetNumCraftLines(prof) > 0 then
local orange, yellow, green, grey = DS:GetNumRecipesByColor(prof)
recipeText = WHITE .. " (" .. (orange + yellow + green + grey) .. " " .. TRADESKILL_SERVICE_LEARN .. ")"
end
-- localized display name where possible
local displayName = pName
local spellID = DS:GetProfessionSpellID(pName)
if spellID then
displayName = GetSpellInfo(spellID) or pName
end
AltoTooltip:AddDoubleLine(WHITE .. displayName, rankText .. recipeText)
end
AltoTooltip:Show()
return
end
if id == 3 then
skillName = GetSpellInfo(2550) -- cooking
elseif id == 4 then
skillName = GetSpellInfo(3273) -- First Aid
elseif id == 5 then
skillName = GetSpellInfo(24303) -- Fishing
elseif id == 6 then
skillName = L["Riding"]
end
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 >= 3) and (id <= 6) then
if id == 6 then -- riding
rank = ns:GetColor(curRank, 300) .. curRank .. "/" .. maxRank
else
rank = ns:GetColor(curRank) .. curRank .. "/" .. maxRank
end
suggestion = addon:GetSuggestion(skillName, curRank)
elseif id == 7 then -- class
local _, class = DS:GetCharacterClass(character)
if class ~= "ROGUE" then
return
end
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
AltoTooltip:ClearLines();
AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT");
AltoTooltip:AddLine(skillName,1,1,1);
AltoTooltip:AddLine(GREEN..rank,1,1,1);
if (id == 3) or (id == 4) then -- crafting secondary skills (Cooking, First Aid); skips fishing/riding
if skillName ~= GetSpellInfo(13614) and skillName ~= GetSpellInfo(8613) then -- no display for herbalism & skinning
AltoTooltip:AddLine(" ");
if not profession then
AltoTooltip:AddLine(L["No data"]);
AltoTooltip:Show();
return
end
if DS:GetNumCraftLines(profession) == 0 then
AltoTooltip:AddLine(L["No data"].. ": 0 " .. TRADESKILL_SERVICE_LEARN,1,1,1);
else
local orange, yellow, green, grey = DS:GetNumRecipesByColor(profession)
AltoTooltip:AddLine(orange+yellow+green+grey .. " " .. TRADESKILL_SERVICE_LEARN,1,1,1);
AltoTooltip:AddLine(format(WHITE .. "%d " .. RECIPE_GREEN .. "Green|r /"
.. WHITE .. " %d " .. YELLOW .. "Yellow|r /"
.. WHITE .. " %d " .. RECIPE_ORANGE .. "Orange",
green, yellow, orange))
end
end
end
local skillCap = 450
if id == 6 then
skillCap = 300
end
AltoTooltip:AddLine(" ");
AltoTooltip:AddLine(RECIPE_GREY .. L["Grey"] .. "|r " .. L["up to"] .. " " .. (floor(skillCap*0.25)-1),1,1,1);
AltoTooltip:AddLine(RED .. RED_GEM .. "|r " .. L["up to"] .. " " .. (floor(skillCap*0.50)-1),1,1,1);
AltoTooltip:AddLine(ORANGE .. BI["Orange"] .. "|r " .. L["up to"] .. " " .. (floor(skillCap*0.75)-1),1,1,1);
AltoTooltip:AddLine(YELLOW .. YELLOW_GEM .. "|r " .. L["up to"] .. " " .. (skillCap-1),1,1,1);
AltoTooltip:AddLine(GREEN .. BI["Green"] .. "|r " .. L["at"] .. " "..skillCap.." " .. L["and above"],1,1,1);
if suggestion then
AltoTooltip:AddLine(" ",1,1,1);
AltoTooltip:AddLine(L["Suggestion"] .. ": ",1,1,1);
AltoTooltip:AddLine(TEAL .. suggestion,1,1,1);
end
-- parse profession cooldowns
if id ~= 7 and profession then
DS:ClearExpiredCooldowns(profession)
local numCooldows = DS:GetNumActiveCooldowns(profession)
if numCooldows == 0 then
AltoTooltip:AddLine(" ",1,1,1);
AltoTooltip:AddLine(L["All cooldowns are up"],1,1,1);
else
AltoTooltip:AddLine(" ",1,1,1);
for i = 1, numCooldows do
local craftName, expiresIn = DS:GetCraftCooldownInfo(profession, i)
AltoTooltip:AddDoubleLine(craftName, addon:GetTimeString(expiresIn));
end
end
end
AltoTooltip:Show();
local item = frame:GetParent() and frame:GetParent().coaItem
if not item or item.kind ~= "skill" then return end
AltoTooltip:ClearLines()
AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT")
AltoTooltip:AddLine(WHITE .. item.name, 1, 1, 1)
local cap = (item.maxRank > 0) and item.maxRank or 450
AltoTooltip:AddLine( ns:GetColor(item.rank, cap) .. item.rank .. " / " .. item.maxRank, 1, 1, 1 )
AltoTooltip:Show()
end
local VIEW_MOUNTS = 8
function ns:OnClick(frame, button)
local line = frame:GetParent():GetID()
local lineType = Characters:GetLineType(line)
if lineType ~= INFO_CHARACTER_LINE then
return
end
local id = frame:GetID()
if id == 5 then return end -- fishing ? do nothing
addon:SetCurrentCharacter( Characters:GetInfo(line) )
local skillName
if id == 1 then
-- CoA: id 1 is the combined Professions cell. A single click can't pick one
-- of several professions, so default to the first known primary profession
-- (opens its recipes / supplies its trade link on shift-click).
local professions = Characters:GetField(line, "professions")
if professions and professions[1] then
skillName = professions[1].name
end
elseif id == 3 then
skillName = GetSpellInfo(2550) -- cooking
elseif id == 4 then
skillName = GetSpellInfo(3273) -- First Aid
end
local DS = DataStore
local character = DS:GetCharacter(Characters:GetInfo(line))
local profession = DS:GetProfession(character, skillName)
if skillName then
if not profession or DS:GetNumCraftLines(profession) == 0 then -- if profession hasn't been scanned (or scan failed), exit
return
end
end
local charName, realm, account = addon:GetCurrentCharacter()
local chat = ChatEdit_GetLastActiveWindow()
if chat:IsShown() and IsShiftKeyDown() and realm == GetRealmName() and id ~= 6 then
-- if shift-click, then display the profession link and exit
local link = profession.FullLink
if link and link:match("trade:") then
chat:Insert(link);
end
return
end
addon.Tabs.Characters:SetCurrent(charName, realm, account)
addon.Tabs:OnClick(2)
if id == 6 then
addon.Tabs.Characters:ViewCharInfo(VIEW_MOUNTS)
else
addon.Tabs.Characters:ViewRecipes(skillName)
-- CoA: clicking a profession row opens that character's profession recipe list, if available.
local item = frame:GetParent() and frame:GetParent().coaItem
if not item or item.kind ~= "skill" or not item.character then return end
if addon.Tabs and addon.Tabs.Characters and addon.Tabs.Characters.ViewCharInfo then
local name, realm, account = Characters:GetInfo(item.viewLine)
addon:SetCurrentCharacter(name, realm, account)
addon.Tabs.Characters:SetCurrent(name, realm, account)
addon.Tabs:OnClick(2)
end
end
local skillColors = { RECIPE_GREY, RED, ORANGE, YELLOW, GREEN }
function ns:GetColor(rank, skillCap)
@@ -442,3 +157,4 @@ function ns:GetColor(rank, skillCap)
if index > #skillColors then index = #skillColors end
return skillColors[index]
end
-44
View File
@@ -257,50 +257,6 @@
</Anchor>
</Anchors>
</Button>
<!-- CoA: characters can know many more than 2 primary professions. Extra slots;
UpdateViewIcons fills/hides them dynamically and stops at the last one. -->
<Button name="$parent_Prof3" inherits="AltoViewIconTemplate" hidden="true">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parent_Prof2" relativePoint="BOTTOMRIGHT" >
<Offset x="5" y="0" />
</Anchor>
</Anchors>
</Button>
<Button name="$parent_Prof4" inherits="AltoViewIconTemplate" hidden="true">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parent_Prof3" relativePoint="BOTTOMRIGHT" >
<Offset x="5" y="0" />
</Anchor>
</Anchors>
</Button>
<Button name="$parent_Prof5" inherits="AltoViewIconTemplate" hidden="true">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parent_Prof1" relativePoint="BOTTOMLEFT" >
<Offset x="5" y="0" />
</Anchor>
</Anchors>
</Button>
<Button name="$parent_Prof6" inherits="AltoViewIconTemplate" hidden="true">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parent_Prof5" relativePoint="BOTTOMRIGHT" >
<Offset x="5" y="0" />
</Anchor>
</Anchors>
</Button>
<Button name="$parent_Prof7" inherits="AltoViewIconTemplate" hidden="true">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parent_Prof6" relativePoint="BOTTOMRIGHT" >
<Offset x="5" y="0" />
</Anchor>
</Anchors>
</Button>
<Button name="$parent_Prof8" inherits="AltoViewIconTemplate" hidden="true">
<Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parent_Prof7" relativePoint="BOTTOMRIGHT" >
<Offset x="5" y="0" />
</Anchor>
</Anchors>
</Button>
<Button name="$parent_Sort1" inherits="AltoSortButtonTemplate" id="1">
<Size>
+5 -14
View File
@@ -158,20 +158,11 @@ function ns:SetMode(mode)
Columns:Add(L["free"], 50, function(self) addon.Characters:Sort(self, "GetNumFreeBankSlots") end)
elseif currentMode == 3 then
Columns:Add(NAME, 100, function(self) addon.Characters:Sort(self, "GetCharacterName") end)
Columns:Add(LEVEL, 60, function(self) addon.Characters:Sort(self, "GetCharacterLevel") end)
-- CoA: a character can know ALL professions at once, so the old fixed
-- Prof. 1 / Prof. 2 columns are replaced by one wide "Professions" column
-- that lists every known primary profession (incl. the customs Woodcutting
-- and Woodworking). Width matches the widened Skill1 cell in Skills.xml.
Columns:Add(L["Professions"] or "Professions", 325, function(self) addon.Characters:Sort(self, "GetCharacterName") end)
title = GetSpellInfo(2550) -- cooking
Columns:Add(title, 65, function(self) addon.Characters:Sort(self, "GetCookingRank") end)
title = GetSpellInfo(3273) -- First Aid
Columns:Add(title, 65, function(self) addon.Characters:Sort(self, "GetFirstAidRank") end)
title = GetSpellInfo(24303) -- Fishing
Columns:Add(title, 65, function(self) addon.Characters:Sort(self, "GetFishingRank") end)
Columns:Add(L["Riding"], 65, function(self) addon.Characters:Sort(self, "GetRidingRank") end)
-- CoA: the Skills view is now a vertical list (character header, then one row per
-- known profession/secondary skill, top to bottom). A single wide column header
-- replaces the old per-skill columns, which no longer match the layout.
Columns:Add((L["Character"] or "Character") .. " / " .. (L["Professions"] or "Professions"), 460, function(self) addon.Characters:Sort(self, "GetCharacterName") end)
Columns:Add((SKILL or "Skill"), 220, function(self) addon.Characters:Sort(self, "GetCharacterName") end)
elseif currentMode == 4 then
Columns:Add(NAME, 100, function(self) addon.Characters:Sort(self, "GetCharacterName") end)
+1
View File
@@ -337,6 +337,7 @@ function tns:Update(treeIndex)
-- textures are 90.625% of the original size
local _, bg = DS:GetTreeInfo(class, treeName)
if not bg then return end -- CoA: no talent-tree background for this class (e.g. custom class with no reference data)
AltoholicFrameTalents_bgTopLeft:SetTexture(bg.."-TopLeft")
AltoholicFrameTalents_bgTopRight:SetTexture(bg.."-TopRight")
AltoholicFrameTalents_bgBottomLeft:SetTexture(bg.."-BottomLeft")
+1 -1
View File
@@ -380,7 +380,7 @@ local function GetItemCount(searchedID)
for tabID = 1, 6 do
local tabCount = DataStore:GetGuildBankTabItemCount(guildKey, tabID, searchedID)
if tabCount > 0 then
table.insert(tabCounters, format("%s: %s", WHITE .. DataStore:GetGuildBankTabName(guildKey, tabID), TEAL..tabCount))
table.insert(tabCounters, format("%s: %s", WHITE .. (DataStore:GetGuildBankTabName(guildKey, tabID) or ""), TEAL..tabCount)) -- CoA: tab name nil on partial guild-bank data
end
end
@@ -55,8 +55,8 @@ local function ScanSingleAchievement(id, isCompleted, month, day, year)
if critCompleted then
table.insert(CriteriaCache, tostring(j))
else
if reqQuantity > 1 then
table.insert(CriteriaCache, j .. ":" .. quantity)
if (reqQuantity or 0) > 1 then -- CoA: GetAchievementCriteriaInfo can return nil quantities for custom/partial achievements
table.insert(CriteriaCache, j .. ":" .. (quantity or 0))
end
end
end
@@ -86,10 +86,15 @@ local function OnPlayerMoney()
addon.ThisCharacter.money = GetMoney();
end
local hasScannedThisSession
local function OnPlayerAlive()
-- print("DataStore_Characters.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
-- (unchanged data), so skip those. The previous "only when ghost" gate skipped LOGIN
-- too, so name/level/class/money/XP never populated on a normal login - the root of
-- "no character data". (Same trap as DataStore_Inventory / _Skills; see commit fdcb25a.)
if hasScannedThisSession then return end
hasScannedThisSession = true
local character = addon.ThisCharacter
character.name = UnitName("player") -- to simplify processing a bit, the name is saved in the table too, in addition to being part of the key
@@ -263,7 +268,7 @@ local function _GetGuildInfo(character)
end
local function _GetPlayTime(character)
return character.played
return character.played or 0 -- CoA: nil on partial-data alt; callers do arithmetic (AccountSummary)
end
local function _GetLocation(character)
+10 -7
View File
@@ -678,25 +678,28 @@ local BagTypeStrings = {
local function _GetContainerInfo(character, containerID)
local bag = _GetContainer(character, containerID)
if type(bag) ~= "table" then return end -- CoA: unscanned bag on partial-data alt; was an index-nil crash
return bag.icon, bag.link, bag.size, bag.freeslots, BagTypeStrings[bag.bagtype]
end
local function _GetContainerSize(character, containerID)
-- containerID can be number or string
return character.Containers["Bag" .. containerID].size
local bag = character.Containers["Bag" .. containerID] -- CoA: nil for unscanned bag on partial-data alt
return bag and bag.size
end
local function _GetSlotInfo(bag, slotID)
assert(type(bag) == "table") -- this is the pointer to a bag table, obtained through addon:GetContainer()
assert(type(slotID) == "number")
-- CoA: partial-data alts can have an unscanned/nil bag pointer (GetContainer returns nil
-- for a "BagN" the Containers module never scanned); return empties instead of asserting.
if type(bag) ~= "table" or type(slotID) ~= "number" then return end
-- return itemID, itemLink, itemCount
return bag.ids[slotID], bag.links[slotID], bag.counts[slotID] or 1
return bag.ids and bag.ids[slotID], bag.links and bag.links[slotID], (bag.counts and bag.counts[slotID]) or 1
end
local function _GetContainerCooldownInfo(bag, slotID)
assert(type(bag) == "table") -- this is the pointer to a bag table, obtained through addon:GetContainer()
assert(type(slotID) == "number")
-- CoA: partial-data alts can have an unscanned/nil bag pointer; degrade to nil gracefully.
if type(bag) ~= "table" or type(slotID) ~= "number" or type(bag.cooldowns) ~= "table" then return end
local cd = bag.cooldowns[slotID]
if cd then
@@ -868,7 +871,7 @@ end
local function _GetGuildBankTabItemCount(guild, tabID, searchedID)
local count = 0
local container = guild.Tabs[tabID]
if type(container) ~= "table" or type(container.ids) ~= "table" then return count end -- CoA: unscanned guild bank tab; was a pairs(nil) crash on item tooltips
for slotID, id in pairs(container.ids) do
if (id == searchedID) then
count = count + (container.counts[slotID] or 1)
+4 -1
View File
@@ -625,6 +625,9 @@ local function _GetProfessionInfo(profession)
end
local function _GetNumCraftLines(profession)
-- CoA: profession is nil for an unscanned/custom profession on a partial-data alt;
-- callers use this as a numeric `for` limit, so return 0 instead of crashing on #nil.Crafts
if type(profession) ~= "table" or type(profession.Crafts) ~= "table" then return 0 end
return #profession.Crafts
end
@@ -709,7 +712,7 @@ local function _GetNumRecipesByColor(profession)
for i = 1, _GetNumCraftLines(profession) do
local isHeader, color = _GetCraftLineInfo(profession, i)
if not isHeader then
if not isHeader and color and counts[color] then -- CoA: custom-profession craft lines can carry an out-of-range/nil color
counts[color] = counts[color] + 1
end
end
+4 -2
View File
@@ -274,8 +274,10 @@ local function _GetQuestLogRewardInfo(character, index, rewardIndex)
end
local function _GetQuestInfo(link)
assert(type(link) == "string")
-- CoA: GetQuestLogInfo can hand back a nil link for a partial-data alt; degrade to nil
-- returns instead of asserting (callers already nil-check the returned name/level).
if type(link) ~= "string" then return end
local questID, questLevel = link:match("quest:(%d+):(-?%d+)")
local questName = link:match("%[(.+)%]")
+19 -14
View File
@@ -234,41 +234,47 @@ local function _GetReferenceTable()
end
local function _GetClassReference(class)
assert(type(class) == "string")
-- CoA: custom classes (MONK, BARBARIAN, …) have no vanilla reference table; return nil
-- instead of asserting/crashing so callers can degrade gracefully.
if type(class) ~= "string" then return end
return addon.ref.global[class]
end
local function _GetTreeReference(class, tree)
assert(type(class) == "string")
assert(type(tree) == "string")
return addon.ref.global[class].Trees[tree]
-- CoA: custom classes (MONK, etc.) may have no/partial talent reference data, so a
-- tree lookup can arrive with a nil tree name. Degrade to nil instead of asserting.
if type(class) ~= "string" or type(tree) ~= "string" then return end
local c = addon.ref.global[class]
if not c or not c.Trees then return end
return c.Trees[tree]
end
local function _IsClassKnown(class)
class = class or "" -- if by any chance nil is passed, trap it to make sure the function does not fail, but returns nil anyway
local ref = _GetClassReference(class)
if ref.Order then -- if the Order field is not nil, we have data for this class
if ref and ref.Order then -- CoA: ref is nil for custom classes; was an unguarded index crash
return true
end
end
local function _ImportClassReference(class, data)
assert(type(class) == "string")
assert(type(data) == "table")
-- CoA: data arrives over Comm/AccountSharing; a peer with no reference for a custom
-- class can send nil, which used to crash the import. Skip silently instead.
if type(class) ~= "string" or type(data) ~= "table" then return end
addon.ref.global[class] = data
end
local function _GetClassTrees(class)
assert(type(class) == "string")
-- CoA: ref is nil for custom classes; guard so the `for tree in DS:GetClassTrees()`
-- loops in Talents.lua get an empty iterator instead of an index-nil crash.
local ref = _GetClassReference(class)
local order = ref.Order
local order = ref and ref.Order
if order then
return order:gmatch("([^,]+)")
end
-- to do, add a return value that does not require validity testing by the caller
return function() return nil end -- empty iterator so callers can loop safely
end
local function _GetTreeInfo(class, tree)
@@ -281,8 +287,7 @@ end
local function _GetTreeNameByID(class, id)
-- returns the name of tree "id" for a given class
assert(type(class) == "string")
-- CoA: _GetClassTrees now yields an empty iterator for custom classes, so no assert needed
local index = 1
for name in _GetClassTrees(class) do
if index == id then