Compare commits
10 commits
f6db3caee0
...
8b9f29fc62
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b9f29fc62 | |||
| 4cd6d6a612 | |||
| 694f6262ee | |||
| e7b3d4fb93 | |||
| a969cf5fbd | |||
| 1887013749 | |||
| 3b8d9b36b7 | |||
| 3f478be582 | |||
| 47c02e09c7 | |||
| 7a2d38d6e0 |
12 changed files with 414 additions and 150 deletions
75
.gitea/workflows/release.yml
Normal file
75
.gitea/workflows/release.yml
Normal file
|
|
@ -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 ]
|
||||
77
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
77
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -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
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
1
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -1 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -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.
|
||||
28
.github/PULL_REQUEST_TEPMLATE.md
vendored
28
.github/PULL_REQUEST_TEPMLATE.md
vendored
|
|
@ -1,28 +0,0 @@
|
|||
# Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
|
||||
<!-- A #issueNumber will be sufficient. -->
|
||||
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
|
||||
<!-- These can be checked off after the pull request is submitted, in case you want discussion before they are completely ready -->
|
||||
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
|
||||
<!-- Is there any additional work that needs to be done? If so, add it to the above list -->
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,4 +4,5 @@
|
|||
.install
|
||||
.lua/*
|
||||
.vscode
|
||||
.idea
|
||||
.idea
|
||||
dist/
|
||||
|
|
|
|||
143
RatingBuster/CoAClassSpecData.lua
Normal file
143
RatingBuster/CoAClassSpecData.lua
Normal file
|
|
@ -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" },
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -555,6 +559,69 @@ elseif class == "DEATHKNIGHT" then
|
|||
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 = {}
|
||||
defaults.profile = profileDefault
|
||||
|
||||
|
|
@ -2252,7 +2319,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 +2377,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 +2396,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 +2433,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 +2470,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 +2498,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 +2545,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 +2582,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 +2600,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 +2628,7 @@ elseif class == "DEATHKNIGHT" then
|
|||
},
|
||||
}
|
||||
end
|
||||
end -- applyClassOptions
|
||||
|
||||
function RatingBuster:SetupOptions()
|
||||
-- Inject profile options
|
||||
|
|
@ -2660,6 +2731,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 +2749,7 @@ function RatingBuster:OnInitialize()
|
|||
|
||||
profileDB = self.db.profile
|
||||
|
||||
applyClassOptions(class)
|
||||
self:SetupOptions()
|
||||
|
||||
-- Hook ShoppingTooltips to enable options to Hide Blizzard Item Comparisons
|
||||
|
|
@ -2680,6 +2762,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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
## Interface: 30301
|
||||
## Interface: 30300
|
||||
## Title: RatingBuster
|
||||
## Notes: Item stat breakdown, analysis and comparison
|
||||
## Notes-zhTW: 裝備數值解析與比較
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
71
tools/build_zip.sh
Executable file
71
tools/build_zip.sh
Executable file
|
|
@ -0,0 +1,71 @@
|
|||
#!/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 <repo>-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"
|
||||
|
||||
# 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.
|
||||
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 <folder>/... 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue