diff --git a/Altoholic/Altoholic.lua b/Altoholic/Altoholic.lua
index 437115d..26af057 100644
--- a/Altoholic/Altoholic.lua
+++ b/Altoholic/Altoholic.lua
@@ -732,13 +732,22 @@ function Altoholic:ShowClassIcons()
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]);
+ -- 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);
diff --git a/Altoholic/Altoholic.toc b/Altoholic/Altoholic.toc
index 00f8720..e3a2b04 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.9
+## Version: 3.3.002b-coa.10
## 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/Characters.lua b/Altoholic/Characters.lua
index 9c5634a..8a6d365 100644
--- a/Altoholic/Characters.lua
+++ b/Altoholic/Characters.lua
@@ -76,8 +76,6 @@ local function AddRealm(AccountName, RealmName)
local realmBankSlots = 0
local realmFreeBankSlots = 0
- local SkillsCache = { {name = "", rank = 0}, {name = "", rank = 0} }
-
-- 1) Add the realm name
table.insert(characterList, { linetype = INFO_REALM_LINE + (realmCount*3),
isCollapsed = false,
@@ -87,36 +85,27 @@ local function AddRealm(AccountName, RealmName)
-- 2) Add the characters
for characterName, character in pairs(DataStore:GetCharacters(RealmName, AccountName)) do
- SkillsCache[1].name = ""
- SkillsCache[1].rank = 0
- SkillsCache[1].spellID = nil
- SkillsCache[2].name = ""
- SkillsCache[2].rank = 0
- SkillsCache[2].spellID = nil
-
- local i = 1
- local professions = DataStore:GetPrimaryProfessions(character)
- if professions then
- for SkillName, s in pairs(professions) do
- SkillsCache[i].name = SkillName
- SkillsCache[i].rank = DataStore:GetSkillInfo(character, SkillName)
- SkillsCache[i].spellID = DataStore:GetProfessionSpellID(SkillName)
- i = i + 1
-
- if i > 2 then -- it seems that under certain conditions, the loop continues after 2 professions.., so break
- break
- end
+ -- CoA: characters can know ALL professions at once (no retail 2-primary
+ -- limit) plus the customs Woodcutting/Woodworking. Build a dynamic list of
+ -- every known primary profession instead of the old fixed 2 slots. Each
+ -- entry carries its own name/rank/spellID(icon) so Skills.lua can render an
+ -- arbitrary number of professions. GetPrimaryProfessionList never returns
+ -- nil (returns {} for unscanned chars), but guard anyway.
+ local professions = {}
+ if DataStore.GetPrimaryProfessionList then
+ local list = DataStore:GetPrimaryProfessionList(character) or {}
+ for _, p in ipairs(list) do
+ professions[#professions + 1] = {
+ name = p.name,
+ rank = p.rank or 0,
+ spellID = DataStore:GetProfessionSpellID(p.name),
+ }
end
end
-
+
table.insert(characterList, { linetype = INFO_CHARACTER_LINE + (realmCount*3),
key = character,
- skillName1 = SkillsCache[1].name,
- skillRank1 = SkillsCache[1].rank,
- spellID1 = SkillsCache[1].spellID,
- skillName2 = SkillsCache[2].name,
- skillRank2 = SkillsCache[2].rank,
- spellID2 = SkillsCache[2].spellID,
+ professions = professions, -- CoA: dynamic list of all primary professions
cooking = DataStore:GetCookingRank(character),
firstaid = DataStore:GetFirstAidRank(character),
fishing = DataStore:GetFishingRank(character),
diff --git a/Altoholic/CoAClassColors.lua b/Altoholic/CoAClassColors.lua
index f4f1e3d..29b8888 100644
--- a/Altoholic/CoAClassColors.lua
+++ b/Altoholic/CoAClassColors.lua
@@ -24,7 +24,8 @@
-- Source of truth: db.exil.es /coa/dev for the full palette;
-- _G.RAID_CLASS_COLORS at FrameXML load time for the running client.
-local AC = _G.Altoholic and _G.Altoholic.ClassInfo
+local Alto = _G.Altoholic
+local AC = Alto and Alto.ClassInfo
if type(AC) ~= "table" then return end
local source = _G.RAID_CLASS_COLORS
@@ -45,3 +46,72 @@ for token, color in pairs(source) do
end
end
end
+
+-- Class ICONS
+-- -----------
+-- WoW's _G.CLASS_ICON_TCOORDS only carries texcoords for the playable
+-- classes the *client* shipped with — on the CoA Voljin client that is
+-- the vanilla 10 + DEATHKNIGHT only. The 21 CoA custom classes
+-- (BARBARIAN, WITCHDOCTOR, CHRONOMANCER, …) have no entry, so any draw
+-- site that does `CLASS_ICON_TCOORDS[class]` falls back to a wrong or
+-- blank icon (Altoholic.lua:ShowClassIcons hit this).
+--
+-- The realm-authoritative class-icon atlas is the 512x512 (8x8 grid of
+-- 64px cells) BLP that the CoA Details! fork bundles and renders for
+-- all 32 classes. We ship a copy of that atlas as
+-- Interface\AddOns\Altoholic\images\coa-classes.blp and reproduce its
+-- per-token texcoords below (source: Details/functions/profiles.lua
+-- class_coords). Keyed by the UPPERCASE englishClass token — the same
+-- key DataStore stores (DataStore_Characters: UnitClass()'s 2nd return)
+-- and CLASS_ICON_TCOORDS uses, so it is a drop-in for both.
+--
+-- Includes the vanilla 10 + DK too, so a single lookup covers every
+-- CoA-playable class uniformly out of one texture.
+
+local COA_CLASS_ICON_TEXTURE = [[Interface\AddOns\Altoholic\images\coa-classes]]
+
+-- left, right, top, bottom (verbatim from the CoA Details atlas)
+local COA_CLASS_ICON_TCOORDS = {
+ WITCHHUNTER = { 0.875, 1, 0.375, 0.5 },
+ WITCHDOCTOR = { 0.75, 0.875, 0.375, 0.5 },
+ WILDWALKER = { 0.625, 0.75, 0.375, 0.5 },
+ WARRIOR = { 0.5, 0.625, 0.375, 0.5 },
+ WARLOCK = { 0.375, 0.5, 0.375, 0.5 },
+ TINKER = { 0.25, 0.375, 0.375, 0.5 },
+ SUNCLERIC = { 0.125, 0.25, 0.375, 0.5 },
+ STORMBRINGER = { 0, 0.125, 0.375, 0.5 },
+ STARCALLER = { 0.875, 1, 0.25, 0.375 },
+ SPIRITMAGE = { 0.75, 0.875, 0.25, 0.375 },
+ SONOFARUGAL = { 0.625, 0.75, 0.25, 0.375 },
+ SHAMAN = { 0.5, 0.625, 0.25, 0.375 },
+ ROGUE = { 0.375, 0.5, 0.25, 0.375 },
+ REAPER = { 0.25, 0.375, 0.25, 0.375 },
+ RANGER = { 0.125, 0.25, 0.25, 0.375 },
+ PYROMANCER = { 0, 0.125, 0.25, 0.375 },
+ PROPHET = { 0.875, 1, 0.125, 0.25 },
+ PRIEST = { 0.75, 0.875, 0.125, 0.25 },
+ PALADIN = { 0.625, 0.75, 0.125, 0.25 },
+ NECROMANCER = { 0.5, 0.625, 0.125, 0.25 },
+ MONK = { 0.375, 0.5, 0.125, 0.25 },
+ MAGE = { 0.25, 0.375, 0.125, 0.25 },
+ HUNTER = { 0.125, 0.25, 0.125, 0.25 },
+ HERO = { 0, 0.125, 0.125, 0.25 },
+ GUARDIAN = { 0.875, 1, 0, 0.125 },
+ FLESHWARDEN = { 0.75, 0.875, 0, 0.125 },
+ DRUID = { 0.625, 0.75, 0, 0.125 },
+ DEMONHUNTER = { 0.5, 0.625, 0, 0.125 },
+ DEATHKNIGHT = { 0.375, 0.5, 0, 0.125 },
+ CULTIST = { 0.25, 0.375, 0, 0.125 },
+ CHRONOMANCER = { 0.125, 0.25, 0, 0.125 },
+ BARBARIAN = { 0, 0.125, 0, 0.125 },
+}
+
+-- Returns texture, left, right, top, bottom for a CoA-playable class
+-- token, or nil if the token is unknown (caller should fall back to the
+-- stock CLASS_ICON_TCOORDS path). Tolerant of a nil/missing token.
+function Alto:GetCoAClassIcon(token)
+ if type(token) ~= "string" then return end
+ local tc = COA_CLASS_ICON_TCOORDS[token]
+ if not tc then return end
+ return COA_CLASS_ICON_TEXTURE, tc[1], tc[2], tc[3], tc[4]
+end
diff --git a/Altoholic/Frames/Reputations.lua b/Altoholic/Frames/Reputations.lua
index 5046711..556ffa1 100644
--- a/Altoholic/Frames/Reputations.lua
+++ b/Altoholic/Frames/Reputations.lua
@@ -14,6 +14,13 @@ local DARK_RED = "|cFFF00000"
local ICON_UNKNOWN = "\124TInterface\\RaidFrame\\ReadyCheck-NotReady:14\124t"
local ICON_EXALTED = "\124TInterface\\RaidFrame\\ReadyCheck-Ready:14\124t"
+-- NOTE (Exiles/CoA): The Reputations view is DATA-DRIVEN.
+-- The hardcoded `Factions` table below is used ONLY as an icon lookup (faction name -> icon),
+-- so well-known Blizzard factions keep their nice icons. The list of factions actually shown,
+-- and their grouping, is built at runtime from what DataStore_Reputations scanned on each
+-- character (faction name + in-game category header). This means CoA's custom factions
+-- (and any new ones added over time) appear automatically, with no code edits required.
+-- Factions not present in the icon lookup fall back to a generic faction icon.
local Factions = {
-- Factions reference table, based on http://www.wowwiki.com/Factions
{ -- [1]
@@ -151,8 +158,96 @@ local VertexColors = {
[FACTION_STANDING_LABEL8] = { r = 1.0, g = 1.0, b = 1.0 }, -- exalted
}
-local currentXPack = 1 -- default to wow classic
-local currentFactionGroup = (UnitFactionGroup("player") == "Alliance") and 1 or 2 -- default to alliance or horde
+local GENERIC_FACTION_ICON = "Achievement_Reputation_01" -- fallback icon for factions not in the lookup (ex: CoA custom factions)
+
+-- Flat icon lookup built once from the hardcoded reference table above: faction name -> icon name.
+local FactionIcons = {}
+for _, xpack in ipairs(Factions) do
+ for _, factionGroup in ipairs(xpack) do
+ for _, faction in ipairs(factionGroup) do
+ if faction.name and faction.icon then
+ FactionIcons[faction.name] = faction.icon
+ end
+ end
+ end
+end
+
+local function GetFactionIcon(name)
+ return FactionIcons[name] or GENERIC_FACTION_ICON
+end
+
+-- *** Dynamic, data-driven group/faction model ***
+-- currentGroup = "" means "All factions" (every scanned faction, flat). Otherwise it's an in-game
+-- category header name (ex: "Wrath of the Lich King", or a CoA custom category).
+local ALL_GROUPS = ""
+local currentGroup = ALL_GROUPS
+
+-- Rebuilt on each Update from the union of all characters' scanned reputations on the current realm.
+local displayedGroups = {} -- ordered list of { name = headerName } for the dropdown
+local displayedFactions = {} -- ordered list of faction names currently shown (filtered by currentGroup)
+
+local function BuildModel()
+ local DS = DataStore
+ local realm, account = addon:GetCurrentRealm()
+
+ -- header (group) name -> { set of faction names }, plus first-seen order for stable display
+ local groupSet = {}
+ local factionHeader = {} -- faction name -> its header (last writer wins; headers are consistent across chars)
+ local factionOrder = {} -- faction name -> first-seen index (stable ordering)
+ local orderCounter = 0
+ local groupOrder = {} -- header name -> first-seen index
+
+ for _, characterKey in pairs(DS:GetCharacters(realm, account)) do
+ local reputations = DS:GetReputations(characterKey) or {}
+ local headers = DS:GetReputationHeaders(characterKey) or {}
+ for factionName in pairs(reputations) do
+ local header = headers[factionName] or ""
+ if factionOrder[factionName] == nil then
+ orderCounter = orderCounter + 1
+ factionOrder[factionName] = orderCounter
+ end
+ factionHeader[factionName] = header
+ if not groupSet[header] then
+ groupSet[header] = true
+ groupOrder[header] = orderCounter
+ end
+ end
+ end
+
+ -- Build the ordered group list for the dropdown.
+ wipe(displayedGroups)
+ local groupNames = {}
+ for header in pairs(groupSet) do
+ tinsert(groupNames, header)
+ end
+ table.sort(groupNames, function(a, b) return (groupOrder[a] or 0) < (groupOrder[b] or 0) end)
+ for _, header in ipairs(groupNames) do
+ tinsert(displayedGroups, header)
+ end
+
+ -- If the previously selected group no longer exists, fall back to "All factions".
+ if currentGroup ~= ALL_GROUPS and not groupSet[currentGroup] then
+ currentGroup = ALL_GROUPS
+ end
+
+ -- Build the ordered faction list for the currently selected group.
+ wipe(displayedFactions)
+ local names = {}
+ for factionName in pairs(factionHeader) do
+ if currentGroup == ALL_GROUPS or factionHeader[factionName] == currentGroup then
+ tinsert(names, factionName)
+ end
+ end
+ table.sort(names, function(a, b) return (factionOrder[a] or 0) < (factionOrder[b] or 0) end)
+ for _, factionName in ipairs(names) do
+ tinsert(displayedFactions, factionName)
+ end
+end
+
+local function GroupLabel(header)
+ if header == ALL_GROUPS or header == "" then return ALL end -- "All", a Blizzard global string
+ return header
+end
addon.Reputations = {}
@@ -194,46 +289,43 @@ local function DDM_AddCloseMenu()
UIDropDownMenu_AddButton(info, 1)
end
-local function DDM_OnClick(self, xpackIndex, factionGroupIndex)
- currentXPack = xpackIndex
- currentFactionGroup = factionGroupIndex
-
- local factionGroup = Factions[currentXPack][currentFactionGroup]
- UIDropDownMenu_SetText(AltoholicFrameReputations_SelectFaction, factionGroup.name)
-
+local function DDM_OnClick(self, header)
+ currentGroup = header or ALL_GROUPS
+
+ UIDropDownMenu_SetText(AltoholicFrameReputations_SelectFaction, GroupLabel(currentGroup))
+
ns:Update()
end
local function Reputations_UpdateEx(self, offset, entry, desc)
local line
local size = desc:GetSize()
-
+
local DS = DataStore
local realm, account = addon:GetCurrentRealm()
local character
- local factionGroup = Factions[currentXPack][currentFactionGroup]
-
+
for i=1, desc.NumLines do
line = i + offset
if line <= size then
- local faction = factionGroup[line]
-
- _G[entry..i.."Name"]:SetText(WHITE .. faction.name)
+ local factionName = displayedFactions[line]
+
+ _G[entry..i.."Name"]:SetText(WHITE .. (factionName or ""))
_G[entry..i.."Name"]:SetJustifyH("LEFT")
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 15, 0)
-
+
for j = 1, 10 do -- loop through the 10 alts
local itemName = entry.. i .. "Item" .. j;
local itemButton = _G[itemName]
local classButton = _G["AltoholicFrameClassesItem" .. j]
-
+
local itemTexture = _G[itemName .. "_Background"]
- itemTexture:SetTexture("Interface\\Icons\\"..faction.icon)
+ itemTexture:SetTexture("Interface\\Icons\\"..GetFactionIcon(factionName))
local status, rate
if classButton.CharName then -- if there's an alt in this column..
character = DS:GetCharacter(classButton.CharName, realm, account)
- status, _, _, rate = DS:GetReputationInfo(character, faction.name)
+ status, _, _, rate = DS:GetReputationInfo(character, factionName)
if status and rate then
local vc = VertexColors[status]
@@ -276,22 +368,24 @@ local ReputationsScrollFrame_Desc = {
NumLines = 8,
LineHeight = 41,
Frame = "AltoholicFrameReputations",
- GetSize = function() return #Factions[currentXPack][currentFactionGroup] end,
+ GetSize = function() return #displayedFactions end,
Update = Reputations_UpdateEx,
}
function ns:DropDownFaction_Initialize()
- for xpackIndex, xpack in ipairs(Factions) do
- DDM_AddTitle(xpack.name)
-
- for factionGroupIndex, factionGroup in ipairs(Factions[xpackIndex]) do
- DDM_Add(factionGroup.name, DDM_OnClick, xpackIndex, factionGroupIndex)
- end
+ -- Dropdown is built dynamically from the categories actually scanned across all characters.
+ BuildModel()
+
+ DDM_Add(GroupLabel(ALL_GROUPS), DDM_OnClick, ALL_GROUPS) -- "All factions" pseudo-group
+ for _, header in ipairs(displayedGroups) do
+ DDM_Add(GroupLabel(header), DDM_OnClick, header)
end
DDM_AddCloseMenu()
end
function ns:Update()
+ BuildModel()
+ UIDropDownMenu_SetText(AltoholicFrameReputations_SelectFaction, GroupLabel(currentGroup))
addon:ScrollFrameUpdate(ReputationsScrollFrame_Desc)
end
@@ -302,12 +396,12 @@ function ns:OnEnter(frame)
local DS = DataStore
local realm, account = addon:GetCurrentRealm()
local character = DS:GetCharacter(charName, realm, account)
- local factionGroup = Factions[currentXPack][currentFactionGroup]
- local faction = factionGroup[ frame:GetParent():GetID() ].name
-
+ local faction = displayedFactions[ frame:GetParent():GetID() ]
+ if not faction then return end
+
local status, currentLevel, maxLevel, rate = DS:GetReputationInfo(character, faction)
if not status then return end
-
+
AltoTooltip:SetOwner(frame, "ANCHOR_LEFT");
AltoTooltip:ClearLines();
AltoTooltip:AddLine((DS:GetColoredCharacterName(character) or "?") .. WHITE .. " @ " .. TEAL .. faction,1,1,1);
@@ -346,12 +440,12 @@ function ns:OnClick(frame, button)
local DS = DataStore
local realm, account = addon:GetCurrentRealm()
local character = DS:GetCharacter(charName, realm, account)
- local factionGroup = Factions[currentXPack][currentFactionGroup]
- local faction = factionGroup[ frame:GetParent():GetID() ].name
-
+ local faction = displayedFactions[ frame:GetParent():GetID() ]
+ if not faction then return end
+
local status, currentLevel, maxLevel, rate = DS:GetReputationInfo(character, faction)
if not status then return end
-
+
if ( button == "LeftButton" ) and ( IsShiftKeyDown() ) then
local chat = ChatEdit_GetLastActiveWindow()
if chat:IsShown() then
diff --git a/Altoholic/Frames/Reputations.xml b/Altoholic/Frames/Reputations.xml
index 88647e3..b36c483 100644
--- a/Altoholic/Frames/Reputations.xml
+++ b/Altoholic/Frames/Reputations.xml
@@ -213,11 +213,11 @@
- local faction = (UnitFactionGroup("player") == "Alliance") and FACTION_ALLIANCE or FACTION_HORDE
-
- UIDropDownMenu_SetWidth(self, 100)
+ -- Default to "All factions"; the dropdown contents are built dynamically
+ -- from the categories actually scanned (see Reputations.lua / BuildModel).
+ UIDropDownMenu_SetWidth(self, 140)
UIDropDownMenu_SetButtonWidth(self, 20)
- UIDropDownMenu_SetText(self, faction)
+ UIDropDownMenu_SetText(self, ALL)
UIDropDownMenu_Initialize(self, Altoholic.Reputations.DropDownFaction_Initialize)
diff --git a/Altoholic/Frames/Skills.lua b/Altoholic/Frames/Skills.lua
index ae052bf..c726531 100644
--- a/Altoholic/Frames/Skills.lua
+++ b/Altoholic/Frames/Skills.lua
@@ -81,13 +81,10 @@ function ns:Update()
end
_G[entry..i.."Level"]:SetText("")
_G[entry..i.."Skill1NormalText"]:SetText("")
- _G[entry..i.."Skill2NormalText"]:SetText("")
_G[entry..i.."CookingNormalText"]:SetText("")
_G[entry..i.."FirstAidNormalText"]:SetText("")
_G[entry..i.."FishingNormalText"]:SetText("")
_G[entry..i.."RidingNormalText"]:SetText("")
- _G[entry..i.."WoodcuttingNormalText"]:SetText("")
- _G[entry..i.."WoodworkingNormalText"]:SetText("")
_G[ entry..i ]:SetID(line)
_G[ entry..i ]:Show()
@@ -113,28 +110,26 @@ function ns:Update()
_G[entry..i.."NameNormalText"]:SetWidth(170)
addon:SetCharacterRowNameLevel(entry, i, icon, character)
- -- profession 1
- local field = Characters:GetField(line, "spellID1")
- if field then
- -- icon = addon:TextureToFontstring(addon:GetSpellIcon(field), size, size) .. " "
- icon = addon:TextureToFontstring2(addon:GetSpellIcon(field), size, size, inset, inset, inset, inset) .. " "
- else
- icon = ""
+ -- 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
- field = Characters:GetField(line, "skillRank1") or 0
- _G[entry..i.."Skill1NormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-
- -- profession 2
- field = Characters:GetField(line, "spellID2")
- if field then
- -- icon = addon:TextureToFontstring(addon:GetSpellIcon(field), size, size) .. " "
- icon = addon:TextureToFontstring2(addon:GetSpellIcon(field), size, size, inset, inset, inset, inset) .. " "
- else
- icon = ""
- end
- field = Characters:GetField(line, "skillRank2") or 0
- _G[entry..i.."Skill2NormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-
+ _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) .. " "
@@ -193,20 +188,6 @@ function ns:Update()
end
_G[entry..i.."RidingNormalText"]:SetText(icon .. ns:GetColor(field, 300) .. field)
-
- -- CoA custom professions: Woodcutting (spell 13977860) + Woodworking (spell 1005008).
- -- Ranks are read live from DataStore_Skills' name-based getters (not from a
- -- precomputed Characters field) so this stays self-contained. The getters return
- -- 0 on non-CoA chars or chars not yet scanned; still guarded with "or 0" before
- -- GetColor/concat. DataStore methods may be absent if DataStore_Skills is an older
- -- build, so guard each call with an existence check.
- field = (DS.GetWoodcuttingRank and (DS:GetWoodcuttingRank(character)) or 0) or 0
- icon = addon:TextureToFontstring2(addon:GetSpellIcon(13977860), size, size, inset, inset, inset, inset) .. " "
- _G[entry..i.."WoodcuttingNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-
- field = (DS.GetWoodworkingRank and (DS:GetWoodworkingRank(character)) or 0) or 0
- icon = addon:TextureToFontstring2(addon:GetSpellIcon(1005008), size, size, inset, inset, inset, inset) .. " "
- _G[entry..i.."WoodworkingNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
elseif (lineType == INFO_TOTAL_LINE) then
_G[entry..i.."Collapse"]:Hide()
_G[entry..i.."Name"]:SetWidth(200)
@@ -215,13 +196,10 @@ function ns:Update()
_G[entry..i.."NameNormalText"]:SetText(L["Totals"])
_G[entry..i.."Level"]:SetText(Characters:GetField(line, "level"))
_G[entry..i.."Skill1NormalText"]:SetText("")
- _G[entry..i.."Skill2NormalText"]:SetText("")
_G[entry..i.."CookingNormalText"]:SetText("")
_G[entry..i.."FirstAidNormalText"]:SetText("")
_G[entry..i.."FishingNormalText"]:SetText("")
_G[entry..i.."RidingNormalText"]:SetText("")
- _G[entry..i.."WoodcuttingNormalText"]:SetText("")
- _G[entry..i.."WoodworkingNormalText"]:SetText("")
end
_G[ entry..i ]:SetID(line)
_G[ entry..i ]:Show()
@@ -250,12 +228,46 @@ function ns:OnEnter(frame)
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
- skillName = Characters:GetField(line, "skillName1")
- elseif id == 2 then
- skillName = Characters:GetField(line, "skillName2")
- elseif id == 3 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
@@ -263,22 +275,13 @@ function ns:OnEnter(frame)
skillName = GetSpellInfo(24303) -- Fishing
elseif id == 6 then
skillName = L["Riding"]
- elseif id == 8 then
- skillName = GetSpellInfo(13977860) or "Woodcutting" -- CoA custom: Woodcutting
- elseif id == 9 then
- skillName = GetSpellInfo(1005008) or "Woodworking" -- CoA custom: Woodworking
end
- 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 == 8) or (id == 9) then -- CoA custom professions: show rank + recipe summary like the primary professions
- rank = ns:GetColor(curRank) .. curRank .. "/" .. maxRank
- suggestion = addon:GetSuggestion(skillName, curRank)
- elseif (id >= 1) and (id <= 6) then
+ if (id >= 3) and (id <= 6) then
if id == 6 then -- riding
rank = ns:GetColor(curRank, 300) .. curRank .. "/" .. maxRank
else
@@ -303,7 +306,7 @@ function ns:OnEnter(frame)
AltoTooltip:AddLine(skillName,1,1,1);
AltoTooltip:AddLine(GREEN..rank,1,1,1);
- if (id <= 4) or (id == 9) then -- crafting skills (incl. CoA Woodworking, id 9); skips fishing/riding/Woodcutting
+ 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(" ");
@@ -381,17 +384,17 @@ function ns:OnClick(frame, button)
local skillName
if id == 1 then
- skillName = Characters:GetField(line, "skillName1")
- elseif id == 2 then
- skillName = Characters:GetField(line, "skillName2")
+ -- 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
- elseif id == 8 then
- skillName = GetSpellInfo(13977860) or "Woodcutting" -- CoA custom (gathering, no recipes)
- elseif id == 9 then
- skillName = GetSpellInfo(1005008) or "Woodworking" -- CoA custom (crafting)
end
local DS = DataStore
@@ -431,5 +434,11 @@ local skillColors = { RECIPE_GREY, RED, ORANGE, YELLOW, GREEN }
function ns:GetColor(rank, skillCap)
rank = rank or 0 -- CoA: skill fields are nil for chars DataStore_Characters hasn't scanned
skillCap = skillCap or 450
- return skillColors[ floor(rank / (skillCap/4)) + 1 ]
+ -- CoA: custom professions can exceed skillCap (e.g. ranks > 450), which would
+ -- push the index past the 5-colour table and return nil -> crash on concat.
+ -- Clamp into [1, #skillColors].
+ local index = floor(rank / (skillCap / 4)) + 1
+ if index < 1 then index = 1 end
+ if index > #skillColors then index = #skillColors end
+ return skillColors[index]
end
diff --git a/Altoholic/Frames/Skills.xml b/Altoholic/Frames/Skills.xml
index 34a5186..9d62dea 100644
--- a/Altoholic/Frames/Skills.xml
+++ b/Altoholic/Frames/Skills.xml
@@ -108,7 +108,14 @@
+
-
-
-
diff --git a/Altoholic/Frames/TabSummary.lua b/Altoholic/Frames/TabSummary.lua
index 866c078..4fff08f 100644
--- a/Altoholic/Frames/TabSummary.lua
+++ b/Altoholic/Frames/TabSummary.lua
@@ -160,8 +160,11 @@ function ns:SetMode(mode)
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)
- Columns:Add(L["Prof. 1"], 65, function(self) addon.Characters:Sort(self, "skillName1") end)
- Columns:Add(L["Prof. 2"], 65, function(self) addon.Characters:Sort(self, "skillName2") 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
diff --git a/Altoholic/images/coa-classes.blp b/Altoholic/images/coa-classes.blp
new file mode 100644
index 0000000..7d408b7
Binary files /dev/null and b/Altoholic/images/coa-classes.blp differ
diff --git a/DataStore_Reputations/DataStore_Reputations.lua b/DataStore_Reputations/DataStore_Reputations.lua
index 5591e5b..f1bb674 100644
--- a/DataStore_Reputations/DataStore_Reputations.lua
+++ b/DataStore_Reputations/DataStore_Reputations.lua
@@ -15,9 +15,10 @@ local THIS_ACCOUNT = "Default"
local AddonDB_Defaults = {
global = {
Characters = {
- ['*'] = { -- ["Account.Realm.Name"]
+ ['*'] = { -- ["Account.Realm.Name"]
lastUpdate = nil,
- Factions = {},
+ Factions = {}, -- [factionName] = "bottom|top|earned"
+ Headers = {}, -- [factionName] = headerName (in-game category, ex: "Wrath of the Lich King"). Lets the UI group dynamically incl. CoA custom factions.
}
}
}
@@ -59,13 +60,19 @@ local function _GetRawReputationInfo(character, faction)
end
local function _GetReputations(character)
- return character.Factions
+ return character.Factions or {}
+end
+
+local function _GetReputationHeaders(character)
+ -- [factionName] = headerName (in-game category). May be empty for chars scanned before this field existed; UI must guard with "or {}".
+ return character.Headers or {}
end
local PublicMethods = {
GetReputationInfo = _GetReputationInfo,
GetRawReputationInfo = _GetRawReputationInfo,
GetReputations = _GetReputations,
+ GetReputationHeaders = _GetReputationHeaders,
}
function addon:OnInitialize()
@@ -75,6 +82,7 @@ function addon:OnInitialize()
DataStore:SetCharacterBasedMethod("GetReputationInfo")
DataStore:SetCharacterBasedMethod("GetRawReputationInfo")
DataStore:SetCharacterBasedMethod("GetReputations")
+ DataStore:SetCharacterBasedMethod("GetReputationHeaders")
end
function addon:OnEnable()
@@ -125,13 +133,24 @@ local function ScanReputations()
SaveHeaders()
local factions = addon.ThisCharacter.Factions
+ local headers = addon.ThisCharacter.Headers or {}
+ addon.ThisCharacter.Headers = headers
wipe(factions)
-
+ wipe(headers)
+
+ local currentHeader = "" -- track the in-game category so the UI can group factions dynamically (incl. CoA custom factions)
+
for i = 1, GetNumFactions() do -- 2nd pass, data collection
local name, _, _, bottom, top, earned, _, _, isHeader, _, hasRep = GetFactionInfo(i)
if (not isHeader) or (isHeader and hasRep) then
-- new in 3.0.2, headers may have rep, ex: alliance vanguard + horde expedition
factions[name] = bottom .. "|" .. top .. "|" .. earned
+ headers[name] = currentHeader
+ end
+ if isHeader then
+ -- any header (pure category like "Classic"/"Wrath of the Lich King", or a rep-bearing
+ -- header like "Alliance Vanguard") becomes the group for the factions listed beneath it
+ currentHeader = name or ""
end
end
diff --git a/DataStore_Reputations/DataStore_Reputations.toc b/DataStore_Reputations/DataStore_Reputations.toc
index 7e73be5..dd015bf 100644
--- a/DataStore_Reputations/DataStore_Reputations.toc
+++ b/DataStore_Reputations/DataStore_Reputations.toc
@@ -2,7 +2,7 @@
## Title: DataStore_Reputations
## Notes: Stores information about character reputation levels
## Author: Thaoky (EU-Marécages de Zangar)
-## Version: 3.3.001
+## Version: 3.3.001-coa.10
## Dependencies: DataStore
## OptionalDeps: Ace3
## SavedVariables: DataStore_ReputationsDB
diff --git a/DataStore_Skills/DataStore_Skills.lua b/DataStore_Skills/DataStore_Skills.lua
index eb6ed69..22b04b3 100644
--- a/DataStore_Skills/DataStore_Skills.lua
+++ b/DataStore_Skills/DataStore_Skills.lua
@@ -7,7 +7,7 @@ if not DataStore then return end
local addonName = "DataStore_Skills"
-_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0")
+_G[addonName] = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceTimer-3.0")
local addon = _G[addonName]
@@ -35,6 +35,44 @@ local function _GetPrimaryProfessions(character)
return character.Skills[L["Professions"]]
end
+-- CoA: a character can know ALL professions at once (no retail 2-primary limit),
+-- and the two custom professions Woodcutting/Woodworking may sit under a different
+-- skill-window header. To render "all professions" reliably we don't trust the
+-- "Professions" category header alone: we collect every known crafting/gathering
+-- profession by name across all categories. English skill names are the keys; on
+-- non-English clients these names won't match and the list falls back to whatever
+-- sits under L["Professions"] via _GetPrimaryProfessions (caller handles that).
+local PRIMARY_PROFESSION_NAMES = {
+ "Alchemy", "Blacksmithing", "Enchanting", "Engineering", "Inscription",
+ "Jewelcrafting", "Leatherworking", "Tailoring", "Skinning", "Mining",
+ "Herbalism", "Woodcutting", "Woodworking",
+}
+
+-- Returns an ordered array of { name = , rank = , maxRank = }
+-- for every primary profession the character actually knows. Never returns nil.
+local function _GetPrimaryProfessionList(character)
+ local result = {}
+ local skills = character.Skills
+ if not skills then return result end
+
+ for _, profName in ipairs(PRIMARY_PROFESSION_NAMES) do
+ for _, category in pairs(skills) do
+ local skill = category[profName]
+ if skill then
+ local rank, maxRank = strsplit("|", skill)
+ result[#result + 1] = {
+ name = profName,
+ rank = tonumber(rank) or 0,
+ maxRank = tonumber(maxRank) or 0,
+ }
+ break -- found this profession, move to the next name
+ end
+ end
+ end
+
+ return result
+end
+
local function _GetSecondaryProfessions(character)
return character.Skills[L["Secondary Skills"]]
end
@@ -95,6 +133,7 @@ end
local PublicMethods = {
GetPrimaryProfessions = _GetPrimaryProfessions,
+ GetPrimaryProfessionList = _GetPrimaryProfessionList,
GetSecondaryProfessions = _GetSecondaryProfessions,
GetSkillInfo = _GetSkillInfo,
GetSkillInfoByCategory = _GetSkillInfoByCategory,
@@ -111,6 +150,7 @@ function addon:OnInitialize()
DataStore:RegisterModule(addonName, addon, PublicMethods)
DataStore:SetCharacterBasedMethod("GetPrimaryProfessions")
+ DataStore:SetCharacterBasedMethod("GetPrimaryProfessionList")
DataStore:SetCharacterBasedMethod("GetSecondaryProfessions")
DataStore:SetCharacterBasedMethod("GetSkillInfo")
DataStore:SetCharacterBasedMethod("GetSkillInfoByCategory")
@@ -123,12 +163,20 @@ function addon:OnInitialize()
end
function addon:OnEnable()
+ -- CoA fix: the old build only scanned in PLAYER_ALIVE while the player was a
+ -- ghost (see below), so a living character that never died was never scanned
+ -- and every profession rank rendered as 0 ("Skills shows no data"). We now scan
+ -- on login and whenever the skill window changes so every character populates.
addon:RegisterEvent("PLAYER_ALIVE")
+ addon:RegisterEvent("PLAYER_ENTERING_WORLD", "ScanOnLogin")
+ addon:RegisterEvent("SKILL_LINES_CHANGED", "ScanOnLogin")
addon:RegisterEvent("CHAT_MSG_SKILL")
end
function addon:OnDisable()
addon:UnregisterEvent("PLAYER_ALIVE")
+ addon:UnregisterEvent("PLAYER_ENTERING_WORLD")
+ addon:UnregisterEvent("SKILL_LINES_CHANGED")
addon:UnregisterEvent("CHAT_MSG_SKILL")
end
@@ -192,10 +240,24 @@ end
function addon:PLAYER_ALIVE()
-- print("DataStore_Skills.lua") -- DEBUG 2025 07 21
if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard
-
+
ScanSkills()
end
+-- CoA fix: scan once shortly after login / when skills change. SKILL_LINES_CHANGED
+-- can fire many times in a burst (once per skill line during login), so throttle to
+-- a single deferred scan instead of scanning on every event.
+local scanScheduled
+function addon:ScanOnLogin()
+ if scanScheduled then return end
+ scanScheduled = true
+ -- defer so the skill UI / API is fully populated before we read GetNumSkillLines()
+ addon:ScheduleTimer(function()
+ scanScheduled = nil
+ ScanSkills()
+ end, 2)
+end
+
-- this turns
-- "Your skill in %s has increased to %d."
-- into
diff --git a/DataStore_Skills/DataStore_Skills.toc b/DataStore_Skills/DataStore_Skills.toc
index 21ec2ca..82b7f05 100644
--- a/DataStore_Skills/DataStore_Skills.toc
+++ b/DataStore_Skills/DataStore_Skills.toc
@@ -2,7 +2,7 @@
## Title: DataStore_Skills
## Notes: Stores information about character skills
## Author: Thaoky (EU-Marécages de Zangar)
-## Version: 3.3.002-coa.9
+## Version: 3.3.002-coa.10
## Dependencies: DataStore
## OptionalDeps: Ace3
## SavedVariables: DataStore_SkillsDB
diff --git a/README.md b/README.md
index 7c1ad90..987c7b9 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.10** — Three CoA data-coverage features:
+ - **Reputation** is now data-driven: shows every faction a character actually has (grouped by in-game category), so CoA's custom factions (and future ones) appear automatically. The old hardcoded faction tree is kept only as an icon lookup.
+ - **Class icons** for CoA custom classes (12–32) now render from a bundled CoA atlas (`Altoholic/images/coa-classes.blp`, texcoords from the CoA Details fork) instead of falling back to the Warrior glue icon.
+ - **Skills** tab shows ALL known professions (dynamic list, not 2 fixed slots) incl. Woodcutting/Woodworking, and fixes "no profession data" — `DataStore_Skills` now scans on login (`PLAYER_ENTERING_WORLD`/`SKILL_LINES_CHANGED`) instead of only on death/ghost-release.
- **3.3.002b-coa.9** — Reverted the 1.4 default scale (it only zoomed, didn't show more content; scale stays user-opt-in at 1.0 default, applied on open). Hardened `Options:Get/Set` against a nil `options` table (`TabOptions.lua:442` crash). Guild Members: guard `Level_OnClick` against cleared/stale row IDs (clicking AiL crashed). **New:** Personal + Realm bank tracking ported from coa-bagnon (detects CoA `BANK_PERMISSIONS_PAYLOAD`, personal=per-char, realm=per-realm; surfaced in Search + BagUsage tooltip). **New:** Woodcutting + Woodworking columns on the Skills tab (CoA custom professions). NOTE: Skills "all professions" redesign, profession data population, character icons, and reputation factions are still in progress.
- **3.3.002b-coa.8** — Title bar reads just `Altoholic ` (from the live `.toc`), dropping the "by Thaoky (Edited by Telkar-RG 1.04a)" string. Window now opens at the AtlasLoot-ish default scale (`UIScale` 1.4, ≈ 1105×640); scale is applied on every open (upstream only applied it after visiting Options), with a one-time bump for profiles still on the old 1.0 default.
- **3.3.002b-coa.7** — Skills tab: `GetColor()` now nil-safe and the per-skill rank fields (`skillRank1/2`, `cooking`, `firstaid`, `fishing`, `riding`) default to `0` — they're nil for chars `DataStore_Characters` hasn't scanned, which crashed the Skills summary (`floor(rank/…)` arithmetic and the `>= 300` riding check).