// ═══════════════════════════════════════════════════════════════════════ // ABILITIES — spell & skill unlock guide for abilities page // ═══════════════════════════════════════════════════════════════════════ function enhanceAbilities() { if (document.getElementById('hv-abilities')) return; const lv = STATE.level || 1; // Detect current tree const url = window.location.href || ''; const treeMap = { 'tree=general': 'General', 'tree=onehanded': 'One-Handed', 'tree=twohanded': 'Two-Handed', 'tree=dualwield': 'Dual Wield', 'tree=niten': 'Niten', 'tree=staff': 'Staff', 'tree=cloth': 'Cloth', 'tree=light': 'Light', 'tree=heavy': 'Heavy', 'tree=deprecating1': 'Deprecating 1', 'tree=deprecating2': 'Deprecating 2', 'tree=supportive1': 'Supportive 1', 'tree=supportive2': 'Supportive 2', 'tree=elemental': 'Elemental', 'tree=forbidden': 'Forbidden', 'tree=divine': 'Divine', }; let tree = 'General'; for (const [k, v] of Object.entries(treeMap)) { if (url.includes(k)) { tree = v; break; } } // Read AP from CSS-digit counter let ap = 0; $$('#ability_top [class*="f4b"]').some(div => { const txt = div.textContent || ''; if (txt.includes('Ability') || txt.includes('Mastery')) { const d = readCSSDigits(div); if (d !== null) ap = d; return true; } }); // Parse all visible abilities const abilities = []; $$('[id^="slot_"][onmouseover]').forEach(el => { const mo = el.getAttribute('onmouseover') || ''; const m = mo.match(/overability\((\d+),\s*'([^']+)',\s*'([^']*)',\s*'([^']*)',\s*'([^']*)'/); if (!m) return; const name = m[2]; const status = m[4]; const nextHtml = m[5]; const lvlMatch = nextHtml.match(/Level\s*(\d+)/i); const lvlReq = lvlMatch ? parseInt(lvlMatch[1]) : 0; const apMatch = nextHtml.match(/(\d+)\s*Ability\s*Points?/i); const apCost = apMatch ? parseInt(apMatch[1]) : 0; const tierMatch = status.match(/Tier\s*(\d+)/i); const curTier = tierMatch ? parseInt(tierMatch[1]) : 0; const acquired = status !== 'Not Acquired' && status !== 'Locked'; abilities.push({ name, status, acquired, curTier, lvlReq, apCost, el }); }); // Free slots const freeSlots = $$('#ability_top [id^="slot_"]').filter(el => { return (el.style.backgroundImage || '').includes('t/0.png'); }).length; // Build panel let body = `💎 AP: ${ap}
`; if (freeSlots > 0) { body += `
⚠ ${freeSlots} empty slots — assign abilities!
`; } body += `
🌳 ${tree}`; const affordable = abilities.filter(a => !a.acquired && lv >= a.lvlReq && ap >= a.apCost); const lockedByLevel = abilities.filter(a => !a.acquired && lv < a.lvlReq); const owned = abilities.filter(a => a.acquired); if (owned.length > 0) body += `
✅ ${owned.length} owned
`; if (affordable.length > 0) { const best = affordable.sort((a, b) => a.apCost - b.apCost)[0]; body += `
Buy: ${best.name} (${best.apCost} AP)
`; } if (lockedByLevel.length > 0) { const next = lockedByLevel.sort((a, b) => a.lvlReq - b.lvlReq)[0]; body += `
🔒 ${next.name} @ Lv${next.lvlReq}
`; } body += `
`; // Full table if (abilities.length > 0) { body += `
`; const sorted = [].concat( affordable.sort((a, b) => a.apCost - b.apCost), owned, lockedByLevel.sort((a, b) => a.lvlReq - b.lvlReq), ); const seen = new Set(); for (const a of sorted) { const k = a.name + a.curTier; if (seen.has(k)) continue; seen.add(k); const color = a.acquired ? '#8f8' : (lv >= a.lvlReq && ap >= a.apCost) ? '#0f0' : lv >= a.lvlReq ? '#fdcb00' : '#888'; const lbl = a.acquired ? `T${a.curTier}` : (lv >= a.lvlReq && ap >= a.apCost) ? '⬆ Buy' : lv >= a.lvlReq ? '🔒AP' : `Lv${a.lvlReq}`; body += ``; } body += `
Ability APLv Status
${a.name} ${a.apCost} ${a.lvlReq} ${lbl}
`; } // Global priority body += `
🗺 Priority: General (Tanks) → Supportive → Weapon → Elemental → Deprecating
Visit each tree tab for detailed recommendations.
`; const panel = document.createElement('div'); panel.id = 'hv-abilities'; panel.style.cssText = css({ position: 'fixed', top: '60px', right: '4px', zIndex: '9995', background: '#111827', color: '#d1d5db', padding: '0', borderRadius: '8px', fontSize: '10px', fontFamily: 'monospace', maxWidth: '320px', boxShadow: '0 0 15px rgba(0,0,0,0.6)', border: '1px solid #374151', }); const collapsed = localStorage[SP + 'abCollapsed'] === '1'; panel.innerHTML = `
📖 Abilities (Lv${lv}) ${collapsed ? '▶' : '▼'}
${body}
`; panel.querySelector('#hv-ab-header').onclick = () => { const b = document.getElementById('hv-ab-body'); const t = document.getElementById('hv-ab-toggle'); const h = b.style.display === 'none'; b.style.display = h ? 'block' : 'none'; t.textContent = h ? '▼' : '▶'; localStorage[SP + 'abCollapsed'] = h ? '0' : '1'; }; document.body.appendChild(panel); }