first commit

This commit is contained in:
2025-12-08 13:45:59 +01:00
commit bd66424c2a
14 changed files with 1324 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
# Wiki.js Formatting Guide for AI/LLM
This document provides formatting guidelines for creating and editing Wiki.js articles in this repository.
## Project Overview
This is the ExilesWiki project for an Ascension WoW private server. Articles are written in Markdown format with Wiki.js-specific features enabled.
## Markdown Features
### Standard Markdown
- Headers: `#`, `##`, `###`, etc.
- **Bold**: `**text**`
- *Italic*: `*text*`
- Lists: `-` or `1.`
- Links: `[text](url)`
- Code blocks: triple backticks
### Wiki.js Specific
#### Icons
Icons are stored in `/icons/` directory and referenced as:
```markdown
![Icon Alt Text](/icons/path/to/icon.png)
```
Examples:
- Class icons: `![Paladin](/icons/classes/classicon_paladin.png)`
- Item icons: `![Item Name](/icons/items/itemicon_name.png)`
#### Tables
MultiMarkdown Table format is enabled:
```markdown
| Column 1 | Column 2 | Column 3 |
|----------|----------|----------|
| Data | Data | Data |
```
- Use `<br>` for line breaks within cells
- Tables support standard markdown formatting inside cells
#### PlantUML Diagrams
PlantUML code blocks are rendered as diagrams:
````markdown
```plantuml
@startuml
title Diagram Title
' Your PlantUML code here
@enduml
```
````
**C4 PlantUML for Rotations:**
For ability rotations/priority systems, use C4 PlantUML format:
````markdown
```plantuml
@startuml
!define C4 true
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
title Ability Priority System
rectangle "Priority System" {
(1) Ability Name : Description
(2) Another Ability : Description
(1) -down-> (2)
}
@enduml
```
````
#### External Links
**Ascension Database Links:**
Link items/spells to the Ascension database:
```markdown
[Item Name](https://db.ascension.gg/?item=ITEMID)
[Spell Name](https://db.ascension.gg/?spell=SPELLID)
```
Example:
```markdown
[Lionheart Helm](https://db.ascension.gg/?item=12640)
[Deranged Druid](https://db.ascension.gg/?spell=84531)
```
## Content Structure
### Class Guides
Typical structure for class guides:
1. **Title & Icon**: Class name with class icon
2. **Introduction**: Brief overview and author credit
3. **Pre-Raid BiS Gear**: Table format with slot/item options
4. **Enchants**: Table with slot/enchant/stats
5. **Talents**: Key talent descriptions
6. **Mystic Enchants**: Organized by rarity (Artifact, Legendary, Epic, Rare)
7. **Macros**: Code blocks with macro examples
8. **Rotation**: PlantUML priority diagram + text breakdown
### Formatting Best Practices
- Use tables for gear lists, enchants, and stat comparisons
- Use code blocks for macros, commands, and configuration
- Include icons for visual appeal (class icons, item icons)
- Link items to db.ascension.gg when possible
- Use PlantUML for visual representations of rotations/priorities
- Break long lists with headers and categories
- Use bold for important terms or item/ability names
- Use italic for quest/flavor text
## File Organization
```
exileswiki/
├── .ai/ # AI/LLM documentation
│ └── wiki-formatting-guide.md
├── scripts/ # Utility scripts
│ ├── add_item_links.py # Auto-link items to db
│ └── item_links_found.json # Cache of found item IDs
├── icons/ # Icon assets
│ ├── classes/
│ └── items/
└── *.md # Wiki articles
```
## Scripts
### add_item_links.py
Automatically searches db.ascension.gg for items in guides and adds proper links.
Usage:
```bash
python3 scripts/add_item_links.py
```
The script:
1. Extracts item names from markdown files
2. Searches db.ascension.gg for each item
3. Saves results to `item_links_found.json`
4. Reports items that need manual lookup
## Tips for AI/LLM
When creating or editing wiki articles:
1. **Always check existing articles** for formatting consistency
2. **Use the icon path format** `/icons/category/name.png` (absolute path from root)
3. **MultiMarkdown tables** support `<br>` for multi-line cells
4. **PlantUML C4 diagrams** work well for ability priority systems
5. **Item links** should use format `[Name](https://db.ascension.gg/?item=ID)`
6. **Code blocks** for macros should specify language (e.g., ````lua` or just ``` for plain)
7. **Test PlantUML** syntax before committing (validate it renders)
8. **Keep tables readable** in source - align columns when possible
## Example Article Structure
```markdown
# Class Name Guide
![Class Icon](/icons/classes/classicon_name.png)
*By Author*
Brief introduction...
## Section Header
Content with [linked items](https://db.ascension.gg/?item=12640)...
| Header 1 | Header 2 |
|----------|----------|
| Data | Data |
```plantuml
@startuml
title Diagram
@enduml
```
## Another Section
More content...
```
## Resources
- [Wiki.js Documentation](https://docs.requarks.io/)
- [PlantUML Documentation](https://plantuml.com/)
- [Ascension Database](https://db.ascension.gg/)
- [MultiMarkdown Table Syntax](https://fletcher.github.io/MultiMarkdown-6/syntax/tables.html)
+11
View File
@@ -0,0 +1,11 @@
# Ignore project files
.idea/
*.iml
# Ignore all .java files except the ones from the 'src/main' folder
venv/
# Ignore logs and temporary files
logs/
*.log
*.tmp
+45
View File
@@ -0,0 +1,45 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Python files
[*.py]
indent_style = space
indent_size = 4
max_line_length = 120
multi_line_output = 3
# YAML files
[*.{yml,yaml}]
indent_style = space
indent_size = 2
# JSON files
[*.json]
indent_style = space
indent_size = 2
# Markdown files
[*.md]
trim_trailing_whitespace = false
max_line_length = off
# Requirements files
[requirements*.txt]
insert_final_newline = false
# Shell scripts
[*.sh]
end_of_line = lf
# Windows scripts
[*.{cmd,bat}]
end_of_line = crlf
+36
View File
@@ -0,0 +1,36 @@
# Basic .gitattributes for a python repo.
# py source files
*.pxd text
*.py text
*.py3 text
*.pyw text
*.pyx text
# py binary files
*.db binary
*.p binary
*.pkl binary
*.pyc binary
*.pyd binary
*.pyo binary
# Note: .db, .p, and .pkl files are associated
# with the python modules ``pickle``, ``dbm.*``,
# ``shelve``, ``marshal``, ``anydbm``, & ``bsddb``
# (among others).
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.h text
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
+87
View File
@@ -0,0 +1,87 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
ENV/
*.egg-info/
dist/
build/
# Icon files (downloaded and converted)
icons/ICONS/
icons/*.zip
icons_converted/
# IDE
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Temporary files
*.tmp
*.bak
.cache/
# ---> Ansible
*.retry
# Temporary files
temp/
temp/*
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
env/
ENV/
# IDE
.idea/
.vscode/
*.swp
*.swo
# Testing
.coverage
htmlcov/
.pytest_cache/
.mypy_cache/
# Logs
*.log
# Environment variables
.env
.env.local
+13
View File
@@ -0,0 +1,13 @@
## Interface: 30300
## Title: Ascension Exporter
## Notes: Export active talents, worn items, and active Mystic Enchants to JSON
## Author: Subd from Exiles EU
## Version: 0.1.0
## SavedVariables: AscensionExporterSaved, AscensionExporterConfig
Core.lua
Util/Json.lua
UI/ExportFrame.lua
Collectors/Talents.lua
Collectors/Gear.lua
Collectors/Enchants.lua
+58
View File
@@ -0,0 +1,58 @@
-- AscensionExporter - Mystic Enchants collector (from equipped item tooltips)
local AE = AscensionExporter
local SLOT_NAMES = {
[1] = "HEAD", [2] = "NECK", [3] = "SHOULDER", [4] = "SHIRT", [5] = "CHEST",
[6] = "WAIST", [7] = "LEGS", [8] = "FEET", [9] = "WRIST", [10] = "HANDS",
[11] = "FINGER1", [12] = "FINGER2", [13] = "TRINKET1", [14] = "TRINKET2",
[15] = "BACK", [16] = "MAINHAND", [17] = "OFFHAND", [18] = "RANGED", [19] = "TABARD",
}
local function extract_mystic_from_tooltip(slot)
GameTooltip:SetOwner(UIParent, "ANCHOR_NONE")
GameTooltip:SetInventoryItem("player", slot)
local found = nil
for i = 1, GameTooltip:NumLines() do
local line = _G["GameTooltipTextLeft" .. i]
if line then
local txt = tostring(line:GetText() or "")
if txt and txt ~= "" then
local l = txt:lower()
if (l:find("mystic") or l:find("mythic")) and (l:find("enchant") or l:find("rune")) then
-- Try to pull the enchant name after a colon, else use the whole line
local name = txt
local after = txt:match("[:]%s*(.+)$")
if after and after ~= "" then name = after end
-- Trim color codes if any
name = name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
found = name
break
end
end
end
end
GameTooltip:Hide()
return found
end
function AE.CollectMysticEnchants()
local perSlot = {}
local activeSet = {}
for slot = 1, 19 do
local link = GetInventoryItemLink("player", slot)
if link then
local name = extract_mystic_from_tooltip(slot)
if name and name ~= "" then
table.insert(perSlot, { slot = slot, slotName = SLOT_NAMES[slot] or tostring(slot), name = name })
activeSet[name] = true
end
end
end
local active = {}
for n, _ in pairs(activeSet) do
table.insert(active, { name = n })
end
table.sort(active, function(a,b) return a.name < b.name end)
return { perSlot = perSlot, active = active }
end
+109
View File
@@ -0,0 +1,109 @@
-- AscensionExporter - Gear collector (equipped items only)
local AE = AscensionExporter
local SLOT_NAMES = {
[1] = "HEAD", [2] = "NECK", [3] = "SHOULDER", [4] = "SHIRT", [5] = "CHEST",
[6] = "WAIST", [7] = "LEGS", [8] = "FEET", [9] = "WRIST", [10] = "HANDS",
[11] = "FINGER1", [12] = "FINGER2", [13] = "TRINKET1", [14] = "TRINKET2",
[15] = "BACK", [16] = "MAINHAND", [17] = "OFFHAND", [18] = "RANGED", [19] = "TABARD",
}
local function parse_item_link(itemLink)
if not itemLink then return nil end
local itemString = itemLink:match("Hitem:([%d:]+)")
if not itemString then return nil end
local parts = {}
for v in string.gmatch(itemString, "([^:]+)") do
parts[#parts+1] = tonumber(v) or 0
end
local itemId = parts[1] or 0
local enchantId = parts[2] or 0
local gems = { parts[3] or 0, parts[4] or 0, parts[5] or 0, parts[6] or 0 }
local suffixId = parts[7] or 0
return {
itemId = itemId,
enchantId = enchantId,
gems = gems,
suffixId = suffixId,
}
end
local function resolve_gems(itemLink, gemIds)
local arr = {}
for i = 1, 4 do
local gid = gemIds[i] or 0
if gid and gid > 0 then
-- GetItemGem gives name & link by index
local name, gemLink = GetItemGem(itemLink, i)
table.insert(arr, { itemId = gid, name = name or "", link = gemLink or "" })
end
end
return arr
end
local function read_enchant_from_tooltip(slot)
-- Try to extract human-readable base enchant (not mystic) if present
GameTooltip:SetOwner(UIParent, "ANCHOR_NONE")
GameTooltip:SetInventoryItem("player", slot)
local name = nil
for i = 1, GameTooltip:NumLines() do
local line = _G["GameTooltipTextLeft" .. i]
if line then
local txt = tostring(line:GetText() or "")
if txt and txt ~= "" then
local l = txt:lower()
-- Skip mystic/mythic lines; those are handled separately
if not ((l:find("mystic") or l:find("mythic")) and (l:find("enchant") or l:find("rune"))) then
-- Heuristic: lines starting with "Enchanted" or containing "Enchantment" often denote base enchants
if l:find("^enchanted") or l:find("enchant") then
name = txt
end
end
end
end
end
GameTooltip:Hide()
return name
end
function AE.CollectGear()
local out = { slots = {} }
for slot = 1, 19 do
local itemLink = GetInventoryItemLink("player", slot)
if itemLink then
local parsed = parse_item_link(itemLink) or {}
local itemId = parsed.itemId or 0
local itemName, _, itemQuality, itemLevel, _, itemType, itemSubType, _, equipSlot, texture = GetItemInfo(itemLink)
local enchantName = read_enchant_from_tooltip(slot)
local enchant = nil
if parsed.enchantId and parsed.enchantId > 0 then
enchant = { id = parsed.enchantId, name = enchantName or "" }
elseif enchantName then
enchant = { id = 0, name = enchantName }
end
local gems = resolve_gems(itemLink, parsed.gems or {})
table.insert(out.slots, {
slot = slot,
slotName = SLOT_NAMES[slot] or tostring(slot),
itemId = itemId,
name = itemName or "",
quality = itemQuality or 0,
itemLevel = itemLevel or 0,
type = itemType or "",
subType = itemSubType or "",
equipSlot = equipSlot or "",
texture = texture or "",
link = itemLink,
enchant = enchant,
gems = gems,
})
else
-- Empty slot omitted intentionally
end
end
return out
end
+56
View File
@@ -0,0 +1,56 @@
-- AscensionExporter - Talents collector (active spec only)
local AE = AscensionExporter
local function get_active_group()
if GetActiveTalentGroup then
local g = GetActiveTalentGroup()
if g == 0 then g = 1 end
return g or 1
end
return 1
end
local function collect_selected_for_tab(tabIndex, talentGroup)
local arr = {}
local numTalents = GetNumTalents(tabIndex)
for talentIndex = 1, numTalents do
local name, _, _, _, rank, maxRank = GetTalentInfo(tabIndex, talentIndex, false, false, talentGroup)
if rank and rank > 0 then
local link = GetTalentLink(tabIndex, talentIndex)
local spellId = 0
if link then
spellId = tonumber(string.match(link, "talent:(%d+)")) or 0
end
table.insert(arr, {
tabIndex = tabIndex,
talentIndex = talentIndex,
name = name or "",
rank = rank or 0,
maxRank = maxRank or 0,
spellId = spellId,
})
end
end
return arr
end
function AE.CollectTalents()
local active = get_active_group()
local out = {
activeTalentGroup = active,
selected = {},
}
local numTabs = GetNumTalentTabs()
for tabIndex = 1, numTabs do
local tabName, _, pointsSpent = GetTalentTabInfo(tabIndex, false, false, active)
-- Only export selected ranks for the active group
local selected = collect_selected_for_tab(tabIndex, active)
for _, v in ipairs(selected) do
table.insert(out.selected, v)
end
end
return out
end
+250
View File
@@ -0,0 +1,250 @@
-- AscensionExporter - Core
local ADDON_NAME = ...
local AE = {}
AscensionExporter = AE
-- SavedVariables defaults
AscensionExporterConfig = AscensionExporterConfig or {}
AscensionExporterConfig.enableSavedVariables = AscensionExporterConfig.enableSavedVariables == true and true or false
AscensionExporterSaved = AscensionExporterSaved or {}
-- Utils
local function IsArray(t)
if type(t) ~= "table" then return false end
local n = 0
for k, _ in pairs(t) do
if type(k) ~= "number" then return false end
n = n + 1
end
for i = 1, n do
if t[i] == nil then return false end
end
return true
end
-- Shims for safety
local function SafeCall(fn, ...)
local ok, r = pcall(fn, ...)
if ok then return r end
return nil
end
-- Export assembly
function AE:AssembleExport(which)
local nowIso = date("!%Y-%m-%dT%H:%M:%SZ")
local version, build, buildDate, toc = GetBuildInfo()
local name = UnitName("player") or ""
local level = UnitLevel("player") or 0
local _, class = UnitClass("player")
local _, race = UnitRace("player")
local faction = UnitFactionGroup("player") or ""
local realm = GetRealmName and GetRealmName() or (GetCVar and GetCVar("realmName")) or ""
local out = {
schemaVersion = 1,
exportedAt = nowIso,
client = { interface = toc or 30300, version = version, build = build, buildDate = buildDate },
character = { name = name or "", realm = realm or "", level = level or 0, class = class or "", race = race or "", faction = faction or "" },
}
local wantAll = (which == nil) or (which == "all")
if wantAll or which == "talents" then
if AE.CollectTalents then
out.talents = SafeCall(AE.CollectTalents) or {}
end
end
if wantAll or which == "gear" then
if AE.CollectGear then
out.gear = SafeCall(AE.CollectGear) or {}
end
end
if wantAll or which == "enchants" then
if AE.CollectMysticEnchants then
out.mysticEnchants = SafeCall(AE.CollectMysticEnchants) or {}
end
end
return out
end
function AE:Export(which)
local data = self:AssembleExport(which)
-- Prefer global encoder from Util/Json.lua; fallback to a tiny local encoder if missing
local function tiny_json_encode(v)
local tv = type(v)
if tv == 'nil' then return 'null' end
if tv == 'boolean' then return v and 'true' or 'false' end
if tv == 'number' then return tostring(v) end
if tv == 'string' then
local s = v:gsub('\\', '\\\\'):gsub('"', '\\"'):gsub('\n', '\\n'):gsub('\r', '\\r'):gsub('\t', '\\t')
return '"' .. s .. '"'
end
if tv == 'table' then
-- detect array-like
local n = 0
for k,_ in pairs(v) do if type(k) ~= 'number' then n = -1 break else n = math.max(n, k) end end
if n >= 1 then
local parts = {}
for i=1,n do parts[#parts+1] = tiny_json_encode(v[i]) end
return '[' .. table.concat(parts, ',') .. ']'
else
local parts = {}
for k,val in pairs(v) do parts[#parts+1] = tiny_json_encode(tostring(k)) .. ':' .. tiny_json_encode(val) end
return '{' .. table.concat(parts, ',') .. '}'
end
end
return 'null'
end
local encoder = _G.AscensionExporter_Json_Encode or tiny_json_encode
local json = encoder(data)
local title = "Ascension Export"
if which and which ~= "all" then title = title .. " - " .. which end
AscensionExporter_ShowExportFrame(json, title .. " - Copy All (Ctrl+C)")
if AscensionExporterConfig.enableSavedVariables then
local key = (data.character.realm or "") .. ":" .. (data.character.name or "")
AscensionExporterSaved[key] = data
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter: export saved to SavedVariables for " .. key)
end
end
-- Markdown gear table generator (for guide tables like mage-guide.md)
function AE:GenerateMarkdownGear()
local gear = self.CollectGear and self.CollectGear() or { slots = {} }
local ench = self.CollectMysticEnchants and self.CollectMysticEnchants() or { perSlot = {}, active = {} }
-- Map gear by slot and mystic enchant per slot
local bySlot = {}
for _, s in ipairs(gear.slots or {}) do
bySlot[s.slot] = s
end
local mystPer = {}
for _, e in ipairs(ench.perSlot or {}) do
mystPer[e.slot] = e.name
end
-- Desired order and pretty slot names matching the mage guide
local ORDER = {
{1, "Head"},
{2, "Neck"},
{3, "Shoulders"},
{15, "Back"},
{5, "Chest"},
{9, "Wrist"},
{16, "Mainhand"},
{17, "Offhand"},
{18, "Wand"},
{10, "Gloves"},
{6, "Belt"},
{7, "Legs"},
{8, "Boots"},
{11, "Ring 1"},
{12, "Ring 2"},
{13, "Trinket 1"},
{14, "Trinket 2"},
}
local function md_link(name, itemId)
if (itemId or 0) > 0 and name and name ~= "" then
return string.format("[%s](https://db.ascension.gg/?item=%d)", name, itemId)
elseif name and name ~= "" then
return name
else
return "-"
end
end
local lines = {}
table.insert(lines, "| Slot | Item | Location | Enchant | Alternative |")
table.insert(lines, "|------|------|----------|---------|-------------|")
for _, ent in ipairs(ORDER) do
local slotId, label = ent[1], ent[2]
local s = bySlot[slotId]
local itemCell = "-"
local enchantCell = "-"
if s then
itemCell = md_link(s.name, s.itemId)
local base = s.enchant and s.enchant.name or nil
local myst = mystPer[slotId]
if base and base ~= "" then
enchantCell = base
end
if myst and myst ~= "" then
enchantCell = (enchantCell ~= "-" and enchantCell ~= nil and enchantCell ~= "")
and (enchantCell .. " / Mystic: " .. myst)
or ("Mystic: " .. myst)
end
if not enchantCell or enchantCell == "" then enchantCell = "-" end
end
local row = string.format("| **%s** | %s | - | %s | - |", label, itemCell, enchantCell)
table.insert(lines, row)
end
return table.concat(lines, "\n") .. "\n"
end
-- Slash command handling
SLASH_ASCX1 = "/ascx"
SLASH_ASCX2 = "/asxc" -- alias: both map to the same handler key "ASCX"
SlashCmdList["ASCX"] = function(msg)
msg = msg or ""
msg = msg:lower()
if msg == "" or msg == "help" then
-- Open the export window with helpful text and let buttons drive actions
if AscensionExporter_ShowExportFrame then
local help = "Use the buttons above or type:\n/ascx export all|talents|gear|enchants|mdgear\n/ascx sv on|off (SavedVariables is currently " .. (AscensionExporterConfig.enableSavedVariables and "ON" or "OFF") .. ")\n"
AscensionExporter_ShowExportFrame(help, "Ascension Export - Tools & Help")
return
else
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter usage:")
DEFAULT_CHAT_FRAME:AddMessage(" /ascx export all|talents|gear|enchants|mdgear")
DEFAULT_CHAT_FRAME:AddMessage(" /ascx sv on|off - toggle SavedVariables export (currently " .. (AscensionExporterConfig.enableSavedVariables and "ON" or "OFF") .. ")")
return
end
end
local cmd, rest = msg:match("^(%S+)%s*(.*)$")
if cmd == "export" then
rest = rest ~= "" and rest or "all"
if rest == "mdgear" or rest == "md" then
local md = AE:GenerateMarkdownGear()
AscensionExporter_ShowExportFrame(md, "Ascension Export - Markdown Gear - Copy All (Ctrl+C)")
elseif rest == "all" or rest == "talents" or rest == "gear" or rest == "enchants" then
AE:Export(rest)
else
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter: unknown export target '" .. tostring(rest) .. "'")
end
return
elseif cmd == "sv" then
if rest == "on" then
AscensionExporterConfig.enableSavedVariables = true
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter: SavedVariables export ENABLED")
elseif rest == "off" then
AscensionExporterConfig.enableSavedVariables = false
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter: SavedVariables export DISABLED")
else
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter: sv on|off")
end
return
else
DEFAULT_CHAT_FRAME:AddMessage("AscensionExporter: unknown command. Type /ascx help")
end
end
-- Basic event binding (not strictly necessary for slash-only usage)
local f = CreateFrame("Frame")
f:RegisterEvent("ADDON_LOADED")
f:SetScript("OnEvent", function(_, event, addon)
if event == "ADDON_LOADED" and addon == ADDON_NAME then
-- init defaults if needed
AscensionExporterConfig.enableSavedVariables = AscensionExporterConfig.enableSavedVariables and true or false
end
end)
+54
View File
@@ -0,0 +1,54 @@
AscensionExporter
=================
Export the following from your Ascension (WotLK 3.3.5) client:
- Active talents (current spec only)
- Worn items (equipped gear only), including base enchant text and gems
- Active Mystic/Mythic Enchants (derived from equipped item tooltips)
Installation (Linux path provided by you)
- Copy the `AscensionExporter` folder to:
`/srv/add01/wow-ascension/Interface/AddOns/AscensionExporter`
- Ensure the structure is:
`/srv/add01/wow-ascension/Interface/AddOns/AscensionExporter/AscensionExporter.toc`
Usage
- In-game, type:
- `/ascx export all` — talents + gear + mystic enchants
- `/ascx export talents` — only active spec selected talents
- `/ascx export gear` — only equipped gear
- `/ascx export enchants` — only mystic enchants
- `/ascx export mdgear` — equipped gear as a Markdown table (for guides)
- A copyable JSON window will open. Press Ctrl+C to copy.
Export window button
- The export window now has a "MD Gear" button. Click it to populate the window with a Markdown gear table matching the guide format:
- Columns: `Slot | Item | Location | Enchant | Alternative`
- `Item` links to db.ascension.gg when the itemId is known.
- `Enchant` includes base enchant text and any active Mystic/Mythic enchant (e.g., `... / Mystic: Rune of Power`).
- `Location` and `Alternative` are placeholders (`-`) since those are guide-specific.
SavedVariables (optional)
- Disabled by default. Toggle with:
- `/ascx sv on` — enable saving last export to SavedVariables
- `/ascx sv off` — disable
- Saved in: `AscensionExporterSaved` table (path depends on your client setup).
JSON schema (v1)
- Top-level fields:
- `schemaVersion` (number)
- `exportedAt` (ISO UTC string)
- `client` (interface/build info)
- `character` (name, realm, level, class, race, faction)
- `talents``{ activeTalentGroup, selected[] }` for current spec only
- `gear``{ slots[] }` equipped items with parsed IDs, names, enchant text, gems
- `mysticEnchants``{ perSlot[], active[] }` from equipped item tooltips
Notes & limits
- Mystic vs Mythic wording varies; detection is case-insensitive for lines containing both `mystic|mythic` and `enchant|rune`.
- Profession-only enchants (e.g., Enchanting ring enchants) are not included in `mysticEnchants`; base enchant text is placed under `gear.slots[].enchant`.
- Some item info may be `nil` if not cached; reopen the character pane or shift-hover items to cache if needed.
Troubleshooting
- If `/ascx` doesnt respond, verify the addon is enabled on the character select screen.
- If the export window is empty, try `/reload` and re-run the command out of combat.
+104
View File
@@ -0,0 +1,104 @@
-- AscensionExporter - Export UI
local function CreateOrGetFrame()
if AscensionExporterFrame then return AscensionExporterFrame end
local frame = CreateFrame("Frame", "AscensionExporterFrame", UIParent, "DialogBoxFrame")
frame:SetSize(700, 500)
frame:SetPoint("CENTER")
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", frame.StartMoving)
frame:SetScript("OnDragStop", frame.StopMovingOrSizing)
-- Title text
local title = frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
title:SetPoint("TOP", 0, -8)
frame.title = title
-- Action buttons row
local function makeBtn(label, x, click)
local btn = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
btn:SetSize(90, 22)
btn:SetPoint("TOPLEFT", 16 + x, -36)
btn:SetText(label)
btn:SetScript("OnClick", click)
return btn
end
local x = 0
makeBtn("All", x, function()
if AscensionExporter and AscensionExporter.Export then
local data = AscensionExporter:AssembleExport("all")
local encoder = _G.AscensionExporter_Json_Encode
local json
if encoder then json = encoder(data) else
-- very small fallback mirroring Core's behavior
local function E(v)
local t=type(v)
if t=='nil' then return 'null' end
if t=='boolean' then return v and 'true' or 'false' end
if t=='number' then return tostring(v) end
if t=='string' then v=v:gsub('\\','\\\\'):gsub('"','\\"'):gsub('\n','\\n'):gsub('\r','\\r'):gsub('\t','\\t'); return '"'..v..'"' end
if t=='table' then local n=0 for k,_ in pairs(v) do if type(k)~='number' then n=-1 break else if k>n then n=k end end end if n>=1 then local p={} for i=1,n do p[#p+1]=E(v[i]) end return '['..table.concat(p,',')..']' else local p={} for k,val in pairs(v) do p[#p+1]=E(tostring(k))..':'..E(val) end return '{'..table.concat(p,',')..'}' end end
return 'null'
end
json = E(data)
end
AscensionExporter_ShowExportFrame(json, "Ascension Export - all - Copy All (Ctrl+C)")
end
end)
x = x + 95
makeBtn("Talents", x, function()
if AscensionExporter then AscensionExporter:Export("talents") end
end)
x = x + 95
makeBtn("Gear", x, function()
if AscensionExporter then AscensionExporter:Export("gear") end
end)
x = x + 95
makeBtn("Enchants", x, function()
if AscensionExporter then AscensionExporter:Export("enchants") end
end)
x = x + 95
makeBtn("MD Gear", x, function()
if AscensionExporter and AscensionExporter.GenerateMarkdownGear then
local md = AscensionExporter:GenerateMarkdownGear() or ""
AscensionExporter_ShowExportFrame(md, "Ascension Export - Markdown Gear - Copy All (Ctrl+C)")
end
end)
-- ScrollFrame + EditBox
local scrollFrame = CreateFrame("ScrollFrame", "AscensionExporterScrollFrame", frame, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT", 16, -64)
scrollFrame:SetPoint("BOTTOMRIGHT", -32, 16)
local editBox = CreateFrame("EditBox", "AscensionExporterEditBox", scrollFrame)
editBox:SetMultiLine(true)
editBox:SetAutoFocus(true)
editBox:SetFontObject(ChatFontNormal)
editBox:SetWidth(640)
editBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
editBox:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)
scrollFrame:SetScrollChild(editBox)
frame.scrollFrame = scrollFrame
frame.editBox = editBox
-- Close button
local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", -4, -4)
AscensionExporterFrame = frame
return frame
end
function AscensionExporter_ShowExportFrame(text, titleText)
local f = CreateOrGetFrame()
f:Show()
f.editBox:SetText(text or "")
f.title:SetText(titleText or "Ascension Export - Copy All (Ctrl+C)")
f.editBox:HighlightText()
f.editBox:SetFocus()
end
+58
View File
@@ -0,0 +1,58 @@
-- Minimal JSON encoder for AscensionExporter
local function escape_str(s)
s = tostring(s)
s = s:gsub('\\', '\\\\')
s = s:gsub('"', '\\"')
s = s:gsub('\n', '\\n')
s = s:gsub('\r', '\\r')
s = s:gsub('\t', '\\t')
return '"' .. s .. '"'
end
local function is_array(t)
if type(t) ~= 'table' then return false end
local n = 0
for k, _ in pairs(t) do
if type(k) ~= 'number' then return false end
n = n + 1
end
for i = 1, n do
if t[i] == nil then return false end
end
return true
end
local function encode_value(v)
local tv = type(v)
if tv == 'nil' then
return 'null'
elseif tv == 'boolean' then
return v and 'true' or 'false'
elseif tv == 'number' then
return tostring(v)
elseif tv == 'string' then
return escape_str(v)
elseif tv == 'table' then
if is_array(v) then
local parts = {}
for i = 1, #v do
parts[#parts+1] = encode_value(v[i])
end
return '[' .. table.concat(parts, ',') .. ']'
else
local parts = {}
for k, val in pairs(v) do
local key = escape_str(k)
parts[#parts+1] = key .. ':' .. encode_value(val)
end
return '{' .. table.concat(parts, ',') .. '}'
end
else
return 'null'
end
end
function AscensionExporter_Json_Encode(obj)
return encode_value(obj)
end
+248
View File
@@ -0,0 +1,248 @@
![Mage](/icons/classes/classicon_mage.png)
This guide covers pre-raid best-in-slot gear, enchants, talents, mystic enchants, macros, and rotation to help you master this powerful spellcasting class.
98% stolen from Author - Mikeyjackson (Minimike)- https://docs.google.com/document/d/e/2PACX-1vRlCRWmOhDHk8_iWEv71tw989BnT7WIjGLgE_T6MNu6SNRKNhItay_QLbJepH065mRPeiqCAj5EoYg0/pub
> **Note:** Some items marked with `ITEMID` or `SPELLID` are custom Ascension items (Heroic Dungeon drops, Worldforged items, custom mystic enchants). These IDs will be updated when they become available in db.ascension.gg. All standard WoW Classic and ZG items have been linked.
>
{.is-info}
> TODO: Work in progress
>
{.is-danger}
## Pre-Raid Best-in-Slot Gear
**Note:** Only included alternatives if they are really close in value. For crafted items, SP/INT is ideal but SP + anything or INT + anything is fine. With ZG out, worldforged items can be upgraded one more time.
| Slot | Item | Location | Enchant | Alternative |
|------|------|----------|---------|-------------|
| **Head** | [The Hexxer's Cover](https://db.ascension.gg/?item=19886) | ZG - Jin'do the Hexxer | ZG Enchant - Shaman (+13 SP / 15 INT) | [Sylvan Crown of Prophecy](https://db.ascension.gg/?item=ITEMID) |
| **Neck** | [Jeklik's Opaline Talisman](https://db.ascension.gg/?item=19923) | ZG - High Priestess Jeklik | - | [Necklace of the Sunken Empire](https://db.ascension.gg/?item=1379171) |
| **Shoulders** | [Zandalar Illusionist's Mantle](https://db.ascension.gg/?item=19845) (Mage Set) | ZG - Multiple Bosses | Zandalar Signet (+18 SP) | [Ironweave Mantle](https://db.ascension.gg/?item=260562) (Heroic Dungeon) |
| **Back** | [Hide of the Wild](https://db.ascension.gg/?item=1154794) | Crafted - Tailoring | Sustaining Cloak Lining (+6 SP / 6 STAM) | [Hide of the Wild of the Conjurer](https://db.ascension.gg/?item=ITEMID) |
| **Chest** | [Bloodvine Vest](https://db.ascension.gg/?item=1156333) | Crafted - Tailoring | Flamescale Gambeson (+6 HASTE) | [Robe of the Archmage of the Arcane](https://db.ascension.gg/?item=ITEMID) |
| **Wrist** | [Zandalar Illusionist's Wraps](https://db.ascension.gg/?item=19846) (Mage Set) | ZG - Multiple Bosses | Enchant Bracer - Healing Power (+15 SP) | [Crystalline Cuffs](https://db.ascension.gg/?item=14148) (Heroic Dungeon) |
| **Mainhand** | [Bloodcaller](https://db.ascension.gg/?item=19864) | ZG - Hakkar | Arcane Artillery (+80 SP Proc) | - |
| **Offhand** | [Jin'do's Bag of Whammies](https://db.ascension.gg/?item=319891) | ZG - Jin'do the Hexxer | - | [Tome of Knowledge](https://db.ascension.gg/?item=13385) |
| **Wand** | [Cold Snap](https://db.ascension.gg/?item=19130) | Azuregos World Boss (BoE) | Reflex Scope (+6 HASTE) | [Touch of Chaos](https://db.ascension.gg/?item=19861) (Hakkar Drop) |
| **Gloves** | [Combed Furline Handwraps](https://db.ascension.gg/?item=856011) | Timbermaw Hold Rep - Exalted | Enchant Glove - Minor Haste (+10 HASTE) | - |
| **Belt** | [Belt of Untapped Power](https://db.ascension.gg/?item=222716) | ZG - Multiple Bosses | Magus Belt Buckle (+10 INT) | [Whipvine Cord](https://db.ascension.gg/?item=246873) |
| **Legs** | [Bloodvine Leggings](https://db.ascension.gg/?item=1156336) | Crafted - Tailoring | ZG Enchant - Shaman (+13 SP / 15 INT) | - |
| **Boots** | [Bloodvine Boots](https://db.ascension.gg/?item=1156339) | Crafted - Tailoring | Enchant Boots - Lesser Accuracy (+5 HIT) | [Dragonrider Boots](https://db.ascension.gg/?item=102373) (Heroic Dungeon) |
| **Ring 1** | [Arcane Loop](https://db.ascension.gg/?item=1544934) | Ring Vendor (in ORG or SW, 600g) | - | - |
| **Ring 2** | [Band of Servitude](https://db.ascension.gg/?item=19898) | ZG - Multiple Bosses | - | [Band of the Titans](https://db.ascension.gg/?item=1414517) |
| **Trinket 1** | [Hazza'rah's Charm of Magic (Mage Set)](https://db.ascension.gg/?item=19897) | ZG - Summoned Bosses | - | [Rune Band of Wizardry](https://db.ascension.gg/?item=260814) (Heroic Dungeon) |
| **Trinket 2** | [Zandalarian Hero Charm](https://db.ascension.gg/?item=219950) | ZG - Hakkar | - | [Burst of Knowledge](https://db.ascension.gg/?item=11832) (Heroic Dungeon)<br>[Draconic Infused Emblem](https://db.ascension.gg/?item=102339) (Heroic Dungeon) |
## Enchants
Enchants are listed in the gear table above. Key enchants include:
### ZG Enchants
The ZG enchants are incredibly important as they grant significant spell power and stats. Each enchant requires:
- 1x Primal Hakkari Idol (drops from ZG bosses/trash)
- 1x Voodoo Doll (looted from Hoodoo Pile nodes in ZG)
- Shaman: Blue Punctured Voodoo Doll (+13 SP / +15 INT)
Turn in these items to Zanza the Restless in a cleared ZG. Check with Sub and the guild bank before purchasing from the AH.
### Zandalar Shoulder Enchant
The Zandalar Signet enchant (+18 SP) requires farming reputation with the Zandalar Tribe through ZG runs and turning in bijous and coins.
## Talents
The Arcane talent tree focuses on maximizing single-target burst damage with mana efficiency. Below is the recommended pre-raid build:
<iframe src="/static/talents/?build=mage...02_13_25_63_93_b1_e3_f3_h1_j5_l3_m2_o3_p1_t2_u3_y3_113_122&embed=true" width="100%" height="840" style="border: 0px solid #333; border-radius: 8px;"></iframe>
### Defensive/Utility Options
Consider these based on encounter needs:
- <img src="/icons/spells/spell_holy_dispelmagic.png" width="20" height="20" style="vertical-align: middle;" /> **Arcane Subtlety** (3/3) - 20% threat reduction, 12% reduced Arcane spell mana cost
- <img src="/icons/spells/spell_arcane_arcaneresilience.png" width="20" height="20" style="vertical-align: middle;" /> **Arcane Fortitude** (3/3) - Increases armor by 150% of Intellect while Mana-forged Barrier is active
- <img src="/icons/spells/spell_arcane_blink.png" width="20" height="20" style="vertical-align: middle;" /> **Improved Blink** (3/3) - Reduces Blink cooldown by 12 seconds, 20% damage reduction for 3 sec after Blink
## Mystic Enchants
### <span class="legendary">Artifact</span>
{.legendary}
**[Rune of Power](https://db.ascension.gg/?spell=116011)** - *"Place a rune of power on the ground for 1 minute. While standing in your rune, your mana regeneration is increased by 100% and your spell power is increased by 15%."*
Best choice for single-target encounters. Stand still and maximize damage output.
### <span class="legendary">Legendary</span>
{.legendary}
**[Barrage Overload](https://db.ascension.gg/?spell=SPELLID)** - *"Your Arcane Barrage has a chance to cast an additional Arcane Barrage at your target at 50% effectiveness."*
Excellent for movement-heavy fights where Rune of Power is difficult to utilize effectively.
**[Touch of the Magi](https://db.ascension.gg/?spell=210824)** - *"Your Arcane Blast has a chance to apply Touch of the Magi, which stores 25% of all damage dealt to the target and releases it as Arcane damage after 6 seconds."*
Situational option that can be strong on longer encounters with burst windows.
### <span class="epic">Epic Enchants</span>
{.epic}
**Essential:**
- **[Esoteric Power](https://db.ascension.gg/?spell=SPELLID)** - "If you are consistently reaching 5 stacks of Arcane Blast, this enchant significantly boosts your damage output". This is your top priority epic enchant if you can maintain 5 stacks
**Core:**
- **[Arcane Surge](https://db.ascension.gg/?spell=SPELLID)** - "After casting 4 Arcane Blasts in a row, your next Arcane Missiles deals 30% increased damage". Synergizes perfectly with the rotation
- **[Improved Arcane Missiles](https://db.ascension.gg/?spell=SPELLID)** - "Reduces the mana cost of Arcane Missiles by 20% and increases its damage by 10%". Essential for mana management
**Recommended:**
- **[Arcane Tempo](https://db.ascension.gg/?spell=SPELLID)** - "Your Arcane Blast increases your haste by 1% per stack, up to 4%". More haste means more casts
- **[Mana Adept](https://db.ascension.gg/?spell=SPELLID)** - "Your spell damage is increased by up to 15% of your current mana percentage". Rewards intelligent mana management
**Situational:**
- **[Arcane Familiar](https://db.ascension.gg/?spell=205022)** - "Summon a familiar that periodically casts Arcane Blast at your target". Passive damage increase
- **[Nether Tempest](https://db.ascension.gg/?spell=114923)** - "Places a periodic damage effect on the target that damages nearby enemies". Good for multi-target situations
### <span class="rare">Rare Enchants</span>
{.rare}
**Essential:**
- **[Glyph of Arcane Blast](https://db.ascension.gg/?spell=56360)** - "Increases damage done by Arcane Blast by 10%". Absolutely essential
- **[Arcane Intellect](https://db.ascension.gg/?spell=1459)** - "Increases your total Intellect by 15%". Absolutely essential
**Highly Recommended:**
- **[Glyph of Arcane Missiles](https://db.ascension.gg/?spell=56363)** - "Increases the damage of Arcane Missiles by 15%". Core part of your rotation
- **[Glyph of Mana Gem](https://db.ascension.gg/?spell=56383)** - "Increases the mana restored by your Mana Gem by 40%". Critical for mana management on longer fights
- **[Glyph of Evocation](https://db.ascension.gg/?spell=56380)** - "Your Evocation ability also regenerates 15% of your health over its duration". Adds survivability
**Situational:**
- **[Glyph of Ice Block](https://db.ascension.gg/?spell=56372)** - "Your Frost Nova cooldown is reduced by 30%". Good for movement/kiting situations
- **[Glyph of Arcane Power](https://db.ascension.gg/?spell=56357)** - "Increases the duration of Arcane Power by 3 seconds". Extends your burst window
## Consumables
Proper consumables are essential for maximizing your damage output:
**Flasks:**
- Flask of the Kirin Tor (preferred)
- Flask of Pure Mojo (budget option)
**Oils:**
- Brilliant Wizard Oil
**Potions:**
- Major Mana Potion (use throughout fight)
- Potion of Lesser Haste (pre-pot and during burn phases)
- Dark Rune / Demonic Rune (emergency mana)
**Food:**
- Wizard Wontons (best buff food)
**Scrolls:**
- Scroll of Intellect
**Elixirs (if not using flask):**
- Greater Arcane Elixir
- Elixir of Wisdom
## Macros
These macros help streamline your rotation and improve responsiveness:
**Rune of Power at Feet:**
```
#showtooltip Rune of Power
/cast [target=player] Rune of Power
```
**Arcane Blast with Target Switching:**
```
#showtooltip Arcane Blast
/cast Arcane Blast
```
**Cooldown Macro (Icy Veins + Trinket + Arcane Power):**
```
#showtooltip Icy Veins
/use [trinket slot 13 or 14]
/cast Icy Veins
/cast Arcane Power
```
**Emergency Mana:**
```
#showtooltip Major Mana Potion
/use Major Mana Potion
/use Dark Rune
```
## Rotation
```kroki
graphviz
digraph G {
bgcolor="transparent";
rankdir=LR;
node [shape=box, style="rounded,filled", fillcolor="#1a1a1a", color="#cccccc", fontcolor="#ffffff", fontname="Arial", fontsize=11, margin=0.25, penwidth=1.5];
edge [color="#999999", penwidth=2, arrowsize=0.8];
PRE [label="Pre-pot\nHaste"];
PRECAST [label="Pre-cast\nArcane Blast"];
STACK [label="Stack\nArcane Blasts - 3 or 4"];
ROP [label="Rune of\nPower"];
CDS [label="Pop All\nCooldowns"];
BURN [label="Burn Phase\n(Max Stacks)"];
SUSTAIN [label="Sustain Phase\n(4-5 Stacks)"];
MISSILES [label="Arcane\nMissiles"];
PRE -> PRECAST -> STACK -> ROP -> CDS -> BURN -> SUSTAIN -> MISSILES -> SUSTAIN;
}
```
### Rotation Breakdown
**Pre-Pull:**
1. Pre-pot Potion of Lesser Haste 2-3 seconds before pull
2. Begin casting Arcane Blast to land as tank pulls
**Opening Burst:**
1. Cast Arcane Blast to 4 stacks
2. Drop Rune of Power at your feet
3. Pop all cooldowns (Icy Veins, Arcane Power, Trinkets)
4. Continue casting Arcane Blast at max stacks while cooldowns are active
**Sustain Phase:**
1. Let Arcane Blast stacks drop to 4-5
2. Cast 4-5 Arcane Blasts
3. Cast Arcane Missiles to dump mana and maintain stacks
4. Repeat this cycle
**Mana Management:**
- Goal is to end the fight nearly out of mana (OOM)
- Use Evocation when you drop below 20-30% mana (fight dependent)
- Use Mana Gem on cooldown during sustain phases
- Don't be afraid to use Dark Runes and Major Mana Potions
- Adjust your burn/sustain ratio based on fight length
**Movement:**
- If using Rune of Power, minimize movement and plan positioning carefully
- If using Barrage Overload, movement is less punishing
- Use Arcane Barrage while moving if necessary
- Never stop casting if you can avoid it
## Stat Weights (Pre-Raid)
Priority for stat weights:
1. **Spell Power:** 1.0 (scales all damage)
2. **Haste:** 0.9 (more casts = more damage)
3. **Spell Penetration:** 0.8 (crucial for PvE)
4. **Hit:** 0.75 (cap at 16% for raid bosses)
5. **Intellect:** 0.65 (mana pool and spell crit)
6. **Spell Crit:** 0.5 (valuable but not as strong as above)
**Note:** Spell Hit is crucial. Aim for the 16% hit cap through gear and talents. After hit cap, prioritize Spell Power > Haste > Spell Penetration.