From 7a2d38d6e0f15fcde6824e1bf36c19c57ca94680 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Sun, 24 May 2026 19:30:39 +0200 Subject: [PATCH 01/10] fix(libs): pick up coa-ace3 9583952 (AceDB falsy-defaults PR #10 backport) Re-sync after coa-ace3 9583952 backported WoWUIDev/Ace3 PR #10 which fixes the AceDB-3.0 simple-value defaults metatable: previously falsy defaults like ["*"] = false read back as nil because of `k2~=nil and v or nil` short-circuiting. Now they round-trip correctly. --- RatingBuster/libs/AceDB-3.0/AceDB-3.0.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/RatingBuster/libs/AceDB-3.0/AceDB-3.0.lua b/RatingBuster/libs/AceDB-3.0/AceDB-3.0.lua index 231196c..f83a715 100644 --- a/RatingBuster/libs/AceDB-3.0/AceDB-3.0.lua +++ b/RatingBuster/libs/AceDB-3.0/AceDB-3.0.lua @@ -111,7 +111,15 @@ local function copyDefaults(dest, src) end else -- Values are not tables, so this is just a simple return - local mt = {__index = function(t,k2) return k2~=nil and v or nil end} + -- (PR #10 backport: the old `k2~=nil and v or nil` short-circuits to + -- nil whenever the default `v` itself is falsy — so `["*"] = false` + -- defaults silently became nil. Make the read explicit instead.) + local mt = { + __index = function(t,k2) + if k2 == nil then return nil end + return v + end, + } setmetatable(dest, mt) end elseif type(v) == "table" then From 47c02e09c7d627bdb64e372c123f747d8d78ee77 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Mon, 25 May 2026 11:02:51 +0200 Subject: [PATCH 02/10] chore: remove .github/ (upstream templates, not relevant on Gitea) --- .github/ISSUE_TEMPLATE/bug_report.yml | 77 ----------------------- .github/ISSUE_TEMPLATE/config.yml | 1 - .github/ISSUE_TEMPLATE/feature_request.md | 20 ------ .github/PULL_REQUEST_TEPMLATE.md | 28 --------- 4 files changed, 126 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/PULL_REQUEST_TEPMLATE.md 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 From 3f478be5827ccfbf0f767530c912f507169bbf4a Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Mon, 25 May 2026 12:01:38 +0200 Subject: [PATCH 03/10] ci: add Gitea Actions release workflow (per-addon git-archive zip) --- .gitea/workflows/release.yml | 49 +++++++++++++++++++++++++++++ .gitignore | 3 +- tools/build_zip.sh | 60 ++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 .gitea/workflows/release.yml create mode 100755 tools/build_zip.sh diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..7c1ffe1 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,49 @@ +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 + 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}')" \ + | jq -r '.id') + fi + echo "release id: $RID" + # Upload every dist/*.zip + for f in dist/*.zip; do + name=$(basename "$f") + echo "uploading $name" + 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' + done 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/tools/build_zip.sh b/tools/build_zip.sh new file mode 100755 index 0000000..f9c2d68 --- /dev/null +++ b/tools/build_zip.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Build per-addon zip artefacts from HEAD via git-archive. +# +# - Discovers top-level addon folders (Foo/Foo.toc). +# - Re-creates dist/ each run. +# - Always archives HEAD, so the working tree state is irrelevant. +# - If more than one addon folder is present, also emits -all.zip +# with every addon folder side-by-side at the zip root. +set -euo pipefail + +root=$(git rev-parse --show-toplevel) +cd "$root" + +repo_name=$(basename "$root") +dist="$root/dist" + +# Find Foo/Foo.toc pairs at depth 1; ignore libs nested deeper. +addons=() +while IFS= read -r toc; do + dir=$(dirname "$toc") + folder=$(basename "$dir") + base=$(basename "$toc" .toc) + # Accept Foo.toc and Foo_Wrath.toc style flavour variants; folder must match + # at least one toc basename prefix (Foo). + case "$base" in + "$folder"|"$folder"_*) addons+=("$folder") ;; + esac +done < <(command find . -mindepth 2 -maxdepth 2 -type f -name '*.toc' | sed 's|^\./||' | sort) + +# Dedupe (a folder with Foo.toc + Foo_Wrath.toc shows up twice). +if [ ${#addons[@]} -gt 0 ]; then + mapfile -t addons < <(printf '%s\n' "${addons[@]}" | awk '!seen[$0]++') +fi + +if [ ${#addons[@]} -eq 0 ]; then + echo "no addon folders found (looking for */Foo.toc with matching folder name)" >&2 + exit 1 +fi + +rm -rf "$dist" +mkdir -p "$dist" + +for folder in "${addons[@]}"; do + out="$dist/$folder.zip" + # No --prefix: the folder already sits at the repo root, so git-archive + # emits entries as /... which is exactly what + # Interface/AddOns/ expects after extraction. + git archive HEAD --format=zip -o "$out" -- "$folder" + echo "built dist/$folder.zip" +done + +# Combined bundle only makes sense when there are multiple addons. +if [ ${#addons[@]} -gt 1 ]; then + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + git archive HEAD --format=tar -- "${addons[@]}" | tar -x -C "$tmp" + out="$dist/$repo_name-all.zip" + ( cd "$tmp" && zip -qr "$out" "${addons[@]}" ) + echo "built dist/$repo_name-all.zip" +fi From 3b8d9b36b7b7563c2a37b583303420b7c4debea3 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Mon, 25 May 2026 12:16:51 +0200 Subject: [PATCH 04/10] ci: respect GITHUB_REPOSITORY + tolerate per-asset upload failures --- .gitea/workflows/release.yml | 34 ++++++++++++++++++++++++++++------ tools/build_zip.sh | 13 ++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 7c1ffe1..709a7cd 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -24,6 +24,9 @@ jobs: 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). @@ -38,12 +41,31 @@ jobs: | jq -r '.id') fi echo "release id: $RID" - # Upload every dist/*.zip + # 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") - echo "uploading $name" - 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' + 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/tools/build_zip.sh b/tools/build_zip.sh index f9c2d68..45d1400 100755 --- a/tools/build_zip.sh +++ b/tools/build_zip.sh @@ -6,12 +6,23 @@ # - Always archives HEAD, so the working tree state is irrelevant. # - If more than one addon folder is present, also emits -all.zip # with every addon folder side-by-side at the zip root. +# - When run inside Gitea Actions the working tree lives under a +# per-job dir like /var/lib/act_runner/work/.../hostexecutor, so the +# repo name comes from $GITHUB_REPOSITORY (set by the runner) and +# only falls back to the toplevel basename for local invocations. set -euo pipefail root=$(git rev-parse --show-toplevel) cd "$root" -repo_name=$(basename "$root") +# Gitea Actions sets GITHUB_REPOSITORY=owner/repo. The basename of +# `git rev-parse --show-toplevel` inside the runner is the worker dir +# (e.g. `hostexecutor`), which would name the bundle wrong. +if [ -n "${GITHUB_REPOSITORY:-}" ]; then + repo_name="${GITHUB_REPOSITORY##*/}" +else + repo_name=$(basename "$root") +fi dist="$root/dist" # Find Foo/Foo.toc pairs at depth 1; ignore libs nested deeper. From 188701374911a80385339cbeb142baee009f4412 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Fri, 29 May 2026 10:43:54 +0200 Subject: [PATCH 05/10] fix(class): resolve player class at login and defer class-specific defaults/options (was hardcoded DRUID) --- RatingBuster/RatingBuster.lua | 67 ++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/RatingBuster/RatingBuster.lua b/RatingBuster/RatingBuster.lua index 49eb4f7..2feffa2 100644 --- a/RatingBuster/RatingBuster.lua +++ b/RatingBuster/RatingBuster.lua @@ -352,7 +352,11 @@ Spi -> MP5, MP5NC, HP5, SpellDmg, Healing } -- Class specific defaults -if class == "DRUID" then +-- Applied at OnInitialize after UnitClass("player") is available. +-- CoA custom classes (not in the vanilla if/elseif ladder) fall through +-- to no block, which is the correct generic fallback for classless chars. +local function applyClassProfileDefaults(cls) +if cls == "DRUID" then profileDefault.ratingSpell = true profileDefault.ratingPhysical = true profileDefault.sumHP = true @@ -381,7 +385,7 @@ if class == "DRUID" then profileDefault.showHealingFromSpi = true profileDefault.sumIgnoreCloth = false profileDefault.sumIgnoreLeather = false -elseif class == "HUNTER" then +elseif cls == "HUNTER" then profileDefault.ratingPhysical = true profileDefault.sumHP = true profileDefault.sumMP = true @@ -399,7 +403,7 @@ elseif class == "HUNTER" then profileDefault.showAPFromSta = true profileDefault.sumIgnoreLeather = false profileDefault.sumIgnoreMail = false -elseif class == "MAGE" then +elseif cls == "MAGE" then profileDefault.ratingSpell = true profileDefault.sumHP = true profileDefault.sumMP = true @@ -417,7 +421,7 @@ elseif class == "MAGE" then profileDefault.showMP5FromSpi = true profileDefault.showSpellCritFromSpi = true -- Molten Armor profileDefault.sumIgnoreCloth = false -elseif class == "PALADIN" then +elseif cls == "PALADIN" then profileDefault.ratingSpell = true profileDefault.ratingPhysical = true profileDefault.sumHP = true @@ -448,7 +452,7 @@ elseif class == "PALADIN" then profileDefault.sumIgnoreLeather = false profileDefault.sumIgnoreMail = false profileDefault.sumIgnorePlate = false -elseif class == "PRIEST" then +elseif cls == "PRIEST" then profileDefault.ratingSpell = true profileDefault.sumHP = true profileDefault.sumMP = true @@ -466,7 +470,7 @@ elseif class == "PRIEST" then profileDefault.showSpellDmgFromSpi = true profileDefault.showHealingFromSpi = true profileDefault.sumIgnoreCloth = false -elseif class == "ROGUE" then +elseif cls == "ROGUE" then profileDefault.ratingPhysical = true profileDefault.sumHP = true profileDefault.sumResilience = true @@ -478,7 +482,7 @@ elseif class == "ROGUE" then profileDefault.sumArmorPenetration = true profileDefault.showSpellCritFromInt = false profileDefault.sumIgnoreLeather = false -elseif class == "SHAMAN" then +elseif cls == "SHAMAN" then profileDefault.ratingSpell = true profileDefault.ratingPhysical = true profileDefault.sumHP = true @@ -499,7 +503,7 @@ elseif class == "SHAMAN" then profileDefault.sumIgnoreCloth = false profileDefault.sumIgnoreLeather = false profileDefault.sumIgnoreMail = false -elseif class == "WARLOCK" then +elseif cls == "WARLOCK" then profileDefault.ratingSpell = true profileDefault.sumHP = true profileDefault.sumMP = true @@ -515,7 +519,7 @@ elseif class == "WARLOCK" then profileDefault.showMP5FromSpi = true profileDefault.showSpellDmgFromSpi = true profileDefault.sumIgnoreCloth = false -elseif class == "WARRIOR" then +elseif cls == "WARRIOR" then profileDefault.ratingPhysical = true profileDefault.sumHP = true profileDefault.sumResilience = true @@ -536,7 +540,7 @@ elseif class == "WARRIOR" then profileDefault.sumIgnoreMail = false end profileDefault.sumIgnorePlate = false -elseif class == "DEATHKNIGHT" then +elseif cls == "DEATHKNIGHT" then profileDefault.ratingPhysical = true profileDefault.sumHP = true profileDefault.sumResilience = true @@ -554,6 +558,7 @@ elseif class == "DEATHKNIGHT" then profileDefault.showAPFromArmor = true profileDefault.sumIgnorePlate = false end +end -- applyClassProfileDefaults local defaults = {} defaults.profile = profileDefault @@ -2252,7 +2257,10 @@ end -- Class specific options -if class == "DRUID" then +-- Applied at OnInitialize (before SetupOptions) after UnitClass("player") is available. +-- CoA custom classes fall through to no block: no class-specific UI entries shown. +local function applyClassOptions(cls) +if cls == "DRUID" then options.args.stat.args.agi.args.heal = { -- Nurturing Instinct (Rank 2) - 2,14 type = 'toggle', width = "full", @@ -2307,7 +2315,7 @@ if class == "DRUID" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "HUNTER" then +elseif cls == "HUNTER" then options.args.stat.args.int.args.rap = { -- Careful Aim type = 'toggle', width = "full", @@ -2326,7 +2334,7 @@ elseif class == "HUNTER" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "MAGE" then +elseif cls == "MAGE" then options.args.stat.args.int.args.dmg = { -- Mind Mastery (Rank 5) - 1,22 type = 'toggle', width = "full", @@ -2363,7 +2371,7 @@ elseif class == "MAGE" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "PALADIN" then +elseif cls == "PALADIN" then options.args.stat.args.int.args.dmg = { -- Paladin: Holy Guidance (Rank 5) - 1,19 type = 'toggle', width = "full", @@ -2400,7 +2408,7 @@ elseif class == "PALADIN" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "PRIEST" then +elseif cls == "PRIEST" then options.args.stat.args.spi.args.mp5 = { -- Meditation (Rank 3) - 1,9 type = 'toggle', width = "full", @@ -2428,8 +2436,8 @@ elseif class == "PRIEST" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "ROGUE" then -elseif class == "SHAMAN" then +elseif cls == "ROGUE" then +elseif cls == "SHAMAN" then options.args.stat.args.str.args.dmg = { -- Mental Quickness (Rank 3) - 2,15 type = 'toggle', width = "full", @@ -2475,7 +2483,7 @@ elseif class == "SHAMAN" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "WARLOCK" then +elseif cls == "WARLOCK" then options.args.stat.args.sta.args.dmg = { -- Demonic Knowledge (Rank 3) - 2,20 - UnitExists("pet") type = 'toggle', width = "full", @@ -2512,7 +2520,7 @@ elseif class == "WARLOCK" then get = getProfileOption, set = setProfileOptionAndClearCache, } -elseif class == "WARRIOR" then +elseif cls == "WARRIOR" then options.args.stat.args.armor = { -- Armored to the Teeth (Rank 3) - 2,1 type = 'group', order = 7, @@ -2530,7 +2538,7 @@ elseif class == "WARRIOR" then }, }, } -elseif class == "DEATHKNIGHT" then +elseif cls == "DEATHKNIGHT" then options.args.stat.args.str.args.parry = { -- Death Knight: Forceful Deflection - Passive type = 'toggle', width = "full", @@ -2558,6 +2566,7 @@ elseif class == "DEATHKNIGHT" then }, } end +end -- applyClassOptions function RatingBuster:SetupOptions() -- Inject profile options @@ -2660,6 +2669,16 @@ end -- OnInitialize(name) called at ADDON_LOADED function RatingBuster:OnInitialize() + -- Resolve player class as early as possible (ADDON_LOADED). + -- UnitClass("player") is nil at file-load time on this client (CoA 3.3.5a) + -- but is populated by ADDON_LOADED. Apply class-specific profileDefault + -- mutations BEFORE AceDB:New() so that first-install defaults are correct, + -- and apply class-specific options mutations BEFORE SetupOptions/RegisterOptionsTable. + local _, resolvedClass = UnitClass("player") + if resolvedClass then + class = resolvedClass + end + applyClassProfileDefaults(class) -- Create DB self.db = AceDB:New("RatingBusterDB", defaults) self.db.RegisterCallback(self, "OnProfileChanged", "OnProfileChanged") @@ -2668,6 +2687,7 @@ function RatingBuster:OnInitialize() profileDB = self.db.profile + applyClassOptions(class) self:SetupOptions() -- Hook ShoppingTooltips to enable options to Hide Blizzard Item Comparisons @@ -2680,6 +2700,13 @@ end function RatingBuster:OnEnable() -- Hook item tooltips TipHooker:Hook(self.ProcessTooltip, "item") + -- Ensure class is resolved at PLAYER_LOGIN in case ADDON_LOADED fired too early. + -- This guards the runtime stat-conversion paths (class is used throughout + -- ProcessTooltip and StatLogic calls). + local _, resolvedClass = UnitClass("player") + if resolvedClass then + class = resolvedClass + end -- Initialize playerLevel playerLevel = UnitLevel("player") -- for setting a new level From a969cf5fbd82a28620c92c3c8fca8e212b4c523d Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Fri, 29 May 2026 20:23:33 +0200 Subject: [PATCH 06/10] fix(libs): pcall AceGUI OnGamePadButtonDown (3.3.5 has no gamepad script type) --- .../libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RatingBuster/libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua b/RatingBuster/libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua index ee5a83b..07c8f76 100644 --- a/RatingBuster/libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua +++ b/RatingBuster/libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua @@ -199,7 +199,7 @@ local function Constructor() button:SetScript("OnKeyDown", Keybinding_OnKeyDown) button:SetScript("OnMouseDown", Keybinding_OnMouseDown) button:SetScript("OnMouseWheel", Keybinding_OnMouseWheel) - button:SetScript("OnGamePadButtonDown", Keybinding_OnKeyDown) + pcall(button.SetScript, button, "OnGamePadButtonDown", Keybinding_OnKeyDown) button:SetPoint("BOTTOMLEFT") button:SetPoint("BOTTOMRIGHT") button:SetHeight(24) From e7b3d4fb93b1a521377ed2f773f19568b28d464f Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Fri, 29 May 2026 20:51:15 +0200 Subject: [PATCH 07/10] ci(release): hide auto-generated source archives (hide_archive_links) --- .gitea/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 709a7cd..1b1b66b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -37,7 +37,7 @@ 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" From 694f6262ee701d7d5edc4d3797e2262d1267ba1e Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Sat, 30 May 2026 01:27:49 +0200 Subject: [PATCH 08/10] feat(class): add CoA custom-class stat-summary defaults via CoAClassPrimaryStats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copy CoAClassSpecData.lua into the addon folder and load it before RatingBuster.lua in the .toc. After the vanilla if/elseif ladder in applyClassProfileDefaults(), a new CoA branch checks CoAClassPrimaryStats[cls]: if the player's token is a CoA custom class, enable the stat-summary keys that match the class's union of primary stats (Agility→physical melee block, Strength→physical melee block, Intellect→spell block, Spirit→healing/mp5 block, Stamina→health/sta). Vanilla classes and unrelated settings are not touched. --- RatingBuster/CoAClassSpecData.lua | 143 ++++++++++++++++++++++++++++++ RatingBuster/RatingBuster.lua | 62 +++++++++++++ RatingBuster/RatingBuster.toc | 3 + 3 files changed, 208 insertions(+) create mode 100644 RatingBuster/CoAClassSpecData.lua diff --git a/RatingBuster/CoAClassSpecData.lua b/RatingBuster/CoAClassSpecData.lua new file mode 100644 index 0000000..5057aeb --- /dev/null +++ b/RatingBuster/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/RatingBuster/RatingBuster.lua b/RatingBuster/RatingBuster.lua index 2feffa2..8ec59a9 100644 --- a/RatingBuster/RatingBuster.lua +++ b/RatingBuster/RatingBuster.lua @@ -558,6 +558,68 @@ elseif cls == "DEATHKNIGHT" then profileDefault.showAPFromArmor = true profileDefault.sumIgnorePlate = false end + +-- CoA custom classes: enable stat-summary defaults based on primary stats. +-- CoAClassPrimaryStats is defined in CoAClassSpecData.lua (loaded before this file). +if CoAClassPrimaryStats and CoAClassPrimaryStats[cls] then + local stats = CoAClassPrimaryStats[cls] + local hasAgi, hasStr, hasInt, hasSpi, hasSta = false, false, false, false, false + for _, stat in ipairs(stats) do + if stat == "Agility" then hasAgi = true end + if stat == "Strength" then hasStr = true end + if stat == "Intellect" then hasInt = true end + if stat == "Spirit" then hasSpi = true end + if stat == "Stamina" then hasSta = true end + end + -- Always: HP and resilience + profileDefault.sumHP = true + profileDefault.sumResilience = true + if hasAgi then + profileDefault.ratingPhysical = true + profileDefault.sumAgi = true + profileDefault.sumAP = true + profileDefault.sumHit = true + profileDefault.sumCrit = true + profileDefault.sumHaste = true + profileDefault.sumExpertise = true + profileDefault.sumArmorPenetration = true + end + if hasStr then + profileDefault.ratingPhysical = true + profileDefault.sumStr = true + profileDefault.sumAP = true + profileDefault.sumHit = true + profileDefault.sumCrit = true + profileDefault.sumHaste = true + profileDefault.sumExpertise = true + profileDefault.sumArmorPenetration = true + end + if hasInt then + profileDefault.ratingSpell = true + profileDefault.sumMP = true + profileDefault.sumInt = true + profileDefault.sumSpellDmg = true + profileDefault.sumSpellHit = true + profileDefault.sumSpellCrit = true + profileDefault.sumSpellHaste = true + profileDefault.sumHealing = true + profileDefault.sumMP5 = true + profileDefault.showSpellDmgFromInt = true + profileDefault.showMP5FromInt = true + end + if hasSpi then + profileDefault.ratingSpell = true + profileDefault.sumMP = true + profileDefault.sumSpi = true + profileDefault.sumHealing = true + profileDefault.sumMP5 = true + profileDefault.showMP5FromSpi = true + profileDefault.showHealingFromSpi = true + end + if hasSta then + profileDefault.sumSta = true + end +end end -- applyClassProfileDefaults local defaults = {} diff --git a/RatingBuster/RatingBuster.toc b/RatingBuster/RatingBuster.toc index b72b283..f45f994 100644 --- a/RatingBuster/RatingBuster.toc +++ b/RatingBuster/RatingBuster.toc @@ -36,5 +36,8 @@ RatingBuster-Locale-frFR.lua RatingBuster-Locale-koKR.lua RatingBuster-Locale-esES.lua +# CoA class/spec data (must load before RatingBuster.lua) # +CoAClassSpecData.lua + # Core # RatingBuster.lua \ No newline at end of file From 4cd6d6a6121596672b8a85e38726e76774474eab Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Wed, 10 Jun 2026 02:11:46 +0200 Subject: [PATCH 09/10] ci(release): sync release.yml from coa-template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hide_archive_links is only honored by Gitea on release edit, not create — add the PATCH step after create/lookup so auto-generated source archive links actually stay hidden (coa-template 90874c5). --- .gitea/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 1b1b66b..2f93975 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -41,6 +41,10 @@ jobs: | 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. From 8b9f29fc628f8ab48a7973fe9a4c51d2ac004073 Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Wed, 10 Jun 2026 02:16:14 +0200 Subject: [PATCH 10/10] chore: fix Interface version 30301 -> 30300 --- RatingBuster/RatingBuster.toc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RatingBuster/RatingBuster.toc b/RatingBuster/RatingBuster.toc index f45f994..9aa18dd 100644 --- a/RatingBuster/RatingBuster.toc +++ b/RatingBuster/RatingBuster.toc @@ -1,4 +1,4 @@ -## Interface: 30301 +## Interface: 30300 ## Title: RatingBuster ## Notes: Item stat breakdown, analysis and comparison ## Notes-zhTW: 裝備數值解析與比較