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).