Compare commits

..

8 Commits

Author SHA1 Message Date
florian.berthold 0565051302 coa.10: CoA reputation factions (data-driven) + custom-class icons + all-professions Skills
release / release (push) Successful in 5s
- Reputation view rebuilt data-driven from each char's scanned factions grouped
  by in-game category; CoA custom factions (and future ones) appear automatically.
  Old hardcoded tree kept only as an icon lookup.
- CoA custom-class icons (classes 12-32) render from bundled atlas
  Altoholic/images/coa-classes.blp (texcoords from coa-details) instead of the
  Warrior glue-icon fallback in ShowClassIcons.
- Skills tab shows ALL known professions (dynamic list incl Woodcutting/Woodworking),
  not 2 fixed slots; DataStore_Skills scans on PLAYER_ENTERING_WORLD/SKILL_LINES_CHANGED
  (fixes 'no profession data' that only scanned on ghost-release).
2026-05-29 15:55:25 +02:00
florian.berthold ec868716ed coa.9: revert scale default + crash fixes + personal/realm bank + woodworking
release / release (push) Successful in 5s
- Revert UIScale default 1.4->1.0 (scaling only zoomed, no extra content; real
  larger layout tracked separately). Apply saved scale on open, default 1.0.
- Fix Options:Get/Set nil 'options' crash (TabOptions.lua:442).
- Guild Members: guard Level_OnClick against cleared/stale row IDs.
- Personal + Realm bank tracking ported from coa-bagnon (BANK_PERMISSIONS_PAYLOAD
  detection; personal per-char, realm per-realm; Search + BagUsage surfacing).
- Woodcutting/Woodworking columns on Skills tab (CoA custom professions).
2026-05-29 15:43:42 +02:00
florian.berthold ad54312ad9 coa.8: simplify title bar + default to AtlasLoot-size window
release / release (push) Successful in 6s
- Title bar: 'Altoholic <toc version>' (was 'Altoholic v.. by Thaoky (Edited by
  Telkar-RG 1.04a)'); reads GetAddOnMetadata Version so it tracks releases. Drop
  dead VERSION_STRING.
- Default UIScale 1.0 -> 1.4 (~AtlasLoot 1105x640). Apply scale in OnShow (was only
  applied after visiting Options); one-time migration bumps old-default profiles.
  Options reset/default slider -> 1.4.
2026-05-29 15:29:55 +02:00
florian.berthold 11c47a86db coa.7: guard Skills tab summary against unscanned skill ranks
release / release (push) Successful in 5s
GetColor() did floor(rank/..) with no nil-guard and riding did 'field >= 300';
skillRank1/2/cooking/firstaid/fishing/riding are nil for chars DataStore_Characters
hasn't scanned. GetColor now defaults rank to 0 and each field falls back to 0.
2026-05-29 15:22:21 +02:00
florian.berthold 86e5b3485f coa.6: guard AccountSharing realm/name format (last straggler)
release / release (push) Successful in 5s
2026-05-29 10:44:59 +02:00
florian.berthold 97e38d5c3d coa.5: refactor char display into guarded helpers; fix missed sites; restore login scan
release / release (push) Successful in 5s
- Extract AddCharacterTooltipHeader() + SetCharacterRowNameLevel() (Altoholic.lua);
  nil-guards centralized, callers in AccountSummary/Activity/BagUsage/Skills/tooltip.
- Fix sites the manual sweep missed: Skills.lua (row + skill ranks), Keys.lua x3,
  ShowClassIcons sort (Altoholic.lua:705, getters bypass their own or-0 via the wrapper).
- Restore login scan: OnPlayerAlive was ghost-only (fdcb25a) so iLvl never populated;
  now scans once per session. Removed dated DEBUG leftovers.
2026-05-29 01:02:24 +02:00
florian.berthold 3fde42454e coa.4: rebrand title to Exiles fork + frame hardening changelog
release / release (push) Successful in 4s
Title drops Telkar-RG's personal 1.04a; now 'Altoholic (Exiles)'.
Author keeps Thaoky/Telkar-RG credit. Bundles the frame partial-record
guards from the previous commit.
2026-05-28 23:54:32 +02:00
florian.berthold f19ff36733 Harden Altoholic frames against partial alt records (no-value char getters)
DataStore char-based getters return *no value* for any module that hasn't
scanned a given char (DataStore.lua: 'if not arg1.lastUpdate then return end').
Fresh CoA alts have partial per-module data, so the frames crashed feeding
'no value' into format()/concat/arithmetic/pairs. Guarded every such site:
AccountSummary, Activity, BagUsage, Quests, Reputations, TabCharacters,
DrawCharacterTooltip, recipe tooltip. No DataStore contract change.
2026-05-28 23:45:09 +02:00
30 changed files with 892 additions and 250 deletions
+64 -33
View File
@@ -17,7 +17,6 @@ local TEAL = "|cFF00FF9A"
local GOLD = "|cFFFFD700" local GOLD = "|cFFFFD700"
local THIS_ACCOUNT = "Default" local THIS_ACCOUNT = "Default"
local VERSION_STRING = "1.04a"
Altoholic.ClassInfo = { Altoholic.ClassInfo = {
["MAGE"] = "|cFF69CCF0", ["MAGE"] = "|cFF69CCF0",
@@ -249,10 +248,12 @@ end
-- *** Event Handlers *** -- *** Event Handlers ***
local hasScannedThisSession
local function OnPlayerAlive() local function OnPlayerAlive()
-- print("Altoholic.lua") -- DEBUG 2025 07 21 -- CoA: scan once at login (see DataStore_Inventory / commit fdcb25a). FRIENDLIST_UPDATE also
if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard -- keeps the friends list fresh; this just guarantees an initial scan without rescanning on res.
if hasScannedThisSession then return end
hasScannedThisSession = true
ScanFriends() ScanFriends()
end end
@@ -332,7 +333,10 @@ function addon:OnEnable()
addon:RegisterEvent("AUCTION_HOUSE_SHOW", addon.AuctionHouse.OnShow) addon:RegisterEvent("AUCTION_HOUSE_SHOW", addon.AuctionHouse.OnShow)
addon:RegisterEvent("PLAYER_TALENT_UPDATE", addon.Talents.OnUpdate); addon:RegisterEvent("PLAYER_TALENT_UPDATE", addon.Talents.OnUpdate);
AltoholicFrameName:SetText("Altoholic |cFFFFFFFF".. addon.Version .. "|r by |cFF69CCF0Thaoky|r" .. " (Edited by |cFF69CCF0Telkar-RG|r ".."|cFFFFFFFF".. VERSION_STRING .."|r)") -- 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")
local realm = GetRealmName() local realm = GetRealmName()
local player = UnitName("player") local player = UnitName("player")
@@ -398,7 +402,15 @@ function addon:ToggleUI()
end end
function addon:OnShow() function addon:OnShow()
SetPortraitTexture(AltoholicFramePortrait, "player"); SetPortraitTexture(AltoholicFramePortrait, "player");
-- CoA: apply the saved UI scale on open (upstream only applied it after the Options
-- tab was visited, so the window opened un-scaled). Default is 1.0 — scaling is opt-in
-- via Options; a true larger layout is tracked separately.
local O = addon.db.global.options
if O and O.UIScale then
AltoholicFrame:SetScale(O.UIScale)
end
addon.Characters:BuildList() addon.Characters:BuildList()
addon.Characters:BuildView() addon.Characters:BuildView()
@@ -585,6 +597,22 @@ function addon:GetDelayInDays(delay)
return floor((time() - delay) / 86400) return floor((time() - delay) / 86400)
end end
-- CoA: shared, nil-safe character display helpers.
-- DataStore char-based getters return *no value* for any module that hasn't
-- scanned a given character (DataStore.lua: "if not arg1.lastUpdate then return end").
-- Fresh alts have partial per-module data, so every field is guarded here once
-- instead of being copy-pasted (and missed) across the frames.
function Altoholic:AddCharacterTooltipHeader(character)
AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character) or "?", DS:GetColoredCharacterFaction(character) or "")
AltoTooltip:AddLine(format("%s %s |r%s %s", L["Level"],
GREEN..(DS:GetCharacterLevel(character) or 0), DS:GetCharacterRace(character) or "", DS:GetCharacterClass(character) or ""), 1, 1, 1)
end
function Altoholic:SetCharacterRowNameLevel(entry, i, icon, character)
_G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character) or "?", DS:GetCharacterClass(character) or ""))
_G[entry..i.."Level"]:SetText(GREEN .. (DS:GetCharacterLevel(character) or 0))
end
function Altoholic:FormatDelay(timeStamp) function Altoholic:FormatDelay(timeStamp)
-- timeStamp = value when time() was last called for a given variable (ex: last time the mailbox was checked) -- timeStamp = value when time() was last called for a given variable (ex: last time the mailbox was checked)
if not timeStamp then if not timeStamp then
@@ -679,21 +707,18 @@ function Altoholic:ShowClassIcons()
local realm, account = Altoholic:GetCurrentRealm() local realm, account = Altoholic:GetCurrentRealm()
-- #################### -- Sort characters by level first, then average item level. The getters yield no value
-- for alts whose Characters/Inventory module hasn't scanned them, so default [3]/[4] to 0.
local CharNameList = DS:GetCharacters(realm, account) local CharNameList = DS:GetCharacters(realm, account)
local CharNameList_sort = {} local CharNameList_sort = {}
for k,v in pairs(CharNameList) do for k,v in pairs(CharNameList) do
table.insert(CharNameList_sort,{k,v, DS:GetAverageItemLevel(v), DS:GetCharacterLevel(v)}) table.insert(CharNameList_sort, {k, v, DS:GetAverageItemLevel(v) or 0, DS:GetCharacterLevel(v) or 0})
end end
-- sort for level first, avg iLevel secondly
table.sort(CharNameList_sort, function(a,b) return b[3]+b[4]*10000 < a[3]+a[4]*10000 end) table.sort(CharNameList_sort, function(a,b) return b[3]+b[4]*10000 < a[3]+a[4]*10000 end)
-- DEBUG_CHARLIST = CharNameList
-- print("-- altoholic DEBUG READY")
-- ####################
-- for characterName, character in pairs(DS:GetCharacters(realm, account)) do -- for characterName, character in pairs(DS:GetCharacters(realm, account)) do
for _,charTbl in ipairs(CharNameList_sort) do for _,charTbl in ipairs(CharNameList_sort) do
local characterName, character = charTbl[1], charTbl[2] local characterName, character = charTbl[1], charTbl[2]
@@ -707,13 +732,22 @@ function Altoholic:ShowClassIcons()
end) end)
local _, class = DS:GetCharacterClass(character) 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"] local itemTexture = _G[itemName .. "IconTexture"]
itemTexture:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes"); -- CoA: CLASS_ICON_TCOORDS only carries the vanilla 10 + DK on Voljin,
itemTexture:SetTexCoord(tc[1], tc[2], tc[3], tc[4]); -- 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:SetWidth(36);
itemTexture:SetHeight(36); itemTexture:SetHeight(36);
itemTexture:SetAllPoints(itemButton); itemTexture:SetAllPoints(itemButton);
@@ -763,25 +797,22 @@ function Altoholic:DrawCharacterTooltip(self, charName)
AltoTooltip:SetOwner(self, "ANCHOR_LEFT"); AltoTooltip:SetOwner(self, "ANCHOR_LEFT");
AltoTooltip:ClearLines(); AltoTooltip:ClearLines();
AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) Altoholic:AddCharacterTooltipHeader(character)
AltoTooltip:AddLine(format("%s %s |r%s %s", L["Level"],
GREEN..DS:GetCharacterLevel(character), DS:GetCharacterRace(character), DS:GetCharacterClass(character)),1,1,1)
local zone, subZone = DS:GetLocation(character) local zone, subZone = DS:GetLocation(character)
AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..zone, GOLD..subZone),1,1,1) AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..(zone or "?"), GOLD..(subZone or "")),1,1,1)
local restXP = DS:GetRestXP(character) local restXP = DS:GetRestXP(character)
if restXP and restXP > 0 then if restXP and restXP > 0 then
AltoTooltip:AddLine(format("%s: %s", L["Rest XP"], GREEN..restXP),1,1,1) AltoTooltip:AddLine(format("%s: %s", L["Rest XP"], GREEN..restXP),1,1,1)
end end
AltoTooltip:AddLine("Average iLevel: " .. GREEN .. format("%.1f", DS:GetAverageItemLevel(character)),1,1,1); AltoTooltip:AddLine("Average iLevel: " .. GREEN .. format("%.1f", DS:GetAverageItemLevel(character) or 0),1,1,1);
if IsAddOnLoaded("DataStore_Achievements") then if IsAddOnLoaded("DataStore_Achievements") then
if DS:GetNumCompletedAchievements(character) > 0 then if (DS:GetNumCompletedAchievements(character) or 0) > 0 then
AltoTooltip:AddLine(ACHIEVEMENTS_COMPLETED ..": " .. GREEN .. DS:GetNumCompletedAchievements(character) .. "/"..DS:GetNumAchievements(character)) AltoTooltip:AddLine(ACHIEVEMENTS_COMPLETED ..": " .. GREEN .. DS:GetNumCompletedAchievements(character) .. "/"..(DS:GetNumAchievements(character) or 0))
AltoTooltip:AddLine(ACHIEVEMENT_TITLE ..": " .. GREEN .. DS:GetNumAchievementPoints(character)) AltoTooltip:AddLine(ACHIEVEMENT_TITLE ..": " .. GREEN .. (DS:GetNumAchievementPoints(character) or 0))
end end
end end
+2 -2
View File
@@ -1,5 +1,5 @@
## Interface: 30300 ## Interface: 30300
## Title: Altoholic (|cFF69CCF0Telkar-RG|cFFFFFFFF 1.04a|r |cFFFFD100CoA|r) ## Title: Altoholic (|cFFFFD100Exiles|r)
## X-Curse-Packaged-Version: r90 ## X-Curse-Packaged-Version: r90
## X-Curse-Project-Name: Altoholic ## X-Curse-Project-Name: Altoholic
@@ -13,7 +13,7 @@
## Author: Thaoky, Telkar-RG ## Author: Thaoky, Telkar-RG
## X-Edited-By: Exiles (Sub-Net) — florian.berthold@sub-net.at ## X-Edited-By: Exiles (Sub-Net) — florian.berthold@sub-net.at
## Version: 3.3.002b-coa.3 ## Version: 3.3.002b-coa.10
## X-Category: Inventory, Tradeskill, Mail ## X-Category: Inventory, Tradeskill, Mail
## X-Localizations: enUS, frFR, zhCN, zhTW, deDE, koKR, esES, esMX, ruRU ## X-Localizations: enUS, frFR, zhCN, zhTW, deDE, koKR, esES, esMX, ruRU
## X-Website: http://wow.curse.com/downloads/wow-addons/details/altoholic.aspx ## X-Website: http://wow.curse.com/downloads/wow-addons/details/altoholic.aspx
+17 -28
View File
@@ -76,8 +76,6 @@ local function AddRealm(AccountName, RealmName)
local realmBankSlots = 0 local realmBankSlots = 0
local realmFreeBankSlots = 0 local realmFreeBankSlots = 0
local SkillsCache = { {name = "", rank = 0}, {name = "", rank = 0} }
-- 1) Add the realm name -- 1) Add the realm name
table.insert(characterList, { linetype = INFO_REALM_LINE + (realmCount*3), table.insert(characterList, { linetype = INFO_REALM_LINE + (realmCount*3),
isCollapsed = false, isCollapsed = false,
@@ -87,36 +85,27 @@ local function AddRealm(AccountName, RealmName)
-- 2) Add the characters -- 2) Add the characters
for characterName, character in pairs(DataStore:GetCharacters(RealmName, AccountName)) do for characterName, character in pairs(DataStore:GetCharacters(RealmName, AccountName)) do
SkillsCache[1].name = "" -- CoA: characters can know ALL professions at once (no retail 2-primary
SkillsCache[1].rank = 0 -- limit) plus the customs Woodcutting/Woodworking. Build a dynamic list of
SkillsCache[1].spellID = nil -- every known primary profession instead of the old fixed 2 slots. Each
SkillsCache[2].name = "" -- entry carries its own name/rank/spellID(icon) so Skills.lua can render an
SkillsCache[2].rank = 0 -- arbitrary number of professions. GetPrimaryProfessionList never returns
SkillsCache[2].spellID = nil -- nil (returns {} for unscanned chars), but guard anyway.
local professions = {}
local i = 1 if DataStore.GetPrimaryProfessionList then
local professions = DataStore:GetPrimaryProfessions(character) local list = DataStore:GetPrimaryProfessionList(character) or {}
if professions then for _, p in ipairs(list) do
for SkillName, s in pairs(professions) do professions[#professions + 1] = {
SkillsCache[i].name = SkillName name = p.name,
SkillsCache[i].rank = DataStore:GetSkillInfo(character, SkillName) rank = p.rank or 0,
SkillsCache[i].spellID = DataStore:GetProfessionSpellID(SkillName) spellID = DataStore:GetProfessionSpellID(p.name),
i = i + 1 }
if i > 2 then -- it seems that under certain conditions, the loop continues after 2 professions.., so break
break
end
end end
end end
table.insert(characterList, { linetype = INFO_CHARACTER_LINE + (realmCount*3), table.insert(characterList, { linetype = INFO_CHARACTER_LINE + (realmCount*3),
key = character, key = character,
skillName1 = SkillsCache[1].name, professions = professions, -- CoA: dynamic list of all primary professions
skillRank1 = SkillsCache[1].rank,
spellID1 = SkillsCache[1].spellID,
skillName2 = SkillsCache[2].name,
skillRank2 = SkillsCache[2].rank,
spellID2 = SkillsCache[2].spellID,
cooking = DataStore:GetCookingRank(character), cooking = DataStore:GetCookingRank(character),
firstaid = DataStore:GetFirstAidRank(character), firstaid = DataStore:GetFirstAidRank(character),
fishing = DataStore:GetFishingRank(character), fishing = DataStore:GetFishingRank(character),
+71 -1
View File
@@ -24,7 +24,8 @@
-- Source of truth: db.exil.es /coa/dev for the full palette; -- Source of truth: db.exil.es /coa/dev for the full palette;
-- _G.RAID_CLASS_COLORS at FrameXML load time for the running client. -- _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 if type(AC) ~= "table" then return end
local source = _G.RAID_CLASS_COLORS local source = _G.RAID_CLASS_COLORS
@@ -45,3 +46,72 @@ for token, color in pairs(source) do
end end
end 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
+1 -1
View File
@@ -279,7 +279,7 @@ local ContentScrollFrame_Desc = {
[CHARACTER_HEADER_LINE] = { [CHARACTER_HEADER_LINE] = {
GetText = function(self, line) GetText = function(self, line)
local _, realm, name = strsplit(".", line.key) local _, realm, name = strsplit(".", line.key)
return format("%s|r / %s", WHITE..realm, DataStore:GetColoredCharacterName(line.key)) return format("%s|r / %s", WHITE..realm, DataStore:GetColoredCharacterName(line.key) or "?")
end, end,
GetOffset = function(self, line) GetOffset = function(self, line)
return 20 return 20
+15 -18
View File
@@ -249,12 +249,11 @@ function ns:Update()
_G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetWidth(170)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0)
_G[entry..i.."NameNormalText"]:SetWidth(170) _G[entry..i.."NameNormalText"]:SetWidth(170)
_G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character), DS:GetCharacterClass(character))) addon:SetCharacterRowNameLevel(entry, i, icon, character)
_G[entry..i.."Level"]:SetText(GREEN .. DS:GetCharacterLevel(character))
_G[entry..i.."Money"]:SetText(addon:GetMoneyString(DS:GetMoney(character))) _G[entry..i.."Money"]:SetText(addon:GetMoneyString(DS:GetMoney(character) or 0))
_G[entry..i.."Played"]:SetText(addon:GetTimeString(DS:GetPlayTime(character))) _G[entry..i.."Played"]:SetText(addon:GetTimeString(DS:GetPlayTime(character) or 0))
_G[entry..i.."XP"]:SetText(GREEN .. DS:GetXPRate(character) .. "%") _G[entry..i.."XP"]:SetText(GREEN .. (DS:GetXPRate(character) or 0) .. "%")
if DS:GetCharacterLevel(character) == MAX_PLAYER_LEVEL then if DS:GetCharacterLevel(character) == MAX_PLAYER_LEVEL then
_G[entry..i.."Rested"]:SetText(WHITE .. "0%") _G[entry..i.."Rested"]:SetText(WHITE .. "0%")
@@ -262,7 +261,7 @@ function ns:Update()
_G[entry..i.."Rested"]:SetText( addon:GetRestedXP(character) ) _G[entry..i.."Rested"]:SetText( addon:GetRestedXP(character) )
end end
_G[entry..i.."AvgILevelNormalText"]:SetText(YELLOW..format("%.1f", DS:GetAverageItemLevel(character))) _G[entry..i.."AvgILevelNormalText"]:SetText(YELLOW..format("%.1f", DS:GetAverageItemLevel(character) or 0))
elseif (lineType == INFO_TOTAL_LINE) then elseif (lineType == INFO_TOTAL_LINE) then
_G[entry..i.."Collapse"]:Hide() _G[entry..i.."Collapse"]:Hide()
@@ -342,23 +341,21 @@ function ns:Level_OnEnter(frame)
AltoTooltip:ClearLines(); AltoTooltip:ClearLines();
AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT"); AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT");
AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) addon:AddCharacterTooltipHeader(character)
AltoTooltip:AddLine(format("%s %s |r%s %s", L["Level"],
GREEN..DS:GetCharacterLevel(character), DS:GetCharacterRace(character), DS:GetCharacterClass(character)),1,1,1)
local zone, subZone = DS:GetLocation(character) local zone, subZone = DS:GetLocation(character)
AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..zone, GOLD..subZone),1,1,1) AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..(zone or "?"), GOLD..(subZone or "")),1,1,1)
local guildName = DS:GetGuildInfo(character) local guildName = DS:GetGuildInfo(character)
if guildName then if guildName then
AltoTooltip:AddLine(format("%s: %s", GUILD, GREEN..guildName),1,1,1) AltoTooltip:AddLine(format("%s: %s", GUILD, GREEN..guildName),1,1,1)
end end
AltoTooltip:AddLine(EXPERIENCE_COLON .. " " AltoTooltip:AddLine(EXPERIENCE_COLON .. " "
.. GREEN .. DS:GetXP(character) .. WHITE .. "/" .. GREEN .. (DS:GetXP(character) or 0) .. WHITE .. "/"
.. GREEN .. DS:GetXPMax(character) .. WHITE .. " (" .. GREEN .. (DS:GetXPMax(character) or 0) .. WHITE .. " ("
.. GREEN .. DS:GetXPRate(character) .. "%" .. GREEN .. (DS:GetXPRate(character) or 0) .. "%"
.. WHITE .. ")",1,1,1); .. WHITE .. ")",1,1,1);
local restXP = DS:GetRestXP(character) local restXP = DS:GetRestXP(character)
if restXP and restXP > 0 then if restXP and restXP > 0 then
@@ -448,7 +445,7 @@ function ns:AIL_OnEnter(frame)
AltoTooltip:ClearLines(); AltoTooltip:ClearLines();
AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT"); AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT");
AltoTooltip:AddLine(DS:GetColoredCharacterName(character),1,1,1); AltoTooltip:AddLine(DS:GetColoredCharacterName(character),1,1,1);
AltoTooltip:AddLine(WHITE .. L["Average Item Level"] ..": " .. GREEN.. format("%.1f", DS:GetAverageItemLevel(character)),1,1,1); AltoTooltip:AddLine(WHITE .. L["Average Item Level"] ..": " .. GREEN.. format("%.1f", DS:GetAverageItemLevel(character) or 0),1,1,1);
addon:AiLTooltip() addon:AiLTooltip()
AltoTooltip:Show(); AltoTooltip:Show();
+10 -13
View File
@@ -100,8 +100,7 @@ function ns:Update()
_G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetWidth(170)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0)
_G[entry..i.."NameNormalText"]:SetWidth(170) _G[entry..i.."NameNormalText"]:SetWidth(170)
_G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character), DS:GetCharacterClass(character))) addon:SetCharacterRowNameLevel(entry, i, icon, character)
_G[entry..i.."Level"]:SetText(GREEN .. DS:GetCharacterLevel(character))
local color local color
local num = DS:GetNumMails(character) or 0 local num = DS:GetNumMails(character) or 0
@@ -112,7 +111,7 @@ function ns:Update()
color = GREEN -- green by default, red if at least one mail is about to expire color = GREEN -- green by default, red if at least one mail is about to expire
local threshold = DataStore:GetOption("DataStore_Mails", "MailWarningThreshold") local threshold = DataStore:GetOption("DataStore_Mails", "MailWarningThreshold")
if DS:GetNumExpiredMails(character, threshold) > 0 then if (DS:GetNumExpiredMails(character, threshold) or 0) > 0 then
color = RED color = RED
end end
end end
@@ -181,18 +180,16 @@ function ns:OnEnter(self)
AltoTooltip:ClearLines(); AltoTooltip:ClearLines();
AltoTooltip:SetOwner(self, "ANCHOR_RIGHT"); AltoTooltip:SetOwner(self, "ANCHOR_RIGHT");
AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) addon:AddCharacterTooltipHeader(character)
AltoTooltip:AddLine(format("%s %s |r%s %s", L["Level"],
GREEN..DS:GetCharacterLevel(character), DS:GetCharacterRace(character), DS:GetCharacterClass(character)),1,1,1)
local zone, subZone = DS:GetLocation(character) local zone, subZone = DS:GetLocation(character)
AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..zone, GOLD..subZone),1,1,1) AltoTooltip:AddLine(format("%s: %s |r(%s|r)", L["Zone"], GOLD..(zone or "?"), GOLD..(subZone or "")),1,1,1)
AltoTooltip:AddLine(EXPERIENCE_COLON .. " " AltoTooltip:AddLine(EXPERIENCE_COLON .. " "
.. GREEN .. DS:GetXP(character) .. WHITE .. "/" .. GREEN .. (DS:GetXP(character) or 0) .. WHITE .. "/"
.. GREEN .. DS:GetXPMax(character) .. WHITE .. " (" .. GREEN .. (DS:GetXPMax(character) or 0) .. WHITE .. " ("
.. GREEN .. DS:GetXPRate(character) .. "%" .. GREEN .. (DS:GetXPRate(character) or 0) .. "%"
.. WHITE .. ")",1,1,1); .. WHITE .. ")",1,1,1);
local restXP = DS:GetRestXP(character) local restXP = DS:GetRestXP(character)
if restXP and restXP > 0 then if restXP and restXP > 0 then
+25 -18
View File
@@ -96,26 +96,25 @@ function ns:Update()
_G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetWidth(170)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0)
_G[entry..i.."NameNormalText"]:SetWidth(170) _G[entry..i.."NameNormalText"]:SetWidth(170)
_G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character), DS:GetCharacterClass(character))) addon:SetCharacterRowNameLevel(entry, i, icon, character)
_G[entry..i.."Level"]:SetText(GREEN .. DS:GetCharacterLevel(character))
_G[entry..i.."FreeBags"]:SetText(GREEN .. (DS:GetNumFreeBagSlots(character) or 0))
_G[entry..i.."FreeBags"]:SetText(GREEN .. DS:GetNumFreeBagSlots(character)) _G[entry..i.."FreeBank"]:SetText(GREEN .. (DS:GetNumFreeBankSlots(character) or 0))
_G[entry..i.."FreeBank"]:SetText(GREEN .. DS:GetNumFreeBankSlots(character))
_G[entry..i.."BagSlotsNormalText"]:SetJustifyH("LEFT") _G[entry..i.."BagSlotsNormalText"]:SetJustifyH("LEFT")
_G[entry..i.."BankSlotsNormalText"]:SetJustifyH("LEFT") _G[entry..i.."BankSlotsNormalText"]:SetJustifyH("LEFT")
-- Normal bags -- Normal bags
_G[entry..i.."BagSlotsNormalText"]:SetText(format("%s/%s|r/%s|r/%s|r/%s |r(%s|r)", _G[entry..i.."BagSlotsNormalText"]:SetText(format("%s/%s|r/%s|r/%s|r/%s |r(%s|r)",
DS:GetContainerSize(character, 0), DS:GetContainerSize(character, 0) or 0,
WHITE .. DS:GetContainerSize(character, 1), WHITE .. (DS:GetContainerSize(character, 1) or 0),
WHITE .. DS:GetContainerSize(character, 2), WHITE .. (DS:GetContainerSize(character, 2) or 0),
WHITE .. DS:GetContainerSize(character, 3), WHITE .. (DS:GetContainerSize(character, 3) or 0),
WHITE .. DS:GetContainerSize(character, 4), WHITE .. (DS:GetContainerSize(character, 4) or 0),
CYAN .. DS:GetNumBagSlots(character))) CYAN .. (DS:GetNumBagSlots(character) or 0)))
-- Bank bags -- Bank bags
if DS:GetNumBankSlots(character) < 28 then if (DS:GetNumBankSlots(character) or 0) < 28 then
_G[entry..i.."BankSlotsNormalText"]:SetText(L["Bank not visited yet"]) _G[entry..i.."BankSlotsNormalText"]:SetText(L["Bank not visited yet"])
else else
_G[entry..i.."BankSlotsNormalText"]:SetText(format("%s/%s|r/%s|r/%s|r/%s|r/%s|r/%s|r/%s |r(%s|r)", _G[entry..i.."BankSlotsNormalText"]:SetText(format("%s/%s|r/%s|r/%s|r/%s|r/%s|r/%s|r/%s |r(%s|r)",
@@ -182,9 +181,7 @@ function ns:OnEnter(self)
AltoTooltip:ClearLines(); AltoTooltip:ClearLines();
AltoTooltip:SetOwner(self, "ANCHOR_RIGHT"); AltoTooltip:SetOwner(self, "ANCHOR_RIGHT");
AltoTooltip:AddDoubleLine(DS:GetColoredCharacterName(character), DS:GetColoredCharacterFaction(character)) addon:AddCharacterTooltipHeader(character)
AltoTooltip:AddLine(format("%s %s |r%s %s", L["Level"],
GREEN..DS:GetCharacterLevel(character), DS:GetCharacterRace(character), DS:GetCharacterClass(character)),1,1,1)
AltoTooltip:AddLine(" ",1,1,1); AltoTooltip:AddLine(" ",1,1,1);
local id = self:GetID() local id = self:GetID()
@@ -203,7 +200,7 @@ function ns:OnEnter(self)
end end
numSlots = DS:GetNumBagSlots(character) numSlots = DS:GetNumBagSlots(character)
numFree = DS:GetNumFreeBagSlots(character) numFree = DS:GetNumFreeBagSlots(character)
elseif DS:GetNumBankSlots(character) < 28 then elseif (DS:GetNumBankSlots(character) or 0) < 28 then
AltoTooltip:AddLine(L["Bank not visited yet"],1,1,1); AltoTooltip:AddLine(L["Bank not visited yet"],1,1,1);
AltoTooltip:Show(); AltoTooltip:Show();
return return
@@ -221,5 +218,15 @@ function ns:OnEnter(self)
AltoTooltip:AddLine(" ",1,1,1); AltoTooltip:AddLine(" ",1,1,1);
AltoTooltip:AddLine(CYAN .. numSlots .. " |r" .. L["slots"] .. " (" .. GREEN .. numFree .. "|r " ..L["free"] .. ") ",1,1,1); AltoTooltip:AddLine(CYAN .. numSlots .. " |r" .. L["slots"] .. " (" .. GREEN .. numFree .. "|r " ..L["free"] .. ") ",1,1,1);
AltoTooltip:Show();
-- CoA personal bank (per-character). Realm bank is per-realm, not per
-- character, so it is intentionally not shown on this character tooltip.
local personalTabs = DS:GetPersonalBankTabCount(character) or 0
if personalTabs > 0 then
AltoTooltip:AddLine(" ",1,1,1);
AltoTooltip:AddLine(format("%sPersonal Bank|r: %s%d|r %s",
CYAN, WHITE, personalTabs, (personalTabs == 1) and "tab" or "tabs"), 1, 1, 1);
end
AltoTooltip:Show();
end end
+2
View File
@@ -460,7 +460,9 @@ function ns:Level_OnClick(self, button)
if button ~= "LeftButton" then return end if button ~= "LeftButton" then return end
local id = self:GetParent():GetID() local id = self:GetParent():GetID()
if id == 0 then return end -- CoA: cleared/hidden rows keep SetID(0); view[0] is nil and indexing line.lineType below errors. Level_OnEnter & Collapse_OnClick already guard this; Level_OnClick (the AiL click that opens equipment) was the only handler missing it.
local line = view[id] local line = view[id]
if not line then return end -- CoA: stale button ID after a GUILD_ROSTER_UPDATE view rebuild can point past #view
if line.lineType == NORMALPLAYER_LINE then return end if line.lineType == NORMALPLAYER_LINE then return end
local member = self:GetParent().CharName local member = self:GetParent().CharName
+3 -3
View File
@@ -789,7 +789,7 @@ function ns:Item_OnEnter(frame)
AltoTooltip:SetOwner(frame, "ANCHOR_LEFT") AltoTooltip:SetOwner(frame, "ANCHOR_LEFT")
AltoTooltip:ClearLines() AltoTooltip:ClearLines()
AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character), nameKey) ) AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character) or "?", nameKey) )
AltoTooltip:AddLine(" ") AltoTooltip:AddLine(" ")
local questDone, cr,cg,cb local questDone, cr,cg,cb
@@ -848,7 +848,7 @@ function ns:Item_OnEnter(frame)
else else
AltoTooltip:SetOwner(frame, "ANCHOR_LEFT") AltoTooltip:SetOwner(frame, "ANCHOR_LEFT")
AltoTooltip:ClearLines() AltoTooltip:ClearLines()
AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character), nameKey) ) AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character) or "?", nameKey) )
AltoTooltip:AddLine(" ") AltoTooltip:AddLine(" ")
local questDone, cr,cg,cb local questDone, cr,cg,cb
@@ -912,7 +912,7 @@ function ns:Item_OnEnter(frame)
else else
AltoTooltip:SetOwner(frame, "ANCHOR_LEFT") AltoTooltip:SetOwner(frame, "ANCHOR_LEFT")
AltoTooltip:ClearLines() AltoTooltip:ClearLines()
AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character), nameKey) ) AltoTooltip:AddLine( format("%s|r: %s", DS:GetColoredCharacterName(character) or "?", nameKey) )
AltoTooltip:AddLine(" ") AltoTooltip:AddLine(" ")
AltoTooltip:AddLine(L["Required reputation"] .. ":",1,1,1) AltoTooltip:AddLine(L["Required reputation"] .. ":",1,1,1)
+1 -1
View File
@@ -40,7 +40,7 @@ function ns:Update()
local DS = DataStore local DS = DataStore
if DS:GetQuestLogSize(character) == 0 then if (DS:GetQuestLogSize(character) or 0) == 0 then
AltoholicTabCharactersStatus:SetText(L["No quest found for "] .. addon:GetCurrentCharacter()) AltoholicTabCharactersStatus:SetText(L["No quest found for "] .. addon:GetCurrentCharacter())
addon:ClearScrollFrame( _G[ frame.."ScrollFrame" ], entry, VisibleLines, 18) addon:ClearScrollFrame( _G[ frame.."ScrollFrame" ], entry, VisibleLines, 18)
return return
+129 -35
View File
@@ -14,6 +14,13 @@ local DARK_RED = "|cFFF00000"
local ICON_UNKNOWN = "\124TInterface\\RaidFrame\\ReadyCheck-NotReady:14\124t" local ICON_UNKNOWN = "\124TInterface\\RaidFrame\\ReadyCheck-NotReady:14\124t"
local ICON_EXALTED = "\124TInterface\\RaidFrame\\ReadyCheck-Ready: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 = { local Factions = {
-- Factions reference table, based on http://www.wowwiki.com/Factions -- Factions reference table, based on http://www.wowwiki.com/Factions
{ -- [1] { -- [1]
@@ -151,8 +158,96 @@ local VertexColors = {
[FACTION_STANDING_LABEL8] = { r = 1.0, g = 1.0, b = 1.0 }, -- exalted [FACTION_STANDING_LABEL8] = { r = 1.0, g = 1.0, b = 1.0 }, -- exalted
} }
local currentXPack = 1 -- default to wow classic local GENERIC_FACTION_ICON = "Achievement_Reputation_01" -- fallback icon for factions not in the lookup (ex: CoA custom factions)
local currentFactionGroup = (UnitFactionGroup("player") == "Alliance") and 1 or 2 -- default to alliance or horde
-- 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 = {} addon.Reputations = {}
@@ -194,46 +289,43 @@ local function DDM_AddCloseMenu()
UIDropDownMenu_AddButton(info, 1) UIDropDownMenu_AddButton(info, 1)
end end
local function DDM_OnClick(self, xpackIndex, factionGroupIndex) local function DDM_OnClick(self, header)
currentXPack = xpackIndex currentGroup = header or ALL_GROUPS
currentFactionGroup = factionGroupIndex
UIDropDownMenu_SetText(AltoholicFrameReputations_SelectFaction, GroupLabel(currentGroup))
local factionGroup = Factions[currentXPack][currentFactionGroup]
UIDropDownMenu_SetText(AltoholicFrameReputations_SelectFaction, factionGroup.name)
ns:Update() ns:Update()
end end
local function Reputations_UpdateEx(self, offset, entry, desc) local function Reputations_UpdateEx(self, offset, entry, desc)
local line local line
local size = desc:GetSize() local size = desc:GetSize()
local DS = DataStore local DS = DataStore
local realm, account = addon:GetCurrentRealm() local realm, account = addon:GetCurrentRealm()
local character local character
local factionGroup = Factions[currentXPack][currentFactionGroup]
for i=1, desc.NumLines do for i=1, desc.NumLines do
line = i + offset line = i + offset
if line <= size then if line <= size then
local faction = factionGroup[line] local factionName = displayedFactions[line]
_G[entry..i.."Name"]:SetText(WHITE .. faction.name) _G[entry..i.."Name"]:SetText(WHITE .. (factionName or ""))
_G[entry..i.."Name"]:SetJustifyH("LEFT") _G[entry..i.."Name"]:SetJustifyH("LEFT")
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 15, 0) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 15, 0)
for j = 1, 10 do -- loop through the 10 alts for j = 1, 10 do -- loop through the 10 alts
local itemName = entry.. i .. "Item" .. j; local itemName = entry.. i .. "Item" .. j;
local itemButton = _G[itemName] local itemButton = _G[itemName]
local classButton = _G["AltoholicFrameClassesItem" .. j] local classButton = _G["AltoholicFrameClassesItem" .. j]
local itemTexture = _G[itemName .. "_Background"] local itemTexture = _G[itemName .. "_Background"]
itemTexture:SetTexture("Interface\\Icons\\"..faction.icon) itemTexture:SetTexture("Interface\\Icons\\"..GetFactionIcon(factionName))
local status, rate local status, rate
if classButton.CharName then -- if there's an alt in this column.. if classButton.CharName then -- if there's an alt in this column..
character = DS:GetCharacter(classButton.CharName, realm, account) 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 if status and rate then
local vc = VertexColors[status] local vc = VertexColors[status]
@@ -276,22 +368,24 @@ local ReputationsScrollFrame_Desc = {
NumLines = 8, NumLines = 8,
LineHeight = 41, LineHeight = 41,
Frame = "AltoholicFrameReputations", Frame = "AltoholicFrameReputations",
GetSize = function() return #Factions[currentXPack][currentFactionGroup] end, GetSize = function() return #displayedFactions end,
Update = Reputations_UpdateEx, Update = Reputations_UpdateEx,
} }
function ns:DropDownFaction_Initialize() function ns:DropDownFaction_Initialize()
for xpackIndex, xpack in ipairs(Factions) do -- Dropdown is built dynamically from the categories actually scanned across all characters.
DDM_AddTitle(xpack.name) BuildModel()
for factionGroupIndex, factionGroup in ipairs(Factions[xpackIndex]) do DDM_Add(GroupLabel(ALL_GROUPS), DDM_OnClick, ALL_GROUPS) -- "All factions" pseudo-group
DDM_Add(factionGroup.name, DDM_OnClick, xpackIndex, factionGroupIndex) for _, header in ipairs(displayedGroups) do
end DDM_Add(GroupLabel(header), DDM_OnClick, header)
end end
DDM_AddCloseMenu() DDM_AddCloseMenu()
end end
function ns:Update() function ns:Update()
BuildModel()
UIDropDownMenu_SetText(AltoholicFrameReputations_SelectFaction, GroupLabel(currentGroup))
addon:ScrollFrameUpdate(ReputationsScrollFrame_Desc) addon:ScrollFrameUpdate(ReputationsScrollFrame_Desc)
end end
@@ -302,15 +396,15 @@ function ns:OnEnter(frame)
local DS = DataStore local DS = DataStore
local realm, account = addon:GetCurrentRealm() local realm, account = addon:GetCurrentRealm()
local character = DS:GetCharacter(charName, realm, account) local character = DS:GetCharacter(charName, realm, account)
local factionGroup = Factions[currentXPack][currentFactionGroup] local faction = displayedFactions[ frame:GetParent():GetID() ]
local faction = factionGroup[ frame:GetParent():GetID() ].name if not faction then return end
local status, currentLevel, maxLevel, rate = DS:GetReputationInfo(character, faction) local status, currentLevel, maxLevel, rate = DS:GetReputationInfo(character, faction)
if not status then return end if not status then return end
AltoTooltip:SetOwner(frame, "ANCHOR_LEFT"); AltoTooltip:SetOwner(frame, "ANCHOR_LEFT");
AltoTooltip:ClearLines(); AltoTooltip:ClearLines();
AltoTooltip:AddLine(DS:GetColoredCharacterName(character) .. WHITE .. " @ " .. TEAL .. faction,1,1,1); AltoTooltip:AddLine((DS:GetColoredCharacterName(character) or "?") .. WHITE .. " @ " .. TEAL .. faction,1,1,1);
rate = format("%d", floor(rate)) .. "%" rate = format("%d", floor(rate)) .. "%"
AltoTooltip:AddLine(format("%s: %d/%d (%s)", status, currentLevel, maxLevel, rate),1,1,1 ) AltoTooltip:AddLine(format("%s: %d/%d (%s)", status, currentLevel, maxLevel, rate),1,1,1 )
@@ -346,12 +440,12 @@ function ns:OnClick(frame, button)
local DS = DataStore local DS = DataStore
local realm, account = addon:GetCurrentRealm() local realm, account = addon:GetCurrentRealm()
local character = DS:GetCharacter(charName, realm, account) local character = DS:GetCharacter(charName, realm, account)
local factionGroup = Factions[currentXPack][currentFactionGroup] local faction = displayedFactions[ frame:GetParent():GetID() ]
local faction = factionGroup[ frame:GetParent():GetID() ].name if not faction then return end
local status, currentLevel, maxLevel, rate = DS:GetReputationInfo(character, faction) local status, currentLevel, maxLevel, rate = DS:GetReputationInfo(character, faction)
if not status then return end if not status then return end
if ( button == "LeftButton" ) and ( IsShiftKeyDown() ) then if ( button == "LeftButton" ) and ( IsShiftKeyDown() ) then
local chat = ChatEdit_GetLastActiveWindow() local chat = ChatEdit_GetLastActiveWindow()
if chat:IsShown() then if chat:IsShown() then
+4 -4
View File
@@ -213,11 +213,11 @@
</Anchors> </Anchors>
<Scripts> <Scripts>
<OnLoad> <OnLoad>
local faction = (UnitFactionGroup("player") == "Alliance") and FACTION_ALLIANCE or FACTION_HORDE -- Default to "All factions"; the dropdown contents are built dynamically
-- from the categories actually scanned (see Reputations.lua / BuildModel).
UIDropDownMenu_SetWidth(self, 100) UIDropDownMenu_SetWidth(self, 140)
UIDropDownMenu_SetButtonWidth(self, 20) UIDropDownMenu_SetButtonWidth(self, 20)
UIDropDownMenu_SetText(self, faction) UIDropDownMenu_SetText(self, ALL)
UIDropDownMenu_Initialize(self, Altoholic.Reputations.DropDownFaction_Initialize) UIDropDownMenu_Initialize(self, Altoholic.Reputations.DropDownFaction_Initialize)
</OnLoad> </OnLoad>
</Scripts> </Scripts>
+37 -3
View File
@@ -603,15 +603,17 @@ local function BrowseCharacter(character)
currentResultLocation = L["Bank"] currentResultLocation = L["Bank"]
elseif (containerName == "Bag-2") then elseif (containerName == "Bag-2") then
currentResultLocation = KEYRING currentResultLocation = KEYRING
elseif string.sub(containerName, 1, 5) == "PBank" then -- CoA personal bank tab
currentResultLocation = "Personal Bank"
else else
local bagNum = tonumber(string.sub(containerName, 4)) local bagNum = tonumber(string.sub(containerName, 4))
if (bagNum >= 0) and (bagNum <= 4) then if (bagNum >= 0) and (bagNum <= 4) then
currentResultLocation = L["Bags"] currentResultLocation = L["Bags"]
else else
currentResultLocation = L["Bank"] currentResultLocation = L["Bank"]
end end
end end
for slotID = 1, container.size do for slotID = 1, container.size do
itemID, itemLink, itemCount = DS:GetSlotInfo(container, slotID) itemID, itemLink, itemCount = DS:GetSlotInfo(container, slotID)
@@ -710,7 +712,39 @@ local function BrowseRealm(realm, account, bothFactions)
currentResultType = nil currentResultType = nil
currentResultLocation = nil currentResultLocation = nil
end end
-- CoA realm bank: shared per-realm storage. Scanned once per realm (not per
-- character). Reported on the player-item line, attributed to the realm.
if addon.Options:Get("IncludeGuildBank") == 1 then
currentResultType = PLAYER_ITEM_LINE
-- Use the first scanned character of this realm as the row "source" so
-- the result lists the correct realm/account; if none, skip.
local anyChar
for _, character in pairs(DS:GetCharacters(realm, account)) do
anyChar = character
break
end
if anyChar then
for tabID = 1, 6 do
local tab = DS:GetRealmBankTab(realm, account, tabID)
if tab and tab.name then
currentResultKey = anyChar
for slotID = 1, (tab.size or 0) do
currentResultLocation = format("Realm Bank (%s)", tab.name)
local id = tab.ids[slotID]
if id then
VerifyItem((tab.links or {})[slotID] or id, (tab.counts or {})[slotID] or 1)
end
end
end
end
currentResultKey = nil
end
currentResultType = nil
currentResultLocation = nil
end
if addon.Options:Get("IncludeGuildSkills") == 1 and string.len(currentValue) > 1 then -- Check guild professions ? if addon.Options:Get("IncludeGuildSkills") == 1 and string.len(currentValue) > 1 then -- Check guild professions ?
local guild = addon:GetGuild() local guild = addon:GetGuild()
if guild and LTL then -- LTL won't be valid if there's a version mismatch (see :Init() ) if guild and LTL then -- LTL won't be valid if there's a version mismatch (see :Init() )
+84 -44
View File
@@ -81,12 +81,11 @@ function ns:Update()
end end
_G[entry..i.."Level"]:SetText("") _G[entry..i.."Level"]:SetText("")
_G[entry..i.."Skill1NormalText"]:SetText("") _G[entry..i.."Skill1NormalText"]:SetText("")
_G[entry..i.."Skill2NormalText"]:SetText("")
_G[entry..i.."CookingNormalText"]:SetText("") _G[entry..i.."CookingNormalText"]:SetText("")
_G[entry..i.."FirstAidNormalText"]:SetText("") _G[entry..i.."FirstAidNormalText"]:SetText("")
_G[entry..i.."FishingNormalText"]:SetText("") _G[entry..i.."FishingNormalText"]:SetText("")
_G[entry..i.."RidingNormalText"]:SetText("") _G[entry..i.."RidingNormalText"]:SetText("")
_G[ entry..i ]:SetID(line) _G[ entry..i ]:SetID(line)
_G[ entry..i ]:Show() _G[ entry..i ]:Show()
i = i + 1 i = i + 1
@@ -109,51 +108,48 @@ function ns:Update()
_G[entry..i.."Name"]:SetWidth(170) _G[entry..i.."Name"]:SetWidth(170)
_G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0) _G[entry..i.."Name"]:SetPoint("TOPLEFT", 10, 0)
_G[entry..i.."NameNormalText"]:SetWidth(170) _G[entry..i.."NameNormalText"]:SetWidth(170)
_G[entry..i.."NameNormalText"]:SetText(icon .. format("%s (%s)", DS:GetColoredCharacterName(character), DS:GetCharacterClass(character))) addon:SetCharacterRowNameLevel(entry, i, icon, character)
_G[entry..i.."Level"]:SetText(GREEN .. DS:GetCharacterLevel(character))
-- profession 1 -- CoA: render ALL primary professions the character knows into the
local field = Characters:GetField(line, "spellID1") -- single wide Professions cell, as a row of icon+rank segments.
if field then -- The list is precomputed in Characters.lua (field "professions")
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(field), size, size) .. " " -- and may be empty (unscanned char) -> cell renders blank. Every
icon = addon:TextureToFontstring2(addon:GetSpellIcon(field), size, size, inset, inset, inset, inset) .. " " -- value is guarded with "or 0" before GetColor/concat.
else local professions = Characters:GetField(line, "professions")
icon = "" 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 end
field = Characters:GetField(line, "skillRank1") _G[entry..i.."Skill1NormalText"]:SetText(profText)
_G[entry..i.."Skill1NormalText"]:SetText(icon .. ns:GetColor(field) .. field)
local 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")
_G[entry..i.."Skill2NormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- cooking -- cooking
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(2550), size, size) .. " " -- icon = addon:TextureToFontstring(addon:GetSpellIcon(2550), size, size) .. " "
icon = addon:TextureToFontstring2(addon:GetSpellIcon(2550), size, size, inset, inset, inset, inset) .. " " icon = addon:TextureToFontstring2(addon:GetSpellIcon(2550), size, size, inset, inset, inset, inset) .. " "
field = Characters:GetField(line, "cooking") field = Characters:GetField(line, "cooking") or 0
_G[entry..i.."CookingNormalText"]:SetText(icon .. ns:GetColor(field) .. field) _G[entry..i.."CookingNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- first aid -- first aid
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(3273), size, size) .. " " -- icon = addon:TextureToFontstring(addon:GetSpellIcon(3273), size, size) .. " "
icon = addon:TextureToFontstring2(addon:GetSpellIcon(3273), size, size, inset, inset, inset, inset) .. " " icon = addon:TextureToFontstring2(addon:GetSpellIcon(3273), size, size, inset, inset, inset, inset) .. " "
field = Characters:GetField(line, "firstaid") field = Characters:GetField(line, "firstaid") or 0
_G[entry..i.."FirstAidNormalText"]:SetText(icon .. ns:GetColor(field) .. field) _G[entry..i.."FirstAidNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- fishing -- fishing
-- icon = addon:TextureToFontstring(addon:GetSpellIcon(7733), size, size) .. " " -- icon = addon:TextureToFontstring(addon:GetSpellIcon(7733), size, size) .. " "
icon = addon:TextureToFontstring2(addon:GetSpellIcon(7733), size, size, inset, inset, inset, inset) .. " " icon = addon:TextureToFontstring2(addon:GetSpellIcon(7733), size, size, inset, inset, inset, inset) .. " "
field = Characters:GetField(line, "fishing") field = Characters:GetField(line, "fishing") or 0
_G[entry..i.."FishingNormalText"]:SetText(icon .. ns:GetColor(field) .. field) _G[entry..i.."FishingNormalText"]:SetText(icon .. ns:GetColor(field) .. field)
-- riding -- riding
field = Characters:GetField(line, "riding") field = Characters:GetField(line, "riding") or 0
if field >= 300 then if field >= 300 then
-- icon = addon:TextureToFontstring("Interface\\Icons\\Ability_Mount_Gryphon_01", size, size) .. " " -- 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) icon = addon:TextureToFontstring2("Interface\\Icons\\ability_mount_drake_bronze", size, size, inset, inset, inset, inset)
@@ -200,7 +196,6 @@ function ns:Update()
_G[entry..i.."NameNormalText"]:SetText(L["Totals"]) _G[entry..i.."NameNormalText"]:SetText(L["Totals"])
_G[entry..i.."Level"]:SetText(Characters:GetField(line, "level")) _G[entry..i.."Level"]:SetText(Characters:GetField(line, "level"))
_G[entry..i.."Skill1NormalText"]:SetText("") _G[entry..i.."Skill1NormalText"]:SetText("")
_G[entry..i.."Skill2NormalText"]:SetText("")
_G[entry..i.."CookingNormalText"]:SetText("") _G[entry..i.."CookingNormalText"]:SetText("")
_G[entry..i.."FirstAidNormalText"]:SetText("") _G[entry..i.."FirstAidNormalText"]:SetText("")
_G[entry..i.."FishingNormalText"]:SetText("") _G[entry..i.."FishingNormalText"]:SetText("")
@@ -233,12 +228,46 @@ function ns:OnEnter(frame)
local id = frame:GetID() local id = frame:GetID()
local skillName, rank, suggestion 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 if id == 1 then
skillName = Characters:GetField(line, "skillName1") local professions = Characters:GetField(line, "professions")
elseif id == 2 then AltoTooltip:ClearLines()
skillName = Characters:GetField(line, "skillName2") AltoTooltip:SetOwner(frame, "ANCHOR_RIGHT")
elseif id == 3 then 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 skillName = GetSpellInfo(2550) -- cooking
elseif id == 4 then elseif id == 4 then
skillName = GetSpellInfo(3273) -- First Aid skillName = GetSpellInfo(3273) -- First Aid
@@ -248,12 +277,11 @@ function ns:OnEnter(frame)
skillName = L["Riding"] skillName = L["Riding"]
end end
local DS = DataStore
local character = DS:GetCharacter(Characters:GetInfo(line))
local curRank, maxRank = DS:GetSkillInfo(character, skillName) 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) local profession = DS:GetProfession(character, skillName)
if (id >= 1) and (id <= 6) then if (id >= 3) and (id <= 6) then
if id == 6 then -- riding if id == 6 then -- riding
rank = ns:GetColor(curRank, 300) .. curRank .. "/" .. maxRank rank = ns:GetColor(curRank, 300) .. curRank .. "/" .. maxRank
else else
@@ -268,6 +296,7 @@ function ns:OnEnter(frame)
skillName = L["Rogue Proficiencies"] skillName = L["Rogue Proficiencies"]
local curLock, maxLock = DS:GetSkillInfo(character, L["Lockpicking"]) 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 rank = TEAL .. L["Lockpicking"] .. " " .. curLock .. "/" .. maxLock
suggestion = addon:GetSuggestion(L["Lockpicking"], curLock) suggestion = addon:GetSuggestion(L["Lockpicking"], curLock)
end end
@@ -277,7 +306,7 @@ function ns:OnEnter(frame)
AltoTooltip:AddLine(skillName,1,1,1); AltoTooltip:AddLine(skillName,1,1,1);
AltoTooltip:AddLine(GREEN..rank,1,1,1); AltoTooltip:AddLine(GREEN..rank,1,1,1);
if id <= 4 then -- all skills except fishing & riding 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 if skillName ~= GetSpellInfo(13614) and skillName ~= GetSpellInfo(8613) then -- no display for herbalism & skinning
AltoTooltip:AddLine(" "); AltoTooltip:AddLine(" ");
@@ -355,9 +384,13 @@ function ns:OnClick(frame, button)
local skillName local skillName
if id == 1 then if id == 1 then
skillName = Characters:GetField(line, "skillName1") -- CoA: id 1 is the combined Professions cell. A single click can't pick one
elseif id == 2 then -- of several professions, so default to the first known primary profession
skillName = Characters:GetField(line, "skillName2") -- (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 elseif id == 3 then
skillName = GetSpellInfo(2550) -- cooking skillName = GetSpellInfo(2550) -- cooking
elseif id == 4 then elseif id == 4 then
@@ -399,6 +432,13 @@ end
local skillColors = { RECIPE_GREY, RED, ORANGE, YELLOW, GREEN } local skillColors = { RECIPE_GREY, RED, ORANGE, YELLOW, GREEN }
function ns:GetColor(rank, skillCap) 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 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 end
+22 -12
View File
@@ -34,7 +34,7 @@
<Button name="AltoSkillsTemplate" virtual="true"> <Button name="AltoSkillsTemplate" virtual="true">
<Size> <Size>
<AbsDimension x="615" y="22"/> <AbsDimension x="745" y="22"/>
</Size> </Size>
<Layers> <Layers>
<Layer level="BACKGROUND"> <Layer level="BACKGROUND">
@@ -108,7 +108,14 @@
<NormalFont style="GameFontNormalSmallLeft"/> <NormalFont style="GameFontNormalSmallLeft"/>
<HighlightFont style="GameFontHighlightSmallLeft"/> <HighlightFont style="GameFontHighlightSmallLeft"/>
</Button> </Button>
<!-- CoA: single wide "Professions" cell. Lists every known primary
profession (incl. customs Woodcutting/Woodworking) as icon+rank
segments, replacing the old fixed Prof.1/Prof.2 columns. Width
(325) must match the "Professions" header in TabSummary.lua. -->
<Button name="$parentSkill1" inherits="AltoSkillButtonTemplate" id="1"> <Button name="$parentSkill1" inherits="AltoSkillButtonTemplate" id="1">
<Size>
<AbsDimension x="325" y="18"/>
</Size>
<Anchors> <Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parentLevel" relativePoint="BOTTOMRIGHT"> <Anchor point="BOTTOMLEFT" relativeTo="$parentLevel" relativePoint="BOTTOMRIGHT">
<Offset> <Offset>
@@ -116,19 +123,22 @@
</Offset> </Offset>
</Anchor> </Anchor>
</Anchors> </Anchors>
</Button> <ButtonText name="$parentNormalText" justifyH="LEFT">
<Button name="$parentSkill2" inherits="AltoSkillButtonTemplate" id="2"> <Size>
<Anchors> <AbsDimension x="325" y="18"/>
<Anchor point="BOTTOMLEFT" relativeTo="$parentSkill1" relativePoint="BOTTOMRIGHT"> </Size>
<Offset> <Anchors>
<AbsDimension x="0" y="0"/> <Anchor point="LEFT">
</Offset> <Offset>
</Anchor> <AbsDimension x="0" y="0"/>
</Anchors> </Offset>
</Anchor>
</Anchors>
</ButtonText>
</Button> </Button>
<Button name="$parentCooking" inherits="AltoSkillButtonTemplate" id="3"> <Button name="$parentCooking" inherits="AltoSkillButtonTemplate" id="3">
<Anchors> <Anchors>
<Anchor point="BOTTOMLEFT" relativeTo="$parentSkill2" relativePoint="BOTTOMRIGHT"> <Anchor point="BOTTOMLEFT" relativeTo="$parentSkill1" relativePoint="BOTTOMRIGHT">
<Offset> <Offset>
<AbsDimension x="0" y="0"/> <AbsDimension x="0" y="0"/>
</Offset> </Offset>
@@ -167,7 +177,7 @@
<Frame name="AltoholicFrameSkills" parent="AltoholicTabSummary" hidden="true"> <Frame name="AltoholicFrameSkills" parent="AltoholicTabSummary" hidden="true">
<Size> <Size>
<AbsDimension x="615" y="306"/> <AbsDimension x="745" y="306"/>
</Size> </Size>
<Anchors> <Anchors>
<Anchor point="TOPRIGHT"> <Anchor point="TOPRIGHT">
+1 -1
View File
@@ -228,7 +228,7 @@ function ns:UpdateViewIcons()
AltoholicTabCharacters_FirstAid:Show() AltoholicTabCharacters_FirstAid:Show()
local i = 1 local i = 1
for skillName, skill in pairs(DS:GetPrimaryProfessions(character)) do for skillName, skill in pairs(DS:GetPrimaryProfessions(character) or {}) do -- CoA: getter returns no value for chars DataStore_Crafts hasn't scanned
local itemName = "AltoholicTabCharacters_Prof" .. i local itemName = "AltoholicTabCharacters_Prof" .. i
local item = _G[itemName] local item = _G[itemName]
local spellID = DataStore:GetProfessionSpellID(skillName) local spellID = DataStore:GetProfessionSpellID(skillName)
+2 -2
View File
@@ -432,13 +432,13 @@ function Altoholic:MoveMinimapIcon()
end end
function Altoholic.Options:Get(name) function Altoholic.Options:Get(name)
if addon.db and addon.db.global then if addon.db and addon.db.global and addon.db.global.options then
return addon.db.global.options[name] return addon.db.global.options[name]
end end
end end
function Altoholic.Options:Set(name, value) function Altoholic.Options:Set(name, value)
if addon.db and addon.db.global then if addon.db and addon.db.global and addon.db.global.options then
addon.db.global.options[name] = value addon.db.global.options[name] = value
end end
end end
+5 -2
View File
@@ -160,8 +160,11 @@ function ns:SetMode(mode)
elseif currentMode == 3 then elseif currentMode == 3 then
Columns:Add(NAME, 100, function(self) addon.Characters:Sort(self, "GetCharacterName") end) 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(LEVEL, 60, function(self) addon.Characters:Sort(self, "GetCharacterLevel") end)
Columns:Add(L["Prof. 1"], 65, function(self) addon.Characters:Sort(self, "skillName1") end) -- CoA: a character can know ALL professions at once, so the old fixed
Columns:Add(L["Prof. 2"], 65, function(self) addon.Characters:Sort(self, "skillName2") end) -- 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 title = GetSpellInfo(2550) -- cooking
Columns:Add(title, 65, function(self) addon.Characters:Sort(self, "GetCookingRank") end) Columns:Add(title, 65, function(self) addon.Characters:Sort(self, "GetCookingRank") end)
title = GetSpellInfo(3273) -- First Aid title = GetSpellInfo(3273) -- First Aid
+1 -1
View File
@@ -448,7 +448,7 @@ local function GetRecipeOwners(professionName, link, recipeLevel)
table.insert(know, coloredName) table.insert(know, coloredName)
else else
local currentLevel = DataStore:GetSkillInfo(character, professionName) local currentLevel = DataStore:GetSkillInfo(character, professionName)
if currentLevel > 0 then if currentLevel and currentLevel > 0 then -- CoA: getter returns no value for chars DataStore_Skills hasn't scanned
if currentLevel < recipeLevel then if currentLevel < recipeLevel then
table.insert(willLearn, format("%s |r(%d)", coloredName, currentLevel)) table.insert(willLearn, format("%s |r(%d)", coloredName, currentLevel))
else else
Binary file not shown.
+266 -15
View File
@@ -35,6 +35,24 @@ local MSG_BANKTAB_TRANSFER = 6 -- .. or send the data
local AddonDB_Defaults = { local AddonDB_Defaults = {
global = { global = {
-- CoA (Ascension) Realm Bank: shared per-realm storage, keyed by "Account.Realm"
-- (NOT per-character). Mirrors the guild bank's Tabs layout so the same
-- scan/read helpers can be reused.
RealmBanks = {
['*'] = { -- ["Account.Realm"]
lastUpdate = nil,
Tabs = {
['*'] = { -- tabID = table index [1] to [6]
name = nil,
icon = nil,
size = 0,
ids = {},
links = {},
counts = {}
}
},
}
},
Guilds = { Guilds = {
['*'] = { -- ["Account.Realm.Name"] ['*'] = { -- ["Account.Realm.Name"]
money = nil, money = nil,
@@ -204,6 +222,16 @@ end
local BAGS = 1 -- All bags, 0 to 11, and keyring ( id -2 ) local BAGS = 1 -- All bags, 0 to 11, and keyring ( id -2 )
local BANK = 2 -- 28 main slots local BANK = 2 -- 28 main slots
local GUILDBANK = 3 -- 98 main slots local GUILDBANK = 3 -- 98 main slots
local PERSONALBANK = 4 -- CoA personal bank (per-character), read via guild bank API
local REALMBANK = 5 -- CoA realm bank (per-realm), read via guild bank API
-- CoA reuses the Guild Bank UI for the Personal Bank and the Realm Bank.
-- These container key prefixes keep their scanned tabs out of the regular
-- "Bag0".."Bag11" / "Bag100" / "Bag-2" namespace so item-count loops can tell
-- them apart. Personal bank tabs live under the character; realm bank tabs live
-- in the realm-keyed global table.
local PERSONALBANK_PREFIX = "PBank" -- char.Containers["PBank1".."PBank6"]
local MAX_BANK_TABS = 6
local ContainerTypes = { local ContainerTypes = {
[BAGS] = { [BAGS] = {
@@ -262,9 +290,81 @@ local ContainerTypes = {
GetCooldown = function(self, slotID) GetCooldown = function(self, slotID)
return nil return nil
end, end,
},
-- Personal & Realm banks are read through the guild bank API (CoA reuses that UI).
[PERSONALBANK] = {
GetSize = function(self)
return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end,
GetFreeSlots = function(self)
return nil, nil
end,
GetLink = function(self, slotID, tabID)
return GetGuildBankItemLink(tabID, slotID)
end,
GetCount = function(self, slotID, tabID)
local _, count = GetGuildBankItemInfo(tabID, slotID)
return count
end,
GetCooldown = function(self, slotID)
return nil
end,
},
[REALMBANK] = {
GetSize = function(self)
return MAX_GUILDBANK_SLOTS_PER_TAB or 98
end,
GetFreeSlots = function(self)
return nil, nil
end,
GetLink = function(self, slotID, tabID)
return GetGuildBankItemLink(tabID, slotID)
end,
GetCount = function(self, slotID, tabID)
local _, count = GetGuildBankItemInfo(tabID, slotID)
return count
end,
GetCooldown = function(self, slotID)
return nil
end,
} }
} }
-- *** CoA bank-type detection ***
-- CoA exposes BANK_PERMISSIONS_PAYLOAD via the client-only HasJsonCacheData /
-- GetJsonCacheData / C_Serialize APIs. Guard every call so this is harmless on
-- non-CoA clients (where the personal/realm bank simply never triggers).
local function GetCoABankType()
if not (HasJsonCacheData and GetJsonCacheData and C_Serialize and C_Serialize.FromJSON) then
return "guild"
end
if not HasJsonCacheData("BANK_PERMISSIONS_PAYLOAD", 0) then
return "guild"
end
local json = GetJsonCacheData("BANK_PERMISSIONS_PAYLOAD", 0)
if not json then
return "guild"
end
local jsonObject = C_Serialize:FromJSON(json)
if not jsonObject then
return "guild"
end
if jsonObject.IsPersonalBank then
return "personal"
elseif jsonObject.IsRealmBank then
return "realm"
end
return "guild"
end
local function GetRealmBankKey()
return format("%s.%s", THIS_ACCOUNT, GetRealmName())
end
local function GetThisRealmBank()
return addon.db.global.RealmBanks[GetRealmBankKey()]
end
-- *** Scanning functions *** -- *** Scanning functions ***
local function ScanContainer(bagID, containerType) local function ScanContainer(bagID, containerType)
local Container = ContainerTypes[containerType] local Container = ContainerTypes[containerType]
@@ -273,8 +373,15 @@ local function ScanContainer(bagID, containerType)
if containerType == GUILDBANK then if containerType == GUILDBANK then
local thisGuild = GetThisGuild() local thisGuild = GetThisGuild()
if not thisGuild then return end if not thisGuild then return end
bag = thisGuild.Tabs[bagID] -- bag is actually the current tab bag = thisGuild.Tabs[bagID] -- bag is actually the current tab
elseif containerType == PERSONALBANK then
-- per-character storage, keyed by a tab-specific prefix so it never
-- collides with the normal bag/bank containers
bag = addon.ThisCharacter.Containers[PERSONALBANK_PREFIX .. bagID]
elseif containerType == REALMBANK then
local realmBank = GetThisRealmBank()
bag = realmBank.Tabs[bagID] -- bag is the current tab in the realm-keyed table
else else
bag = addon.ThisCharacter.Containers["Bag" .. bagID] bag = addon.ThisCharacter.Containers["Bag" .. bagID]
wipe(bag.cooldowns) -- does not exist for a guild bank wipe(bag.cooldowns) -- does not exist for a guild bank
@@ -315,8 +422,14 @@ local function ScanContainer(bagID, containerType)
bag.cooldowns[index] = startTime .."|".. duration .. "|" .. 1 bag.cooldowns[index] = startTime .."|".. duration .. "|" .. 1
end end
end end
addon.ThisCharacter.lastUpdate = time() if containerType == REALMBANK then
GetThisRealmBank().lastUpdate = time()
else
-- personal bank, bags, bank and guild bank all stamp the character;
-- this is what gates DataStore's "no value" guard for char-based getters
addon.ThisCharacter.lastUpdate = time()
end
end end
local function ScanBagSlotsInfo() local function ScanBagSlotsInfo()
@@ -463,25 +576,76 @@ local function OnBankFrameOpened()
addon:RegisterEvent("PLAYERBANKSLOTS_CHANGED", OnPlayerBankSlotsChanged) addon:RegisterEvent("PLAYERBANKSLOTS_CHANGED", OnPlayerBankSlotsChanged)
end end
-- Records the name/icon of a personal-bank tab. Reuses the per-character
-- container created by ScanContainer (PERSONALBANK_PREFIX .. tabID).
local function ScanPersonalBankInfo(tabID)
local bag = addon.ThisCharacter.Containers[PERSONALBANK_PREFIX .. tabID]
bag.name, bag.icon = GetGuildBankTabInfo(tabID)
end
local function ScanRealmBankInfo(tabID)
local t = GetThisRealmBank().Tabs[tabID]
t.name, t.icon = GetGuildBankTabInfo(tabID)
end
local function OnGuildBankFrameClosed() local function OnGuildBankFrameClosed()
addon:UnregisterEvent("GUILDBANKFRAME_CLOSED") addon:UnregisterEvent("GUILDBANKFRAME_CLOSED")
addon:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED") addon:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED")
local guildName = GetGuildInfo("player") -- only broadcast guild bank timestamps for the actual guild bank
if guildName then if addon.coaBankType == "guild" then
GuildBroadcast(MSG_SEND_BANK_TIMESTAMPS, GetBankTimestamps(guildName)) local guildName = GetGuildInfo("player")
if guildName then
GuildBroadcast(MSG_SEND_BANK_TIMESTAMPS, GetBankTimestamps(guildName))
end
end end
addon.coaBankType = nil
addon.coaBankAvailableTabs = nil
end end
local function OnGuildBankBagSlotsChanged() local function OnGuildBankBagSlotsChanged()
ScanContainer(GetCurrentGuildBankTab(), GUILDBANK) local currentTab = GetCurrentGuildBankTab()
ScanGuildBankInfo()
if addon.coaBankType == "personal" then
ScanContainer(currentTab, PERSONALBANK)
ScanPersonalBankInfo(currentTab)
elseif addon.coaBankType == "realm" then
ScanContainer(currentTab, REALMBANK)
ScanRealmBankInfo(currentTab)
else
-- regular guild bank, unchanged behaviour
ScanContainer(currentTab, GUILDBANK)
ScanGuildBankInfo()
end
end end
local function OnGuildBankFrameOpened() local function OnGuildBankFrameOpened()
-- CoA reuses the guild bank UI for the personal & realm banks; detect which
-- one this is BEFORE doing anything guild-specific. Harmless ("guild") when
-- the CoA JSON APIs are absent.
addon.coaBankType = GetCoABankType()
addon:RegisterEvent("GUILDBANKFRAME_CLOSED", OnGuildBankFrameClosed) addon:RegisterEvent("GUILDBANKFRAME_CLOSED", OnGuildBankFrameClosed)
addon:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED", OnGuildBankBagSlotsChanged) addon:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED", OnGuildBankBagSlotsChanged)
if addon.coaBankType == "personal" or addon.coaBankType == "realm" then
-- Pre-query the other tabs so we snapshot the whole bank without the
-- player having to click each tab. Each QueryGuildBankTab triggers a
-- GUILDBANKBAGSLOTS_CHANGED for that tab, handled above.
if QueryGuildBankTab then
local currentTab = GetCurrentGuildBankTab and GetCurrentGuildBankTab() or 0
for tabID = 1, MAX_BANK_TABS do
local avail = GetGuildBankTabInfo(tabID)
if type(avail) == "string" and tabID ~= currentTab then
QueryGuildBankTab(tabID)
end
end
end
return
end
-- regular guild bank
local thisGuild = GetThisGuild() local thisGuild = GetThisGuild()
if thisGuild then if thisGuild then
thisGuild.money = GetGuildBankMoney() thisGuild.money = GetGuildBankMoney()
@@ -550,19 +714,22 @@ end
local function _GetContainerItemCount(character, searchedID) local function _GetContainerItemCount(character, searchedID)
local bagCount = 0 local bagCount = 0
local bankCount = 0 local bankCount = 0
local personalBankCount = 0
local id local id
for containerName, container in pairs(character.Containers) do for containerName, container in pairs(character.Containers) do
for slotID=1, container.size do for slotID=1, container.size do
id = container.ids[slotID] id = container.ids[slotID]
if (id) and (id == searchedID) then if (id) and (id == searchedID) then
local itemCount = container.counts[slotID] or 1 local itemCount = container.counts[slotID] or 1
if (containerName == "Bag100") then if (containerName == "Bag100") then
bankCount = bankCount + itemCount bankCount = bankCount + itemCount
elseif (containerName == "Bag-2") then elseif (containerName == "Bag-2") then
bagCount = bagCount + itemCount bagCount = bagCount + itemCount
elseif string.sub(containerName, 1, #PERSONALBANK_PREFIX) == PERSONALBANK_PREFIX then
personalBankCount = personalBankCount + itemCount -- CoA personal bank tab
else else
local bagNum = tonumber(string.sub(containerName, 4)) local bagNum = tonumber(string.sub(containerName, 4))
if (bagNum >= 0) and (bagNum <= 4) then if (bagNum >= 0) and (bagNum <= 4) then
@@ -575,7 +742,79 @@ local function _GetContainerItemCount(character, searchedID)
end end
end end
return bagCount, bankCount -- 3rd return value (personal bank) is additive and backward-compatible:
-- existing callers that expect (bags, bank) just ignore it.
return bagCount, bankCount, personalBankCount
end
-- *** CoA personal bank (per-character) ***
local function _GetPersonalBankItemCount(character, searchedID)
local count = 0
for containerName, container in pairs(character.Containers) do
if string.sub(containerName, 1, #PERSONALBANK_PREFIX) == PERSONALBANK_PREFIX then
for slotID = 1, container.size do
if container.ids[slotID] == searchedID then
count = count + (container.counts[slotID] or 1)
end
end
end
end
return count
end
local function _GetPersonalBankTabCount(character)
-- number of personal bank tabs that have been scanned (have a size)
local n = 0
for tabID = 1, MAX_BANK_TABS do
local container = character.Containers[PERSONALBANK_PREFIX .. tabID]
if container and (container.size or 0) > 0 then
n = n + 1
end
end
return n
end
-- *** CoA realm bank (per-realm, NOT char-based) ***
-- These take a realm key string ("Account.Realm") directly and read the global
-- RealmBanks table, so they bypass DataStore's char/guild "no value" wrapper.
local function _GetRealmBankKey(realm, account)
realm = realm or GetRealmName()
account = account or THIS_ACCOUNT
return format("%s.%s", account, realm)
end
local function _GetRealmBank(realm, account)
return addon.db.global.RealmBanks[_GetRealmBankKey(realm, account)]
end
local function _GetRealmBankItemCount(realm, account, searchedID)
local realmBank = _GetRealmBank(realm, account)
if not realmBank then return 0 end
local count = 0
for _, tab in pairs(realmBank.Tabs) do
for slotID, id in pairs(tab.ids) do
if id == searchedID then
count = count + (tab.counts[slotID] or 1)
end
end
end
return count
end
local function _GetRealmBankTab(realm, account, tabID)
local realmBank = _GetRealmBank(realm, account)
return realmBank and realmBank.Tabs[tabID]
end
local function _GetRealmBankTabName(realm, account, tabID)
local tab = _GetRealmBankTab(realm, account, tabID)
return tab and tab.name
end
local function _GetRealmBankLastUpdate(realm, account)
local realmBank = _GetRealmBank(realm, account)
return realmBank and realmBank.lastUpdate
end end
local function _GetNumBagSlots(character) local function _GetNumBagSlots(character)
@@ -721,6 +960,14 @@ local PublicMethods = {
RejectBankTabRequest = _RejectBankTabRequest, RejectBankTabRequest = _RejectBankTabRequest,
SendBankTabToGuildMember = _SendBankTabToGuildMember, SendBankTabToGuildMember = _SendBankTabToGuildMember,
GetGuildBankTabSuppliers = _GetGuildBankTabSuppliers, GetGuildBankTabSuppliers = _GetGuildBankTabSuppliers,
-- CoA personal bank (per-character)
GetPersonalBankItemCount = _GetPersonalBankItemCount,
GetPersonalBankTabCount = _GetPersonalBankTabCount,
-- CoA realm bank (per-realm; takes realm/account, not a character/guild key)
GetRealmBankItemCount = _GetRealmBankItemCount,
GetRealmBankTab = _GetRealmBankTab,
GetRealmBankTabName = _GetRealmBankTabName,
GetRealmBankLastUpdate = _GetRealmBankLastUpdate,
} }
-- *** Guild Comm *** -- *** Guild Comm ***
@@ -809,7 +1056,11 @@ function addon:OnInitialize()
DataStore:SetCharacterBasedMethod("GetNumFreeBagSlots") DataStore:SetCharacterBasedMethod("GetNumFreeBagSlots")
DataStore:SetCharacterBasedMethod("GetNumBankSlots") DataStore:SetCharacterBasedMethod("GetNumBankSlots")
DataStore:SetCharacterBasedMethod("GetNumFreeBankSlots") DataStore:SetCharacterBasedMethod("GetNumFreeBankSlots")
DataStore:SetCharacterBasedMethod("GetPersonalBankItemCount")
DataStore:SetCharacterBasedMethod("GetPersonalBankTabCount")
-- Realm bank methods are intentionally NOT char/guild based: they take a
-- realm/account string pair directly and read the realm-keyed global table.
DataStore:SetGuildBasedMethod("GetGuildBankItemCount") DataStore:SetGuildBasedMethod("GetGuildBankItemCount")
DataStore:SetGuildBasedMethod("GetGuildBankTab") DataStore:SetGuildBasedMethod("GetGuildBankTab")
DataStore:SetGuildBasedMethod("GetGuildBankTabName") DataStore:SetGuildBasedMethod("GetGuildBankTabName")
@@ -2,7 +2,7 @@
## Title: DataStore_Containers ## Title: DataStore_Containers
## Notes: Stores information about character bags, bank, and guild banks ## Notes: Stores information about character bags, bank, and guild banks
## Author: Thaoky (EU-Marécages de Zangar) ## Author: Thaoky (EU-Marécages de Zangar)
## Version: 3.3.001 ## Version: 3.3.001-coa.9
## Dependencies: DataStore ## Dependencies: DataStore
## OptionalDeps: Ace3 ## OptionalDeps: Ace3
## SavedVariables: DataStore_ContainersDB ## SavedVariables: DataStore_ContainersDB
+6 -3
View File
@@ -167,10 +167,13 @@ function ScanInventory()
end end
-- *** Event Handlers *** -- *** Event Handlers ***
local hasScannedThisSession
local function OnPlayerAlive() local function OnPlayerAlive()
-- print("DataStore_Inventory.lua") -- DEBUG 2025 07 21 -- CoA: scan once at login. PLAYER_ALIVE also fires on resurrect / Feign-Death cancel where gear
if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard -- is unchanged, so skip those. (The previous "only when ghost" gate also skipped login, so iLvl
-- never populated and UNIT_INVENTORY_CHANGED was the only scan path - see commit fdcb25a.)
if hasScannedThisSession then return end
hasScannedThisSession = true
ScanInventory() ScanInventory()
end end
+1 -1
View File
@@ -3,7 +3,7 @@
## Notes: Stores information about character inventory ## Notes: Stores information about character inventory
## Author: Thaoky (EU-Marécages de Zangar) ## Author: Thaoky (EU-Marécages de Zangar)
## X-Edited-By: Exiles (Sub-Net) ## X-Edited-By: Exiles (Sub-Net)
## Version: 3.3.002-coa.2 ## Version: 3.3.002-coa.5
## Dependencies: DataStore ## Dependencies: DataStore
## OptionalDeps: Ace3 ## OptionalDeps: Ace3
## SavedVariables: DataStore_InventoryDB ## SavedVariables: DataStore_InventoryDB
@@ -15,9 +15,10 @@ local THIS_ACCOUNT = "Default"
local AddonDB_Defaults = { local AddonDB_Defaults = {
global = { global = {
Characters = { Characters = {
['*'] = { -- ["Account.Realm.Name"] ['*'] = { -- ["Account.Realm.Name"]
lastUpdate = nil, 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 end
local function _GetReputations(character) 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 end
local PublicMethods = { local PublicMethods = {
GetReputationInfo = _GetReputationInfo, GetReputationInfo = _GetReputationInfo,
GetRawReputationInfo = _GetRawReputationInfo, GetRawReputationInfo = _GetRawReputationInfo,
GetReputations = _GetReputations, GetReputations = _GetReputations,
GetReputationHeaders = _GetReputationHeaders,
} }
function addon:OnInitialize() function addon:OnInitialize()
@@ -75,6 +82,7 @@ function addon:OnInitialize()
DataStore:SetCharacterBasedMethod("GetReputationInfo") DataStore:SetCharacterBasedMethod("GetReputationInfo")
DataStore:SetCharacterBasedMethod("GetRawReputationInfo") DataStore:SetCharacterBasedMethod("GetRawReputationInfo")
DataStore:SetCharacterBasedMethod("GetReputations") DataStore:SetCharacterBasedMethod("GetReputations")
DataStore:SetCharacterBasedMethod("GetReputationHeaders")
end end
function addon:OnEnable() function addon:OnEnable()
@@ -125,13 +133,24 @@ local function ScanReputations()
SaveHeaders() SaveHeaders()
local factions = addon.ThisCharacter.Factions local factions = addon.ThisCharacter.Factions
local headers = addon.ThisCharacter.Headers or {}
addon.ThisCharacter.Headers = headers
wipe(factions) 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 for i = 1, GetNumFactions() do -- 2nd pass, data collection
local name, _, _, bottom, top, earned, _, _, isHeader, _, hasRep = GetFactionInfo(i) local name, _, _, bottom, top, earned, _, _, isHeader, _, hasRep = GetFactionInfo(i)
if (not isHeader) or (isHeader and hasRep) then if (not isHeader) or (isHeader and hasRep) then
-- new in 3.0.2, headers may have rep, ex: alliance vanguard + horde expedition -- new in 3.0.2, headers may have rep, ex: alliance vanguard + horde expedition
factions[name] = bottom .. "|" .. top .. "|" .. earned 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
end end
@@ -2,7 +2,7 @@
## Title: DataStore_Reputations ## Title: DataStore_Reputations
## Notes: Stores information about character reputation levels ## Notes: Stores information about character reputation levels
## Author: Thaoky (EU-Marécages de Zangar) ## Author: Thaoky (EU-Marécages de Zangar)
## Version: 3.3.001 ## Version: 3.3.001-coa.10
## Dependencies: DataStore ## Dependencies: DataStore
## OptionalDeps: Ace3 ## OptionalDeps: Ace3
## SavedVariables: DataStore_ReputationsDB ## SavedVariables: DataStore_ReputationsDB
+84 -2
View File
@@ -7,7 +7,7 @@ if not DataStore then return end
local addonName = "DataStore_Skills" 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] local addon = _G[addonName]
@@ -35,6 +35,44 @@ local function _GetPrimaryProfessions(character)
return character.Skills[L["Professions"]] return character.Skills[L["Professions"]]
end 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 = <skillName>, rank = <n>, maxRank = <n> }
-- 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) local function _GetSecondaryProfessions(character)
return character.Skills[L["Secondary Skills"]] return character.Skills[L["Secondary Skills"]]
end end
@@ -77,8 +115,25 @@ local function _GetRidingRank(character)
return _GetSkillInfoByCategory(character, L["Secondary Skills"], L["Riding"]) return _GetSkillInfoByCategory(character, L["Secondary Skills"], L["Riding"])
end end
-- CoA (Ascension Vol'jin) adds two custom professions on top of the vanilla 15:
-- Woodcutting (gathering, base spell 13977860) and Woodworking (crafting,
-- ranks 1005008-1005011). They register as normal skill lines in the in-game
-- skill UI under their English names "Woodcutting" / "Woodworking" (confirmed
-- against coa-professionmenu, which reads them via GetSkillLineInfo by name).
-- ScanSkills() already buckets them by whatever category header they sit under,
-- so a name-based lookup across all categories retrieves them regardless of the
-- header. On non-CoA realms the skill simply doesn't exist and these return 0, 0.
local function _GetWoodcuttingRank(character)
return _GetSkillInfo(character, "Woodcutting")
end
local function _GetWoodworkingRank(character)
return _GetSkillInfo(character, "Woodworking")
end
local PublicMethods = { local PublicMethods = {
GetPrimaryProfessions = _GetPrimaryProfessions, GetPrimaryProfessions = _GetPrimaryProfessions,
GetPrimaryProfessionList = _GetPrimaryProfessionList,
GetSecondaryProfessions = _GetSecondaryProfessions, GetSecondaryProfessions = _GetSecondaryProfessions,
GetSkillInfo = _GetSkillInfo, GetSkillInfo = _GetSkillInfo,
GetSkillInfoByCategory = _GetSkillInfoByCategory, GetSkillInfoByCategory = _GetSkillInfoByCategory,
@@ -86,6 +141,8 @@ local PublicMethods = {
GetCookingRank = _GetCookingRank, GetCookingRank = _GetCookingRank,
GetFishingRank = _GetFishingRank, GetFishingRank = _GetFishingRank,
GetRidingRank = _GetRidingRank, GetRidingRank = _GetRidingRank,
GetWoodcuttingRank = _GetWoodcuttingRank,
GetWoodworkingRank = _GetWoodworkingRank,
} }
function addon:OnInitialize() function addon:OnInitialize()
@@ -93,6 +150,7 @@ function addon:OnInitialize()
DataStore:RegisterModule(addonName, addon, PublicMethods) DataStore:RegisterModule(addonName, addon, PublicMethods)
DataStore:SetCharacterBasedMethod("GetPrimaryProfessions") DataStore:SetCharacterBasedMethod("GetPrimaryProfessions")
DataStore:SetCharacterBasedMethod("GetPrimaryProfessionList")
DataStore:SetCharacterBasedMethod("GetSecondaryProfessions") DataStore:SetCharacterBasedMethod("GetSecondaryProfessions")
DataStore:SetCharacterBasedMethod("GetSkillInfo") DataStore:SetCharacterBasedMethod("GetSkillInfo")
DataStore:SetCharacterBasedMethod("GetSkillInfoByCategory") DataStore:SetCharacterBasedMethod("GetSkillInfoByCategory")
@@ -100,15 +158,25 @@ function addon:OnInitialize()
DataStore:SetCharacterBasedMethod("GetCookingRank") DataStore:SetCharacterBasedMethod("GetCookingRank")
DataStore:SetCharacterBasedMethod("GetFishingRank") DataStore:SetCharacterBasedMethod("GetFishingRank")
DataStore:SetCharacterBasedMethod("GetRidingRank") DataStore:SetCharacterBasedMethod("GetRidingRank")
DataStore:SetCharacterBasedMethod("GetWoodcuttingRank")
DataStore:SetCharacterBasedMethod("GetWoodworkingRank")
end end
function addon:OnEnable() 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_ALIVE")
addon:RegisterEvent("PLAYER_ENTERING_WORLD", "ScanOnLogin")
addon:RegisterEvent("SKILL_LINES_CHANGED", "ScanOnLogin")
addon:RegisterEvent("CHAT_MSG_SKILL") addon:RegisterEvent("CHAT_MSG_SKILL")
end end
function addon:OnDisable() function addon:OnDisable()
addon:UnregisterEvent("PLAYER_ALIVE") addon:UnregisterEvent("PLAYER_ALIVE")
addon:UnregisterEvent("PLAYER_ENTERING_WORLD")
addon:UnregisterEvent("SKILL_LINES_CHANGED")
addon:UnregisterEvent("CHAT_MSG_SKILL") addon:UnregisterEvent("CHAT_MSG_SKILL")
end end
@@ -172,10 +240,24 @@ end
function addon:PLAYER_ALIVE() function addon:PLAYER_ALIVE()
-- print("DataStore_Skills.lua") -- DEBUG 2025 07 21 -- 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 if not UnitIsGhost("player") then return end -- only scan if player released spirit and went to graveyard
ScanSkills() ScanSkills()
end 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 -- this turns
-- "Your skill in %s has increased to %d." -- "Your skill in %s has increased to %d."
-- into -- into
+1 -1
View File
@@ -2,7 +2,7 @@
## Title: DataStore_Skills ## Title: DataStore_Skills
## Notes: Stores information about character skills ## Notes: Stores information about character skills
## Author: Thaoky (EU-Marécages de Zangar) ## Author: Thaoky (EU-Marécages de Zangar)
## Version: 3.3.002 ## Version: 3.3.002-coa.10
## Dependencies: DataStore ## Dependencies: DataStore
## OptionalDeps: Ace3 ## OptionalDeps: Ace3
## SavedVariables: DataStore_SkillsDB ## SavedVariables: DataStore_SkillsDB
+13
View File
@@ -5,6 +5,19 @@ 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`. 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 (1232) 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 <version>` (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).
- **3.3.002b-coa.6** — Final straggler: guarded `AccountSharing.lua` realm/name line (name getter was the last `format` arg, so a no-value collapsed it to a format error). Concludes the frame sweep.
- **3.3.002b-coa.5** — Refactor + completeness pass:
- Extracted the duplicated character header/row blocks into `Altoholic:AddCharacterTooltipHeader()` and `Altoholic:SetCharacterRowNameLevel()` — the nil-guards now live in one place instead of being copy-pasted across frames.
- Fixed crash sites the per-frame sweep had missed: `Skills.lua` (row + skill-rank tooltip), `Keys.lua` (×3 `format` with possibly-nil name), and the latent `ShowClassIcons` sort (`Altoholic.lua` — getters bypass their own `or 0` via the DataStore wrapper).
- Restored login scanning: `OnPlayerAlive` in `Altoholic.lua` + `DataStore_Inventory` was gated to ghost-only (commit fdcb25a), so inventory/iLvl never populated on login. Now scans once per session (still skips resurrect/Feign-Death rescans). Removed dated DEBUG leftovers.
- **3.3.002b-coa.4** — Rebranded to the Exiles fork (title `Altoholic (Exiles)`; Thaoky/Telkar-RG still credited as Author). Hardened **all** Altoholic frames against partial alt records: DataStore char-based getters return *no value* for any module that hasn't scanned a char, and the frames assumed full data everywhere. Guarded every `format`/concat/arithmetic/`pairs` site across AccountSummary, Activity, BagUsage, Quests, Reputations, TabCharacters, `DrawCharacterTooltip`, and the recipe tooltip. No DataStore contract change.
- **3.3.002b-coa.3** — More partial-record guards in `DataStore_Characters` (own alts seen via guild comm but never fully scanned): - **3.3.002b-coa.3** — More partial-record guards in `DataStore_Characters` (own alts seen via guild comm but never fully scanned):
- `GetXPRate` — guard nil/zero `XPMax` (crashed AccountSummary; also fixes div-by-zero at max level). - `GetXPRate` — guard nil/zero `XPMax` (crashed AccountSummary; also fixes div-by-zero at max level).
- `GetRestXPRate` — guard nil/zero `XPMax` and nil `lastLogoutTimestamp` (`nil ~= 0` was true and crashed). - `GetRestXPRate` — guard nil/zero `XPMax` and nil `lastLogoutTimestamp` (`nil ~= 0` was true and crashed).