diff --git a/.claude/agents/weakaura-dsl-validator.md b/.claude/agents/weakaura-dsl-validator.md new file mode 100644 index 0000000..026f036 --- /dev/null +++ b/.claude/agents/weakaura-dsl-validator.md @@ -0,0 +1,152 @@ +--- +name: weakaura-dsl-validator +description: Use this agent when you need to validate Ruby DSL WeakAura configurations against spell/class data and ensure proper structure. This agent reviews generated WeakAuras, identifies issues with spell IDs, trigger conditions, nesting structure, and coordinates fixes through specialized subagents until validation passes. Context: User has written Ruby DSL code for a WeakAura and wants to ensure it's valid. user: "Check if my retribution paladin weakaura is correct" assistant: "I'll use the weakaura-dsl-validator agent to validate the configuration against spell data and structure requirements" Since validation of WeakAura DSL code is needed, use the weakaura-dsl-validator agent to check spell IDs, trigger conditions, and structure. Context: A WeakAura has been generated but may have incorrect spell IDs or improper nesting. user: "Validate and fix the warrior weakaura I just created" assistant: "Let me launch the weakaura-dsl-validator agent to check the configuration and coordinate any necessary fixes" The user wants validation and correction of a WeakAura, so use the weakaura-dsl-validator agent. +model: opus +--- + +You are an expert data engineer and analyst specializing in World of Warcraft WeakAuras validation. You have deep knowledge of spell data locations in ./simc/, WeakAura2 source code in ./WeakAuras2/, WeakAura nesting structures, and the Ruby DSL implementation documented in CLAUDE.md. + +**CRITICAL REQUIREMENT**: You MUST use the comprehensive validation command `ruby scripts/compile-dsl.rb --analyze ` at the start of EVERY validation task to generate a complete spell analysis table. This command compiles the DSL, extracts all spells from the WeakAura, validates them against SimC rotation profiles, analyzes spell requirements from DBC data, and provides detailed structure analysis. + +Your primary responsibilities: + +1. **Preparation Phase**: + - **FIRST**: Run the validation command: `ruby scripts/compile-dsl.rb --analyze ` + - Create comprehensive task list using TodoWrite tool to track validation steps + - Use the generated spell validation table to identify all potential issues + +2. **Deep JSON Validation**: Thoroughly analyze the generated JSON structure against `/workspace/docs/weakaura_structure.md` for: + - **ID Uniqueness**: Ensure NO duplicate aura IDs exist (critical - causes import failures) + - **Parent-Child Integrity**: Verify all parent references exist and are valid + - **Trigger Structure**: Validate trigger format matches documented LUA structure: + - Triggers must be an array with numeric indices per weakaura_structure.md + - Each trigger must have required fields per the documented trigger types + - Trigger indices in conditions must reference existing triggers + - **Condition Arrays**: Check that no conditions have empty check arrays per structure doc + - **Spell Name Accuracy**: Verify spell names match exactly (no suffixes like " (Missing)") + - **Load Conditions**: Ensure spec/class load conditions match documented format + - **Required Fields**: Verify all mandatory fields per weakaura_structure.md are present + - **Region Type Fields**: Ensure region-specific fields match documented structure + +3. **Automated Spell Validation Analysis**: The validation analysis automatically handles: + - **Class Detection**: Extracts class from WeakAura load conditions or DSL `load spec:` declaration + - **Spell Extraction**: Identifies all spells from compiled JSON triggers and aura names + - **🚨 CRITICAL: SimC Profile Validation**: **FIRST** validates spells against actual SimC rotation profiles: + - **Available Spells**: Found in current class/spec rotation profiles in `/workspace/simc/profiles/TWW3/` + - **Removed Spells**: Not found in profiles (e.g., Abomination Limb for Frost DK, covenant abilities) + - **Class-Specific**: Each spec validated against its specific rotation profile + - **DBC Data Lookup**: Searches structured spell data in `/workspace/simc/engine/dbc/generated/sc_spell_data.inc` for detailed requirements + - **Requirement Analysis**: Parses DBC spell data for: + - **Execute Requirements**: Target health thresholds like "target <20% HP" for Kill Shot, Execute, etc. + - **Resource Costs**: Holy Power, Rage, Energy, Mana, Chi, Soul Shards, etc. + - **Range Requirements**: Range specifications (5 yards, 30 yards, melee range, etc.) + - **Cooldown Constraints**: Charges and cooldown timers + - **Combat/State Dependencies**: Buff requirements, combat restrictions + - **Trigger Validation**: Cross-references WeakAura triggers against spell requirements + + **Example Output Table**: + ``` + Spell ID Aura Status Availability Requirements + -------------------------------------------------------------------------------------------------------------- + Abomination Limb 431048 BAM ✗ NOT FOUND Not found in death_knight profiles + Pillar of Frost 281214 BAM ✓ VALID 45s CD, Physical + Obliterate 445507 WhackAuras ✓ VALID 6s CD, Melee, Physical + Kill Shot 320976 WhackAuras ✓ VALID target <20% HP, 40y range, Physical + Soul Reaper 469180 BAM ✓ VALID 6s CD, Melee + ``` + +4. **Common Spell Requirement Patterns** (cross-class from simc data): + - **Power Requirements**: + - "Resource: 3 Holy Power" (Paladin), "40 Rage" (Warrior), "30 Energy" (Rogue) + - "2 Chi" (Monk), "1 Soul Shard" (Warlock), "3 Combo Points" (Rogue/Druid) + - **Health Thresholds**: "less than 20% health", "below 35% health", "enemies at low health" + - **Range/Weapon**: "Range: 30 yards", "Requires weapon:", "Melee range (5 yards)" + - **Cooldowns/Charges**: "Charges: 1 (X seconds cooldown)", "Cooldown: X seconds" + - **State Dependencies**: "Only usable during", "Requires buff", "In combat only" + - **Target Requirements**: "Enemy target", "Friendly target", "Self target" + - **Class-Specific States**: + - Warrior: "Battle Stance", "Defensive Stance", "Berserker Rage" + - Druid: "Cat Form", "Bear Form", "Moonkin Form", "Travel Form" + - Death Knight: "Blood Presence", "Frost Presence", "Unholy Presence" + - Demon Hunter: "Metamorphosis" + +5. **Validate WeakAura Structure**: Ensure proper nesting according to WeakAura2 requirements: + - Root must be type "group" with "c" array containing children + - Dynamic groups must have valid grow/sort/space settings + - Icons must have regionType "icon" with proper subRegions + - All auras must have unique UIDs and IDs + - Parent references must point to existing group IDs + +6. **Check Ruby DSL Compliance**: Verify the DSL code follows patterns in CLAUDE.md: + - Proper use of icon/dynamic_group blocks + - Valid trigger methods (action_usable!, aura, power_check, etc.) + - Correct condition syntax (glow!, hide_ooc!) + - Proper use of all_triggers! for conjunction logic + +7. **Common Import Failure Patterns** to specifically check: + - Duplicate IDs (use jq to check: `jq '.c[].id' output.json | sort | uniq -d`) + - Empty condition checks that cause hangs + - Invalid trigger references in conditions + - Missing required trigger fields + - Incorrect disjunctive settings ("any" vs "all") + - Mismatched spell requirements vs triggers + +8. **Coordinate Fixes**: When issues are found: + - Document specific problems with exact JSON paths + - Show the problematic JSON snippet + - Invoke appropriate subagents to fix issues + - Re-validate after fixes are applied + - Iterate until import-ready + +## Validation Workflow: + +1. **Run Validation Analysis**: `ruby scripts/compile-dsl.rb --analyze ` +2. **🚨 PRIORITY: Check Availability Status**: Review "Availability" column for ✗ NOT FOUND spells first + - **CRITICAL**: Remove spells not found in current rotation profiles (e.g., Abomination Limb, covenant abilities) + - **WARNING**: Research replacements for deprecated class abilities + - **INFO**: Consider updating to current expansion spells +3. **Review Spell Table**: Identify spells with missing requirements or trigger mismatches +4. **Analyze JSON Structure**: Check for import-blocking issues (duplicate IDs, empty conditions) +5. **Cross-Reference Requirements**: Ensure triggers match spell requirements from DBC data (execute thresholds, cooldowns, ranges) +6. **Coordinate Fixes**: Use appropriate subagents to resolve identified issues +7. **Re-validate**: Run analysis again after fixes to confirm resolution + +## Output Format: +- **✗ NOT FOUND**: Spells not found in current rotation profiles (covenant abilities, removed spells) - **CRITICAL ERROR** +- **✓ VALID**: Spells found in SimC profiles with DBC requirements - **VALIDATED** +- **CRITICAL**: Issues that will cause import failure (duplicate IDs, empty conditions, missing spells) +- **WARNING**: Issues that may cause unexpected behavior (missing triggers, mismatched requirements) +- **INFO**: Spell requirement details and optimization suggestions + +### Removal Categories: +- **covenant_abilities**: Shadowlands covenant spells (Necrolord, Kyrian, Night Fae, Venthyr) - removed 11.2 +- **legendary_powers**: Shadowlands legendary effects - removed 11.2 +- **class_reworks**: Spells removed during talent/class overhauls +- **expansion_specific**: Artifact weapons, tier bonuses, deprecated systems + +## Example Validation Results: + +**Frost Death Knight WeakAura Validation:** +``` +Abomination Limb (ID: 431048) - ✗ NOT FOUND +├─ Reason: Not found in death_knight profiles +├─ Category: removed/covenant abilities +└─ Action: Remove from WeakAura - spell not in current rotations + +Obliterate (ID: 445507) - ✓ VALID +├─ Requirements: 6s CD, Melee, Physical +├─ Triggers: action_usable, killing_machine_buff (✓ Appropriate) +└─ Validation: Found in TWW3_Death_Knight_Frost.simc + +Soul Reaper (ID: 469180) - ✓ VALID +├─ Requirements: 6s CD, Melee +├─ Triggers: action_usable (✓ Appropriate for cooldown tracking) +└─ Validation: Found in TWW3_Death_Knight_Frost.simc + +Kill Shot (ID: 320976) - ✓ VALID +├─ Requirements: target <20% HP, 40y range, Physical +├─ Triggers: action_usable (✓ Appropriate for execute ability) +└─ Suggestion: Consider adding health trigger for <20% HP requirement +``` + +Be precise about JSON paths (e.g., ".c[2].triggers.1.trigger.spell_name") when referencing issues. Always check for the most common import killers first: duplicate IDs and empty condition arrays. diff --git a/.claude/agents/weakauras-dsl-engineer.md b/.claude/agents/weakauras-dsl-engineer.md new file mode 100644 index 0000000..a66a632 --- /dev/null +++ b/.claude/agents/weakauras-dsl-engineer.md @@ -0,0 +1,40 @@ +--- +name: weakauras-dsl-engineer +description: Use this agent when you need to implement WeakAura Ruby DSL features, fix DSL-related bugs, add new trigger types, modify aura behaviors, or enhance the DSL compilation pipeline. This agent expects a clear implementation plan with specific requirements about WeakAura functionality, trigger logic, or DSL syntax changes. Examples: Context: User needs to add a new trigger type to the DSL. user: 'Add support for buff tracking triggers in the DSL' assistant: 'I'll use the weakauras-dsl-engineer agent to implement the buff tracking trigger following the existing DSL patterns' Since this involves implementing new DSL functionality, use the weakauras-dsl-engineer agent. Context: User needs to fix a DSL compilation issue. user: 'The power_check trigger is not generating correct JSON structure' assistant: 'Let me launch the weakauras-dsl-engineer agent to debug and fix the power_check trigger implementation' DSL trigger implementation issue requires the specialized weakauras-dsl-engineer agent. +model: sonnet +--- + +You are an expert WeakAuras2 and Ruby DSL engineer with deep knowledge of World of Warcraft addon development, the WeakAuras2 JSON structure, and Ruby metaprogramming patterns. You understand the complete architecture of the WeakAuras Ruby DSL system including WASM compilation, trigger implementations, and aura generation. + +Your core expertise: +- WeakAuras2 JSON structure and all aura types (Icon, Progress Bar, Dynamic Group, etc.) +- Ruby DSL implementation patterns using method_missing, instance_eval, and context management +- Trigger system architecture including multi-trigger logic and condition application +- WASM integration for browser-based Ruby execution +- Lua encoding/decoding for WeakAura import strings + +When implementing features: +1. Read existing DSL code first to understand current patterns +2. Follow established conventions in public/whack_aura.rb and public/weak_aura/ +3. Ensure new triggers follow the pattern in public/weak_aura/triggers/ +4. Write RSpec tests for any new DSL functionality +5. Test compilation using scripts/compile-dsl.rb before finalizing +6. Maintain backward compatibility with existing DSL syntax + +Implementation workflow: +- Analyze the plan and requirements provided +- Identify affected files (typically whack_aura.rb, weak_aura.rb, or trigger files) +- Read current implementation to understand context +- Implement changes following existing patterns +- Create or update RSpec tests +- Verify with compile-dsl.rb script +- Ensure JSON output matches WeakAuras2 expectations + +Quality checks: +- New triggers must generate valid WeakAuras2 JSON +- DSL methods should be intuitive and follow Ruby conventions +- Error messages must be helpful for DSL users +- Performance considerations for WASM execution +- Maintain clean separation between DSL API and internal implementation + +You work with precision, writing minimal but complete code that integrates seamlessly with the existing DSL architecture. diff --git a/.claude/agents/whackauras-creator.md b/.claude/agents/whackauras-creator.md new file mode 100644 index 0000000..17c6650 --- /dev/null +++ b/.claude/agents/whackauras-creator.md @@ -0,0 +1,82 @@ +--- +name: whackauras-creator +description: Use this agent when you need to create WhackAuras using the Ruby DSL based on analyzed WoW class/spec guides. This agent specializes in translating rotation priorities and cooldown usage into functional WhackAura configurations with two main groups: the primary WhackAuras group for ability availability/priority display, and the BAM group for offensive cooldowns. Examples: Context: User has an analyzed guide for a WoW spec and needs WhackAuras created. user: 'Create WhackAuras for frost mage based on this analyzed guide' assistant: 'I'll use the whackauras-creator agent to build the Ruby DSL code for frost mage WhackAuras' Since we need to create WhackAuras from an analyzed guide, use the whackauras-creator agent. Context: User wants to implement rotation helpers for their class. user: 'Build me rotation helpers for enhancement shaman using our DSL' assistant: 'Let me use the whackauras-creator agent to create the WhackAuras for enhancement shaman' The user needs WhackAuras created, so use the whackauras-creator agent. +model: sonnet +--- + +You are an expert WhackAuras engineer specializing in the Ruby DSL for World of Warcraft WeakAuras. You create highly optimized aura configurations that show abilities only when they're both available and ideal to use. + +Your primary responsibility is translating analyzed class/spec guides into functional WhackAura Ruby DSL code with two core groups: +1. **WhackAuras Group**: Shows abilities when available AND optimal to press (NO DoT/aura trackers - only actionable abilities) +2. **BAM Group**: Displays offensive cooldowns + +Follow this structure pattern from feral.rb: +```ruby +title 'Class Spec Name' +load spec: :class_spec +hide_ooc! +debug_log! # Enable this for debugging imports + +dynamic_group 'BAM' do + scale 0.6 + offset y: -100, x: 80 + + action_usable 'Cooldown 1' do + glow! + end + action_usable 'Cooldown 2' +end + +dynamic_group 'Defensive' do + scale 0.6 + offset y: -100, x: -80 + + action_usable 'Defensive 1' + action_usable 'Defensive 2' +end + +dynamic_group 'WhackAuras' do + scale 0.8 + offset y: -140 + + icon 'Priority Ability' do + action_usable! + power_check :resource, '>= threshold' + glow! + end + + icon 'DoT Ability' do + action_usable! + aura 'DoT Name', show_on: :missing, type: 'debuff', unit: 'target' + aura 'DoT Name', show_on: :active, type: 'debuff', unit: 'target', remaining_time: 5 + end + + action_usable 'Simple Ability' +end +``` + +Key implementation principles: +- ALWAYS include header: title, load spec, hide_ooc!, debug_log! +- Use proper group structure: BAM (scale 0.6, offset), Defensive (scale 0.6), WhackAuras (scale 0.8) +- BAM group positioned at y: -100, x: 80 (right side) +- Defensive group positioned at y: -100, x: -80 (left side) +- WhackAuras group positioned at y: -140 (center, lower position) +- Use `action_usable!` for complex icons with multiple conditions +- Use simple `action_usable 'Name'` for straightforward abilities +- Add resource checks (`power_check`) for builders/spenders +- Use `aura` triggers for buff/debuff conditions (show_on: :active/:missing) +- Apply `glow!` to high-priority abilities +- For DoTs: show ability when missing OR expiring using multiple aura triggers +- Use `talent_active` for talent-specific abilities +- WhackAuras group contains ONLY actionable abilities (things you can press) + +Structure requirements: +1. Header with title, load spec, hide_ooc!, debug_log! +2. BAM group first (offensive cooldowns, scale 0.6, right offset) +3. Defensive group second (defensive cooldowns, scale 0.6, left offset) +4. WhackAuras group last (rotation abilities, scale 0.8, center) +5. Use icon blocks for complex conditions, simple action_usable for basic abilities +6. Multiple aura triggers in icon use OR logic for DoT refresh timing +7. Example DoT pattern: show when missing OR when expiring in X seconds + +Output clean, functional Ruby DSL code with minimal comments. Focus on trigger accuracy over visual complexity. diff --git a/.claude/agents/wow-pve-guide-analyzer.md b/.claude/agents/wow-pve-guide-analyzer.md new file mode 100644 index 0000000..380adca --- /dev/null +++ b/.claude/agents/wow-pve-guide-analyzer.md @@ -0,0 +1,67 @@ +--- +name: wow-pve-guide-analyzer +description: Use this agent when you need to analyze World of Warcraft PvE class guides from sites like Icy Veins or Wowhead to extract rotation priorities, talent choices, and key abilities for WeakAura planning. This agent synthesizes guide information into actionable implementation plans without writing code. Examples: Context: User wants to create WeakAuras for a WoW class/spec based on guide analysis. user: "Analyze the Retribution Paladin guide and tell me what WeakAuras we need" assistant: "I'll use the wow-pve-guide-analyzer agent to analyze the guide and create a WeakAura implementation plan" The user wants guide analysis for WeakAura planning, so use the wow-pve-guide-analyzer agent. Context: User needs to understand rotation priorities from a class guide. user: "What are the key abilities and cooldowns for Frost Mage according to current guides?" assistant: "Let me use the wow-pve-guide-analyzer agent to analyze current Frost Mage guides and extract the key information" The user needs class guide analysis, use the wow-pve-guide-analyzer agent. +tools: Glob, Grep, LS, Read, WebFetch, TodoWrite, WebSearch, BashOutput, KillBash, ListMcpResourcesTool, ReadMcpResourceTool, Bash, mcp__playwright__browser_close, mcp__playwright__browser_resize, mcp__playwright__browser_console_messages, mcp__playwright__browser_handle_dialog, mcp__playwright__browser_evaluate, mcp__playwright__browser_file_upload, mcp__playwright__browser_install, mcp__playwright__browser_press_key, mcp__playwright__browser_type, mcp__playwright__browser_navigate, mcp__playwright__browser_navigate_back, mcp__playwright__browser_navigate_forward, mcp__playwright__browser_network_requests, mcp__playwright__browser_take_screenshot, mcp__playwright__browser_snapshot, mcp__playwright__browser_click, mcp__playwright__browser_drag, mcp__playwright__browser_hover, mcp__playwright__browser_select_option, mcp__playwright__browser_tab_list, mcp__playwright__browser_tab_new, mcp__playwright__browser_tab_select, mcp__playwright__browser_tab_close, mcp__playwright__browser_wait_for, mcp__browsermcp__browser_navigate, mcp__browsermcp__browser_go_back, mcp__browsermcp__browser_go_forward, mcp__browsermcp__browser_snapshot, mcp__browsermcp__browser_click, mcp__browsermcp__browser_hover, mcp__browsermcp__browser_type, mcp__browsermcp__browser_select_option, mcp__browsermcp__browser_press_key, mcp__browsermcp__browser_wait, mcp__browsermcp__browser_get_console_logs, mcp__browsermcp__browser_screenshot, mcp__shopify-dev-mcp__introspect_graphql_schema, mcp__shopify-dev-mcp__learn_shopify_api, mcp__shopify-dev-mcp__search_docs_chunks, mcp__shopify-dev-mcp__fetch_full_docs, mcp__shopify-dev-mcp__validate_graphql_codeblocks +model: opus +color: blue +--- + +You are a Rank 1 World of Warcraft PvE player with deep expertise in all classes, specializations, and raid/mythic+ optimization. You analyze class guides from authoritative sources like Icy Veins and Wowhead to extract critical information for WeakAura development. + +When analyzing a class/spec guide: + +1. **Extract Core Rotation**: + - Identify opener sequence + - Map priority system or rotation loop + - Note resource generators vs spenders + - Flag burst windows and cooldown alignment + +2. **Catalog Key Abilities**: + - Major offensive cooldowns (damage/haste buffs) + - Defensive abilities and damage reduction + - Utility spells (interrupts, dispels, movement) + - Procs and reactive abilities + - Resource thresholds (rage, energy, holy power, etc.) + +3. **Analyze Talent Choices**: + - Identify mandatory talents for the build + - Note situational talent swaps + - Flag talents that modify rotation + - Highlight passive vs active talents + +4. **Synthesize WeakAura Requirements**: + - Group abilities by priority (essential, important, situational) + - Define trigger conditions for each ability type + - Specify visual prominence (size/position) based on importance + - Note dependencies between abilities + - Identify resource tracking needs + - Flag proc/buff tracking requirements + +5. **Output Format**: + ``` + SPEC ANALYSIS: [Class - Specialization] + + ESSENTIAL TRACKING: + - [Ability]: [Trigger type] | [Why critical] + + ROTATION PRIORITIES: + 1. [Condition] → [Action] + + RESOURCE MANAGEMENT: + - [Resource]: [Thresholds to track] + + COOLDOWN GROUPS: + - Burst: [List] + - Defensive: [List] + + PROC/BUFF MONITORING: + - [Buff name]: [Response required] + + WEAKAURA IMPLEMENTATION PLAN: + - Group 1: [Purpose] - [Abilities] + - Group 2: [Purpose] - [Abilities] + ``` + +Focus on actionable information. Exclude lore, gearing advice, or content unrelated to ability usage. When guide information conflicts, prioritize the most recent or highest-rated source. If critical information is missing, note what additional research is needed. + +Your analysis directly informs WeakAura development - be precise about trigger conditions, timing windows, and visual priority. Every recommendation should enhance player performance through better ability tracking and decision-making. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3f5cf1b..abb5e55 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,5 +2,6 @@ "name": "weakauras", "image": "ghcr.io/fx/docker/devcontainer:latest", "containerUser": "vscode", + "postCreateCommand": "bash .devcontainer/post-install.sh", "postStartCommand": "bash .devcontainer/post-start-wrapper.sh" } \ No newline at end of file diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh new file mode 100755 index 0000000..9ffebfe --- /dev/null +++ b/.devcontainer/post-install.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# Install Ruby and dependencies for testing WeakAura DSL +echo "Installing Ruby and dependencies..." +sudo apt-get update +sudo apt-get install -y ruby ruby-dev rubygems libffi-dev libyaml-dev + +# Install required Ruby gems +echo "Installing Ruby gems..." +sudo gem install casting + +echo "Ruby setup complete!" +ruby --version \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 83d63cf..366f3a7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -18,4 +18,10 @@ When reviewing code in this repository: - Focus on logic errors and actual security vulnerabilities - Prioritize code correctness over minor style preferences - Ignore comments about dates being in the future (Copilot's knowledge cutoff may be outdated) -- Accept that SHA256 checksums may be outdated - this is intentional to avoid maintenance burden \ No newline at end of file +- Accept that SHA256 checksums may be outdated - this is intentional to avoid maintenance burden + +## Code Reviews +- EXTRA_TRAIT_IDS_FOR_TALENTS in talent.rb is intentionally designed for game-specific edge cases +- POWER_TYPES constant is already properly extracted to constants.rb - do not suggest re-extraction +- Complex DSL methods like glow! handle multiple trigger types and are acceptable complexity for the domain +- Script-based parsing (compile-dsl.rb) does not need caching - scripts run once and exit \ No newline at end of file diff --git a/.github/workflows/validate-examples.yml b/.github/workflows/validate-examples.yml new file mode 100644 index 0000000..67b44e4 --- /dev/null +++ b/.github/workflows/validate-examples.yml @@ -0,0 +1,53 @@ +name: Validate DSL Examples + +on: + push: + branches: [ main, feature/* ] + pull_request: + branches: [ main ] + +jobs: + validate-examples: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + + - name: Find and validate all DSL examples + run: | + echo "Finding all DSL examples..." + find public/examples -name "*.rb" | sort + + echo "Validating DSL compilation..." + failed=0 + total=0 + + for example in $(find public/examples -name "*.rb" | sort); do + echo "Testing: $example" + total=$((total + 1)) + + if ruby scripts/compile-dsl.rb --analyze "$example" > /tmp/validation.log 2>&1; then + echo "✅ $example - PASSED" + else + echo "❌ $example - FAILED" + echo "Error output:" + cat /tmp/validation.log + failed=$((failed + 1)) + fi + echo "---" + done + + echo "Results: $((total - failed))/$total examples passed" + + if [ $failed -gt 0 ]; then + echo "❌ $failed examples failed validation" + exit 1 + else + echo "✅ All examples passed validation" + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0980825..e108694 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ vendor/ node_modules .next coverage/ +WeakAuras2/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6744e53 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "simc"] + path = simc + url = https://github.com/simulationcraft/simc.git +[submodule "WeakAuras2"] + path = WeakAuras2 + url = https://github.com/WeakAuras/WeakAuras2.git diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b4c7d1d --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +loglevel=silent \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 65741ae..c869b4c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,6 +18,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Build Ruby WASM**: `make pack` (bundles Ruby code with dependencies into public/ruby.wasm) - **Run Ruby specs**: `bundle exec rspec` - **Guard for auto-testing**: `bundle exec guard` +- **Test DSL compilation**: `npm run compile-dsl [file]` or `ruby scripts/compile-dsl.rb [file]` (see below for details) ### Linting - **Next.js lint**: `npm run lint` @@ -25,12 +26,50 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### WeakAura Encoding - **Encode WeakAura JSON to export string**: `echo '{"d": "test"}' | npm run encode` +- **Build complete WeakAura from DSL**: `npm run build-wa path/to/file.rb` (full pipeline: DSL → JSON → WA string) + +### Spell/Talent Data Management +- **Parse SimC spell data**: `npm run parse-simc` (generates JSON from ./simc/engine/dbc/generated/) +- **Build Ruby mappings**: `npm run build-mappings` (generates Ruby modules from JSON) +- **Update all spell data**: `npm run update-spell-data` (runs both parse and build steps) + +The spell data system uses a two-stage process: +1. `ruby scripts/parse_simc_data.rb` - Parses SimC structured DBC data files (.inc) into JSON data files +2. `ruby scripts/build_spell_mappings.rb` - Generates Ruby modules from JSON data + +This allows talent names like "Primal Wrath" to be automatically converted to numeric IDs (285381) for proper WeakAura imports. + +**Data Sources**: Uses SimC's structured DBC data from `/simc/engine/dbc/generated/` which contains: +- `sc_spell_data.inc` - Complete spell database with current game data +- `sc_talent_data.inc` - Talent tree and choice data +- `covenant_data.inc` - Covenant ability data (shows removal status) +- Files with `_ptr.inc` suffix contain PTR/beta data ## Architecture ### Overview WeakAuras Ruby DSL - A Next.js web application that provides a Ruby DSL for generating World of Warcraft WeakAuras. Users write Ruby code in the browser which gets compiled via Ruby WASM to generate WeakAura export strings. +### WeakAuras Concepts +- **Auras**: Visual elements displayed on screen (Icon, Progress Bar, Dynamic Group, etc.) +- **Triggers**: Conditions that control when an aura shows/hides (an aura can have multiple triggers) +- **Conditions**: Modifiers that change aura appearance based on trigger states + +### DSL Design +The Ruby DSL provides methods to: +1. **Create Auras**: `icon`, `dynamic_group`, `action_usable` - these create actual visual elements +2. **Add Triggers**: `power_check`, `rune_check`, `talent_active`, `combat_state` - these add trigger conditions to the current aura context +3. **Apply Conditions**: `glow!`, `hide_ooc!` - these add conditional modifications + +Example: +```ruby +icon 'My Icon' do + action_usable # Main trigger + power_check :mana, '>= 50' # Additional trigger + glow! power: '>= 80' # Conditional glow +end +``` + ### Key Components #### Frontend (Next.js/React) @@ -65,4 +104,103 @@ WeakAuras Ruby DSL - A Next.js web application that provides a Ruby DSL for gene ### Testing Strategy - **TypeScript/React**: Vitest with Playwright browser testing - **Ruby**: RSpec for DSL logic, Guard for auto-testing -- Test files colocated with source (*.test.tsx, *_spec.rb) \ No newline at end of file +- Test files colocated with source (*.test.tsx, *_spec.rb) +- **Important**: When testing Ruby DSL functionality, always create proper RSpec specs (e.g., `*_spec.rb` files) instead of standalone test scripts. Use `bundle exec rspec` to run tests. + +### DSL Compilation Testing +**IMPORTANT**: Use the generic `scripts/compile-dsl.rb` script for testing DSL compilation. DO NOT create standalone test scripts. + +```bash +# Test a DSL file +ruby scripts/compile-dsl.rb public/examples/paladin/retribution.rb + +# Test with analysis output +ruby scripts/compile-dsl.rb --analyze public/examples/test_new_triggers.rb + +# Test from stdin +echo "icon 'Test'" | ruby scripts/compile-dsl.rb + +# Output raw JSON +ruby scripts/compile-dsl.rb --json public/examples/paladin/retribution.rb + +# Get help +ruby scripts/compile-dsl.rb --help +``` + +The script provides: +- Compilation testing without server/WASM dependencies +- JSON output (raw or pretty-printed) +- Structure analysis showing auras, triggers, and parent-child relationships +- Error reporting with helpful context + +## WeakAura Import Troubleshooting + +### Common Import Failures +WeakAura imports can hang or fail silently. Here are critical issues to check: + +#### 1. Duplicate Aura IDs +**Problem**: WeakAuras requires unique IDs for all auras. Having duplicates causes import failures. +```ruby +# BAD - Creates two auras with ID "Rake" +debuff_missing 'Rake' +action_usable 'Rake' + +# GOOD - Use unique IDs +icon 'Rake Tracker' do + aura 'Rake', show_on: :missing, type: 'debuff', unit: 'target' +end +action_usable 'Rake' +``` + +#### 2. Empty Condition Check Arrays +**Problem**: Conditions with empty check arrays cause import to hang. +```ruby +# BAD - glow! with unhandled options creates empty check array +glow! auras: ['Some Buff'] # If not properly implemented + +# GOOD - Ensure all glow! options are handled +glow! # Simple show-based glow +glow! charges: '>= 2' # Implemented charge-based glow +``` + +#### 3. Incorrect Spell Names in Triggers +**Problem**: Using display names instead of actual spell names breaks triggers. +```ruby +# BAD - aura name becomes "Rip (Missing)" +debuff_missing 'Rip (Missing)' + +# GOOD - Use icon blocks to control ID separately from spell name +icon 'Rip Tracker' do + aura 'Rip', show_on: :missing, type: 'debuff', unit: 'target' +end +``` + +#### 4. DoT Tracking Pattern +For DoTs that need to show when missing OR expiring, use icon blocks with multiple triggers: +```ruby +icon 'Shadow Word: Pain Tracker' do + # Trigger 1: Show when missing + aura 'Shadow Word: Pain', show_on: :missing, type: 'debuff', unit: 'target' + # Trigger 2: Show when expiring (< 5.4s remaining) + aura 'Shadow Word: Pain', show_on: :active, type: 'debuff', unit: 'target', remaining_time: 5.4 +end +``` +Multiple triggers in an icon use OR logic by default (`disjunctive: "any"`). + +### Debug Logging +To enable debug logging for successfully imported auras: +```ruby +title 'My WeakAura' +load spec: :feral_druid +debug_log! # Adds information.debugLog = true to all auras +``` + +### Debugging Import Failures +For import failures (when aura won't import at all): +1. Enable Lua errors: `/console scriptErrors 1` +2. Check the generated JSON for: + - Duplicate IDs: `jq '.c[].id' output.json | sort | uniq -d` + - Empty conditions: `jq '.c[] | select(.conditions) | .conditions'` + - Verify spell names match exactly what WoW expects +3. Use BugSack/BugGrabber addons to capture detailed error messages +4. Test with minimal examples to isolate the issue \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 7de6d67..871764b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,30 @@ GEM remote: https://rubygems.org/ specs: - ast (2.4.2) + ast (2.4.3) casting (1.0.2) + cgi (0.5.0) coderay (1.1.3) - debug (1.9.2) + date (3.4.1) + debug (1.11.0) irb (~> 1.10) reline (>= 0.3.8) deep_merge (1.2.2) - diff-lcs (1.5.1) - docile (1.4.0) - ffi (1.17.0) - ffi (1.17.0-x86_64-linux-gnu) - formatador (1.1.0) - guard (2.18.1) + diff-lcs (1.6.2) + docile (1.4.1) + erb (4.0.4) + cgi (>= 0.3.3) + ffi (1.17.2) + formatador (1.2.0) + reline + guard (2.19.1) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) + logger (~> 1.6) lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) + ostruct (~> 0.6) pry (>= 0.13.0) shellany (~> 0.0) thor (>= 0.18.1) @@ -27,89 +33,97 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - io-console (0.7.2) - irb (1.14.0) + io-console (0.8.1) + irb (1.15.2) + pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.7.1) - json_pure (2.7.1) - language_server-protocol (3.17.0.3) + json (2.13.2) + json_pure (2.8.1) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - logger (1.6.1) - lumberjack (1.2.10) + logger (1.7.0) + lumberjack (1.4.0) method_source (1.1.0) nenv (0.3.0) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) - parallel (1.24.0) - parser (3.3.0.2) + ostruct (0.6.3) + parallel (1.27.0) + parser (3.3.9.0) ast (~> 2.4.1) racc - prism (1.0.0) - pry (0.14.2) + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) - psych (5.1.2) + psych (5.2.6) + date stringio - racc (1.7.3) + racc (1.8.1) rainbow (3.1.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) - rbs (3.5.3) + rbs (3.6.1) logger - rdoc (6.7.0) + rdoc (6.14.2) + erb psych (>= 4.0.0) - regexp_parser (2.9.0) - reline (0.5.10) + regexp_parser (2.11.2) + reline (0.6.2) io-console (~> 0.5) - rexml (3.2.6) - rspec (3.13.0) + rspec (3.13.1) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) + rspec-core (3.13.5) rspec-support (~> 3.13.0) - rspec-expectations (3.13.1) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-support (3.13.1) - rubocop (1.59.0) + rspec-support (3.13.5) + rubocop (1.79.2) json (~> 2.3) - language_server-protocol (>= 3.17.0) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.2.2.4) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.30.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.30.0) - parser (>= 3.2.1.0) - ruby-lsp (0.18.1) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.46.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + ruby-lsp (0.26.1) language_server-protocol (~> 3.17.0) - prism (~> 1.0) - rbs (>= 3, < 4) - sorbet-runtime (>= 0.5.10782) + prism (>= 1.2, < 2.0) + rbs (>= 3, < 5) ruby-progressbar (1.13.0) shellany (0.0.1) simplecov (0.22.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) + simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) - sorbet-runtime (0.5.11577) - stringio (3.1.1) - thor (1.3.1) - unicode-display_width (2.5.0) + stringio (3.1.7) + thor (1.4.0) + unicode-display_width (3.1.5) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) PLATFORMS wasm32-unknown diff --git a/WeakAuras2 b/WeakAuras2 new file mode 160000 index 0000000..522c594 --- /dev/null +++ b/WeakAuras2 @@ -0,0 +1 @@ +Subproject commit 522c59410bbea644a4945a2ea73e70f136e48a2f diff --git a/docs/weakaura_structure.md b/docs/weakaura_structure.md new file mode 100644 index 0000000..d8d6e89 --- /dev/null +++ b/docs/weakaura_structure.md @@ -0,0 +1,1028 @@ +# WeakAura LUA Table Structure - Complete Reference + +This document provides a comprehensive overview of how WeakAuras are structured in LUA, based on analysis of the WeakAuras2 codebase. + +## Table of Contents +1. [Core Structure](#core-structure) +2. [Display Types](#display-types) +3. [Trigger System](#trigger-system) +4. [Conditions](#conditions) +5. [Load Conditions](#load-conditions) +6. [Animations](#animations) +7. [Sub-Regions](#sub-regions) +8. [Groups](#groups) +9. [Actions](#actions) + +## Core Structure + +Every WeakAura has these fundamental fields: + +```lua +{ + -- Identifiers + id = "string", -- Unique display name (user-visible) + uid = "string", -- Unique identifier (system-generated) + parent = "string", -- Parent group ID (nil for top-level) + + -- Version & Metadata + internalVersion = 85, -- Current internal version + version = "string", -- User-defined version + semver = "string", -- Semantic version + + -- Display Settings + regionType = "string", -- Type: icon, aurabar, text, progresstexture, texture, group, dynamicgroup, stopmotion, model + + -- Positioning + anchorFrameType = "SCREEN", -- SCREEN, SELECTFRAME, UNITFRAME, CUSTOM + anchorFrameFrame = "string", -- Frame to anchor to + anchorPoint = "CENTER", -- Anchor point on target + selfPoint = "CENTER", -- Anchor point on aura + xOffset = 0, + yOffset = 0, + + -- Size + width = 64, + height = 64, + + -- Frame Level + frameStrata = 1, -- 1=Inherited, 2=BACKGROUND, 3=LOW, 4=MEDIUM, 5=HIGH, 6=DIALOG, 7=FULLSCREEN, 8=FULLSCREEN_DIALOG, 9=TOOLTIP + + -- Core Systems + triggers = {}, -- Trigger configuration + conditions = {}, -- Conditional behavior + load = {}, -- Load conditions + actions = {}, -- Actions to perform + animation = {}, -- Animation settings + subRegions = {}, -- Additional display elements + + -- Information + information = { + forceEvents = false, + ignoreOptionsEventErrors = false, + debugLog = false, + }, + + -- Display-specific settings (varies by regionType) + ... +} +``` + +## Display Types + +### Icon (`regionType = "icon"`) +```lua +{ + icon = true, + desaturate = false, + iconSource = -1, -- -1=auto, 0=manual, 1-n=trigger index + displayIcon = "path", -- Manual icon path + color = {1, 1, 1, 1}, -- RGBA + zoom = 0, -- 0-1 zoom level + keepAspectRatio = false, + cooldown = true, + cooldownTextDisabled = false, + cooldownSwipe = true, + cooldownEdge = false, + useCooldownModRate = true, + inverse = false, + + -- Progress settings + progressSource = {-1, ""}, -- {trigger, property} + adjustedMax = "", + adjustedMin = "", +} +``` + +### Text (`regionType = "text"`) +```lua +{ + displayText = "%p", -- Text with replacements + displayText_format_p_format = "timed", + displayText_format_p_time_type = 0, + displayText_format_p_time_precision = 1, + + font = "Friz Quadrata TT", + fontSize = 12, + fontFlags = "OUTLINE", + justify = "LEFT", + + -- Colors + color = {1, 1, 1, 1}, + + -- Layout + anchorPerUnit = "NAMEPLATE", + wordWrap = "WORDWRAP", + automaticWidth = "Auto", + fixedWidth = 200, + + -- Shadow + shadowColor = {0, 0, 0, 1}, + shadowXOffset = 1, + shadowYOffset = -1, +} +``` + +### Progress Texture (`regionType = "progresstexture"`) +```lua +{ + texture = "spells\\...", + desaturate = false, + + -- Progress + progressSource = {-1, ""}, + auraRotation = 0, + orientation = "HORIZONTAL", -- HORIZONTAL, HORIZONTAL_INVERSE, VERTICAL, VERTICAL_INVERSE, CLOCKWISE, ANTICLOCKWISE + inverse = false, + + -- Appearance + compress = false, + blendMode = "BLEND", + color = {1, 1, 1, 1}, + alpha = 1, + + -- Background + backgroundTexture = "", + backgroundColor = {0.5, 0.5, 0.5, 0.5}, + backgroundOffset = 2, + + -- Slant + slant = 0, + slantMode = "INSIDE", + + -- Texture coordinates + crop_x = 0, + crop_y = 0, + crop = 1, + mirror = false, + + -- User settings + user_x = 0, + user_y = 0, +} +``` + +### Aura Bar (`regionType = "aurabar"`) +```lua +{ + -- Bar settings + texture = "Blizzard", + orientation = "HORIZONTAL", + inverse = false, + + -- Colors + barColor = {1, 0, 0, 1}, + barColor2 = {1, 1, 0, 1}, + backgroundColor = {0, 0, 0, 0.5}, + + -- Spark + spark = false, + sparkTexture = "Interface\\CastingBar\\UI-CastingBar-Spark", + sparkColor = {1, 1, 1, 1}, + sparkHeight = 30, + sparkWidth = 10, + sparkOffsetX = 0, + sparkOffsetY = 0, + sparkRotation = 0, + sparkRotationMode = "AUTO", + sparkHidden = "NEVER", + sparkBlendMode = "ADD", + sparkDesaturate = false, + + -- Icon + icon = true, + iconSource = -1, + icon_side = "LEFT", + icon_color = {1, 1, 1, 1}, + + -- Zoom + zoom = 0, + + -- Bar Model + useAdjustededMin = false, + useAdjustededMax = false, + + -- Text + text1Enabled = true, + text1 = "%p", + text1Color = {1, 1, 1, 1}, + text1Point = "CENTER", + text1Font = "Friz Quadrata TT", + text1FontSize = 12, + text1FontFlags = "OUTLINE", + text1Containment = "INSIDE", + + text2Enabled = false, + -- text2 settings mirror text1 + + -- Timer + timer = true, + timerColor = {1, 1, 1, 1}, + timerFont = "Friz Quadrata TT", + timerFontSize = 12, + timerFontFlags = "OUTLINE", + + -- Stacks + stacks = true, + stacksColor = {1, 1, 1, 1}, + stacksFont = "Friz Quadrata TT", + stacksFontSize = 12, + stacksFontFlags = "OUTLINE", + stacksPoint = "CENTER", + + -- Border + border = false, + borderBackdrop = "Blizzard Tooltip", + borderColor = {0, 0, 0, 1}, + borderSize = 1, + borderInset = 1, + borderOffset = 0, + borderEdge = false, + backdropColor = {1, 1, 1, 0.5}, +} +``` + +## Trigger System + +### Triggers Container +```lua +triggers = { + -- Trigger mode + activeTriggerMode = -10, -- -10=first active, 0=all triggers, 1-n=specific trigger + disjunctive = "all", -- "all", "any", "custom" + customTriggerLogic = "", -- Custom Lua logic when disjunctive="custom" + + -- Array of triggers + [1] = { trigger = {...}, untrigger = {...} }, + [2] = { trigger = {...}, untrigger = {...} }, + ... +} +``` + +### Trigger Types + +#### Aura Trigger (type="aura2") +```lua +trigger = { + type = "aura2", + + -- Target + unit = "player", -- player, target, focus, group, party, raid, etc. + debuffType = "HELPFUL", -- HELPFUL, HARMFUL, BOTH + + -- Aura matching + auranames = {"Buff Name", "123456"}, -- Names or spell IDs + useExactSpellId = false, + useName = true, + useNamePattern = false, + namePattern_operator = "find", + namePattern_name = "", + + -- Instance matching + matchesShowOn = "showOnActive", -- showOnActive, showOnMissing, showAlways + useCount = false, + countOperator = ">=", + count = "1", + + -- Stack matching + useStacks = false, + stacksOperator = ">=", + stacks = "1", + + -- Remaining time + useRem = false, + remOperator = ">=", + rem = "5", + + -- Tooltip matching + useTooltip = false, + tooltip_operator = "find", + tooltip = "", + tooltip_caseSensitive = false, + + -- Special options + ownOnly = nil, -- true, false, nil (show all) + combinePerUnit = false, + combineMatches = "showLowest", + showClones = true, + + -- Sub options + auraspellids = {}, -- Specific spell IDs to track + exactSpellIds = {}, -- Exact spell IDs + perUnitMode = "affected", -- all, unaffected, affected +} +``` + +#### Event Trigger (type="event") +```lua +trigger = { + type = "event", + event = "Combat Log", -- Event name from GenericTrigger + + -- Combat Log specific + subeventPrefix = "SPELL", + subeventSuffix = "_CAST_START", + + -- Source/Dest filtering + use_sourceUnit = true, + sourceUnit = "player", + use_destUnit = false, + destUnit = "target", + + -- Spell filtering + use_spellId = false, + spellId = "", + use_spellName = false, + spellName = "", + + -- Additional filters (event-specific) + ... +} +``` + +#### Status Trigger (type="unit") +```lua +trigger = { + type = "unit", + use_unit = true, + unit = "player", + + -- Status checks (event-specific) + use_health = true, + health_operator = "<=", + health = "50", + health_pct = true, + + use_power = true, + power_operator = ">=", + power = "30", + power_pct = false, + + use_alive = true, + use_inverse = false, + + -- Many more status options... +} +``` + +#### Custom Trigger (type="custom") +```lua +trigger = { + type = "custom", + custom_type = "status", -- status, event, stateupdate + + -- Events to watch (event/stateupdate types) + events = "UNIT_HEALTH, UNIT_POWER_UPDATE", + + -- Custom functions + custom = [[ + function(event, ...) + -- trigger logic + return true + end + ]], + + -- Status type + check = "update", -- event, update + + -- Untrigger + custom_hide = "timed", -- timed, custom + duration = "5", + + -- Variables + customVariables = [[ + { + display = "Custom Var", + name = "customVar", + type = "number", + } + ]], +} +``` + +### Untrigger +```lua +untrigger = { + -- For timed untriggers + use_unit = true, + unit = "player", + + -- For custom untriggers + custom = [[ + function(event, ...) + return true + end + ]], +} +``` + +## Conditions + +Conditions modify display properties based on trigger states: + +```lua +conditions = { + [1] = { + check = { + trigger = 1, -- Trigger index to check + variable = "show", -- Variable to check + op = "==", -- Operator + value = true, -- Value to compare + }, + + -- OR multiple checks + -- check = { + -- checks = { + -- {trigger = 1, variable = "show", op = "==", value = true}, + -- {trigger = 2, variable = "stacks", op = ">", value = 3}, + -- }, + -- trigger = -2, -- -1=any trigger, -2=all triggers + -- }, + + changes = { + [1] = { + property = "color", + value = {1, 0, 0, 1}, + }, + [2] = { + property = "alpha", + value = 0.5, + }, + }, + }, +} +``` + +### Condition Properties +Common properties that can be changed: +- `alpha` - Opacity (0-1) +- `color` - RGBA color table +- `desaturate` - Boolean +- `glow` - External glow settings +- `visible` - Show/hide +- `width`, `height` - Size +- `xOffset`, `yOffset` - Position offsets +- `zoom` - Icon zoom +- `inverse` - Progress inverse +- `text` - Text content +- `fontSize` - Text size +- `sub.n.text_visible` - Sub-region visibility +- `sub.n.text_text` - Sub-region text + +## Load Conditions + +Control when an aura is loaded: + +```lua +load = { + -- Class/Spec + use_class = true, + class = { + single = "WARRIOR", + multi = { + WARRIOR = true, + PALADIN = true, + }, + }, + + use_spec = true, + spec = { + single = 1, + multi = { + [1] = true, + [2] = false, + [3] = true, + }, + }, + + -- Level + use_level = true, + level_operator = ">=", + level = "60", + + -- Combat + use_combat = true, + use_never = false, + + -- Instance Type + use_instance_type = true, + instance_type = { + single = "party", + multi = { + party = true, + raid = true, + pvp = false, + arena = false, + }, + }, + + -- Zone + use_zone = false, + zone = "", + + -- Group + use_group_role = true, + group_role = { + single = "TANK", + multi = { + TANK = true, + HEALER = false, + DAMAGER = false, + }, + }, + + -- Size + size = { + single = "ten", + multi = { + party = true, + ten = true, + twentyfive = false, + fortyman = false, + }, + }, + + -- Talents + talent = { + single = 12345, + multi = { + [12345] = true, + [67890] = true, + }, + }, + + -- Pet + use_petbattle = false, + use_vehicle = false, + use_mounted = false, +} +``` + +## Animations + +```lua +animation = { + start = { + type = "none", -- none, preset, custom + duration_type = "seconds", + duration = 0.2, + + -- Preset animations + preset = "fade", -- fade, slide, grow, shrink, spiral, bounce + + -- Custom animation + use_alpha = true, + alpha = 0, + + use_translate = true, + x = 0, + y = 100, + + use_scale = true, + scalex = 1.5, + scaley = 1.5, + + use_rotate = true, + rotate = 360, + + use_color = true, + colorType = "custom", + colorA = 1, + colorR = 1, + colorG = 0, + colorB = 0, + colorFunc = "", + }, + + main = { + type = "none", + duration_type = "seconds", + duration = 0, + + -- Preset types + preset = "pulse", -- pulse, spin, glow, shake + + -- Custom settings (same as start) + }, + + finish = { + type = "none", + duration_type = "seconds", + duration = 0.2, + + -- Same structure as start + }, +} +``` + +## Sub-Regions + +Additional display elements attached to the main region: + +```lua +subRegions = { + [1] = { + type = "subbackground", + + -- Background specific + border_visible = true, + border_edge = false, + border_color = {0, 0, 0, 1}, + border_size = 1, + border_offset = 0, + + backdrop_visible = true, + backdrop_color = {1, 1, 1, 0.5}, + }, + + [2] = { + type = "subtext", + + -- Text settings + text_text = "%p", + text_text_format_p_time_type = 0, + text_text_format_p_time_precision = 1, + + text_color = {1, 1, 1, 1}, + text_font = "Friz Quadrata TT", + text_fontSize = 12, + text_fontType = "OUTLINE", + + text_visible = true, + text_justify = "CENTER", + text_shadowColor = {0, 0, 0, 1}, + text_shadowXOffset = 1, + text_shadowYOffset = -1, + + -- Anchoring + text_selfPoint = "AUTO", + text_anchorPoint = "CENTER", + text_anchorXOffset = 0, + text_anchorYOffset = 0, + + -- Fixed size + text_fixedWidth = 64, + text_wordWrap = "WORDWRAP", + + anchorPerUnit = "NAMEPLATE", + rotateText = "NONE", + }, + + [3] = { + type = "subborder", + + border_visible = true, + border_edge = false, + border_color = {1, 1, 0, 1}, + border_size = 2, + border_offset = 1, + border_anchor = "bar", + }, + + [4] = { + type = "subglow", + + glow = true, + glow_type = "buttonOverlay", + glow_color = {1, 1, 0, 1}, + glow_lines = 8, + glow_frequency = 0.25, + glow_length = 10, + glow_thickness = 1, + glow_scale = 1, + glow_border = false, + + glow_anchor = "bar", + use_glow_color = true, + }, + + [5] = { + type = "subtick", + + tick_visible = true, + tick_color = {1, 1, 1, 1}, + tick_placement = "50", -- Percentage or value + tick_placement_mode = "AtPercent", -- AtValue, AtPercent + tick_thickness = 2, + tick_length = 30, + + tick_mirror = false, + tick_blend_mode = "ADD", + tick_desaturate = false, + + automatic_length = true, + + -- Manual length + use_texture = false, + tick_texture = "Interface\\...", + tick_xOffset = 0, + tick_yOffset = 0, + }, + + [6] = { + type = "submodel", + + model_visible = true, + model_path = "spells\\...", + model_fileId = "12345", + + model_alpha = 1, + model_scale = 1, + model_x = 0, + model_y = 0, + model_z = 0, + + rotation = 0, + api = false, + }, +} +``` + +## Groups + +### Group (`regionType = "group"`) +```lua +{ + -- Group-specific fields + controlledChildren = {"child1", "child2", ...}, + + -- Border + border = false, + borderOffset = 0, + borderSize = 1, + borderColor = {0, 0, 0, 1}, + borderInset = 0, + borderBackdrop = "Blizzard Tooltip", + backdropColor = {1, 1, 1, 0.5}, + + -- Grouping behavior + groupIcon = 134376, -- Icon for the group + useAdjustededMin = false, + useAdjustededMax = false, +} +``` + +### Dynamic Group (`regionType = "dynamicgroup"`) +```lua +{ + -- All group fields plus: + + -- Dynamic settings + space = 2, -- Space between elements + stagger = 0, -- Stagger amount + + grow = "DOWN", -- UP, DOWN, LEFT, RIGHT, HORIZONTAL, VERTICAL, CIRCLE, COUNTERCIRCLE, GRID, CUSTOM + align = "CENTER", -- LEFT, CENTER, RIGHT + + rotation = 0, -- Group rotation + + -- Constant factor (for circular/custom) + constantFactor = "RADIUS", + radius = 200, + + -- Grid specific + gridType = "RD", -- RD, RU, LD, LU, DR, DL, UR, UL + gridWidth = 5, + fullCircle = true, + + -- Sorting + sort = "none", -- none, ascending, descending, hybrid, custom + sortHybrid = { + { + sortType = "ascending", + sortBy = "remaining", + }, + }, + + -- Animation + animate = true, + animateStretch = false, + scale = 1, + + -- Border/backdrop (same as group) + + -- Self positioning + selfPoint = "TOP", + anchorPoint = "BOTTOM", + anchorPerUnit = "NAMEPLATE", + + -- Limit + limit = 5, -- Max number of children to show + + -- Frame level + frameStrata = 1, + + -- Custom grow function + customGrow = [[ + function(positions, activeRegions) + -- Custom positioning logic + end + ]], + + -- Custom sort function + customSort = [[ + function(a, b) + return a.remaining < b.remaining + end + ]], + + -- Custom anchor function + customAnchorPerUnit = [[ + function(unit) + return "nameplate" + end + ]], + + -- Frame rate + useLimit = false, + frameRate = 30, +} +``` + +## Actions + +Actions to perform when aura shows/hides: + +```lua +actions = { + init = { + do_custom = false, + custom = [[ + -- Initialization code + ]], + }, + + start = { + do_message = false, + message = "Aura started!", + message_type = "PRINT", -- SAY, YELL, PARTY, RAID, GUILD, OFFICER, EMOTE, WHISPER, CHANNEL, PRINT, ERROR, COMBAT + message_dest = "", + message_channel = "", + + do_sound = false, + sound = "Interface\\...", + sound_channel = "Master", + sound_repeat = 1, + sound_volume = 1, + + do_glow = false, + glow_action = "show", + glow_frame_type = "FRAMESELECTOR", + glow_frame = "WeakAuras:...", + glow_type = "buttonOverlay", + + do_custom = false, + custom = [[ + -- Custom action code + ]], + }, + + finish = { + -- Same structure as start + + hide_all_glows = false, + stop_sound = false, + }, +} +``` + +## State System + +WeakAuras use a state system for dynamic updates: + +```lua +-- State object (returned by triggers) +state = { + -- Required + show = true, -- Whether to show + changed = true, -- Whether state changed + + -- Progress + progressType = "timed", -- timed, static + duration = 10, + expirationTime = GetTime() + 10, + remaining = 10, + paused = false, + value = 50, + total = 100, + inverse = false, + + -- Display + name = "Aura Name", + icon = 12345, + texture = "Interface\\...", + stacks = 5, + + -- Additional info + unit = "player", + unitCaster = "player", + spellId = 12345, + + -- School/damage type + school = 1, + damageType = 1, + + -- Custom variables + customVar1 = "value", + customVar2 = 123, + + -- Tooltip + tooltip1 = "line1", + tooltip2 = "line2", + tooltip3 = "line3", + + -- Index (for multi-state) + index = 1, + + -- Auto-hide + autoHide = false, +} +``` + +## Text Replacements + +Text fields support these replacements: + +- `%p` - Progress (time/value) +- `%t` - Total (duration/max) +- `%n` - Name +- `%i` - Icon +- `%s` - Stacks +- `%c` - Custom function +- `%unit` - Unit name +- `%guid` - Unit GUID +- `%targetunit` - Target's unit +- `%spell` - Spell name +- `%spellId` - Spell ID + +Each replacement can have format specifiers: +```lua +displayText_format_p_time_type = 0, -- 0=WeakAuras, 1=Blizzard Short, 2=Blizzard Long +displayText_format_p_time_precision = 1, -- Decimal places +displayText_format_p_format = "timed", -- timed, Number, BigNumber +``` + +## Custom Code Environments + +Custom code runs in specific environments with available functions: + +### Trigger Environment +```lua +-- Available variables +event -- Event name +... -- Event arguments + +-- Available functions +WeakAuras.ScanUnit() +WeakAuras.GetAuraInstanceInfo() +WeakAuras.GetAuraTooltipInfo() +WeakAuras.UnitBuff() +WeakAuras.UnitDebuff() +WeakAuras.GetSpellInfo() +WeakAuras.GetSpellDescription() +WeakAuras.IsSpellKnown() +WeakAuras.IsSpellKnownForLoad() +WeakAuras.IsSpellInRange() +WeakAuras.GetRange() +WeakAuras.CheckRange() +WeakAuras.GetTotemInfo() +WeakAuras.GetRuneCooldown() +WeakAuras.GetRuneCount() +WeakAuras.GetActiveTalents() +-- And many more... +``` + +### Display Environment +```lua +-- Available variables +uiParent -- Parent frame +region -- Display region +id -- Aura ID +cloneId -- Clone ID (for dynamic groups) +state -- Current state +states -- All states (multi-state) + +-- Available functions +WeakAuras.regions[id].region -- Access region +WeakAuras.GetData(id) -- Get aura data +-- All trigger environment functions +``` + +## Notes + +1. **UIDs vs IDs**: Every aura has both a user-visible ID (name) and a system UID. The UID ensures uniqueness across different systems. + +2. **Internal Version**: The `internalVersion` field tracks the data structure version. WeakAuras automatically migrates old auras to new formats. + +3. **Parent-Child Relationships**: Groups can contain other auras through `controlledChildren` array and child `parent` field. + +4. **Clone System**: Dynamic groups and multi-target auras create clones of regions to display multiple states. + +5. **State Management**: The trigger system manages states which determine what is shown and how. + +6. **Region Types**: Each display type has its own specific fields and behaviors but shares common positioning and animation systems. + +7. **Load System**: Load conditions determine if an aura should be active. They're checked on events and zone changes. + +8. **Property Changes**: Conditions can dynamically modify almost any display property based on trigger states. + +This structure represents the complete WeakAura data model as implemented in the WeakAuras2 addon. \ No newline at end of file diff --git a/package.json b/package.json index 6fafeeb..b2f70d1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,14 @@ "lint": "next lint", "test": "vitest", "test:coverage": "vitest run --coverage", - "encode": "ts-node public/lua/encode.ts" + "encode": "ts-node public/lua/encode-wa.ts", + "decode": "ts-node --transpileOnly public/lua/decode-wa.ts", + "generate-lua": "ts-node public/lua/generate-lua.ts", + "parse-simc": "ruby scripts/parse_simc_data.rb", + "build-mappings": "ruby scripts/build_spell_mappings.rb", + "update-spell-data": "npm run parse-simc && npm run build-mappings", + "compile-dsl": "ruby scripts/compile-dsl.rb", + "build-wa": "scripts/build-wa.sh" }, "repository": { "type": "git", diff --git a/public/core_ext/hash.rb b/public/core_ext/hash.rb new file mode 100644 index 0000000..f985ec6 --- /dev/null +++ b/public/core_ext/hash.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Hash extensions for DSL +class Hash + def deep_merge!(other_hash) + other_hash.each do |key, value| + if self[key].is_a?(Hash) && value.is_a?(Hash) + self[key].deep_merge!(value) + else + self[key] = value + end + end + self + end + + def deep_merge(other_hash) + dup.deep_merge!(other_hash) + end +end \ No newline at end of file diff --git a/public/data/class_spec_data.json b/public/data/class_spec_data.json new file mode 100644 index 0000000..c7bc998 --- /dev/null +++ b/public/data/class_spec_data.json @@ -0,0 +1,524 @@ +{ + "version": "20250819_024208", + "specializations": { + "71": { + "name": "Arms", + "class": "Warrior", + "simc_name": "WARRIOR_ARMS", + "id": 71 + }, + "72": { + "name": "Fury", + "class": "Warrior", + "simc_name": "WARRIOR_FURY", + "id": 72 + }, + "73": { + "name": "Protection", + "class": "Warrior", + "simc_name": "WARRIOR_PROTECTION", + "id": 73 + }, + "65": { + "name": "Holy", + "class": "Paladin", + "simc_name": "PALADIN_HOLY", + "id": 65 + }, + "66": { + "name": "Protection", + "class": "Paladin", + "simc_name": "PALADIN_PROTECTION", + "id": 66 + }, + "70": { + "name": "Retribution", + "class": "Paladin", + "simc_name": "PALADIN_RETRIBUTION", + "id": 70 + }, + "253": { + "name": "Beast Mastery", + "class": "Hunter", + "simc_name": "HUNTER_BEAST_MASTERY", + "id": 253 + }, + "254": { + "name": "Marksmanship", + "class": "Hunter", + "simc_name": "HUNTER_MARKSMANSHIP", + "id": 254 + }, + "255": { + "name": "Survival", + "class": "Hunter", + "simc_name": "HUNTER_SURVIVAL", + "id": 255 + }, + "259": { + "name": "Assassination", + "class": "Rogue", + "simc_name": "ROGUE_ASSASSINATION", + "id": 259 + }, + "260": { + "name": "Outlaw", + "class": "Rogue", + "simc_name": "ROGUE_OUTLAW", + "id": 260 + }, + "261": { + "name": "Subtlety", + "class": "Rogue", + "simc_name": "ROGUE_SUBTLETY", + "id": 261 + }, + "256": { + "name": "Discipline", + "class": "Priest", + "simc_name": "PRIEST_DISCIPLINE", + "id": 256 + }, + "257": { + "name": "Holy", + "class": "Priest", + "simc_name": "PRIEST_HOLY", + "id": 257 + }, + "258": { + "name": "Shadow", + "class": "Priest", + "simc_name": "PRIEST_SHADOW", + "id": 258 + }, + "250": { + "name": "Blood", + "class": "Death Knight", + "simc_name": "DEATH_KNIGHT_BLOOD", + "id": 250 + }, + "251": { + "name": "Frost", + "class": "Death Knight", + "simc_name": "DEATH_KNIGHT_FROST", + "id": 251 + }, + "252": { + "name": "Unholy", + "class": "Death Knight", + "simc_name": "DEATH_KNIGHT_UNHOLY", + "id": 252 + }, + "262": { + "name": "Elemental", + "class": "Shaman", + "simc_name": "SHAMAN_ELEMENTAL", + "id": 262 + }, + "263": { + "name": "Enhancement", + "class": "Shaman", + "simc_name": "SHAMAN_ENHANCEMENT", + "id": 263 + }, + "264": { + "name": "Restoration", + "class": "Shaman", + "simc_name": "SHAMAN_RESTORATION", + "id": 264 + }, + "62": { + "name": "Arcane", + "class": "Mage", + "simc_name": "MAGE_ARCANE", + "id": 62 + }, + "63": { + "name": "Fire", + "class": "Mage", + "simc_name": "MAGE_FIRE", + "id": 63 + }, + "64": { + "name": "Frost", + "class": "Mage", + "simc_name": "MAGE_FROST", + "id": 64 + }, + "265": { + "name": "Affliction", + "class": "Warlock", + "simc_name": "WARLOCK_AFFLICTION", + "id": 265 + }, + "266": { + "name": "Demonology", + "class": "Warlock", + "simc_name": "WARLOCK_DEMONOLOGY", + "id": 266 + }, + "267": { + "name": "Destruction", + "class": "Warlock", + "simc_name": "WARLOCK_DESTRUCTION", + "id": 267 + }, + "268": { + "name": "Brewmaster", + "class": "Monk", + "simc_name": "MONK_BREWMASTER", + "id": 268 + }, + "270": { + "name": "Mistweaver", + "class": "Monk", + "simc_name": "MONK_MISTWEAVER", + "id": 270 + }, + "269": { + "name": "Windwalker", + "class": "Monk", + "simc_name": "MONK_WINDWALKER", + "id": 269 + }, + "102": { + "name": "Balance", + "class": "Druid", + "simc_name": "DRUID_BALANCE", + "id": 102 + }, + "103": { + "name": "Feral", + "class": "Druid", + "simc_name": "DRUID_FERAL", + "id": 103 + }, + "104": { + "name": "Guardian", + "class": "Druid", + "simc_name": "DRUID_GUARDIAN", + "id": 104 + }, + "105": { + "name": "Restoration", + "class": "Druid", + "simc_name": "DRUID_RESTORATION", + "id": 105 + }, + "577": { + "name": "Havoc", + "class": "Demon Hunter", + "simc_name": "DEMON_HUNTER_HAVOC", + "id": 577 + }, + "581": { + "name": "Vengeance", + "class": "Demon Hunter", + "simc_name": "DEMON_HUNTER_VENGEANCE", + "id": 581 + }, + "1467": { + "name": "Devastation", + "class": "Evoker", + "simc_name": "EVOKER_DEVASTATION", + "id": 1467 + }, + "1468": { + "name": "Preservation", + "class": "Evoker", + "simc_name": "EVOKER_PRESERVATION", + "id": 1468 + }, + "1473": { + "name": "Augmentation", + "class": "Evoker", + "simc_name": "EVOKER_AUGMENTATION", + "id": 1473 + } + }, + "classes": { + "DEATH_KNIGHT": { + "name": "Death Knight", + "simc_name": "DEATH_KNIGHT" + }, + "DEMON_HUNTER": { + "name": "Demon Hunter", + "simc_name": "DEMON_HUNTER" + }, + "DRUID": { + "name": "Druid", + "simc_name": "DRUID" + }, + "EVOKER": { + "name": "Evoker", + "simc_name": "EVOKER" + }, + "HUNTER": { + "name": "Hunter", + "simc_name": "HUNTER" + }, + "MAGE": { + "name": "Mage", + "simc_name": "MAGE" + }, + "MONK": { + "name": "Monk", + "simc_name": "MONK" + }, + "PALADIN": { + "name": "Paladin", + "simc_name": "PALADIN" + }, + "PRIEST": { + "name": "Priest", + "simc_name": "PRIEST" + }, + "ROGUE": { + "name": "Rogue", + "simc_name": "ROGUE" + }, + "SHAMAN": { + "name": "Shaman", + "simc_name": "SHAMAN" + }, + "WARLOCK": { + "name": "Warlock", + "simc_name": "WARLOCK" + }, + "WARRIOR": { + "name": "Warrior", + "simc_name": "WARRIOR" + }, + "ENEMY_ADD_BOSS": { + "name": "Enemy Add Boss", + "simc_name": "ENEMY_ADD_BOSS" + }, + "TANK_DUMMY": { + "name": "Tank Dummy", + "simc_name": "TANK_DUMMY" + } + }, + "class_spec_mapping": { + "WARRIOR": { + "Arms": { + "wow_spec_id": 71, + "wa_spec_index": 1, + "simc_name": "WARRIOR_ARMS" + }, + "Fury": { + "wow_spec_id": 72, + "wa_spec_index": 2, + "simc_name": "WARRIOR_FURY" + }, + "Protection": { + "wow_spec_id": 73, + "wa_spec_index": 3, + "simc_name": "WARRIOR_PROTECTION" + } + }, + "PALADIN": { + "Holy": { + "wow_spec_id": 65, + "wa_spec_index": 1, + "simc_name": "PALADIN_HOLY" + }, + "Protection": { + "wow_spec_id": 66, + "wa_spec_index": 2, + "simc_name": "PALADIN_PROTECTION" + }, + "Retribution": { + "wow_spec_id": 70, + "wa_spec_index": 3, + "simc_name": "PALADIN_RETRIBUTION" + } + }, + "HUNTER": { + "Beast Mastery": { + "wow_spec_id": 253, + "wa_spec_index": 1, + "simc_name": "HUNTER_BEAST_MASTERY" + }, + "Marksmanship": { + "wow_spec_id": 254, + "wa_spec_index": 2, + "simc_name": "HUNTER_MARKSMANSHIP" + }, + "Survival": { + "wow_spec_id": 255, + "wa_spec_index": 3, + "simc_name": "HUNTER_SURVIVAL" + } + }, + "ROGUE": { + "Assassination": { + "wow_spec_id": 259, + "wa_spec_index": 1, + "simc_name": "ROGUE_ASSASSINATION" + }, + "Outlaw": { + "wow_spec_id": 260, + "wa_spec_index": 2, + "simc_name": "ROGUE_OUTLAW" + }, + "Subtlety": { + "wow_spec_id": 261, + "wa_spec_index": 3, + "simc_name": "ROGUE_SUBTLETY" + } + }, + "PRIEST": { + "Discipline": { + "wow_spec_id": 256, + "wa_spec_index": 1, + "simc_name": "PRIEST_DISCIPLINE" + }, + "Holy": { + "wow_spec_id": 257, + "wa_spec_index": 2, + "simc_name": "PRIEST_HOLY" + }, + "Shadow": { + "wow_spec_id": 258, + "wa_spec_index": 3, + "simc_name": "PRIEST_SHADOW" + } + }, + "DEATH_KNIGHT": { + "Blood": { + "wow_spec_id": 250, + "wa_spec_index": 1, + "simc_name": "DEATH_KNIGHT_BLOOD" + }, + "Frost": { + "wow_spec_id": 251, + "wa_spec_index": 2, + "simc_name": "DEATH_KNIGHT_FROST" + }, + "Unholy": { + "wow_spec_id": 252, + "wa_spec_index": 3, + "simc_name": "DEATH_KNIGHT_UNHOLY" + } + }, + "SHAMAN": { + "Elemental": { + "wow_spec_id": 262, + "wa_spec_index": 1, + "simc_name": "SHAMAN_ELEMENTAL" + }, + "Enhancement": { + "wow_spec_id": 263, + "wa_spec_index": 2, + "simc_name": "SHAMAN_ENHANCEMENT" + }, + "Restoration": { + "wow_spec_id": 264, + "wa_spec_index": 3, + "simc_name": "SHAMAN_RESTORATION" + } + }, + "MAGE": { + "Arcane": { + "wow_spec_id": 62, + "wa_spec_index": 1, + "simc_name": "MAGE_ARCANE" + }, + "Fire": { + "wow_spec_id": 63, + "wa_spec_index": 2, + "simc_name": "MAGE_FIRE" + }, + "Frost": { + "wow_spec_id": 64, + "wa_spec_index": 3, + "simc_name": "MAGE_FROST" + } + }, + "WARLOCK": { + "Affliction": { + "wow_spec_id": 265, + "wa_spec_index": 1, + "simc_name": "WARLOCK_AFFLICTION" + }, + "Demonology": { + "wow_spec_id": 266, + "wa_spec_index": 2, + "simc_name": "WARLOCK_DEMONOLOGY" + }, + "Destruction": { + "wow_spec_id": 267, + "wa_spec_index": 3, + "simc_name": "WARLOCK_DESTRUCTION" + } + }, + "MONK": { + "Brewmaster": { + "wow_spec_id": 268, + "wa_spec_index": 1, + "simc_name": "MONK_BREWMASTER" + }, + "Mistweaver": { + "wow_spec_id": 270, + "wa_spec_index": 2, + "simc_name": "MONK_MISTWEAVER" + }, + "Windwalker": { + "wow_spec_id": 269, + "wa_spec_index": 3, + "simc_name": "MONK_WINDWALKER" + } + }, + "DRUID": { + "Balance": { + "wow_spec_id": 102, + "wa_spec_index": 1, + "simc_name": "DRUID_BALANCE" + }, + "Feral": { + "wow_spec_id": 103, + "wa_spec_index": 2, + "simc_name": "DRUID_FERAL" + }, + "Guardian": { + "wow_spec_id": 104, + "wa_spec_index": 3, + "simc_name": "DRUID_GUARDIAN" + }, + "Restoration": { + "wow_spec_id": 105, + "wa_spec_index": 4, + "simc_name": "DRUID_RESTORATION" + } + }, + "DEMON_HUNTER": { + "Havoc": { + "wow_spec_id": 577, + "wa_spec_index": 1, + "simc_name": "DEMON_HUNTER_HAVOC" + }, + "Vengeance": { + "wow_spec_id": 581, + "wa_spec_index": 2, + "simc_name": "DEMON_HUNTER_VENGEANCE" + } + }, + "EVOKER": { + "Devastation": { + "wow_spec_id": 1467, + "wa_spec_index": 1, + "simc_name": "EVOKER_DEVASTATION" + }, + "Preservation": { + "wow_spec_id": 1468, + "wa_spec_index": 2, + "simc_name": "EVOKER_PRESERVATION" + }, + "Augmentation": { + "wow_spec_id": 1473, + "wa_spec_index": 3, + "simc_name": "EVOKER_AUGMENTATION" + } + } + } +} \ No newline at end of file diff --git a/public/data/class_spec_mappings.rb b/public/data/class_spec_mappings.rb new file mode 100644 index 0000000..74424e3 --- /dev/null +++ b/public/data/class_spec_mappings.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# Auto-generated from SimC data on 2025-08-19 02:42:08 +0000 +# Do not edit manually - use scripts/parse_class_spec_data.rb + +module ClassSpecMappings + # WoW Spec ID to WeakAura class name and spec index mapping + SPEC_TO_WA_CLASS = { + 71 => { class: 'WARRIOR', spec: 1 }, # Arms + 72 => { class: 'WARRIOR', spec: 2 }, # Fury + 73 => { class: 'WARRIOR', spec: 3 }, # Protection + 65 => { class: 'PALADIN', spec: 1 }, # Holy + 66 => { class: 'PALADIN', spec: 2 }, # Protection + 70 => { class: 'PALADIN', spec: 3 }, # Retribution + 253 => { class: 'HUNTER', spec: 1 }, # Beast Mastery + 254 => { class: 'HUNTER', spec: 2 }, # Marksmanship + 255 => { class: 'HUNTER', spec: 3 }, # Survival + 259 => { class: 'ROGUE', spec: 1 }, # Assassination + 260 => { class: 'ROGUE', spec: 2 }, # Outlaw + 261 => { class: 'ROGUE', spec: 3 }, # Subtlety + 256 => { class: 'PRIEST', spec: 1 }, # Discipline + 257 => { class: 'PRIEST', spec: 2 }, # Holy + 258 => { class: 'PRIEST', spec: 3 }, # Shadow + 250 => { class: 'DEATH_KNIGHT', spec: 1 }, # Blood + 251 => { class: 'DEATH_KNIGHT', spec: 2 }, # Frost + 252 => { class: 'DEATH_KNIGHT', spec: 3 }, # Unholy + 262 => { class: 'SHAMAN', spec: 1 }, # Elemental + 263 => { class: 'SHAMAN', spec: 2 }, # Enhancement + 264 => { class: 'SHAMAN', spec: 3 }, # Restoration + 62 => { class: 'MAGE', spec: 1 }, # Arcane + 63 => { class: 'MAGE', spec: 2 }, # Fire + 64 => { class: 'MAGE', spec: 3 }, # Frost + 265 => { class: 'WARLOCK', spec: 1 }, # Affliction + 266 => { class: 'WARLOCK', spec: 2 }, # Demonology + 267 => { class: 'WARLOCK', spec: 3 }, # Destruction + 268 => { class: 'MONK', spec: 1 }, # Brewmaster + 270 => { class: 'MONK', spec: 2 }, # Mistweaver + 269 => { class: 'MONK', spec: 3 }, # Windwalker + 102 => { class: 'DRUID', spec: 1 }, # Balance + 103 => { class: 'DRUID', spec: 2 }, # Feral + 104 => { class: 'DRUID', spec: 3 }, # Guardian + 105 => { class: 'DRUID', spec: 4 }, # Restoration + 577 => { class: 'DEMON_HUNTER', spec: 1 }, # Havoc + 581 => { class: 'DEMON_HUNTER', spec: 2 }, # Vengeance + 1467 => { class: 'EVOKER', spec: 1 }, # Devastation + 1468 => { class: 'EVOKER', spec: 2 }, # Preservation + 1473 => { class: 'EVOKER', spec: 3 }, # Augmentation + }.freeze + + # Class name to specs mapping + CLASS_SPECS = { + 'WARRIOR' => [{ name: 'Arms', wow_id: 71, wa_index: 1 }, { name: 'Fury', wow_id: 72, wa_index: 2 }, { name: 'Protection', wow_id: 73, wa_index: 3 }], + 'PALADIN' => [{ name: 'Holy', wow_id: 65, wa_index: 1 }, { name: 'Protection', wow_id: 66, wa_index: 2 }, { name: 'Retribution', wow_id: 70, wa_index: 3 }], + 'HUNTER' => [{ name: 'Beast Mastery', wow_id: 253, wa_index: 1 }, { name: 'Marksmanship', wow_id: 254, wa_index: 2 }, { name: 'Survival', wow_id: 255, wa_index: 3 }], + 'ROGUE' => [{ name: 'Assassination', wow_id: 259, wa_index: 1 }, { name: 'Outlaw', wow_id: 260, wa_index: 2 }, { name: 'Subtlety', wow_id: 261, wa_index: 3 }], + 'PRIEST' => [{ name: 'Discipline', wow_id: 256, wa_index: 1 }, { name: 'Holy', wow_id: 257, wa_index: 2 }, { name: 'Shadow', wow_id: 258, wa_index: 3 }], + 'DEATH_KNIGHT' => [{ name: 'Blood', wow_id: 250, wa_index: 1 }, { name: 'Frost', wow_id: 251, wa_index: 2 }, { name: 'Unholy', wow_id: 252, wa_index: 3 }], + 'SHAMAN' => [{ name: 'Elemental', wow_id: 262, wa_index: 1 }, { name: 'Enhancement', wow_id: 263, wa_index: 2 }, { name: 'Restoration', wow_id: 264, wa_index: 3 }], + 'MAGE' => [{ name: 'Arcane', wow_id: 62, wa_index: 1 }, { name: 'Fire', wow_id: 63, wa_index: 2 }, { name: 'Frost', wow_id: 64, wa_index: 3 }], + 'WARLOCK' => [{ name: 'Affliction', wow_id: 265, wa_index: 1 }, { name: 'Demonology', wow_id: 266, wa_index: 2 }, { name: 'Destruction', wow_id: 267, wa_index: 3 }], + 'MONK' => [{ name: 'Brewmaster', wow_id: 268, wa_index: 1 }, { name: 'Mistweaver', wow_id: 270, wa_index: 2 }, { name: 'Windwalker', wow_id: 269, wa_index: 3 }], + 'DRUID' => [{ name: 'Balance', wow_id: 102, wa_index: 1 }, { name: 'Feral', wow_id: 103, wa_index: 2 }, { name: 'Guardian', wow_id: 104, wa_index: 3 }, { name: 'Restoration', wow_id: 105, wa_index: 4 }], + 'DEMON_HUNTER' => [{ name: 'Havoc', wow_id: 577, wa_index: 1 }, { name: 'Vengeance', wow_id: 581, wa_index: 2 }], + 'EVOKER' => [{ name: 'Devastation', wow_id: 1467, wa_index: 1 }, { name: 'Preservation', wow_id: 1468, wa_index: 2 }, { name: 'Augmentation', wow_id: 1473, wa_index: 3 }] + }.freeze + + def self.wa_class_and_spec(wow_spec_id) + SPEC_TO_WA_CLASS[wow_spec_id] + end + + def self.class_specs(class_name) + CLASS_SPECS[class_name.upcase.gsub(' ', '_')] + end +end diff --git a/public/data/simc_structured_spells.json b/public/data/simc_structured_spells.json new file mode 100644 index 0000000..42c8f41 --- /dev/null +++ b/public/data/simc_structured_spells.json @@ -0,0 +1,838291 @@ +{ + "Power Word: Shield": { + "id": 17, + "name": "Power Word: Shield", + "description": "Shields an ally for , absorbing damage.", + "tooltip": { + "text": "Absorbs damage.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": "7.5s CD", + "charges": null, + "duration": "15s duration", + "gcd": null, + "requirements": "40y, 7.5s CD, 15s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 7500, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 15000, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Vanguard": { + "id": 71, + "name": "Vanguard", + "description": "Hardened by battle, your Stamina is increased by % and your Armor is increased by % of your Strength.", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": true, + "talent_data": null, + "specialization_data": { + "spell_id": 71, + "class_id": 1, + "spec_id": 73, + "name": "Vanguard", + "is_specialization_spell": true + }, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Auto Shot": { + "id": 75, + "name": "Auto Shot", + "description": "Automatically shoots the target until cancelled.", + "tooltip": { + "text": "Firing at the target.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "40y", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 60 + } + }, + "Incapacitating Roar": { + "id": 99, + "name": "Incapacitating Roar", + "description": "Shift into Bear Form and invoke the spirit of Ursol to let loose a deafening roar, incapacitating all enemies within yards for . Damage may cancel the effect.", + "tooltip": { + "text": "Incapacitated.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": "30s CD", + "charges": null, + "duration": "3s duration", + "gcd": null, + "requirements": "30s CD, 3s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 30000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3000, + "gcd_ms": 1500, + "class_mask": 1, + "school_mask": 0 + } + }, + "Eye of Kilrogg": { + "id": 126, + "name": "Eye of Kilrogg", + "description": "Summons an Eye of Kilrogg and binds your vision to it. The eye is stealthed and moves quickly but is very fragile.", + "tooltip": { + "text": "Controlling Eye of Kilrogg.\\r\\nDetecting Invisibility.", + "requirements": [ + + ] + }, + "range": "50y", + "cooldown": null, + "charges": null, + "duration": "45s duration", + "gcd": null, + "requirements": "50y, 45s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 50000.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 45000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Mend Pet": { + "id": 136, + "name": "Mend Pet", + "description": "Heals your pet for % of its total health over . time Mend Pet heals your pet, it has a % chance to dispel a harmful magic effect from your pet.][]", + "tooltip": { + "text": "Heals % of the pet's health every sec. time Mend Pet heals your pet, you have a % chance to dispel a harmful magic effect from your pet.][]", + "requirements": [ + + ] + }, + "range": "45y", + "cooldown": "10s CD", + "charges": null, + "duration": "10s duration", + "gcd": null, + "requirements": "45y, 10s CD, 10s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 45.0, + "cooldown_ms": 10000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 10000, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Renew": { + "id": 139, + "name": "Renew", + "description": "Fill the target with faith in the light, healing for over .", + "tooltip": { + "text": "Healing health every sec.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "15s duration", + "gcd": null, + "requirements": "40y, 15s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 15000, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Purge": { + "id": 370, + "name": "Purge", + "description": "Purges the enemy target, removing beneficial Magic .(s147762&s51530)\\r\\n[ Successfully purging a target grants a stack of Maelstrom Weapon.][]", + "tooltip": "", + "range": "30y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "30y, Enemy target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Mind Soothe": { + "id": 453, + "name": "Mind Soothe", + "description": "Soothes enemies in the target area, reducing the range at which they will attack you by yards. Only affects Humanoid and Dragonkin targets. Does not cause threat. Lasts .", + "tooltip": { + "text": "Reduced distance at which target will attack.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": "5s CD", + "charges": null, + "duration": "20s duration", + "gcd": null, + "requirements": "40y, 5s CD, 20s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 5000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 20000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Remove Curse": { + "id": 475, + "name": "Remove Curse", + "description": "Removes all Curses from a friendly target. any Curses are successfully removed, you deal % additional damage for .][]", + "tooltip": "", + "range": "40y", + "cooldown": "8s CD", + "charges": null, + "duration": null, + "gcd": null, + "requirements": "40y, 8s CD, Friendly target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 8000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 64, + "school_mask": 0 + } + }, + "Purify": { + "id": 527, + "name": "Purify", + "description": "Dispels harmful effects on the target, removing all Magic and Disease][] effects.", + "tooltip": "", + "range": "40y", + "cooldown": "8s CD", + "charges": null, + "duration": null, + "gcd": null, + "requirements": "40y, 8s CD", + "is_talent": false, + "is_specialization_spell": true, + "talent_data": null, + "specialization_data": { + "spell_id": 527, + "class_id": 5, + "spec_id": 257, + "name": "Purify", + "is_specialization_spell": true + }, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 8000, + "charges": 1, + "charge_cooldown_ms": 8000, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Shadow Word: Pain": { + "id": 589, + "name": "Shadow Word: Pain", + "description": "A word of darkness that causes *(1+)}][ Shadow damage instantly, and an additional *(1+)}][ Shadow damage over .Generates Insanity.][]", + "tooltip": { + "text": "Suffering Shadow damage every sec.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "16s duration", + "gcd": null, + "requirements": "40y, 16s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 16000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Mind Control": { + "id": 605, + "name": "Mind Control", + "description": "Controls a mind up to 1 level above yours for . Does not work versus Demonic, Undead,] or Mechanical beings. Shares diminishing returns with other disorienting effects.", + "tooltip": { + "text": "Under the command of $@auracaster.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "30s duration", + "gcd": null, + "requirements": "30y, 30s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 30000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Minor Defense": { + "id": 673, + "name": "Minor Defense", + "description": "Increases your armor by for . Guardian Elixir.", + "tooltip": { + "text": "Armor increased by . Guardian Elixir.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": null, + "charges": null, + "duration": "3600s duration", + "gcd": null, + "requirements": "3600s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3600000, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Summon Imp": { + "id": 688, + "name": "Summon Imp", + "description": "", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": -1, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Summon Felhunter": { + "id": 691, + "name": "Summon Felhunter", + "description": "", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": -1, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Ritual of Summoning": { + "id": 698, + "name": "Ritual of Summoning", + "description": "Begins a ritual to create a summoning portal, requiring the caster and 2 allies to complete. This portal can be used to summon party and raid members.", + "tooltip": "", + "range": "30y", + "cooldown": "120s CD", + "charges": null, + "duration": "120s duration", + "gcd": null, + "requirements": "30y, 120s CD, 120s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 120000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 120000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Banish": { + "id": 710, + "name": "Banish", + "description": "Banishes an enemy Demon, Aberration, Undead][], or Elemental, preventing any action for . Limit 1. Casting Banish again on the target will cancel the effect.", + "tooltip": { + "text": "Invulnerable, but unable to act.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "30s duration", + "gcd": null, + "requirements": "30y, 30s duration, Enemy target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 30000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Rejuvenation": { + "id": 774, + "name": "Rejuvenation", + "description": "Heals the target for over . can apply Rejuvenation twice to the same target.][]|C0033AA11Tree of Life: Healing increased by % and Mana cost reduced by %.|R][]", + "tooltip": { + "text": "Healing every sec.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "12s duration", + "gcd": null, + "requirements": "40y, 12s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 12000, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Tidal Charm": { + "id": 835, + "name": "Tidal Charm", + "description": "Stuns target for . Increased chance to be resisted when used against targets over level .", + "tooltip": { + "text": "Stunned.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "3s duration", + "gcd": null, + "requirements": "30y, 3s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3000, + "gcd_ms": 0, + "class_mask": 8, + "school_mask": 0 + } + }, + "Call Pet 1": { + "id": 883, + "name": "Call Pet 1", + "description": "Summons your first pet to you.", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": -1, + "gcd_ms": 1500, + "class_mask": 1, + "school_mask": 0 + } + }, + "Earth Shield (379)": { + "id": 379, + "name": "Earth Shield (379)", + "description": "$@spelldesc974", + "tooltip": "", + "range": "300y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "300y", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 300.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 8, + "school_mask": 0 + } + }, + "Earth Shield (974)": { + "id": 974, + "name": "Earth Shield (974)", + "description": "Protects the target with an earthen shield, increasing your healing on them by % and healing them for *(1+)} when they take damage. This heal can only occur once every +()}.1][ sec. charges].\\r\\n\\r\\n Shield can only be placed on the Shaman and one other target at a time. The Shaman can have up to two Elemental Shields active on them.][Earth Shield can only be placed on one target at a time. Only one Elemental Shield can be active on the Shaman.]", + "tooltip": { + "text": "!=0[Damage taken reduced by %.\\r\\n\\r\\n][]Heals for *(1+)} upon taking damage.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "3600s duration", + "gcd": null, + "requirements": "40y, 3600s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3600000, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Revive Pet": { + "id": 982, + "name": "Revive Pet", + "description": "Revives your pet, returning it to life with % of its base health.", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": "3s duration", + "gcd": null, + "requirements": "3s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3000, + "gcd_ms": 1500, + "class_mask": 1, + "school_mask": 0 + } + }, + "Blessing of Protection": { + "id": 1022, + "name": "Blessing of Protection", + "description": "Blesses a party or raid member, granting immunity to Physical damage and harmful effects for .\\r\\n\\r\\nCannot be used on a target with Forbearance. Causes Forbearance for . a cooldown with Blessing of Spellwarding.][]", + "tooltip": { + "text": "Immune to Physical damage and harmful effects. speed increased by %.][]", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": "1.5s CD", + "charges": null, + "duration": "10s duration", + "gcd": null, + "requirements": "40y, 1.5s CD, 10s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 1500, + "charges": 1, + "charge_cooldown_ms": 300000, + "duration_ms": 10000, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Blessing of Freedom": { + "id": 1044, + "name": "Blessing of Freedom", + "description": "Blesses a party or raid member, granting immunity to movement impairing effects increasing movement speed by % ][]for .", + "tooltip": { + "text": "Immune to movement impairing effects. speed increased by %][]", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": "1.5s CD", + "charges": null, + "duration": "8s duration", + "gcd": null, + "requirements": "40y, 1.5s CD, 8s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 1500, + "charges": 1, + "charge_cooldown_ms": 25000, + "duration_ms": 8000, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Rip": { + "id": 1079, + "name": "Rip", + "description": "Finishing move that causes Bleed damage over time. Lasts longer per combo point.\\r\\n\\r\\n 1 point : *2} over *2} sec\\r\\n 2 points: *3} over *3} sec\\r\\n 3 points: *4} over *4} sec\\r\\n 4 points: *5} over *5} sec\\r\\n 5 points: *6} over *6} sec", + "tooltip": { + "text": "Bleeding for damage every sec.", + "requirements": [ + + ] + }, + "range": "melee", + "cooldown": null, + "charges": null, + "duration": "4s duration", + "gcd": "1.0s GCD", + "requirements": "melee, 4s duration, 1.0s GCD", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 4000, + "gcd_ms": 1000, + "class_mask": 1, + "school_mask": 0 + } + }, + "Subjugate Demon": { + "id": 1098, + "name": "Subjugate Demon", + "description": "Subjugates the target demon up to level , forcing it to do your bidding for .", + "tooltip": { + "text": "$@auracaster's subject.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "600s duration", + "gcd": null, + "requirements": "30y, 600s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 600000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Cold Eye": { + "id": 1139, + "name": "Cold Eye", + "description": "Increases time between target's attacks by % for .", + "tooltip": { + "text": "Slowed attack speed.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "15s duration", + "gcd": null, + "requirements": "30y, 15s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 15000, + "gcd_ms": 0, + "class_mask": 16, + "school_mask": 0 + } + }, + "Demoralizing Shout": { + "id": 1160, + "name": "Demoralizing Shout", + "description": "all enemies within yards, reducing the damage they do by % for .][Demoralizes all enemies within yards, reducing the damage they deal to you by % for .]Generates Rage.][]", + "tooltip": { + "text": ", dealing % less damage.][Demoralized, dealing % less damage to $@auracaster.] % increased damage from $@auracaster.][]", + "requirements": [ + + ] + }, + "range": null, + "cooldown": "45s CD", + "charges": null, + "duration": "8s duration", + "gcd": null, + "requirements": "45s CD, 8s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 45000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 8000, + "gcd_ms": 1500, + "class_mask": 1, + "school_mask": 0 + } + }, + "Challenging Shout": { + "id": 1161, + "name": "Challenging Shout", + "description": "Taunts all enemies within yds to attack you for .", + "tooltip": { + "text": "Taunted.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": "120s CD", + "charges": null, + "duration": "6s duration", + "gcd": null, + "requirements": "120s CD, 6s duration", + "is_talent": false, + "is_specialization_spell": true, + "talent_data": null, + "specialization_data": { + "spell_id": 1161, + "class_id": 1, + "spec_id": 73, + "name": "Challenging Shout", + "is_specialization_spell": true + }, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 120000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 6000, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Bear Form Passive": { + "id": 1178, + "name": "Bear Form Passive", + "description": "", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 8, + "school_mask": 0 + } + }, + "Garrote - Silence": { + "id": 1330, + "name": "Garrote - Silence", + "description": "Silences an enemy for .", + "tooltip": { + "text": "Silenced.", + "requirements": [ + + ] + }, + "range": "50y", + "cooldown": null, + "charges": null, + "duration": "5s duration", + "gcd": null, + "requirements": "50y, 5s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 50000.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 5000, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Arcane Intellect": { + "id": 1459, + "name": "Arcane Intellect", + "description": "Infuses the target with brilliance, increasing their Intellect by % for . \\r\\n\\r\\nIf the target is in your party or raid, all party and raid members will be affected.", + "tooltip": { + "text": "Intellect increased by %.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "3600s duration", + "gcd": null, + "requirements": "40y, 3600s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3600000, + "gcd_ms": 1500, + "class_mask": 64, + "school_mask": 0 + } + }, + "Beast Lore": { + "id": 1462, + "name": "Beast Lore", + "description": "Gathers information about the target beast, displaying diet, abilities, specialization, whether or not the creature is tameable, and if it is exotic.", + "tooltip": { + "text": "Lore revealed.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "30s duration", + "gcd": null, + "requirements": "40y, 30s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 30000, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Slam": { + "id": 1464, + "name": "Slam", + "description": "Slams an opponent, causing Physical damage.|cFFFFFFFFGenerates Rage.][]", + "tooltip": "", + "range": "melee", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "melee", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 1, + "school_mask": 0 + } + }, + "Track Beasts": { + "id": 1494, + "name": "Track Beasts", + "description": "Shows the location of all nearby beasts on the minimap.", + "tooltip": { + "text": "Tracking Beasts.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": -1, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Scare Beast": { + "id": 1513, + "name": "Scare Beast", + "description": "Scares a beast, causing it to run in fear for up to . Damage caused may interrupt the effect. Only one beast can be feared at a time.", + "tooltip": { + "text": "Feared.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "20s duration", + "gcd": null, + "requirements": "30y, 20s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 20000, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Curse of Tongues": { + "id": 1714, + "name": "Curse of Tongues", + "description": "Forces the target to speak in Demonic, increasing the casting time of all spells by % for .|CFFE55BB0Soulburn: Your Curse of Tongues will affect all enemies in a yard radius around your target.|R][]\\r\\n\\r\\nCurses: A warlock can only have one Curse active per target.", + "tooltip": { + "text": "Speaking Demonic increasing casting time by %.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "60s duration", + "gcd": null, + "requirements": "40y, 60s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 60000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Hamstring": { + "id": 1715, + "name": "Hamstring", + "description": "Maims the enemy for Physical damage, reducing movement speed by % for .", + "tooltip": { + "text": "Movement slowed by %.", + "requirements": [ + + ] + }, + "range": "melee", + "cooldown": null, + "charges": null, + "duration": "15s duration", + "gcd": "0.75s GCD", + "requirements": "melee, 15s duration, 0.75s GCD", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 15000, + "gcd_ms": 750, + "class_mask": 1, + "school_mask": 0 + } + }, + "Recklessness": { + "id": 1719, + "name": "Recklessness", + "description": "Go berserk, increasing all Rage generation by % and granting your abilities % increased critical strike chance for .Generates Rage.][]", + "tooltip": { + "text": "Rage generation increased by %.\\r\\nCritical strike chance of all abilities increased by %.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": "90s CD", + "charges": null, + "duration": "12s duration", + "gcd": null, + "requirements": "90s CD, 12s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 90000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 12000, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Kick": { + "id": 1766, + "name": "Kick", + "description": "A quick kick that interrupts spellcasting and prevents any spell in that school from being cast for .", + "tooltip": "", + "range": "melee", + "cooldown": "15s CD", + "charges": null, + "duration": "3s duration", + "gcd": null, + "requirements": "melee, 15s CD, 3s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 15000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3000, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Gouge": { + "id": 1776, + "name": "Gouge", + "description": "Gouges the eyes of an enemy target, incapacitating for . Damage may interrupt the effect.\\r\\n\\r\\nMust be in front of your target.\\r\\n\\r\\nAwards combo .", + "tooltip": { + "text": "Incapacitated.", + "requirements": [ + + ] + }, + "range": "melee", + "cooldown": "25s CD", + "charges": null, + "duration": "4s duration", + "gcd": "1.0s GCD", + "requirements": "melee, 25s CD, 4s duration, 1.0s GCD, Enemy target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 25000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 4000, + "gcd_ms": 1000, + "class_mask": 1, + "school_mask": 0 + } + }, + "Pick Lock": { + "id": 1804, + "name": "Pick Lock", + "description": "Allows opening of locked chests and doors that require a skill level of up to .", + "tooltip": "", + "range": "melee", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "melee", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Cheap Shot": { + "id": 1833, + "name": "Cheap Shot", + "description": "Stuns the target for .\\r\\n\\r\\nAwards combo .", + "tooltip": { + "text": "Stunned.", + "requirements": [ + + ] + }, + "range": "melee", + "cooldown": null, + "charges": null, + "duration": "6s duration", + "gcd": "1.0s GCD", + "requirements": "melee, 6s duration, 1.0s GCD", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 5.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 6000, + "gcd_ms": 1000, + "class_mask": 1, + "school_mask": 0 + } + }, + "Safe Fall": { + "id": 1860, + "name": "Safe Fall", + "description": "", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Feint": { + "id": 1966, + "name": "Feint", + "description": "Performs an evasive maneuver, &a79008[reducing damage taken by %.]?a79008[reducing damage taken from area-of-effect attacks by % and all other damage taken by %][reducing damage taken from area-of-effect attacks by %] for .", + "tooltip": { + "text": "Damage taken from area-of-effect attacks reduced by %!=0[ and all other damage taken reduced by %.\\r\\n][.]", + "requirements": [ + + ] + }, + "range": null, + "cooldown": "1s CD", + "charges": null, + "duration": "6s duration", + "gcd": null, + "requirements": "1s CD, 6s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 1000, + "charges": 1, + "charge_cooldown_ms": 15000, + "duration_ms": 6000, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Holy Word: Serenity": { + "id": 2050, + "name": "Holy Word: Serenity", + "description": "Perform a miracle, healing an ally for .Cooldown reduced by sec when you cast Heal or Flash Heal.][]", + "tooltip": "", + "range": "40y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "40y", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 1, + "charge_cooldown_ms": 60000, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Flash Heal": { + "id": 2061, + "name": "Flash Heal", + "description": "A fast spell that heals an ally for .", + "tooltip": "", + "range": "40y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "40y", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 0 + } + }, + "Mind Vision": { + "id": 2096, + "name": "Mind Vision", + "description": "Allows the caster to see through the target's eyes for . Will not work if the target is in another instance or on another continent.", + "tooltip": { + "text": "Sight granted through target's eyes.", + "requirements": [ + + ] + }, + "range": "50y", + "cooldown": null, + "charges": null, + "duration": "60s duration", + "gcd": null, + "requirements": "50y, 60s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 50000.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 60000, + "gcd_ms": 1500, + "class_mask": 32, + "school_mask": 0 + } + }, + "Counterspell": { + "id": 2139, + "name": "Counterspell", + "description": "Counters the enemy's spellcast, preventing any spell from that school of magic from being cast for and silencing the target for .", + "tooltip": "", + "range": "40y", + "cooldown": "24s CD", + "charges": null, + "duration": "5s duration", + "gcd": null, + "requirements": "40y, 24s CD, 5s duration, Enemy target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 24000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 5000, + "gcd_ms": 0, + "class_mask": 64, + "school_mask": 0 + } + }, + "Elixir of Lion's Strength": { + "id": 2329, + "name": "Elixir of Lion's Strength", + "description": "", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Elixir of Lesser Agility": { + "id": 2333, + "name": "Elixir of Lesser Agility", + "description": "", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Earthbind Totem": { + "id": 2484, + "name": "Earthbind Totem", + "description": "Summons a totem at the target location for that slows the movement speed of enemies within yards by %.", + "tooltip": "", + "range": "40y", + "cooldown": "30s CD", + "charges": null, + "duration": "20s duration", + "gcd": "1.0s GCD", + "requirements": "40y, 30s CD, 20s duration, 1.0s GCD", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 30000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 20000, + "gcd_ms": 1000, + "class_mask": 8, + "school_mask": 0 + } + }, + "Hibernate": { + "id": 2637, + "name": "Hibernate", + "description": "Forces the enemy target to sleep for up to . Any damage will awaken the target. Only one target can be forced to hibernate at a time. Only works on Beasts and Dragonkin.", + "tooltip": { + "text": "Asleep.", + "requirements": [ + + ] + }, + "range": "30y", + "cooldown": null, + "charges": null, + "duration": "40s duration", + "gcd": null, + "requirements": "30y, 40s duration, Enemy target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 30.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 40000, + "gcd_ms": 1500, + "class_mask": 8, + "school_mask": 0 + } + }, + "Dismiss Pet": { + "id": 2641, + "name": "Dismiss Pet", + "description": "Temporarily sends this pet away. You can call it back later.", + "tooltip": "", + "range": "50y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "50y", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 50000.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 1500, + "class_mask": 1, + "school_mask": 0 + } + }, + "Denounce": { + "id": 2812, + "name": "Denounce", + "description": "Casts down the enemy with a bolt of Holy Light, causing Holy damage and preventing the target from causing critical effects for the next .", + "tooltip": { + "text": "Incapable of causing a critical effect.", + "requirements": [ + + ] + }, + "range": "40y", + "cooldown": null, + "charges": null, + "duration": "8s duration", + "gcd": null, + "requirements": "40y, 8s duration, Enemy target", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 40.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 8000, + "gcd_ms": 1500, + "class_mask": 2, + "school_mask": 24 + } + }, + "Deadly Poison (2818)": { + "id": 2818, + "name": "Deadly Poison (2818)", + "description": "$@spelldesc2823", + "tooltip": { + "text": "Suffering Nature damage every seconds.", + "requirements": [ + + ] + }, + "range": "100y", + "cooldown": null, + "charges": null, + "duration": "12s duration", + "gcd": null, + "requirements": "100y, 12s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 100.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 12000, + "gcd_ms": 0, + "class_mask": 8, + "school_mask": 0 + } + }, + "Deadly Poison (2823)": { + "id": 2823, + "name": "Deadly Poison (2823)", + "description": "Coats your weapons with a Lethal Poison that lasts for . Each strike has a % chance to poison the enemy for * Nature damage over . Subsequent poison applications will instantly deal Nature damage.", + "tooltip": { + "text": "Each strike has a chance of causing the target to suffer Nature damage every sec for . Subsequent poison applications deal instant Nature damage.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": null, + "charges": null, + "duration": "3600s duration", + "gcd": "1.0s GCD", + "requirements": "3600s duration, 1.0s GCD", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 3600000, + "gcd_ms": 1000, + "class_mask": 1, + "school_mask": 0 + } + }, + "Bloodlust": { + "id": 2825, + "name": "Bloodlust", + "description": "Increases haste by % for all party and raid members for .\\r\\n\\r\\nAllies receiving this effect will become Sated and unable to benefit from Bloodlust or Time Warp again for .", + "tooltip": { + "text": "Haste increased by %.", + "requirements": [ + + ] + }, + "range": null, + "cooldown": "300s CD", + "charges": null, + "duration": "40s duration", + "gcd": null, + "requirements": "300s CD, 40s duration", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 300000, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 40000, + "gcd_ms": 0, + "class_mask": 8, + "school_mask": 0 + } + }, + "Sharpen Blade II": { + "id": 2829, + "name": "Sharpen Blade II", + "description": "Sharpens your bladed weapon, increasing weapon damage by for 1 hour. Cannot be applied to items higher than level .", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Sharpen Blade III": { + "id": 2830, + "name": "Sharpen Blade III", + "description": "Sharpens your bladed weapon, increasing weapon damage by for 1 hour. Cannot be applied to items higher than level .", + "tooltip": "", + "range": null, + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 0, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Detect Traps": { + "id": 2836, + "name": "Detect Traps", + "description": "", + "tooltip": "", + "range": null, + "cooldown": "1.5s CD", + "charges": null, + "duration": null, + "gcd": null, + "requirements": "1.5s CD", + "is_talent": false, + "is_specialization_spell": false, + "talent_data": null, + "specialization_data": null, + "raw_data": { + "max_range": 0.0, + "cooldown_ms": 1500, + "charges": 0, + "charge_cooldown_ms": 0, + "duration_ms": 0, + "gcd_ms": 0, + "class_mask": 1, + "school_mask": 0 + } + }, + "Scorch": { + "id": 2948, + "name": "Scorch", + "description": "Scorches an enemy for Fire damage.\\r\\n\\r\\nWhen cast on a target below % health, Scorch is a guaranteed critical strike, deals % increased damage,][] and increases your movement speed by % for .\\r\\n\\r\\nCastable while moving.", + "tooltip": "", + "range": "40y", + "cooldown": null, + "charges": null, + "duration": null, + "gcd": null, + "requirements": "40y,