diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index 709a7cd..2f93975 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -37,10 +37,14 @@ jobs:
RID=$(curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
"$API/repos/$REPO/releases" \
- -d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false}')" \
+ -d "$(jq -nc --arg t "$TAG" '{tag_name:$t,name:$t,draft:false,prerelease:false,hide_archive_links:true}')" \
| jq -r '.id')
fi
echo "release id: $RID"
+ # Gitea honors hide_archive_links only on edit, not create — PATCH it
+ # so the auto-generated Source Code (zip/tar.gz) links stay hidden.
+ curl -sf -X PATCH -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \
+ "$API/repos/$REPO/releases/$RID" -d '{"hide_archive_links":true}' >/dev/null || true
# Upload every dist/*.zip. Per-asset failures don't fail the job —
# we want partial releases to still publish rather than block the
# whole pipeline on one big file.
diff --git a/Pawn/CoAClassSpecData.lua b/Pawn/CoAClassSpecData.lua
new file mode 100644
index 0000000..5057aeb
--- /dev/null
+++ b/Pawn/CoAClassSpecData.lua
@@ -0,0 +1,143 @@
+-- CoAClassSpecData.lua — GENERATED by coa-db/tools/gen_coa_class_spec_lua.py
+-- Source of truth: coa-db/data/class_spec_meta.json (class.file_string tokens + wiki specs).
+-- Do not hand-edit; regenerate from coa-db. Neutral stat keys; each addon maps them.
+-- Keyed by the in-game class token (2nd return of UnitClass), e.g. Templar=MONK.
+CoAClassSpec = {
+ ["BARBARIAN"] = { name="Barbarian", classId=12, specs={
+ { name="Headhunting", roles={"RANGED"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Brutality", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Ancestry", roles={"MELEE","SUPPORT"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ }},
+ ["WITCHDOCTOR"] = { name="Witch Doctor", classId=13, specs={
+ { name="Shadowhunting", roles={"RANGED"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Voodoo", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Brewing", roles={"HEALER"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Intellect=0.55 } },
+ }},
+ ["DEMONHUNTER"] = { name="Felsworn", classId=14, specs={
+ { name="Slayer", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Infernal", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Tyrant", roles={"TANK"}, primaryStat="Agility", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Agility=0.4 } },
+ }},
+ ["WITCHHUNTER"] = { name="Witch Hunter", classId=15, specs={
+ { name="Boltslinger", roles={"RANGED"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Darkness", roles={"RANGED"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Inquisition", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Black Knight", roles={"TANK"}, primaryStat="Agility", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Agility=0.4 } },
+ }},
+ ["STORMBRINGER"] = { name="Stormbringer", classId=16, specs={
+ { name="Maelstrom", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Lightning", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Wind", roles={"CASTER","SUPPORT"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ }},
+ ["FLESHWARDEN"] = { name="Knight of Xoroth", classId=17, specs={
+ { name="Hellfire", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Defiance", roles={"TANK"}, primaryStat="Strength", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Strength=0.4 } },
+ { name="War", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ }},
+ ["GUARDIAN"] = { name="Guardian", classId=18, specs={
+ { name="Gladiator", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Inspiration", roles={"MELEE","SUPPORT"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Vanguard", roles={"TANK"}, primaryStat="Strength", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Strength=0.4 } },
+ }},
+ ["MONK"] = { name="Templar", classId=19, specs={
+ { name="Oathkeeper", roles={"TANK"}, primaryStat="Agility", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Agility=0.4 } },
+ { name="Zealot", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Crusader", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ }},
+ ["SONOFARUGAL"] = { name="Bloodmage", classId=20, specs={
+ { name="Fleshweaver", roles={"SUPPORT"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Sanguine", roles={"CASTER"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Accursed", roles={"MELEE","CASTER"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Eternal", roles={"TANK"}, primaryStat="Agility", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Agility=0.4 } },
+ }},
+ ["RANGER"] = { name="Ranger", classId=21, specs={
+ { name="Archery", roles={"RANGED"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Farstrider", roles={"RANGED","SUPPORT"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Brigand", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ }},
+ ["CHRONOMANCER"] = { name="Chronomancer", classId=22, specs={
+ { name="Infinite", roles={"CASTER"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Time", roles={"HEALER"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Intellect=0.55 } },
+ { name="Artificer", roles={"RANGED"}, primaryStat="Spirit", weights={ Spirit=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ }},
+ ["NECROMANCER"] = { name="Necromancer", classId=23, specs={
+ { name="Death", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Animation", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Rime", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ }},
+ ["PYROMANCER"] = { name="Pyromancer", classId=24, specs={
+ { name="Incineration", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Flameweaving", roles={"HEALER"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Intellect=0.55 } },
+ { name="Draconic", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ }},
+ ["CULTIST"] = { name="Cultist", classId=25, specs={
+ { name="Heretic", roles={"HEALER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Spirit=0.55 } },
+ { name="Corruption", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Godblade", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Dreadnought", roles={"TANK"}, primaryStat="Strength", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Strength=0.4 } },
+ }},
+ ["STARCALLER"] = { name="Starcaller", classId=26, specs={
+ { name="Sentinel", roles={"RANGED"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Warden", roles={"MELEE"}, primaryStat="Intellect", weights={ Intellect=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Moon Priest", roles={"HEALER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Spirit=0.55 } },
+ { name="Moon Guard", roles={"TANK"}, primaryStat="Intellect", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Intellect=0.4 } },
+ }},
+ ["SUNCLERIC"] = { name="Sun Cleric", classId=27, specs={
+ { name="Piety", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Valkyrie", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Seraphim", roles={"TANK"}, primaryStat="Strength", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Strength=0.4 } },
+ { name="Blessings", roles={"HEALER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Spirit=0.55 } },
+ }},
+ ["TINKER"] = { name="Tinker", classId=28, specs={
+ { name="Demolition", roles={"RANGED"}, primaryStat="Agility", weights={ Agility=1, RangedAttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5 } },
+ { name="Mechanics", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Invention", roles={"HEALER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Spirit=0.55 } },
+ }},
+ ["PROPHET"] = { name="Venomancer", classId=29, specs={
+ { name="Fortitude", roles={"TANK"}, primaryStat="Intellect", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Intellect=0.4 } },
+ { name="Stalking", roles={"MELEE"}, primaryStat="Intellect", weights={ Intellect=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Rot", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Vizier", roles={"HEALER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, CritRating=0.5, HasteRating=0.5, Mp5=0.5, Spirit=0.55 } },
+ }},
+ ["REAPER"] = { name="Reaper", classId=30, specs={
+ { name="Soul", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Harvest", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Domination", roles={"TANK"}, primaryStat="Strength", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Strength=0.4 } },
+ }},
+ ["WILDWALKER"] = { name="Primalist", classId=31, specs={
+ { name="Grovekeeper", roles={"SUPPORT"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Wildwalker", roles={"MELEE"}, primaryStat="Strength", weights={ Strength=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Mountain King", roles={"TANK"}, primaryStat="Strength", weights={ Stamina=1, Armor=0.6, Dodge=0.55, Parry=0.55, Defense=0.6, Strength=0.4 } },
+ { name="Geomancy", roles={"CASTER"}, primaryStat="Intellect", weights={ Intellect=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ }},
+ ["SPIRITMAGE"] = { name="Runemaster", classId=32, specs={
+ { name="Engravement", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ { name="Glyphic", roles={"CASTER"}, primaryStat="Spirit", weights={ Spirit=1, SpellPower=0.8, HitRating=0.7, CritRating=0.55, HasteRating=0.55 } },
+ { name="Riftblade", roles={"MELEE"}, primaryStat="Agility", weights={ Agility=1, AttackPower=0.5, CritRating=0.55, HitRating=0.6, HasteRating=0.5, ArmorPenetration=0.5 } },
+ }},
+}
+
+CoAClassPrimaryStats = {
+ ["BARBARIAN"] = { "Agility" },
+ ["WITCHDOCTOR"] = { "Agility", "Intellect", "Spirit" },
+ ["DEMONHUNTER"] = { "Agility", "Intellect", "Stamina" },
+ ["WITCHHUNTER"] = { "Agility", "Intellect", "Stamina" },
+ ["STORMBRINGER"] = { "Intellect" },
+ ["FLESHWARDEN"] = { "Strength", "Intellect", "Stamina" },
+ ["GUARDIAN"] = { "Strength", "Stamina" },
+ ["MONK"] = { "Agility", "Stamina" },
+ ["SONOFARUGAL"] = { "Spirit", "Stamina", "Agility" },
+ ["RANGER"] = { "Agility" },
+ ["CHRONOMANCER"] = { "Spirit" },
+ ["NECROMANCER"] = { "Intellect" },
+ ["PYROMANCER"] = { "Intellect", "Spirit" },
+ ["CULTIST"] = { "Intellect", "Strength", "Stamina" },
+ ["STARCALLER"] = { "Intellect", "Stamina" },
+ ["SUNCLERIC"] = { "Intellect", "Strength", "Stamina" },
+ ["TINKER"] = { "Agility", "Intellect" },
+ ["PROPHET"] = { "Intellect", "Stamina" },
+ ["REAPER"] = { "Strength", "Stamina" },
+ ["WILDWALKER"] = { "Strength", "Intellect", "Stamina" },
+ ["SPIRITMAGE"] = { "Agility", "Spirit" },
+}
+
diff --git a/Pawn/CoAScales.lua b/Pawn/CoAScales.lua
new file mode 100644
index 0000000..5f3d5b6
--- /dev/null
+++ b/Pawn/CoAScales.lua
@@ -0,0 +1,132 @@
+-- CoAScales.lua — Pawn integration for CoA custom-class stat scales
+-- Registers one scale per class+spec from CoAClassSpec (CoAClassSpecData.lua).
+-- Auto-enables all scales for the logged-in player's class on first login.
+-- Saved in PawnCoAScaleProviderOptions.LastAdded (per SavedVariables).
+--
+-- Neutral-key → Pawn-internal-key map used here:
+-- AttackPower → Ap
+-- RangedAttackPower → Rap
+-- Dodge → DodgeRating
+-- Parry → ParryRating
+-- Defense → DefenseRating
+-- All others → unchanged (Agility, Strength, Intellect, Spirit,
+-- Stamina, SpellPower, CritRating, HitRating,
+-- HasteRating, ArmorPenetration, Mp5, Armor)
+------------------------------------------------------------
+
+local CoAScaleProviderName = "CoAClasses"
+
+-- Class-token → 6-digit hex colour (roughly matching CoA class colours).
+-- Fallback "a0a0a0" (grey) for any token not listed.
+local CoAClassColor = {
+ BARBARIAN = "c69b3a",
+ WITCHDOCTOR = "00ff96",
+ DEMONHUNTER = "a330c9",
+ WITCHHUNTER = "6e95ff",
+ STORMBRINGER = "69ccf0",
+ FLESHWARDEN = "c79c6e",
+ GUARDIAN = "f58cba",
+ MONK = "f0eba0",
+ SONOFARUGAL = "ff0000",
+ RANGER = "abd473",
+ CHRONOMANCER = "40c0e0",
+ NECROMANCER = "556699",
+ PYROMANCER = "ff6600",
+ CULTIST = "9966cc",
+ STARCALLER = "e0d060",
+ SUNCLERIC = "ffe680",
+ TINKER = "aaaaaa",
+ PROPHET = "5a8a00",
+ REAPER = "336699",
+ WILDWALKER = "1aaa55",
+ SPIRITMAGE = "c0a0ff",
+}
+
+-- Translate one neutral-key weights table to Pawn internal keys.
+local function TranslateWeights(neutralWeights)
+ local out = {}
+ for k, v in pairs(neutralWeights) do
+ local pawnKey
+ if k == "AttackPower" then pawnKey = "Ap"
+ elseif k == "RangedAttackPower" then pawnKey = "Rap"
+ elseif k == "Dodge" then pawnKey = "DodgeRating"
+ elseif k == "Parry" then pawnKey = "ParryRating"
+ elseif k == "Defense" then pawnKey = "DefenseRating"
+ else pawnKey = k
+ end
+ out[pawnKey] = v
+ end
+ return out
+end
+
+-- Sanitise a string for use as an internal scale name (no double-quotes,
+-- no spaces — keeps names safe for PawnGetProviderScaleName).
+local function SafeInternalName(token, specName)
+ local s = token .. "_" .. specName
+ s = s:gsub("[^%w_]", "_")
+ return s
+end
+
+-- Build a human-readable localized name for tooltip display.
+local function LocalizedScaleName(classData, specName)
+ return classData.name .. ": " .. specName
+end
+
+------------------------------------------------------------
+-- Scale provider registration
+------------------------------------------------------------
+
+local function CoAScaleProvider_AddScales()
+ if not CoAClassSpec then return end
+
+ for token, classData in pairs(CoAClassSpec) do
+ local color = CoAClassColor[token] or "a0a0a0"
+ for _, spec in ipairs(classData.specs) do
+ local internalName = SafeInternalName(token, spec.name)
+ local localizedName = LocalizedScaleName(classData, spec.name)
+ local pawnWeights = TranslateWeights(spec.weights)
+ pcall(PawnAddPluginScale,
+ CoAScaleProviderName,
+ internalName,
+ localizedName,
+ color,
+ pawnWeights,
+ 1
+ )
+ end
+ end
+
+ ------------------------------------------------------------
+ -- Auto-enable scales for the player's class
+ -- Mirror the same C_Timer.After(0) pattern used by Wowhead.lua.
+ -- PawnCoAScaleProviderOptions is a SavedVariable declared in .toc;
+ -- only enable once (LastAdded == 0 means never done).
+ ------------------------------------------------------------
+ if not PawnCoAScaleProviderOptions then PawnCoAScaleProviderOptions = {} end
+ if not PawnCoAScaleProviderOptions.LastAdded then PawnCoAScaleProviderOptions.LastAdded = 0 end
+
+ local _lastAdded = PawnCoAScaleProviderOptions.LastAdded
+ C_Timer.After(0, function()
+ local _, playerToken = UnitClass("player")
+ if not playerToken then return end
+ if _lastAdded >= 1 then return end -- already enabled once; honour user's subsequent changes
+
+ local classData = CoAClassSpec and CoAClassSpec[playerToken]
+ if not classData then return end -- vanilla class; nothing to do
+
+ for _, spec in ipairs(classData.specs) do
+ local internalName = SafeInternalName(playerToken, spec.name)
+ local fullName = PawnGetProviderScaleName(CoAScaleProviderName, internalName)
+ pcall(PawnSetScaleVisible, fullName, true)
+ end
+
+ PawnCoAScaleProviderOptions.LastAdded = 1
+ end)
+
+ -- Self-destruct to free memory (matches Wowhead.lua pattern).
+ CoAScaleProvider_AddScales = nil
+end
+
+------------------------------------------------------------
+
+PawnAddPluginScaleProvider(CoAScaleProviderName, "CoA class scales", CoAScaleProvider_AddScales)
diff --git a/Pawn/Pawn.toc b/Pawn/Pawn.toc
index d63b4a8..6f7ccf3 100644
--- a/Pawn/Pawn.toc
+++ b/Pawn/Pawn.toc
@@ -4,13 +4,15 @@
## Notes: Pawn calculates scores for items that let you easily see which one is better for you.
## OptionalDependencies: AtlasLoot, EQCompare, EquipCompare, MultiTips, Outfitter
## SavedVariables: PawnCommon
-## SavedVariablesPerCharacter: PawnOptions, PawnWowheadScaleProviderOptions
+## SavedVariablesPerCharacter: PawnOptions, PawnWowheadScaleProviderOptions
, PawnCoAScaleProviderOptions
VgerCore\VgerCore.lua
+CoAClassSpecData.lua
Localization.lua
Gems.lua
Pawn.lua
PawnUI.lua
PawnUI.xml
-Wowhead.lua
\ No newline at end of file
+Wowhead.lua
+CoAScales.lua
\ No newline at end of file
diff --git a/Pawn/PawnUI.lua b/Pawn/PawnUI.lua
index ad66dff..a2f307e 100644
--- a/Pawn/PawnUI.lua
+++ b/Pawn/PawnUI.lua
@@ -1801,8 +1801,11 @@ function PawnInterfaceOptionsFrame_OnLoad()
-- NOTE: If you need anything from PawnCommon in the future, you should call PawnInitializeOptions first.
-- Register the Interface Options page.
+ -- CoA/3.3.5a: InterfaceOptions_AddCategory is a retail-only global; guard it.
PawnInterfaceOptionsFrame.name = "Pawn"
- InterfaceOptions_AddCategory(PawnInterfaceOptionsFrame)
+ if InterfaceOptions_AddCategory then
+ InterfaceOptions_AddCategory(PawnInterfaceOptionsFrame)
+ end
-- Update the version display.
local Version = GetAddOnMetadata("Pawn", "Version")
if Version then
diff --git a/Pawn/PawnUI.xml b/Pawn/PawnUI.xml
index c3b5422..bedff5e 100644
--- a/Pawn/PawnUI.xml
+++ b/Pawn/PawnUI.xml
@@ -1405,7 +1405,8 @@
- InterfaceOptionsFrameOkay_OnClick()
+ -- CoA/3.3.5a: InterfaceOptionsFrameOkay_OnClick is retail-only; guard it.
+ if InterfaceOptionsFrameOkay_OnClick then InterfaceOptionsFrameOkay_OnClick() end
HideUIPanel(GameMenuFrame)
PawnUIShow()
diff --git a/Pawn/Textures/CompareBanner.blp b/Pawn/Textures/CompareBanner.blp
new file mode 100644
index 0000000..d62e886
Binary files /dev/null and b/Pawn/Textures/CompareBanner.blp differ
diff --git a/Pawn/Textures/CompareBanner.tga b/Pawn/Textures/CompareBanner.tga
deleted file mode 100644
index 32879db..0000000
Binary files a/Pawn/Textures/CompareBanner.tga and /dev/null differ
diff --git a/Pawn/Textures/CompareBar.blp b/Pawn/Textures/CompareBar.blp
new file mode 100644
index 0000000..fefef4a
Binary files /dev/null and b/Pawn/Textures/CompareBar.blp differ
diff --git a/Pawn/Textures/CompareBar.tga b/Pawn/Textures/CompareBar.tga
deleted file mode 100644
index 91b08d5..0000000
Binary files a/Pawn/Textures/CompareBar.tga and /dev/null differ
diff --git a/Pawn/Textures/CompareBarLeft.blp b/Pawn/Textures/CompareBarLeft.blp
new file mode 100644
index 0000000..1935456
Binary files /dev/null and b/Pawn/Textures/CompareBarLeft.blp differ
diff --git a/Pawn/Textures/CompareBarLeft.tga b/Pawn/Textures/CompareBarLeft.tga
deleted file mode 100644
index cdd63d0..0000000
Binary files a/Pawn/Textures/CompareBarLeft.tga and /dev/null differ
diff --git a/Pawn/Textures/CompareBarRight.blp b/Pawn/Textures/CompareBarRight.blp
new file mode 100644
index 0000000..665e4f3
Binary files /dev/null and b/Pawn/Textures/CompareBarRight.blp differ
diff --git a/Pawn/Textures/CompareBarRight.tga b/Pawn/Textures/CompareBarRight.tga
deleted file mode 100644
index f98eadb..0000000
Binary files a/Pawn/Textures/CompareBarRight.tga and /dev/null differ
diff --git a/Pawn/Textures/HorizontalBar.blp b/Pawn/Textures/HorizontalBar.blp
new file mode 100644
index 0000000..d36b3ba
Binary files /dev/null and b/Pawn/Textures/HorizontalBar.blp differ
diff --git a/Pawn/Textures/HorizontalBar.tga b/Pawn/Textures/HorizontalBar.tga
deleted file mode 100644
index c030279..0000000
Binary files a/Pawn/Textures/HorizontalBar.tga and /dev/null differ
diff --git a/Pawn/Textures/PawnButton.blp b/Pawn/Textures/PawnButton.blp
new file mode 100644
index 0000000..fe2d6b3
Binary files /dev/null and b/Pawn/Textures/PawnButton.blp differ
diff --git a/Pawn/Textures/PawnButton.tga b/Pawn/Textures/PawnButton.tga
deleted file mode 100644
index e1d5596..0000000
Binary files a/Pawn/Textures/PawnButton.tga and /dev/null differ
diff --git a/Pawn/Textures/PawnLogo.blp b/Pawn/Textures/PawnLogo.blp
new file mode 100644
index 0000000..aeaa7a1
Binary files /dev/null and b/Pawn/Textures/PawnLogo.blp differ
diff --git a/Pawn/Textures/PawnLogo.tga b/Pawn/Textures/PawnLogo.tga
deleted file mode 100644
index 4936d80..0000000
Binary files a/Pawn/Textures/PawnLogo.tga and /dev/null differ
diff --git a/Pawn/Textures/PawnUIHeader.blp b/Pawn/Textures/PawnUIHeader.blp
new file mode 100644
index 0000000..c4b3d3f
Binary files /dev/null and b/Pawn/Textures/PawnUIHeader.blp differ
diff --git a/Pawn/Textures/PawnUIHeader.tga b/Pawn/Textures/PawnUIHeader.tga
deleted file mode 100644
index b9b10fd..0000000
Binary files a/Pawn/Textures/PawnUIHeader.tga and /dev/null differ
diff --git a/Pawn/Textures/Question.blp b/Pawn/Textures/Question.blp
new file mode 100644
index 0000000..2ac3ff9
Binary files /dev/null and b/Pawn/Textures/Question.blp differ
diff --git a/Pawn/Textures/Question.tga b/Pawn/Textures/Question.tga
deleted file mode 100644
index b6903f1..0000000
Binary files a/Pawn/Textures/Question.tga and /dev/null differ
diff --git a/Pawn/Textures/UpgradeArrowBig.blp b/Pawn/Textures/UpgradeArrowBig.blp
new file mode 100644
index 0000000..4c4567b
Binary files /dev/null and b/Pawn/Textures/UpgradeArrowBig.blp differ
diff --git a/Pawn/Textures/UpgradeArrowBig.tga b/Pawn/Textures/UpgradeArrowBig.tga
deleted file mode 100644
index 641658d..0000000
Binary files a/Pawn/Textures/UpgradeArrowBig.tga and /dev/null differ
diff --git a/Pawn/Wowhead.lua b/Pawn/Wowhead.lua
index d6a3559..d6c2e4c 100644
--- a/Pawn/Wowhead.lua
+++ b/Pawn/Wowhead.lua
@@ -420,62 +420,67 @@ PawnAddPluginScale(
if not PawnWowheadScaleProviderOptions then PawnWowheadScaleProviderOptions = { } end
if not PawnWowheadScaleProviderOptions.LastAdded then PawnWowheadScaleProviderOptions.LastAdded = 0 end
-local _, Class = UnitClass("player")
-if PawnWowheadScaleProviderOptions.LastAdded < 1 then
- -- Enable round one of scales based on the player's class.
- if Class == "WARRIOR" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorFury"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorTank"), true)
- elseif Class == "PALADIN" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinHoly"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinTank"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinRetribution"), true)
- elseif Class == "HUNTER" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterBeastMastery"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterMarksman"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterSurvival"), true)
- elseif Class == "ROGUE" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueAssassination"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueCombat"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueSubtlety"), true)
- elseif Class == "PRIEST" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestDiscipline"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestHoly"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestShadow"), true)
- elseif Class == "DEATHKNIGHT" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightBloodDps"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightBloodTank"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightFrostDps"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightFrostTank"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightUnholyDps"), true)
- elseif Class == "SHAMAN" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanElemental"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanEnhancement"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanRestoration"), true)
- elseif Class == "MAGE" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageArcane"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageFire"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageFrost"), true)
- elseif Class == "WARLOCK" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockAffliction"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockDemonology"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockDestruction"), true)
- elseif Class == "DRUID" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidBalance"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidFeralDps"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidFeralTank"), true)
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidRestoration"), true)
+-- CoA/3.3.5a: UnitClass("player") returns nil at file-load and even at VARIABLES_LOADED on this client.
+-- Defer the class-based auto-enable one frame so the unit is fully initialised.
+local _PawnWowheadLastAdded = PawnWowheadScaleProviderOptions.LastAdded
+C_Timer.After(0, function()
+ local _, Class = UnitClass("player")
+ if _PawnWowheadLastAdded < 1 then
+ -- Enable round one of scales based on the player's class.
+ if Class == "WARRIOR" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorFury"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorTank"), true)
+ elseif Class == "PALADIN" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinHoly"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinTank"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinRetribution"), true)
+ elseif Class == "HUNTER" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterBeastMastery"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterMarksman"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterSurvival"), true)
+ elseif Class == "ROGUE" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueAssassination"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueCombat"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueSubtlety"), true)
+ elseif Class == "PRIEST" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestDiscipline"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestHoly"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestShadow"), true)
+ elseif Class == "DEATHKNIGHT" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightBloodDps"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightBloodTank"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightFrostDps"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightFrostTank"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightUnholyDps"), true)
+ elseif Class == "SHAMAN" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanElemental"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanEnhancement"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanRestoration"), true)
+ elseif Class == "MAGE" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageArcane"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageFire"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageFrost"), true)
+ elseif Class == "WARLOCK" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockAffliction"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockDemonology"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockDestruction"), true)
+ elseif Class == "DRUID" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidBalance"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidFeralDps"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidFeralTank"), true)
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidRestoration"), true)
+ end
end
-end
-if PawnWowheadScaleProviderOptions.LastAdded < 2 then
- if Class == "WARRIOR" then
- PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorArms"), true)
+ if _PawnWowheadLastAdded < 2 then
+ if Class == "WARRIOR" then
+ PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorArms"), true)
+ end
end
-end
--- Don't reenable those scales again after the user has disabled them previously.
-PawnWowheadScaleProviderOptions.LastAdded = 2
+ -- Don't reenable those scales again after the user has disabled them previously.
+ PawnWowheadScaleProviderOptions.LastAdded = 2
+end)
-- After this function terminates there's no need for it anymore, so cause it to self-destruct to save memory.
PawnWowheadScaleProvider_AddScales = nil