first commit
This commit is contained in:
@@ -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
|
||||

|
||||
```
|
||||
|
||||
Examples:
|
||||
- Class icons: ``
|
||||
- Item icons: ``
|
||||
|
||||
#### 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
|
||||
|
||||

|
||||
|
||||
*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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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` doesn’t 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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,248 @@
|
||||

|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user