diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fa1385d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..2f93975 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,75 @@ +name: release + +on: + push: + tags: + - '*-coa.*' # Asc-1.1.6-coa.2, 9.1.40-coa.3, etc. + - 'v*' # v0.3.0 for repos without an upstream version + +jobs: + release: + runs-on: linux-amd64 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # build_zip uses git archive HEAD; full history is fine + + - name: Build per-addon zip(s) + run: bash tools/build_zip.sh + + - name: Publish release (Gitea API direct; no action dependency) + env: + GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + API: ${{ github.server_url }}/api/v1 + # Gitea attachment ceiling is 200 MiB (see roles/gitea config). + # Skip anything larger so one oversized asset doesn't fail the job. + MAX_BYTES: 209715200 + run: | + set -euo pipefail + # Create the release (or reuse if it already exists for this tag). + RID=$(curl -s -H "Authorization: token $GITEA_TOKEN" \ + "$API/repos/$REPO/releases/tags/$TAG" 2>/dev/null \ + | jq -r '.id // empty') + if [ -z "$RID" ]; then + 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,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. + failed=0 + uploaded=0 + for f in dist/*.zip; do + name=$(basename "$f") + size=$(stat -c '%s' "$f") + if [ "$size" -gt "$MAX_BYTES" ]; then + echo "::warning::skip $name (${size} B > ${MAX_BYTES} B Gitea limit; host on CDN instead)" + failed=$((failed+1)) + continue + fi + echo "uploading $name ($(numfmt --to=iec "$size"))" + if curl -sf -X POST -H "Authorization: token $GITEA_TOKEN" \ + -F "attachment=@$f" \ + "$API/repos/$REPO/releases/$RID/assets?name=$name" \ + | jq -r '" -> " + .browser_download_url'; then + uploaded=$((uploaded+1)) + else + echo "::warning::upload failed for $name" + failed=$((failed+1)) + fi + done + echo "release published: $uploaded uploaded, $failed skipped/failed" + # Only fail the job if NO assets uploaded — a release with zero + # attachments isn't useful to anyone. + [ "$uploaded" -gt 0 ] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index cb7b432..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: "Bug Report" -description: Create a report to help us improve this addon -labels: '🐛 Bug' -body: -- type: markdown - attributes: - value: | - Please search for existing issues before creating a new one. - -- type: textarea - attributes: - label: Description - description: What did you expect to happen and what happened instead? - validations: - required: true - -- type: dropdown - id: flavor - attributes: - label: Realm - description: What realm did this occur on? - options: - - Area 52 (Default) - - Seasonal - - Grizzly Hills - - Rexxar - - Other - validations: - required: true - -- type: checkboxes - id: testing - attributes: - label: Tested with only this addon - description: Did you try having just this addon as the only enabled addon and everything else disabled? - options: - - label: "Yes" - - label: "No" - validations: - required: true - -- type: textarea - attributes: - label: Lua Error - description: | - Do you have an error log of what happened? If you don't see any errors, make sure that error reporting is enabled (`/console scriptErrors 1`) - validations: - required: false - -- type: textarea - attributes: - label: Reproduction Steps - description: Please list out the steps to reproduce your bug. - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - validations: - required: true - -- type: input - attributes: - label: Last Good Version - description: | - Was it working in a previous version? If yes, which update did it stop working? If you don't know, when was the last date you were aware it was working - placeholder: "MM/DD/YYYY" - validations: - required: false - -- type: textarea - attributes: - label: Screenshots - description: If applicable, add screenshots to help explain your problem. - placeholder: Click here to attach your screenshots via the editor button in the top right. - validations: - required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index ec4bb38..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index bbcbbe7..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEPMLATE.md b/.github/PULL_REQUEST_TEPMLATE.md deleted file mode 100644 index 30d2afa..0000000 --- a/.github/PULL_REQUEST_TEPMLATE.md +++ /dev/null @@ -1,28 +0,0 @@ -# Description - -Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. - -Fixes #(issue) - -## Type of change - -Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - -## How Has This Been Tested - -Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration - -- [ ] Test A -- [ ] Test B - -## Checklist - - -- [ ] I have performed a self-review of my own code -- [ ] I have commented my code, particularly in hard-to-understand areas - - \ No newline at end of file diff --git a/.gitignore b/.gitignore index 44356e3..ec92999 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ .install .lua/* .vscode -.idea \ No newline at end of file +.idea +dist/ diff --git a/Kui_Nameplates/CoAClassColors.lua b/Kui_Nameplates/CoAClassColors.lua new file mode 100644 index 0000000..cc9b3de --- /dev/null +++ b/Kui_Nameplates/CoAClassColors.lua @@ -0,0 +1,74 @@ +-- CoAClassColors.lua +-- +-- Forwards the live client's RAID_CLASS_COLORS palette into +-- _G.CUSTOM_CLASS_COLORS so Kui_Nameplates renders Conquest-of-Azeroth +-- class names and bars correctly when the !ClassColors addon is +-- loaded. +-- +-- Background +-- ---------- +-- The CoA Voljin client (and the Ascension classic+ client) ship +-- Interface/SharedXML/SharedConstants.lua with all 32 class +-- file_strings populated in _G.RAID_CLASS_COLORS — 10 vanilla + HERO + +-- 21 CoA customs (BARBARIAN, WITCHDOCTOR, DEMONHUNTER, FLESHWARDEN, +-- MONK = Templar, PROPHET = Venomancer, …). +-- +-- Kui_Nameplates picks its source table once in +-- Modules/ClassColours.lua:90 with +-- cc_table = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS +-- and Libs/Kui/Kui.lua:56-60 has the same shape: +-- if CUSTOM_CLASS_COLORS then class = CUSTOM_CLASS_COLORS[class] +-- else class = RAID_CLASS_COLORS[class] +-- end +-- When !ClassColors is loaded it injects a separate +-- _G.CUSTOM_CLASS_COLORS populated from its own (vanilla-10-only) +-- defaults plus user overrides. There is *no* per-key fallback to +-- RAID_CLASS_COLORS, so on a !ClassColors-equipped client the 22 CoA +-- tokens silently miss and SetTextColor is called with a nil colour +-- (no class-coloured friendly names; nil-arg error in +-- kui.GetClassColour for the str path). +-- +-- Strategy +-- -------- +-- If CUSTOM_CLASS_COLORS exists at file-load time (i.e. !ClassColors +-- already loaded — Kui_Nameplates.toc declares it as an OptionalDep, +-- so it loads first when installed), copy any RAID_CLASS_COLORS entry +-- that CUSTOM_CLASS_COLORS is missing. Never overwrite — +-- !ClassColors user preferences and any other addon's earlier write +-- win. +-- +-- We deliberately don't touch RAID_CLASS_COLORS itself: the client +-- already populates it, and any value we'd choose here would be a +-- guess relative to the realm-authoritative palette in +-- SharedConstants.lua. +-- +-- Source of truth: _G.RAID_CLASS_COLORS at FrameXML load time +-- (Voljin/PTR realm: patch-B.MPQ → SharedXML/SharedConstants.lua). + +local CCC = _G.CUSTOM_CLASS_COLORS +if type(CCC) ~= "table" then return end + +local CC = _G.RAID_CLASS_COLORS +if type(CC) ~= "table" then return end + +local function colorStr(r, g, b) + return string.format("ff%02x%02x%02x", r * 255 + 0.5, g * 255 + 0.5, b * 255 + 0.5) +end + +local function unpackColor(c) + if type(c) ~= "table" then return end + if c.GetRGB then return c:GetRGB() end + return c.r, c.g, c.b +end + +for token, src in pairs(CC) do + if CCC[token] == nil then + local r, g, b = unpackColor(src) + if r and g and b then + CCC[token] = { + r = r, g = g, b = b, + colorStr = colorStr(r, g, b), + } + end + end +end diff --git a/Kui_Nameplates/CoAClassSpecData.lua b/Kui_Nameplates/CoAClassSpecData.lua new file mode 100644 index 0000000..5057aeb --- /dev/null +++ b/Kui_Nameplates/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/Kui_Nameplates/Core/Config.lua b/Kui_Nameplates/Core/Config.lua index 8ff5928..75ea2e6 100644 --- a/Kui_Nameplates/Core/Config.lua +++ b/Kui_Nameplates/Core/Config.lua @@ -1250,4 +1250,20 @@ do UpdateFrameSize ) end -end \ No newline at end of file +end + +StaticPopupDialogs.KUINAMEPLATES_INCOMPATIBLE = { + text = "KuiNameplates is not compatible with PlateBuffs. Please disable PlateBuffs or KuiNameplates.", + button1 = "Disable PlateBuffs", + button2 = "Disable KuiNameplates", + whileDead = 1, + OnButton1 = function() + DisableAddOn("PlateBuffs") + ReloadUI() + end, + OnButton2 = function() + DisableAddOn("KuiNameplates") + ReloadUI() + end, + hideOnEscape = 0, +} \ No newline at end of file diff --git a/Kui_Nameplates/Core/Core.lua b/Kui_Nameplates/Core/Core.lua index 1c2ee33..3328cd5 100644 --- a/Kui_Nameplates/Core/Core.lua +++ b/Kui_Nameplates/Core/Core.lua @@ -500,6 +500,15 @@ function addon:OnInitialize() -- on messages addon.Castbar = addon:GetModule("Castbar") addon.TankModule = addon:GetModule("TankMode") + + if IsAddOnLoaded("PlateBuffs") then + -- CoA: StaticPopup_Show silently fails when called from OnInitialize (ADDON_LOADED, + -- before PLAYER_LOGIN). Defer via C_Timer.After(0, …) so the popup fires after + -- the StaticPopup system is ready. See PORTING.md §StaticPopup_Show during PLAYER_LOGIN. + C_Timer.After(0, function() + StaticPopup_Show("KUINAMEPLATES_INCOMPATIBLE") + end) + end end ---------------------------------------------------------------------- enable -- function addon:OnEnable() diff --git a/Kui_Nameplates/Kui_Nameplates.toc b/Kui_Nameplates/Kui_Nameplates.toc index 324f619..f56c66b 100644 --- a/Kui_Nameplates/Kui_Nameplates.toc +++ b/Kui_Nameplates/Kui_Nameplates.toc @@ -1,8 +1,9 @@ ## Interface: 30300 ## Author: Kesava ## Title: Kui |cff9966ffNameplates|r -## Version: 262 +## Version: 264 ## Notes: Prettier nameplates. +## OptionalDeps: !ClassColors ## SavedVariables: KuiNameplatesGDB ## X-Curse-Packaged-Version: 262 ## X-Curse-Project-Name: KuiNameplates @@ -13,5 +14,18 @@ Embeds.xml Locales.xml + +## CoA patches ## +# Mirror RAID_CLASS_COLORS into CUSTOM_CLASS_COLORS for the 22 CoA +# class tokens before any module captures cc_table. Loaded after +# Embeds (defensive — this file uses no libs) and before Core/Modules +# so ClassColours.lua:90 sees the populated CCC at OnInitialize time. +CoAClassColors.lua + +# CoA class/spec metadata: defines CoAClassSpec keyed by class token +# (2nd return of UnitClass). Must load before TankMode.lua so that +# GetPlayerCoARole() can look up spec roles at OnEnable time. +CoAClassSpecData.lua + Core.xml Modules.xml \ No newline at end of file diff --git a/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.lua b/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.lua index 603ce07..00e4e48 100644 --- a/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.lua +++ b/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.lua @@ -30,7 +30,7 @@ -- @name AceAddon-3.0.lua -- @release $Id$ -local MAJOR, MINOR = "AceAddon-3.0", 12 +local MAJOR, MINOR = "AceAddon-3.0", 13 local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not AceAddon then return end -- No Upgrade needed. @@ -49,10 +49,6 @@ local select, pairs, next, type, unpack = select, pairs, next, type, unpack local loadstring, assert, error = loadstring, assert, error local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget --- Global vars/functions that we don't upvalue since they might get hooked, or upgraded --- List them here for Mikk's FindGlobals script --- GLOBALS: LibStub, IsLoggedIn, geterrorhandler - --[[ xpcall safecall implementation ]] @@ -62,43 +58,12 @@ local function errorhandler(err) return geterrorhandler()(err) end -local function CreateDispatcher(argCount) - local code = [[ - local xpcall, eh = ... - local method, ARGS - local function call() return method(ARGS) end - - local function dispatch(func, ...) - method = func - if not method then return end - ARGS = ... - return xpcall(call, eh) - end - - return dispatch - ]] - - local ARGS = {} - for i = 1, argCount do ARGS[i] = "arg"..i end - code = code:gsub("ARGS", tconcat(ARGS, ", ")) - return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) -end - -local Dispatchers = setmetatable({}, {__index=function(self, argCount) - local dispatcher = CreateDispatcher(argCount) - rawset(self, argCount, dispatcher) - return dispatcher -end}) -Dispatchers[0] = function(func) - return xpcall(func, errorhandler) -end - local function safecall(func, ...) -- we check to see if the func is passed is actually a function here and don't error when it isn't -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not -- present execution should continue without hinderance if type(func) == "function" then - return Dispatchers[select('#', ...)](func, ...) + return xpcall(func, errorhandler, ...) end end @@ -632,10 +597,20 @@ function AceAddon:IterateAddonStatus() return pairs(self.statuses) end function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end +-- Blizzard AddOns which can load very early in the loading process and mess with Ace3 addon loading +local BlizzardEarlyLoadAddons = { + Blizzard_DebugTools = true, + Blizzard_TimeManager = true, + Blizzard_BattlefieldMap = true, + Blizzard_MapCanvas = true, + Blizzard_SharedMapDataProviders = true, + Blizzard_CombatLog = true, +} + -- Event Handling local function onEvent(this, event, arg1) - -- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up - if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then + -- 2020-08-28 nevcairiel - ignore the load event of Blizzard addons which occur early in the loading process + if (event == "ADDON_LOADED" and (arg1 == nil or not BlizzardEarlyLoadAddons[arg1])) or event == "PLAYER_LOGIN" then -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration while(#AceAddon.initializequeue > 0) do local addon = tremove(AceAddon.initializequeue, 1) @@ -671,4 +646,4 @@ if oldminor and oldminor < 10 then tinsert(addon.orderedModules, module) end end -end \ No newline at end of file +end diff --git a/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.xml b/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.xml new file mode 100644 index 0000000..e6ad639 --- /dev/null +++ b/Kui_Nameplates/Libs/AceAddon-3.0/AceAddon-3.0.xml @@ -0,0 +1,4 @@ + +