Mudanças entre as edições de "Widget:Character.Subskills"
Ir para navegação
Ir para pesquisar
Etiqueta: Reversão manual |
|||
| Linha 1: | Linha 1: | ||
<!-- SUBSKILLS SYSTEM --> | <!-- SUBSKILLS SYSTEM COMPATÍVEL COM MÓDULO:INFO.SKILLS --> | ||
<script> | <script> | ||
(function () { | (function () { | ||
const SUBVIDEO_DEBUG = | const SUBVIDEO_DEBUG = true; // Ativado para debug | ||
function logSubVideo(...args) { | function logSubVideo(...args) { | ||
if (!SUBVIDEO_DEBUG) return; | if (!SUBVIDEO_DEBUG) return; | ||
console.log('[SubVideo]', ...args); | |||
} | } | ||
const api = (window.__subskills ||= {}); | const api = (window.__subskills ||= {}); | ||
const subskillVideoElementCache = new Map(); | |||
const subskillVideoElementCache = new Map(); | |||
const imagePreloadCache = new Map(); | const imagePreloadCache = new Map(); | ||
let subRail, subBar, spacer; | let subRail, subBar, spacer; | ||
let cachedMainSkills = null; | |||
// | // ===== DEBUG ESTRUTURA ===== | ||
const DEBUG = true; | |||
function debugLog(...args) { | |||
if (DEBUG) console.log('[Subskills]', ...args); | |||
} | |||
// ===== | // ===== FUNÇÕES AUXILIARES ===== | ||
function getMainSkillsMap() { | function getMainSkillsMap() { | ||
if (cachedMainSkills && cachedMainSkills.byIndex.size > 0) { | if (cachedMainSkills && cachedMainSkills.byIndex.size > 0) { | ||
return cachedMainSkills; | return cachedMainSkills; | ||
| Linha 29: | Linha 31: | ||
}; | }; | ||
const icons = document.querySelectorAll('.icon-bar .skill-icon[data-index][data-nome]'); | const icons = document.querySelectorAll('.icon-bar .skill-icon[data-index][data-nome]'); | ||
| Linha 36: | Linha 37: | ||
if (!name) return; | if (!name) return; | ||
const atrRaw = icon.dataset.atr || ''; | const atrRaw = icon.dataset.atr || ''; | ||
const parts = atrRaw.split(',').map(x => (x || '').trim()); | const parts = atrRaw.split(',').map(x => (x || '').trim()); | ||
| Linha 44: | Linha 44: | ||
const cooldown = parts[3] && parts[3] !== '-' ? parts[3] : ''; | const cooldown = parts[3] && parts[3] !== '-' ? parts[3] : ''; | ||
let iconFile = (icon.dataset.iconFile || '').trim(); | let iconFile = (icon.dataset.iconFile || '').trim(); | ||
if (!iconFile) { | if (!iconFile) { | ||
| Linha 52: | Linha 51: | ||
} | } | ||
let videoFile = (icon.dataset.videoFile || '').trim(); | let videoFile = (icon.dataset.videoFile || '').trim(); | ||
if (!videoFile) { | if (!videoFile) { | ||
| Linha 63: | Linha 61: | ||
const data = { | const data = { | ||
name: name, | name: name, | ||
icon: iconFile, | icon: iconFile, | ||
level: icon.dataset.level || '', | level: icon.dataset.level || '', | ||
| Linha 73: | Linha 71: | ||
}; | }; | ||
if (icon.dataset.descPt) data.descPt = icon.dataset.descPt; | if (icon.dataset.descPt) data.descPt = icon.dataset.descPt; | ||
if (icon.dataset.descEn) data.descEn = icon.dataset.descEn; | if (icon.dataset.descEn) data.descEn = icon.dataset.descEn; | ||
| Linha 81: | Linha 78: | ||
maps.byName.set(name, data); | maps.byName.set(name, data); | ||
if (index) { | if (index) { | ||
maps.byIndex.set(index, data); | maps.byIndex.set(index, data); | ||
maps.byIndex.set(parseInt(index, 10), data); | maps.byIndex.set(parseInt(index, 10), data); | ||
} | } | ||
}); | }); | ||
cachedMainSkills = maps; | cachedMainSkills = maps; | ||
debugLog('Main skills map built:', maps); | |||
return maps; | return maps; | ||
} | } | ||
// | // Processa estrutura aninhada do módulo | ||
function processModuleNestedStructure(subs, level = 1) { | |||
if (!Array.isArray(subs) || level > 3) { | |||
debugLog('processModuleNestedStructure: invalid input or max level', { subs, level }); | |||
return subs || []; | |||
} | |||
const processed = subs.map(sub => { | |||
if (!sub || typeof sub !== 'object') return sub; | |||
const result = { ...sub }; | |||
// O módulo usa 'subs' para sub-subskills | |||
if (result.subs && Array.isArray(result.subs)) { | |||
debugLog(`Processing nested subs at level ${level} for:`, result.n || result.name); | |||
result.subs = processModuleNestedStructure(result.subs, level + 1); | |||
result.hasChildren = true; | |||
result.childrenCount = result.subs.length; | |||
} else { | |||
result.hasChildren = false; | |||
} | |||
// Remove campos desnecessários | |||
delete result.subskills; // Se existir do módulo antigo | |||
return result; | |||
}); | |||
debugLog(`Processed level ${level}:`, processed); | |||
return processed; | |||
} | |||
// Versão melhorada da herança | |||
function applyInheritance(sub, mainSkills) { | function applyInheritance(sub, mainSkills) { | ||
let name = (sub.name || sub.n || '').trim(); | let name = (sub.name || sub.n || '').trim(); | ||
| Linha 99: | Linha 126: | ||
let main = null; | let main = null; | ||
if (refIndex && mainSkills.byIndex.has(refIndex)) { | if (refIndex && mainSkills.byIndex.has(refIndex)) { | ||
main = mainSkills.byIndex.get(refIndex); | main = mainSkills.byIndex.get(refIndex); | ||
} | } | ||
if (!main && refIndex) { | if (!main && refIndex) { | ||
const numIndex = parseInt(refIndex, 10); | const numIndex = parseInt(refIndex, 10); | ||
| Linha 114: | Linha 139: | ||
} | } | ||
if (!main) { | if (!main) { | ||
debugLog('No inheritance for:', { name, refIndex }); | |||
return sub; | return sub; | ||
} | } | ||
if (!name && refIndex && main.name) { | if (!name && refIndex && main.name) { | ||
name = main.name; | name = main.name; | ||
} | } | ||
const hasValue = (val) => { | const hasValue = (val) => { | ||
if (val === undefined || val === null) return false; | if (val === undefined || val === null) return false; | ||
if (typeof val === 'number') return !isNaN(val); | if (typeof val === 'number') return !isNaN(val); | ||
const str = String(val).trim(); | const str = String(val).trim(); | ||
return str !== '' && str !== 'NaN'; | return str !== '' && str !== 'NaN'; | ||
}; | }; | ||
// Vídeo | // Vídeo: se existe no sub (mesmo que vazio), usa; senão, não herda | ||
const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, 'video'); | const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, 'video'); | ||
const finalVideo = hasOwnVideo ? (sub.video || '') : ''; | const finalVideo = hasOwnVideo ? (sub.video || '') : ''; | ||
const inherited = { | |||
...sub, | ...sub, | ||
name: name || main.name || sub.name, | name: name || main.name || sub.name, | ||
icon: (sub.icon && sub.icon !== 'Nada.png' && sub.icon !== '') ? sub.icon : (main.icon || 'Nada.png'), | icon: (sub.icon && sub.icon !== 'Nada.png' && sub.icon !== '') ? sub.icon : (main.icon || 'Nada.png'), | ||
level: hasValue(sub.level) ? sub.level : main.level, | level: hasValue(sub.level) ? sub.level : main.level, | ||
video: finalVideo, | video: finalVideo, | ||
powerpve: (sub.powerpve !== undefined && sub.powerpve !== null) ? sub.powerpve : main.powerpve, | powerpve: (sub.powerpve !== undefined && sub.powerpve !== null) ? sub.powerpve : main.powerpve, | ||
powerpvp: (sub.powerpvp !== undefined && sub.powerpvp !== null) ? sub.powerpvp : main.powerpvp, | powerpvp: (sub.powerpvp !== undefined && sub.powerpvp !== null) ? sub.powerpvp : main.powerpvp, | ||
| Linha 154: | Linha 174: | ||
descPl: sub.descPl || (sub.desc_i18n?.pl) || main.descPl | descPl: sub.descPl || (sub.desc_i18n?.pl) || main.descPl | ||
}; | }; | ||
// Processa sub-subskills recursivamente | |||
if (sub.subs && Array.isArray(sub.subs)) { | |||
inherited.subs = sub.subs.map(nested => applyInheritance(nested, mainSkills)); | |||
inherited.hasChildren = true; | |||
inherited.childrenCount = inherited.subs.length; | |||
} | |||
debugLog('Inheritance applied:', { original: sub.name || sub.n, inherited: inherited.name }); | |||
return inherited; | |||
} | |||
// ===== RENDERIZAÇÃO DE NÍVEIS ANINHADOS ===== | |||
function renderNestedSubskills(nestedSubs, parentElement, parentName, parentIdx) { | |||
debugLog('renderNestedSubskills called:', { | |||
parentName, | |||
nestedCount: nestedSubs?.length, | |||
parentElement | |||
}); | |||
// Remove existente | |||
const existing = parentElement.querySelector('.subskills-nested'); | |||
if (existing) existing.remove(); | |||
if (!nestedSubs || !Array.isArray(nestedSubs) || nestedSubs.length === 0) { | |||
debugLog('No nested subs to render'); | |||
return; | |||
} | |||
// Cria container | |||
const nestedContainer = document.createElement('div'); | |||
nestedContainer.className = 'subskills-nested'; | |||
nestedContainer.dataset.parent = parentName; | |||
nestedContainer.dataset.level = '2'; | |||
nestedContainer.dataset.parentIdx = parentIdx; | |||
// Estilos | |||
Object.assign(nestedContainer.style, { | |||
position: 'absolute', | |||
top: '100%', | |||
left: '50%', | |||
transform: 'translateX(-50%)', | |||
zIndex: '1000', | |||
background: 'rgba(0, 0, 0, 0.95)', | |||
borderRadius: '8px', | |||
padding: '8px', | |||
display: 'flex', | |||
gap: '6px', | |||
flexWrap: 'wrap', | |||
minWidth: '200px', | |||
boxShadow: '0 4px 20px rgba(0,0,0,0.7)', | |||
border: '1px solid rgba(255, 217, 90, 0.4)', | |||
marginTop: '5px' | |||
}); | |||
// Herança das skills principais | |||
const mainSkills = getMainSkillsMap(); | |||
// Processa cada sub-subskill | |||
nestedSubs.forEach((sub, index) => { | |||
const inheritedSub = applyInheritance(sub, mainSkills); | |||
const subName = inheritedSub.name || inheritedSub.n || ''; | |||
const hasNested = inheritedSub.subs && Array.isArray(inheritedSub.subs) && inheritedSub.subs.length > 0; | |||
// Cria elemento | |||
const nestedItem = document.createElement('div'); | |||
nestedItem.className = 'subicon nested-subicon'; | |||
nestedItem.title = subName; | |||
nestedItem.dataset.parent = parentName; | |||
nestedItem.dataset.name = subName; | |||
nestedItem.dataset.index = index; | |||
nestedItem.dataset.subskillData = JSON.stringify(inheritedSub); | |||
// Ícone | |||
const img = document.createElement('img'); | |||
img.src = filePathURL(inheritedSub.icon || 'Nada.png'); | |||
img.alt = ''; | |||
img.width = 36; | |||
img.height = 36; | |||
img.decoding = 'async'; | |||
img.loading = 'lazy'; | |||
nestedItem.appendChild(img); | |||
// Indicador se tiver mais níveis | |||
if (hasNested) { | |||
const indicator = document.createElement('div'); | |||
indicator.className = 'subskill-indicator nested-indicator'; | |||
indicator.innerHTML = '▶'; | |||
nestedItem.appendChild(indicator); | |||
nestedItem.classList.add('has-nested-children'); | |||
} | |||
// Evento de clique | |||
nestedItem.addEventListener('click', function(e) { | |||
e.stopPropagation(); | |||
e.preventDefault(); | |||
const clickedData = JSON.parse(this.dataset.subskillData || '{}'); | |||
const L = getLabels(); | |||
const subName = clickedData.name || clickedData.n || ''; | |||
debugLog('Nested sub clicked:', { subName, hasNested }); | |||
// Atualiza descrição/vídeo | |||
updateSkillDisplay(clickedData); | |||
// Marca como ativa | |||
nestedContainer.querySelectorAll('.nested-subicon').forEach(el => { | |||
el.classList.remove('active'); | |||
}); | |||
this.classList.add('active'); | |||
// Se tiver sub-sub-subskills, renderiza terceiro nível | |||
if (hasNested && clickedData.subs) { | |||
renderThirdLevelSubskills(clickedData.subs, this, subName); | |||
} else { | |||
const thirdLevel = this.querySelector('.subskills-third-level'); | |||
if (thirdLevel) thirdLevel.remove(); | |||
} | |||
}); | |||
nestedContainer.appendChild(nestedItem); | |||
}); | |||
// Adiciona ao DOM | |||
parentElement.style.position = 'relative'; | |||
parentElement.appendChild(nestedContainer); | |||
// Fecha ao clicar fora | |||
setTimeout(() => { | |||
const closeHandler = (e) => { | |||
if (!parentElement.contains(e.target) && !nestedContainer.contains(e.target)) { | |||
nestedContainer.remove(); | |||
document.removeEventListener('click', closeHandler); | |||
} | |||
}; | |||
document.addEventListener('click', closeHandler); | |||
}, 10); | |||
debugLog('Nested subskills rendered:', nestedContainer.children.length); | |||
} | |||
// Terceiro nível (sub-sub-subskills) | |||
function renderThirdLevelSubskills(thirdLevelSubs, parentElement, parentName) { | |||
const existing = parentElement.querySelector('.subskills-third-level'); | |||
if (existing) existing.remove(); | |||
if (!thirdLevelSubs || !Array.isArray(thirdLevelSubs) || thirdLevelSubs.length === 0) { | |||
return; | |||
} | |||
const thirdContainer = document.createElement('div'); | |||
thirdContainer.className = 'subskills-third-level'; | |||
thirdContainer.dataset.parent = parentName; | |||
thirdContainer.dataset.level = '3'; | |||
Object.assign(thirdContainer.style, { | |||
position: 'absolute', | |||
top: '0', | |||
left: '100%', | |||
marginLeft: '5px', | |||
zIndex: '1001', | |||
background: 'rgba(0, 0, 0, 0.98)', | |||
borderRadius: '8px', | |||
padding: '6px', | |||
display: 'flex', | |||
flexDirection: 'column', | |||
gap: '4px', | |||
minWidth: '180px', | |||
boxShadow: '0 4px 20px rgba(0,0,0,0.8)', | |||
border: '1px solid rgba(255, 217, 90, 0.3)' | |||
}); | |||
const mainSkills = getMainSkillsMap(); | |||
thirdLevelSubs.forEach((sub, index) => { | |||
const inheritedSub = applyInheritance(sub, mainSkills); | |||
const subName = inheritedSub.name || inheritedSub.n || ''; | |||
const thirdItem = document.createElement('div'); | |||
thirdItem.className = 'subicon third-level-subicon'; | |||
thirdItem.title = subName; | |||
thirdItem.dataset.parent = parentName; | |||
thirdItem.dataset.name = subName; | |||
thirdItem.dataset.subskillData = JSON.stringify(inheritedSub); | |||
Object.assign(thirdItem.style, { | |||
display: 'flex', | |||
alignItems: 'center', | |||
gap: '8px', | |||
padding: '4px 8px', | |||
borderRadius: '4px', | |||
cursor: 'pointer', | |||
transition: 'background 0.2s' | |||
}); | |||
// Ícone pequeno | |||
const img = document.createElement('img'); | |||
img.src = filePathURL(inheritedSub.icon || 'Nada.png'); | |||
img.alt = ''; | |||
img.width = 24; | |||
img.height = 24; | |||
img.style.borderRadius = '4px'; | |||
thirdItem.appendChild(img); | |||
// Nome | |||
const nameSpan = document.createElement('span'); | |||
nameSpan.textContent = subName; | |||
nameSpan.style.color = '#fff'; | |||
nameSpan.style.fontSize = '12px'; | |||
thirdItem.appendChild(nameSpan); | |||
// Eventos | |||
thirdItem.addEventListener('mouseenter', () => { | |||
thirdItem.style.background = 'rgba(255, 217, 90, 0.1)'; | |||
}); | |||
thirdItem.addEventListener('mouseleave', () => { | |||
thirdItem.style.background = 'transparent'; | |||
}); | |||
thirdItem.addEventListener('click', function(e) { | |||
e.stopPropagation(); | |||
e.preventDefault(); | |||
const clickedData = JSON.parse(this.dataset.subskillData || '{}'); | |||
const L = getLabels(); | |||
const subName = clickedData.name || clickedData.n || ''; | |||
updateSkillDisplay(clickedData); | |||
thirdContainer.querySelectorAll('.third-level-subicon').forEach(el => { | |||
el.style.background = 'transparent'; | |||
}); | |||
this.style.background = 'rgba(255, 217, 90, 0.2)'; | |||
}); | |||
thirdContainer.appendChild(thirdItem); | |||
}); | |||
parentElement.appendChild(thirdContainer); | |||
} | } | ||
// ===== FUNÇÕES EXISTENTES (modificadas) ===== | |||
function filePathURL(fileName) { | function filePathURL(fileName) { | ||
if (!fileName || fileName.trim() === '' || fileName === 'Nada.png' || fileName.toLowerCase() === 'nada.png') { | if (!fileName || fileName.trim() === '' || fileName === 'Nada.png' || fileName.toLowerCase() === 'nada.png') { | ||
return ''; | return ''; | ||
| Linha 165: | Linha 425: | ||
? mw.util.wikiScript() | ? mw.util.wikiScript() | ||
: (window.mw && mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php'); | : (window.mw && mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php'); | ||
let url = `${base}?title=Especial:FilePath/${f}`; | let url = `${base}?title=Especial:FilePath/${f}`; | ||
if (window.location.protocol === 'https:' && url.startsWith('http://')) { | if (window.location.protocol === 'https:' && url.startsWith('http://')) { | ||
| Linha 216: | Linha 475: | ||
} | } | ||
function isWeaponModeOn() { | function isWeaponModeOn() { | ||
try { | try { | ||
| Linha 225: | Linha 483: | ||
} | } | ||
function getEffectiveAttrs(s) { | function getEffectiveAttrs(s) { | ||
const weaponOn = isWeaponModeOn(); | const weaponOn = isWeaponModeOn(); | ||
| Linha 244: | Linha 501: | ||
} | } | ||
function getEffectiveDesc(s) { | function getEffectiveDesc(s) { | ||
const weaponOn = isWeaponModeOn(); | const weaponOn = isWeaponModeOn(); | ||
| Linha 251: | Linha 506: | ||
const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt'); | const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt'); | ||
if (weaponOn && s.weapon) { | if (weaponOn && s.weapon) { | ||
const wDesc = s.weapon.desc_i18n || s.weapon.desc; | const wDesc = s.weapon.desc_i18n || s.weapon.desc; | ||
| Linha 259: | Linha 513: | ||
} | } | ||
const base = s.desc_i18n || s.desc; | const base = s.desc_i18n || s.desc; | ||
if (base) { | if (base) { | ||
| Linha 265: | Linha 518: | ||
} | } | ||
const descI18n = { | const descI18n = { | ||
pt: s.descPt || '', | pt: s.descPt || '', | ||
| Linha 275: | Linha 527: | ||
} | } | ||
function getEffectiveVideo(s) { | function getEffectiveVideo(s) { | ||
const weaponOn = isWeaponModeOn(); | const weaponOn = isWeaponModeOn(); | ||
| Linha 284: | Linha 535: | ||
} | } | ||
function getOrCreateSubskillVideo(videoURL) { | function getOrCreateSubskillVideo(videoURL) { | ||
if (!videoURL || videoURL.trim() === '') return null; | if (!videoURL || videoURL.trim() === '') return null; | ||
| Linha 291: | Linha 541: | ||
if (!normalizedURL || normalizedURL.trim() === '') return null; | if (!normalizedURL || normalizedURL.trim() === '') return null; | ||
if (subskillVideoElementCache.has(normalizedURL)) { | if (subskillVideoElementCache.has(normalizedURL)) { | ||
logSubVideo('getOrCreateSubskillVideo: cache hit', { videoURL: normalizedURL }); | logSubVideo('getOrCreateSubskillVideo: cache hit', { videoURL: normalizedURL }); | ||
| Linha 297: | Linha 546: | ||
} | } | ||
logSubVideo('getOrCreateSubskillVideo: creating new video', { videoURL: normalizedURL }); | logSubVideo('getOrCreateSubskillVideo: creating new video', { videoURL: normalizedURL }); | ||
| Linha 306: | Linha 554: | ||
v.setAttribute('preload', 'metadata'); | v.setAttribute('preload', 'metadata'); | ||
v.setAttribute('playsinline', ''); | v.setAttribute('playsinline', ''); | ||
v.muted = true; | v.muted = true; | ||
v.style.display = 'none'; | v.style.display = 'none'; | ||
v.style.width = '100%'; | v.style.width = '100%'; | ||
| Linha 313: | Linha 561: | ||
v.style.objectFit = 'cover'; | v.style.objectFit = 'cover'; | ||
const ext = (normalizedURL.split('.').pop() || '').toLowerCase().split('?')[0]; | const ext = (normalizedURL.split('.').pop() || '').toLowerCase().split('?')[0]; | ||
const mimeTypes = { | const mimeTypes = { | ||
| Linha 330: | Linha 577: | ||
v.appendChild(src); | v.appendChild(src); | ||
v.setAttribute('webkit-playsinline', ''); | v.setAttribute('webkit-playsinline', ''); | ||
v.setAttribute('x-webkit-airplay', 'allow'); | v.setAttribute('x-webkit-airplay', 'allow'); | ||
subskillVideoElementCache.set(normalizedURL, v); | subskillVideoElementCache.set(normalizedURL, v); | ||
const videoBox = document.querySelector('.video-container'); | const videoBox = document.querySelector('.video-container'); | ||
if (videoBox && !v.isConnected) { | if (videoBox && !v.isConnected) { | ||
videoBox.appendChild(v); | videoBox.appendChild(v); | ||
v.load(); | v.load(); | ||
} | } | ||
| Linha 447: | Linha 690: | ||
} | } | ||
function showSubVideo(videoURL, videoBox) { | function showSubVideo(videoURL, videoBox) { | ||
logSubVideo('showSubVideo called', { videoURL, videoBoxExists: !!videoBox }); | logSubVideo('showSubVideo called', { videoURL, videoBoxExists: !!videoBox }); | ||
| Linha 453: | Linha 695: | ||
if (!videoBox) return; | if (!videoBox) return; | ||
videoBox.querySelectorAll('video.skill-video[data-sub="1"]').forEach(v => { | videoBox.querySelectorAll('video.skill-video[data-sub="1"]').forEach(v => { | ||
v.style.display = 'none'; | v.style.display = 'none'; | ||
}); | }); | ||
const video = getOrCreateSubskillVideo(videoURL); | const video = getOrCreateSubskillVideo(videoURL); | ||
| Linha 467: | Linha 707: | ||
} | } | ||
if (!video.isConnected || video.parentNode !== videoBox) { | if (!video.isConnected || video.parentNode !== videoBox) { | ||
logSubVideo('video not in DOM, appending', { | logSubVideo('video not in DOM, appending', { | ||
| Linha 487: | Linha 726: | ||
}); | }); | ||
videoBox.style.display = 'block'; | videoBox.style.display = 'block'; | ||
video.style.display = 'block'; | video.style.display = 'block'; | ||
try { | try { | ||
video.currentTime = 0; | video.currentTime = 0; | ||
} catch (e) { } | } catch (e) { } | ||
if (video.readyState >= 2) { | if (video.readyState >= 2) { | ||
video.play().then(() => { | video.play().then(() => { | ||
| Linha 504: | Linha 740: | ||
}); | }); | ||
} else { | } else { | ||
logSubVideo('video not ready, waiting for canplay', { videoURL, readyState: video.readyState }); | logSubVideo('video not ready, waiting for canplay', { videoURL, readyState: video.readyState }); | ||
const onCanPlay = () => { | const onCanPlay = () => { | ||
| Linha 514: | Linha 749: | ||
} | } | ||
function updateSkillDisplay(skillData) { | |||
const L = getLabels(); | |||
const weaponOn = isWeaponModeOn(); | |||
const attrs = getEffectiveAttrs(skillData); | |||
const | const desc = getEffectiveDesc(skillData); | ||
if ( | const videoURL = getEffectiveVideo(skillData); | ||
const descBox = document.querySelector('.skill-description'); | |||
const videoBox = document.querySelector('.video-container'); | |||
if (descBox) { | |||
const level = (skillData.level || '').toString().trim(); | |||
const flagsHTML = skillData.flags ? renderFlagsRow(skillData.flags) : ''; | |||
descBox.innerHTML = ` | |||
<div class="skill-title"><h3>${skillData.name || skillData.n || ''}</h3></div> | |||
${level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>` : ''} | |||
${renderSubAttrs(attrs, L)} | |||
<div class="desc">${desc.replace(/'''(.*?)'''/g, '<b>$1</b>')}</div> | |||
`; | |||
} | } | ||
if (videoBox) { | |||
const oldFlags = videoBox.querySelector('.skill-flags'); | |||
if (oldFlags) oldFlags.remove(); | |||
if (skillData.flags && Array.isArray(skillData.flags) && skillData.flags.length > 0) { | |||
const flagsHTML = renderFlagsRow(skillData.flags); | |||
if (flagsHTML) { | |||
videoBox.insertAdjacentHTML('beforeend', flagsHTML); | |||
applyFlagTooltips(videoBox); | |||
if ( | |||
if ( | |||
} | } | ||
} | |||
if (!videoURL || videoURL.trim() === '') { | |||
videoBox.style.display = 'none'; | |||
} else { | } else { | ||
showSubVideo(videoURL, videoBox); | |||
} | } | ||
} | } | ||
} | } | ||
// ===== API PRINCIPAL ===== | |||
api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) { | api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) { | ||
debugLog('api.renderBarFrom START', { | |||
el: !!el, | |||
parentIndex: el?.dataset?.index || 'unknown' | parentIndex: el?.dataset?.index || 'unknown' | ||
}); | }); | ||
| Linha 562: | Linha 801: | ||
const rail = ensureRail(iconsBar); | const rail = ensureRail(iconsBar); | ||
if (!rail) { | if (!rail) { | ||
debugLog('No rail found'); | |||
return; | return; | ||
} | } | ||
| Linha 570: | Linha 809: | ||
const parentIdx = el.dataset.index || ''; | const parentIdx = el.dataset.index || ''; | ||
debugLog('Raw data:', { rawSubs: rawSubs.substring(0, 100) + '...', rawOrder, parentIdx }); | |||
if (!rawSubs.trim()) { | if (!rawSubs.trim()) { | ||
| Linha 585: | Linha 820: | ||
let subs; | let subs; | ||
try { subs = JSON.parse(rawSubs); } catch { subs = []; } | try { | ||
subs = JSON.parse(rawSubs); | |||
debugLog('Parsed subs (first 2):', subs.slice(0, 2)); | |||
// DEBUG: Verifica estrutura | |||
subs.forEach((sub, i) => { | |||
if (sub.subs || sub.subskills) { | |||
debugLog(`Subskill ${i} ("${sub.n || sub.name}") has nested:`, sub.subs || sub.subskills); | |||
} | |||
}); | |||
} catch(e) { | |||
subs = []; | |||
console.error('[Subskills] JSON parse error:', e, 'Raw:', rawSubs.substring(0, 200)); | |||
} | |||
// | // Normaliza weapon | ||
subs = subs.map(sub => { | subs = subs.map(sub => { | ||
if (sub.weaponPacked && !sub.weapon && typeof sub.weaponPacked === 'string' && sub.weaponPacked.trim() !== '') { | if (sub.weaponPacked && !sub.weapon && typeof sub.weaponPacked === 'string' && sub.weaponPacked.trim() !== '') { | ||
const parts = sub.weaponPacked.split('~'); | const parts = sub.weaponPacked.split('~'); | ||
| Linha 601: | Linha 848: | ||
energy: parts[5] || null | energy: parts[5] || null | ||
}; | }; | ||
Object.keys(sub.weapon).forEach(k => { | Object.keys(sub.weapon).forEach(k => { | ||
if (sub.weapon[k] === '' || sub.weapon[k] === null) { | if (sub.weapon[k] === '' || sub.weapon[k] === null) { | ||
| Linha 609: | Linha 855: | ||
} | } | ||
} | } | ||
if (sub.weapon && typeof sub.weapon === 'string') { | if (sub.weapon && typeof sub.weapon === 'string') { | ||
try { | try { | ||
sub.weapon = JSON.parse(sub.weapon); | sub.weapon = JSON.parse(sub.weapon); | ||
} catch { | } catch { | ||
const parts = sub.weapon.split('~'); | const parts = sub.weapon.split('~'); | ||
if (parts.length >= 2) { | if (parts.length >= 2) { | ||
| Linha 647: | Linha 890: | ||
} | } | ||
// | // Processa estrutura do módulo | ||
const mainSkills = getMainSkillsMap(); | const mainSkills = getMainSkillsMap(); | ||
// | // Passo 1: Aplica herança | ||
subs = subs.map(sub => applyInheritance(sub, mainSkills)); | subs = subs.map(sub => applyInheritance(sub, mainSkills)); | ||
// | // Passo 2: Processa estrutura aninhada | ||
subs = subs | subs = processModuleNestedStructure(subs, 1); | ||
debugLog('Final processed subs:', subs); | |||
subRail.classList.add('hidden'); | subRail.classList.add('hidden'); | ||
subBar.innerHTML = ''; | subBar.innerHTML = ''; | ||
let order = subs.map(s => s.name || s.n || ''); | let order = subs.map(s => s.name || s.n || ''); | ||
if (rawOrder.trim()) { | if (rawOrder.trim()) { | ||
| Linha 680: | Linha 915: | ||
} | } | ||
// Pré-carrega | // Pré-carrega ícones | ||
const iconPreloadPromises = []; | const iconPreloadPromises = []; | ||
order.forEach(nm => { | order.forEach(nm => { | ||
| Linha 689: | Linha 924: | ||
}); | }); | ||
// Função de renderização | |||
// Função | |||
const renderSubskillsBar = () => { | const renderSubskillsBar = () => { | ||
order.forEach(nm => { | order.forEach(nm => { | ||
| Linha 705: | Linha 938: | ||
const slugify = window.__skillSlugify || ((str) => (str || '').toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, '')); | const slugify = window.__skillSlugify || ((str) => (str || '').toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, '')); | ||
item.dataset.slug = slugify(s.name || nm); | item.dataset.slug = slugify(s.name || nm); | ||
item.dataset.subskillData = JSON.stringify(s); | |||
debugLog('Creating subicon:', { | |||
name: s.name || s.n, | |||
hasChildren: s.hasChildren, | |||
childrenCount: s.childrenCount | |||
}); | }); | ||
| Linha 724: | Linha 958: | ||
item.appendChild(img); | item.appendChild(img); | ||
// | // Adiciona indicador se tiver sub-subskills | ||
if (s.hasChildren) { | |||
const indicator = document.createElement('div'); | |||
indicator.className = 'subskill-indicator'; | |||
indicator.innerHTML = '▼'; | |||
indicator.title = `Tem ${s.childrenCount || '?'} sub-habilidade(s)`; | |||
item.appendChild(indicator); | |||
item.classList.add('has-children'); | |||
debugLog('Added children indicator for:', s.name || s.n); | |||
} | |||
// Weapon | |||
const hasWeapon = s.weapon && ( | const hasWeapon = s.weapon && ( | ||
(typeof s.weapon === 'object' && Object.keys(s.weapon).length > 0) || | (typeof s.weapon === 'object' && Object.keys(s.weapon).length > 0) || | ||
| Linha 733: | Linha 978: | ||
if (hasWeapon) { | if (hasWeapon) { | ||
let weaponObj = s.weapon; | let weaponObj = s.weapon; | ||
if (typeof weaponObj === 'string') { | if (typeof weaponObj === 'string') { | ||
| Linha 739: | Linha 983: | ||
weaponObj = JSON.parse(weaponObj); | weaponObj = JSON.parse(weaponObj); | ||
} catch { | } catch { | ||
const parts = weaponObj.split('~'); | const parts = weaponObj.split('~'); | ||
if (parts.length >= 2) { | if (parts.length >= 2) { | ||
| Linha 765: | Linha 1 008: | ||
item.dataset.weapon = JSON.stringify(weaponObj); | item.dataset.weapon = JSON.stringify(weaponObj); | ||
} catch (e) { | } catch (e) { | ||
console.error('[Subskills] Erro ao serializar weapon | console.error('[Subskills] Erro ao serializar weapon:', e); | ||
} | } | ||
if (isWeaponModeOn()) { | if (isWeaponModeOn()) { | ||
item.classList.add('has-weapon-available'); | item.classList.add('has-weapon-available'); | ||
| Linha 775: | Linha 1 017: | ||
} | } | ||
// | // Evento de clique | ||
item.addEventListener('click', (e) => { | |||
if (e.target.closest('.subskills-nested') || e.target.closest('.subskills-third-level')) { | |||
return; | |||
} | |||
const L = getLabels(); | const L = getLabels(); | ||
const subName = (s.name || s.n || '').trim(); | const subName = (s.name || s.n || '').trim(); | ||
debugLog('Subicon clicked:', { | |||
subName, | subName, | ||
hasChildren: s.hasChildren, | |||
hasNestedSubs: !!(s.subs && Array.isArray(s.subs) && s.subs.length > 0) | |||
}); | }); | ||
// | // Weapon logic | ||
let subWeaponData = null; | let subWeaponData = null; | ||
if (item.dataset.weapon) { | if (item.dataset.weapon) { | ||
| Linha 803: | Linha 1 042: | ||
} | } | ||
const hasSubWeapon = !!subWeaponData; | const hasSubWeapon = !!subWeaponData; | ||
const weaponOn = isWeaponModeOn(); | const weaponOn = isWeaponModeOn(); | ||
const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData; | const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData; | ||
// Descrição | |||
// | |||
const raw = (document.documentElement.lang || 'pt').toLowerCase(); | const raw = (document.documentElement.lang || 'pt').toLowerCase(); | ||
const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt'); | const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt'); | ||
| Linha 819: | Linha 1 051: | ||
let chosen = ''; | let chosen = ''; | ||
if (weaponEquipped && subWeaponData) { | if (weaponEquipped && subWeaponData) { | ||
const weaponDescPack = subWeaponData.desc_i18n || subWeaponData.desc || {}; | const weaponDescPack = subWeaponData.desc_i18n || subWeaponData.desc || {}; | ||
chosen = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || ''; | chosen = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || ''; | ||
} else { | } else { | ||
const base = s.desc_i18n || s.desc || {}; | const base = s.desc_i18n || s.desc || {}; | ||
chosen = base[lang] || base.pt || base.en || (s.descPt || ''); | chosen = base[lang] || base.pt || base.en || (s.descPt || ''); | ||
} | } | ||
// | // Atributos | ||
let attrsObj = { | let attrsObj = { | ||
powerpve: s.powerpve, | powerpve: s.powerpve, | ||
| Linha 844: | Linha 1 074: | ||
} | } | ||
// Level | // Level | ||
const level = (weaponEquipped && subWeaponData && subWeaponData.level) | const level = (weaponEquipped && subWeaponData && subWeaponData.level) | ||
? subWeaponData.level.toString().trim() | ? subWeaponData.level.toString().trim() | ||
| Linha 854: | Linha 1 084: | ||
} | } | ||
// Atualiza descrição | |||
if (descBox) { | if (descBox) { | ||
descBox.innerHTML = `<div class="skill-title"><h3>${s.name || nm}</h3></div>${level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>` : ''}${renderSubAttrs(attrsObj, L)}<div class="desc">${chosen.replace(/'''(.*?)'''/g, '<b>$1</b>')}</div>`; | descBox.innerHTML = `<div class="skill-title"><h3>${s.name || nm}</h3></div>${level ? `<div class="skill-level-line"><span class="attr-label">${L.level} ${level}</span></div>` : ''}${renderSubAttrs(attrsObj, L)}<div class="desc">${chosen.replace(/'''(.*?)'''/g, '<b>$1</b>')}</div>`; | ||
| Linha 867: | Linha 1 098: | ||
} | } | ||
// Vídeo | // Vídeo | ||
const effectiveVideo = getEffectiveVideo(s); | const effectiveVideo = getEffectiveVideo(s); | ||
if (!effectiveVideo || effectiveVideo.trim() === '' || effectiveVideo === 'Nada.png' || effectiveVideo.toLowerCase().includes('nada.png')) { | if (!effectiveVideo || effectiveVideo.trim() === '' || effectiveVideo === 'Nada.png' || effectiveVideo.toLowerCase().includes('nada.png')) { | ||
if (videoBox) videoBox.style.display = 'none'; | if (videoBox) videoBox.style.display = 'none'; | ||
} else { | } else { | ||
showSubVideo(effectiveVideo, videoBox); | showSubVideo(effectiveVideo, videoBox); | ||
} | } | ||
// Marca como ativa e limpa outras | |||
Array.from(subBar.children).forEach(c => { | Array.from(subBar.children).forEach(c => { | ||
c.classList.remove('active'); | c.classList.remove('active'); | ||
c.classList.remove('weapon-equipped'); | c.classList.remove('weapon-equipped'); | ||
const nested = c.querySelector('.subskills-nested'); | |||
if (nested) nested.remove(); | |||
const thirdLevel = c.querySelector('.subskills-third-level'); | |||
if (thirdLevel) thirdLevel.remove(); | |||
}); | }); | ||
item.classList.add('active'); | item.classList.add('active'); | ||
if (weaponEquipped) { | if (weaponEquipped) { | ||
item.classList.add('weapon-equipped'); | item.classList.add('weapon-equipped'); | ||
} | } | ||
window.__lastActiveSkillIcon = item; | window.__lastActiveSkillIcon = item; | ||
// LÓGICA PARA SUB-SUBSKILLS | |||
if (s.hasChildren && s.subs && Array.isArray(s.subs) && s.subs.length > 0) { | |||
debugLog('Rendering nested subs for:', subName); | |||
renderNestedSubskills(s.subs, item, subName, parentIdx); | |||
} else { | |||
const nested = item.querySelector('.subskills-nested'); | |||
if (nested) nested.remove(); | |||
const thirdLevel = item.querySelector('.subskills-third-level'); | |||
if (thirdLevel) thirdLevel.remove(); | |||
} | |||
}); | }); | ||
| Linha 913: | Linha 1 148: | ||
subBar.appendChild(item); | subBar.appendChild(item); | ||
}); | |||
// Weapon classes | |||
const applyWeaponClassesToSubskills = () => { | |||
const weaponOn = isWeaponModeOn(); | |||
let weaponSubs = document.querySelectorAll('.subicon[data-weapon]'); | |||
weaponSubs.forEach(el => { | |||
if (weaponOn) { | |||
el.classList.add('has-weapon-available'); | |||
if (el.classList.contains('active')) { | |||
el.classList.add('weapon-equipped'); | |||
} | |||
} else { | |||
el.classList.remove('has-weapon-available'); | |||
el.classList.remove('weapon-equipped'); | |||
} | |||
}); | }); | ||
} | }; | ||
applyWeaponClassesToSubskills(); | applyWeaponClassesToSubskills(); | ||
window.dispatchEvent(new CustomEvent('gla:subskills:ready', { detail: { count: order.length } })); | window.dispatchEvent(new CustomEvent('gla:subskills:ready', { detail: { count: order.length } })); | ||
if (subBar._weaponToggleListener) { | if (subBar._weaponToggleListener) { | ||
window.removeEventListener('gla:weaponToggled', subBar._weaponToggleListener); | window.removeEventListener('gla:weaponToggled', subBar._weaponToggleListener); | ||
} | } | ||
subBar._weaponToggleListener = (e) => { | subBar._weaponToggleListener = (e) => { | ||
const enabled = e.detail?.enabled ?? false; | const enabled = e.detail?.enabled ?? false; | ||
applyWeaponClassesToSubskills(); | applyWeaponClassesToSubskills(); | ||
setTimeout(() => { | setTimeout(() => { | ||
const activeSub = subBar.querySelector('.subicon[data-weapon].active') | const activeSub = subBar.querySelector('.subicon[data-weapon].active') | ||
| Linha 960: | Linha 1 201: | ||
}; | }; | ||
// | // Renderiza | ||
Promise.all(iconPreloadPromises).then(() => { | Promise.all(iconPreloadPromises).then(() => { | ||
setTimeout(() => { | setTimeout(() => { | ||
| Linha 970: | Linha 1 211: | ||
}, 10); | }, 10); | ||
}); | }); | ||
}; | |||
api.refreshCurrentSubSafe = function () { | |||
const btn = document.querySelector('.subskills-rail .subicon.active'); | |||
if (!btn) return false; | |||
const had = document.body.dataset.suppressSkillPlay; | |||
document.body.dataset.suppressSkillPlay = '1'; | |||
try { | |||
btn.dispatchEvent(new Event('click', { bubbles: true })); | |||
} finally { | |||
if (had) document.body.dataset.suppressSkillPlay = had; | |||
else delete document.body.dataset.suppressSkillPlay; | |||
} | |||
return true; | |||
}; | }; | ||
| Linha 983: | Linha 1 238: | ||
window.renderSubskillsBarFrom = function (el, ctx) { api.renderBarFrom(el, ctx); }; | window.renderSubskillsBarFrom = function (el, ctx) { api.renderBarFrom(el, ctx); }; | ||
// ===== INICIALIZAÇÃO ===== | |||
function init() { | function init() { | ||
debugLog('Initializing subskills system'); | |||
getMainSkillsMap(); | getMainSkillsMap(); | ||
// Escuta mudanças no localStorage | // Escuta mudanças no localStorage | ||
| Linha 1 052: | Linha 1 250: | ||
}); | }); | ||
window.addEventListener('gla:weaponToggled', (e) => { | window.addEventListener('gla:weaponToggled', (e) => { | ||
const enabled = e.detail?.enabled ?? false; | const enabled = e.detail?.enabled ?? false; | ||
setTimeout(() => { | setTimeout(() => { | ||
const activeSub = document.querySelector('.subicon[data-weapon].active') | const activeSub = document.querySelector('.subicon[data-weapon].active') | ||
| Linha 1 071: | Linha 1 263: | ||
}); | }); | ||
window.addEventListener('gla:subskills:ready', (e) => { | window.addEventListener('gla:subskills:ready', (e) => { | ||
debugLog('Subskills ready:', e.detail); | |||
}); | }); | ||
} | } | ||
| Linha 1 088: | Linha 1 278: | ||
</script> | </script> | ||
<style> | <style> | ||
/* ESTILOS EXISTENTES */ | |||
.subicon-bar { | .subicon-bar { | ||
display: flex; | display: flex; | ||
| Linha 1 093: | Linha 1 284: | ||
padding: 6px 6px; | padding: 6px 6px; | ||
overflow-x: auto; | overflow-x: auto; | ||
scrollbar-width: thin; | scrollbar-width: thin; | ||
scrollbar-color: #ababab transparent; | scrollbar-color: #ababab transparent; | ||
| Linha 1 116: | Linha 1 306: | ||
cursor: pointer; | cursor: pointer; | ||
isolation: isolate; | isolation: isolate; | ||
transition: transform 0.2s ease; | |||
} | } | ||
| Linha 1 161: | Linha 1 352: | ||
box-shadow: 0 0 12px 3px var(--icon-active-glow, rgba(255, 217, 90, .30)), | box-shadow: 0 0 12px 3px var(--icon-active-glow, rgba(255, 217, 90, .30)), | ||
0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, .50)); | 0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, .50)); | ||
} | } | ||
| Linha 1 173: | Linha 1 360: | ||
align-items: center; | align-items: center; | ||
overflow: visible; | overflow: visible; | ||
} | } | ||
| Linha 1 203: | Linha 1 384: | ||
transition: opacity .14s ease, transform .14s ease; | transition: opacity .14s ease, transform .14s ease; | ||
opacity: 1; | opacity: 1; | ||
} | } | ||
| Linha 1 231: | Linha 1 401: | ||
} | } | ||
/* NOVOS ESTILOS PARA SUB-SUBSKILLS */ | |||
.subicon.has-children { | |||
position: relative; | |||
} | } | ||
. | .subskill-indicator { | ||
position: absolute; | |||
bottom: -3px; | |||
right: -3px; | |||
background: var(--icon-active, #FFD95A); | |||
color: #000; | |||
font-size: 9px; | |||
width: 14px; | |||
height: 14px; | |||
border-radius: 50%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
font-weight: bold; | |||
border: 1px solid #000; | |||
z-index: 10; | |||
} | } | ||
. | .nested-indicator { | ||
background: #FF7043 !important; | |||
font-size: 8px; | |||
width: 12px; | |||
height: 12px; | |||
} | } | ||
.subskills- | .subskills-nested { | ||
animation: fadeIn 0.2s ease; | |||
z-index: 1000; | |||
} | } | ||
.subskills- | .subskills-third-level { | ||
animation: fadeIn 0.2s ease; | |||
z-index: 1001; | |||
} | } | ||
. | .nested-subicon { | ||
width: 36px !important; | |||
height: 36px !important; | |||
border-radius: 6px !important; | |||
border-radius: | position: relative; | ||
} | } | ||
. | .nested-subicon.active::after { | ||
box-shadow: inset 0 0 0 2px # | box-shadow: inset 0 0 0 2px var(--icon-active, #FFD95A) !important; | ||
} | } | ||
. | .third-level-subicon:hover { | ||
background: rgba(255, 217, 90, 0.15) !important; | |||
} | } | ||
@keyframes fadeIn { | |||
from { opacity: 0; transform: translateY(-5px); } | |||
to { opacity: 1; transform: translateY(0); } | |||
} | } | ||
/* Responsivo */ | |||
@media (max-width: 900px) { | @media (max-width: 900px) { | ||
.subskills-rail { | .subskills-rail { | ||
| Linha 1 316: | Linha 1 473: | ||
.subskills-spacer { | .subskills-spacer { | ||
height: 0 !important; | height: 0 !important; | ||
} | |||
.subskills-nested, | |||
.subskills-third-level { | |||
position: fixed !important; | |||
top: 50% !important; | |||
left: 50% !important; | |||
transform: translate(-50%, -50%) !important; | |||
max-width: 90vw !important; | |||
z-index: 9999 !important; | |||
} | } | ||
} | } | ||
/* Weapon styles (mantidos do original) */ | |||
/* | |||
.character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after { | .character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after { | ||
box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important; | box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important; | ||
} | } | ||
@keyframes weapon-icon-border-scan { | @keyframes weapon-icon-border-scan { | ||
0% { | 0% { background-position: 0% 0%; } | ||
100% { background-position: 400% 0%; } | |||
100% { | |||
} | } | ||
@keyframes weapon-subicon-border-scan { | @keyframes weapon-subicon-border-scan { | ||
0% { | 0% { background-position: 0% 0%; } | ||
100% { background-position: 400% 0%; } | |||
100% { | |||
} | } | ||
</style> | </style> | ||
Edição das 17h55min de 29 de dezembro de 2025
<script>
(function () {
const SUBVIDEO_DEBUG = true; // Ativado para debug
function logSubVideo(...args) {
if (!SUBVIDEO_DEBUG) return;
console.log('[SubVideo]', ...args);
}
const api = (window.__subskills ||= {});
const subskillVideoElementCache = new Map();
const imagePreloadCache = new Map();
let subRail, subBar, spacer;
let cachedMainSkills = null;
// ===== DEBUG ESTRUTURA =====
const DEBUG = true;
function debugLog(...args) {
if (DEBUG) console.log('[Subskills]', ...args);
}
// ===== FUNÇÕES AUXILIARES =====
function getMainSkillsMap() {
if (cachedMainSkills && cachedMainSkills.byIndex.size > 0) {
return cachedMainSkills;
}
const maps = {
byName: new Map(),
byIndex: new Map()
};
const icons = document.querySelectorAll('.icon-bar .skill-icon[data-index][data-nome]');
icons.forEach(icon => {
const name = (icon.dataset.nome || ).trim();
if (!name) return;
const atrRaw = icon.dataset.atr || ;
const parts = atrRaw.split(',').map(x => (x || ).trim());
const powerpve = parts[0] && parts[0] !== '-' ? parts[0] : ;
const powerpvp = parts[1] && parts[1] !== '-' ? parts[1] : ;
const energy = parts[2] && parts[2] !== '-' ? parts[2] : ;
const cooldown = parts[3] && parts[3] !== '-' ? parts[3] : ;
let iconFile = (icon.dataset.iconFile || ).trim();
if (!iconFile) {
const imgSrc = icon.querySelector('img')?.src || ;
const iconMatch = imgSrc.match(/(?:FilePath|images)\/([^\/?]+)$/);
iconFile = iconMatch ? decodeURIComponent(iconMatch[1]) : ;
}
let videoFile = (icon.dataset.videoFile || ).trim();
if (!videoFile) {
const videoUrl = icon.dataset.video || ;
const videoMatch = videoUrl.match(/FilePath\/([^&?]+)/);
videoFile = videoMatch ? decodeURIComponent(videoMatch[1]) : ;
}
const index = (icon.dataset.index || ).trim();
const data = {
name: name,
icon: iconFile,
level: icon.dataset.level || ,
video: videoFile,
powerpve: powerpve,
powerpvp: powerpvp,
cooldown: cooldown,
energy: energy
};
if (icon.dataset.descPt) data.descPt = icon.dataset.descPt;
if (icon.dataset.descEn) data.descEn = icon.dataset.descEn;
if (icon.dataset.descEs) data.descEs = icon.dataset.descEs;
if (icon.dataset.descPl) data.descPl = icon.dataset.descPl;
maps.byName.set(name, data);
if (index) {
maps.byIndex.set(index, data);
maps.byIndex.set(parseInt(index, 10), data);
}
});
cachedMainSkills = maps;
debugLog('Main skills map built:', maps);
return maps;
}
// Processa estrutura aninhada do módulo
function processModuleNestedStructure(subs, level = 1) {
if (!Array.isArray(subs) || level > 3) {
debugLog('processModuleNestedStructure: invalid input or max level', { subs, level });
return subs || [];
}
const processed = subs.map(sub => {
if (!sub || typeof sub !== 'object') return sub;
const result = { ...sub };
// O módulo usa 'subs' para sub-subskills
if (result.subs && Array.isArray(result.subs)) {
debugLog(`Processing nested subs at level ${level} for:`, result.n || result.name);
result.subs = processModuleNestedStructure(result.subs, level + 1);
result.hasChildren = true;
result.childrenCount = result.subs.length;
} else {
result.hasChildren = false;
}
// Remove campos desnecessários
delete result.subskills; // Se existir do módulo antigo
return result;
});
debugLog(`Processed level ${level}:`, processed);
return processed;
}
// Versão melhorada da herança
function applyInheritance(sub, mainSkills) {
let name = (sub.name || sub.n || ).trim();
const refIndex = ((sub.refM || sub.m || sub.M || ) + ).trim();
let main = null;
if (refIndex && mainSkills.byIndex.has(refIndex)) {
main = mainSkills.byIndex.get(refIndex);
}
if (!main && refIndex) {
const numIndex = parseInt(refIndex, 10);
if (!isNaN(numIndex) && mainSkills.byIndex.has(numIndex)) {
main = mainSkills.byIndex.get(numIndex);
}
}
if (!main && name && mainSkills.byName.has(name)) {
main = mainSkills.byName.get(name);
}
if (!main) {
debugLog('No inheritance for:', { name, refIndex });
return sub;
}
if (!name && refIndex && main.name) {
name = main.name;
}
const hasValue = (val) => {
if (val === undefined || val === null) return false;
if (typeof val === 'number') return !isNaN(val);
const str = String(val).trim();
return str !== && str !== 'NaN';
};
// Vídeo: se existe no sub (mesmo que vazio), usa; senão, não herda
const hasOwnVideo = Object.prototype.hasOwnProperty.call(sub, 'video');
const finalVideo = hasOwnVideo ? (sub.video || ) : ;
const inherited = {
...sub,
name: name || main.name || sub.name,
icon: (sub.icon && sub.icon !== 'Nada.png' && sub.icon !== ) ? sub.icon : (main.icon || 'Nada.png'),
level: hasValue(sub.level) ? sub.level : main.level,
video: finalVideo,
powerpve: (sub.powerpve !== undefined && sub.powerpve !== null) ? sub.powerpve : main.powerpve,
powerpvp: (sub.powerpvp !== undefined && sub.powerpvp !== null) ? sub.powerpvp : main.powerpvp,
cooldown: (sub.cooldown !== undefined && sub.cooldown !== null) ? sub.cooldown : main.cooldown,
energy: (sub.energy !== undefined && sub.energy !== null) ? sub.energy : main.energy,
descPt: sub.descPt || (sub.desc_i18n?.pt) || main.descPt,
descEn: sub.descEn || (sub.desc_i18n?.en) || main.descEn,
descEs: sub.descEs || (sub.desc_i18n?.es) || main.descEs,
descPl: sub.descPl || (sub.desc_i18n?.pl) || main.descPl
};
// Processa sub-subskills recursivamente
if (sub.subs && Array.isArray(sub.subs)) {
inherited.subs = sub.subs.map(nested => applyInheritance(nested, mainSkills));
inherited.hasChildren = true;
inherited.childrenCount = inherited.subs.length;
}
debugLog('Inheritance applied:', { original: sub.name || sub.n, inherited: inherited.name });
return inherited;
}
// ===== RENDERIZAÇÃO DE NÍVEIS ANINHADOS =====
function renderNestedSubskills(nestedSubs, parentElement, parentName, parentIdx) {
debugLog('renderNestedSubskills called:', {
parentName,
nestedCount: nestedSubs?.length,
parentElement
});
// Remove existente
const existing = parentElement.querySelector('.subskills-nested');
if (existing) existing.remove();
if (!nestedSubs || !Array.isArray(nestedSubs) || nestedSubs.length === 0) {
debugLog('No nested subs to render');
return;
}
// Cria container
const nestedContainer = document.createElement('div');
nestedContainer.className = 'subskills-nested';
nestedContainer.dataset.parent = parentName;
nestedContainer.dataset.level = '2';
nestedContainer.dataset.parentIdx = parentIdx;
// Estilos
Object.assign(nestedContainer.style, {
position: 'absolute',
top: '100%',
left: '50%',
transform: 'translateX(-50%)',
zIndex: '1000',
background: 'rgba(0, 0, 0, 0.95)',
borderRadius: '8px',
padding: '8px',
display: 'flex',
gap: '6px',
flexWrap: 'wrap',
minWidth: '200px',
boxShadow: '0 4px 20px rgba(0,0,0,0.7)',
border: '1px solid rgba(255, 217, 90, 0.4)',
marginTop: '5px'
});
// Herança das skills principais
const mainSkills = getMainSkillsMap();
// Processa cada sub-subskill
nestedSubs.forEach((sub, index) => {
const inheritedSub = applyInheritance(sub, mainSkills);
const subName = inheritedSub.name || inheritedSub.n || ;
const hasNested = inheritedSub.subs && Array.isArray(inheritedSub.subs) && inheritedSub.subs.length > 0;
// Cria elemento
const nestedItem = document.createElement('div');
nestedItem.className = 'subicon nested-subicon';
nestedItem.title = subName;
nestedItem.dataset.parent = parentName;
nestedItem.dataset.name = subName;
nestedItem.dataset.index = index;
nestedItem.dataset.subskillData = JSON.stringify(inheritedSub);
// Ícone
const img = document.createElement('img');
img.src = filePathURL(inheritedSub.icon || 'Nada.png');
img.alt = ;
img.width = 36;
img.height = 36;
img.decoding = 'async';
img.loading = 'lazy';
nestedItem.appendChild(img);
// Indicador se tiver mais níveis
if (hasNested) {
const indicator = document.createElement('div');
indicator.className = 'subskill-indicator nested-indicator';
indicator.innerHTML = '▶';
nestedItem.appendChild(indicator);
nestedItem.classList.add('has-nested-children');
}
// Evento de clique
nestedItem.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
const clickedData = JSON.parse(this.dataset.subskillData || '{}');
const L = getLabels();
const subName = clickedData.name || clickedData.n || ;
debugLog('Nested sub clicked:', { subName, hasNested });
// Atualiza descrição/vídeo
updateSkillDisplay(clickedData);
// Marca como ativa
nestedContainer.querySelectorAll('.nested-subicon').forEach(el => {
el.classList.remove('active');
});
this.classList.add('active');
// Se tiver sub-sub-subskills, renderiza terceiro nível
if (hasNested && clickedData.subs) {
renderThirdLevelSubskills(clickedData.subs, this, subName);
} else {
const thirdLevel = this.querySelector('.subskills-third-level');
if (thirdLevel) thirdLevel.remove();
}
});
nestedContainer.appendChild(nestedItem);
});
// Adiciona ao DOM
parentElement.style.position = 'relative';
parentElement.appendChild(nestedContainer);
// Fecha ao clicar fora
setTimeout(() => {
const closeHandler = (e) => {
if (!parentElement.contains(e.target) && !nestedContainer.contains(e.target)) {
nestedContainer.remove();
document.removeEventListener('click', closeHandler);
}
};
document.addEventListener('click', closeHandler);
}, 10);
debugLog('Nested subskills rendered:', nestedContainer.children.length);
}
// Terceiro nível (sub-sub-subskills)
function renderThirdLevelSubskills(thirdLevelSubs, parentElement, parentName) {
const existing = parentElement.querySelector('.subskills-third-level');
if (existing) existing.remove();
if (!thirdLevelSubs || !Array.isArray(thirdLevelSubs) || thirdLevelSubs.length === 0) {
return;
}
const thirdContainer = document.createElement('div');
thirdContainer.className = 'subskills-third-level';
thirdContainer.dataset.parent = parentName;
thirdContainer.dataset.level = '3';
Object.assign(thirdContainer.style, {
position: 'absolute',
top: '0',
left: '100%',
marginLeft: '5px',
zIndex: '1001',
background: 'rgba(0, 0, 0, 0.98)',
borderRadius: '8px',
padding: '6px',
display: 'flex',
flexDirection: 'column',
gap: '4px',
minWidth: '180px',
boxShadow: '0 4px 20px rgba(0,0,0,0.8)',
border: '1px solid rgba(255, 217, 90, 0.3)'
});
const mainSkills = getMainSkillsMap();
thirdLevelSubs.forEach((sub, index) => {
const inheritedSub = applyInheritance(sub, mainSkills);
const subName = inheritedSub.name || inheritedSub.n || ;
const thirdItem = document.createElement('div');
thirdItem.className = 'subicon third-level-subicon';
thirdItem.title = subName;
thirdItem.dataset.parent = parentName;
thirdItem.dataset.name = subName;
thirdItem.dataset.subskillData = JSON.stringify(inheritedSub);
Object.assign(thirdItem.style, {
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '4px 8px',
borderRadius: '4px',
cursor: 'pointer',
transition: 'background 0.2s'
});
// Ícone pequeno
const img = document.createElement('img');
img.src = filePathURL(inheritedSub.icon || 'Nada.png');
img.alt = ;
img.width = 24;
img.height = 24;
img.style.borderRadius = '4px';
thirdItem.appendChild(img);
// Nome
const nameSpan = document.createElement('span');
nameSpan.textContent = subName;
nameSpan.style.color = '#fff';
nameSpan.style.fontSize = '12px';
thirdItem.appendChild(nameSpan);
// Eventos
thirdItem.addEventListener('mouseenter', () => {
thirdItem.style.background = 'rgba(255, 217, 90, 0.1)';
});
thirdItem.addEventListener('mouseleave', () => {
thirdItem.style.background = 'transparent';
});
thirdItem.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
const clickedData = JSON.parse(this.dataset.subskillData || '{}');
const L = getLabels();
const subName = clickedData.name || clickedData.n || ;
updateSkillDisplay(clickedData);
thirdContainer.querySelectorAll('.third-level-subicon').forEach(el => {
el.style.background = 'transparent';
});
this.style.background = 'rgba(255, 217, 90, 0.2)';
});
thirdContainer.appendChild(thirdItem);
});
parentElement.appendChild(thirdContainer);
}
// ===== FUNÇÕES EXISTENTES (modificadas) =====
function filePathURL(fileName) {
if (!fileName || fileName.trim() === || fileName === 'Nada.png' || fileName.toLowerCase() === 'nada.png') {
return ;
}
const f = encodeURIComponent(fileName.replace(/^Arquivo:|^File:/, ));
const base = (window.mw && mw.util && typeof mw.util.wikiScript === 'function')
? mw.util.wikiScript()
: (window.mw && mw.config ? (mw.config.get('wgScript') || '/index.php') : '/index.php');
let url = `${base}?title=Especial:FilePath/${f}`;
if (window.location.protocol === 'https:' && url.startsWith('http://')) {
url = url.replace('http://', 'https://');
}
return url;
}
function normalizeFileURL(raw, fallback = ) {
if (!raw) return fallback;
const val = String(raw).trim();
if (!val) return fallback;
if (/^(https?:)?\/\//i.test(val) || val.startsWith('data:') || val.includes('Especial:FilePath/')) {
return val;
}
return filePathURL(val);
}
function preloadImage(iconFile) {
const url = filePathURL(iconFile || 'Nada.png');
if (imagePreloadCache.has(url)) {
return imagePreloadCache.get(url);
}
const promise = new Promise((resolve, reject) => {
const img = new Image();
img.decoding = 'async';
img.loading = 'eager';
img.referrerPolicy = 'same-origin';
img.onload = () => resolve(url);
img.onerror = () => resolve(url);
img.src = url;
});
imagePreloadCache.set(url, promise);
return promise;
}
function getLabels() {
const skillsRoot = document.getElementById('skills');
const i18nMap = skillsRoot ? JSON.parse(skillsRoot.dataset.i18nAttrs || '{}') : {};
const raw = (document.documentElement.lang || skillsRoot?.dataset.i18nDefault || 'pt').toLowerCase();
const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
return i18nMap[lang] || i18nMap.pt || {
cooldown: 'Recarga',
energy_gain: 'Ganho de energia',
energy_cost: 'Custo de energia',
power: 'Poder',
power_pvp: 'Poder PvP',
level: 'Nível'
};
}
function isWeaponModeOn() {
try {
return localStorage.getItem('glaWeaponEnabled') === '1';
} catch (e) {
return false;
}
}
function getEffectiveAttrs(s) {
const weaponOn = isWeaponModeOn();
if (weaponOn && s.weapon) {
return {
powerpve: s.weapon.powerpve || s.powerpve,
powerpvp: s.weapon.powerpvp || s.powerpvp,
energy: s.weapon.energy || s.energy,
cooldown: s.weapon.cooldown || s.cooldown
};
}
return {
powerpve: s.powerpve,
powerpvp: s.powerpvp,
energy: s.energy,
cooldown: s.cooldown
};
}
function getEffectiveDesc(s) {
const weaponOn = isWeaponModeOn();
const raw = (document.documentElement.lang || 'pt').toLowerCase();
const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
if (weaponOn && s.weapon) {
const wDesc = s.weapon.desc_i18n || s.weapon.desc;
if (wDesc) {
return wDesc[lang] || wDesc.pt || wDesc.en || ;
}
}
const base = s.desc_i18n || s.desc;
if (base) {
return base[lang] || base.pt || base.en || ;
}
const descI18n = {
pt: s.descPt || ,
en: s.descEn || ,
es: s.descEs || ,
pl: s.descPl ||
};
return descI18n[lang] || descI18n.pt || ;
}
function getEffectiveVideo(s) {
const weaponOn = isWeaponModeOn();
if (weaponOn && s.weapon && s.weapon.video && s.weapon.video.trim() !== ) {
return s.weapon.video;
}
return s.video || ;
}
function getOrCreateSubskillVideo(videoURL) {
if (!videoURL || videoURL.trim() === ) return null;
const normalizedURL = normalizeFileURL(videoURL);
if (!normalizedURL || normalizedURL.trim() === ) return null;
if (subskillVideoElementCache.has(normalizedURL)) {
logSubVideo('getOrCreateSubskillVideo: cache hit', { videoURL: normalizedURL });
return subskillVideoElementCache.get(normalizedURL);
}
logSubVideo('getOrCreateSubskillVideo: creating new video', { videoURL: normalizedURL });
const v = document.createElement('video');
v.className = 'skill-video';
v.dataset.sub = '1';
v.setAttribute('controls', );
v.setAttribute('preload', 'metadata');
v.setAttribute('playsinline', );
v.muted = true;
v.style.display = 'none';
v.style.width = '100%';
v.style.height = 'auto';
v.style.aspectRatio = '16/9';
v.style.objectFit = 'cover';
const ext = (normalizedURL.split('.').pop() || ).toLowerCase().split('?')[0];
const mimeTypes = {
'mp4': 'video/mp4',
'm4v': 'video/mp4',
'webm': 'video/webm',
'ogv': 'video/ogg',
'ogg': 'video/ogg',
'mov': 'video/quicktime'
};
const mimeType = mimeTypes[ext] || 'video/mp4';
const src = document.createElement('source');
src.src = normalizedURL;
src.type = mimeType;
v.appendChild(src);
v.setAttribute('webkit-playsinline', );
v.setAttribute('x-webkit-airplay', 'allow');
subskillVideoElementCache.set(normalizedURL, v);
const videoBox = document.querySelector('.video-container');
if (videoBox && !v.isConnected) {
videoBox.appendChild(v);
v.load();
}
return v;
}
function renderSubAttrs(s, L) {
const chip = (label, val) => (val ? `
${label}${val}
` : );
const pve = (s.powerpve || ).toString().trim();
const pvp = (s.powerpvp || ).toString().trim();
const en = (s.energy || ).toString().trim();
const cd = (s.cooldown || ).toString().trim();
const rows = [
cd ? chip(L.cooldown, cd) : ,
en ? chip((en.startsWith('-') ? L.energy_cost : L.energy_gain), en.startsWith('-') ? en.replace(/^-/, ) : en.replace(/^\+?/, )) : ,
pve ? chip(L.power, pve) : ,
pvp ? chip(L.power_pvp, pvp) : ,
].filter(Boolean);
return rows.length ? `
${rows.join()}
` : ;
}
function renderFlagsRow(flags) {
const map = {
aggro: 'Enemyaggro-icon.png',
bridge: 'Bridgemaker-icon.png',
wall: 'Destroywall-icon.png',
quickcast: 'Quickcast-icon.png',
wallpass: 'Passthroughwall-icon.png'
};
const arr = (flags || []).filter(Boolean);
if (!arr.length) return ;
const items = arr.map(k => `<img class="skill-flag" data-flag="${k}" alt="" src="${filePathURL(map[k])}">`).join();
return `
${items}
`;
}
function applyFlagTooltips(container) {
const skillsRoot = document.getElementById('skills');
if (!skillsRoot) return;
let pack = {};
try { pack = JSON.parse(skillsRoot.dataset.i18nFlags || '{}'); } catch (e) { }
const raw = (document.documentElement.lang || 'pt').toLowerCase();
const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
const dict = pack[lang] || pack.pt || {};
const flags = container.querySelectorAll('.skill-flags .skill-flag[data-flag]');
const tooltip = window.__globalSkillTooltip;
if (!tooltip) return;
flags.forEach(el => {
const key = el.getAttribute('data-flag');
const tip = (dict && dict[key]) || ;
if (!tip) return;
if (el.dataset.flagTipWired) return;
el.dataset.flagTipWired = '1';
el.setAttribute('aria-label', tip);
if (el.hasAttribute('title')) el.removeAttribute('title');
el.addEventListener('mouseenter', () => {
const tipEl = document.querySelector('.skill-tooltip');
if (tipEl) tipEl.classList.add('flag-tooltip');
tooltip.show(el, tip);
});
el.addEventListener('mousemove', () => {
if (performance.now() >= tooltip.lockUntil.value) {
tooltip.measureAndPos(el);
}
});
el.addEventListener('click', () => {
tooltip.lockUntil.value = performance.now() + 240;
tooltip.measureAndPos(el);
});
el.addEventListener('mouseleave', () => {
const tipEl = document.querySelector('.skill-tooltip');
if (tipEl) tipEl.classList.remove('flag-tooltip');
tooltip.hide();
});
});
}
function ensureRail(iconsBar) {
const rail = iconsBar.closest('.top-rail');
if (!rail) {
return null;
}
if (!subRail) {
subRail = document.createElement('div');
subRail.className = 'subskills-rail collapsed hidden';
rail.appendChild(subRail);
}
if (!subBar) {
subBar = document.createElement('div');
subBar.className = 'subicon-bar';
subRail.appendChild(subBar);
}
if (!spacer) {
spacer = document.createElement('div');
spacer.className = 'subskills-spacer';
rail.parentNode.insertBefore(spacer, rail.nextSibling);
}
return rail;
}
function showSubVideo(videoURL, videoBox) {
logSubVideo('showSubVideo called', { videoURL, videoBoxExists: !!videoBox });
if (!videoBox) return;
videoBox.querySelectorAll('video.skill-video[data-sub="1"]').forEach(v => {
v.style.display = 'none';
});
const video = getOrCreateSubskillVideo(videoURL);
if (!video) {
logSubVideo('no video found for URL, hiding box', { videoURL });
videoBox.style.display = 'none';
return;
}
if (!video.isConnected || video.parentNode !== videoBox) {
logSubVideo('video not in DOM, appending', {
videoURL,
isConnected: video.isConnected,
parentNode: video.parentNode
});
if (video.parentNode) {
video.parentNode.removeChild(video);
}
videoBox.appendChild(video);
}
logSubVideo('showing video', {
videoURL,
readyState: video.readyState,
paused: video.paused,
currentTime: video.currentTime
});
videoBox.style.display = 'block';
video.style.display = 'block';
try {
video.currentTime = 0;
} catch (e) { }
if (video.readyState >= 2) {
video.play().then(() => {
logSubVideo('play() resolved', { videoURL, currentTime: video.currentTime, readyState: video.readyState });
}).catch(err => {
logSubVideo('play() rejected', { videoURL, error: err });
});
} else {
logSubVideo('video not ready, waiting for canplay', { videoURL, readyState: video.readyState });
const onCanPlay = () => {
video.removeEventListener('canplay', onCanPlay);
video.play().catch(() => { });
};
video.addEventListener('canplay', onCanPlay, { once: true });
}
}
function updateSkillDisplay(skillData) {
const L = getLabels();
const weaponOn = isWeaponModeOn();
const attrs = getEffectiveAttrs(skillData);
const desc = getEffectiveDesc(skillData);
const videoURL = getEffectiveVideo(skillData);
const descBox = document.querySelector('.skill-description');
const videoBox = document.querySelector('.video-container');
if (descBox) {
const level = (skillData.level || ).toString().trim();
const flagsHTML = skillData.flags ? renderFlagsRow(skillData.flags) : ;
descBox.innerHTML = `
${skillData.name || skillData.n || }
${level ? `
${L.level} ${level}
` : }
${renderSubAttrs(attrs, L)}
${desc.replace(/(.*?)/g, '$1')}
`;
}
if (videoBox) {
const oldFlags = videoBox.querySelector('.skill-flags');
if (oldFlags) oldFlags.remove();
if (skillData.flags && Array.isArray(skillData.flags) && skillData.flags.length > 0) {
const flagsHTML = renderFlagsRow(skillData.flags);
if (flagsHTML) {
videoBox.insertAdjacentHTML('beforeend', flagsHTML);
applyFlagTooltips(videoBox);
}
}
if (!videoURL || videoURL.trim() === ) {
videoBox.style.display = 'none';
} else {
showSubVideo(videoURL, videoBox);
}
}
}
// ===== API PRINCIPAL =====
api.renderBarFrom = function (el, { iconsBar, descBox, videoBox }) {
debugLog('api.renderBarFrom START', {
el: !!el,
parentIndex: el?.dataset?.index || 'unknown'
});
const rail = ensureRail(iconsBar);
if (!rail) {
debugLog('No rail found');
return;
}
const rawSubs = el.getAttribute('data-subs') || ;
const rawOrder = el.getAttribute('data-suborder') || ;
const parentIdx = el.dataset.index || ;
debugLog('Raw data:', { rawSubs: rawSubs.substring(0, 100) + '...', rawOrder, parentIdx });
if (!rawSubs.trim()) {
if (subRail) subRail.classList.add('collapsed');
if (subRail) subRail.classList.add('hidden');
if (subBar) subBar.innerHTML = ;
if (spacer) spacer.style.height = '0px';
return;
}
let subs;
try {
subs = JSON.parse(rawSubs);
debugLog('Parsed subs (first 2):', subs.slice(0, 2));
// DEBUG: Verifica estrutura
subs.forEach((sub, i) => {
if (sub.subs || sub.subskills) {
debugLog(`Subskill ${i} ("${sub.n || sub.name}") has nested:`, sub.subs || sub.subskills);
}
});
} catch(e) {
subs = [];
console.error('[Subskills] JSON parse error:', e, 'Raw:', rawSubs.substring(0, 200));
}
// Normaliza weapon
subs = subs.map(sub => {
if (sub.weaponPacked && !sub.weapon && typeof sub.weaponPacked === 'string' && sub.weaponPacked.trim() !== ) {
const parts = sub.weaponPacked.split('~');
if (parts.length >= 2) {
sub.weapon = {
icon: parts[0] || 'Nada.png',
powerpve: parts[1] || null,
powerpvp: parts[2] || null,
cooldown: parts[3] || null,
video: parts[4] || ,
energy: parts[5] || null
};
Object.keys(sub.weapon).forEach(k => {
if (sub.weapon[k] === || sub.weapon[k] === null) {
delete sub.weapon[k];
}
});
}
}
if (sub.weapon && typeof sub.weapon === 'string') {
try {
sub.weapon = JSON.parse(sub.weapon);
} catch {
const parts = sub.weapon.split('~');
if (parts.length >= 2) {
sub.weapon = {
icon: parts[0] || 'Nada.png',
powerpve: parts[1] || null,
powerpvp: parts[2] || null,
cooldown: parts[3] || null,
video: parts[4] || ,
energy: parts[5] || null
};
Object.keys(sub.weapon).forEach(k => {
if (sub.weapon[k] === || sub.weapon[k] === null) {
delete sub.weapon[k];
}
});
} else {
sub.weapon = null;
}
}
}
return sub;
});
if (!Array.isArray(subs) || subs.length === 0) {
subRail.classList.add('collapsed');
subRail.classList.add('hidden');
subBar.innerHTML = ;
if (spacer) spacer.style.height = '0px';
return;
}
// Processa estrutura do módulo
const mainSkills = getMainSkillsMap();
// Passo 1: Aplica herança
subs = subs.map(sub => applyInheritance(sub, mainSkills));
// Passo 2: Processa estrutura aninhada
subs = processModuleNestedStructure(subs, 1);
debugLog('Final processed subs:', subs);
subRail.classList.add('hidden');
subBar.innerHTML = ;
let order = subs.map(s => s.name || s.n || );
if (rawOrder.trim()) {
try {
const preferred = JSON.parse(rawOrder);
if (Array.isArray(preferred) && preferred.length) {
const byName = new Map(subs.map(s => [(s.name || s.n || ), s]));
order = preferred.filter(n => byName.has(n));
}
} catch { }
}
// Pré-carrega ícones
const iconPreloadPromises = [];
order.forEach(nm => {
const s = subs.find(x => (x.name || x.n || ) === nm);
if (s && s.icon && s.icon.trim() !== && s.icon !== 'Nada.png') {
iconPreloadPromises.push(preloadImage(s.icon));
}
});
// Função de renderização
const renderSubskillsBar = () => {
order.forEach(nm => {
const s = subs.find(x => (x.name || x.n || ) === nm);
if (!s) {
return;
}
const item = document.createElement('div');
item.className = 'subicon';
item.title = s.name || nm;
const slugify = window.__skillSlugify || ((str) => (str || ).toLowerCase().replace(/[^\w]+/g, '-').replace(/^-+|-+$/g, ));
item.dataset.slug = slugify(s.name || nm);
item.dataset.subskillData = JSON.stringify(s);
debugLog('Creating subicon:', {
name: s.name || s.n,
hasChildren: s.hasChildren,
childrenCount: s.childrenCount
});
const img = document.createElement('img');
img.alt = ;
const iconUrl = filePathURL(s.icon || 'Nada.png');
if (iconUrl) {
img.src = iconUrl;
}
img.decoding = 'async';
img.loading = 'lazy';
img.width = 42;
img.height = 42;
item.appendChild(img);
// Adiciona indicador se tiver sub-subskills
if (s.hasChildren) {
const indicator = document.createElement('div');
indicator.className = 'subskill-indicator';
indicator.innerHTML = '▼';
indicator.title = `Tem ${s.childrenCount || '?'} sub-habilidade(s)`;
item.appendChild(indicator);
item.classList.add('has-children');
debugLog('Added children indicator for:', s.name || s.n);
}
// Weapon
const hasWeapon = s.weapon && (
(typeof s.weapon === 'object' && Object.keys(s.weapon).length > 0) ||
(typeof s.weapon === 'string' && s.weapon.trim() !== )
);
const subName = (s.name || s.n || ).trim();
if (hasWeapon) {
let weaponObj = s.weapon;
if (typeof weaponObj === 'string') {
try {
weaponObj = JSON.parse(weaponObj);
} catch {
const parts = weaponObj.split('~');
if (parts.length >= 2) {
weaponObj = {
icon: parts[0] || 'Nada.png',
powerpve: parts[1] || null,
powerpvp: parts[2] || null,
cooldown: parts[3] || null,
video: parts[4] || ,
energy: parts[5] || null
};
Object.keys(weaponObj).forEach(k => {
if (weaponObj[k] === || weaponObj[k] === null) {
delete weaponObj[k];
}
});
} else {
weaponObj = null;
}
}
}
if (weaponObj && typeof weaponObj === 'object' && Object.keys(weaponObj).length > 0) {
try {
item.dataset.weapon = JSON.stringify(weaponObj);
} catch (e) {
console.error('[Subskills] Erro ao serializar weapon:', e);
}
if (isWeaponModeOn()) {
item.classList.add('has-weapon-available');
}
}
}
// Evento de clique
item.addEventListener('click', (e) => {
if (e.target.closest('.subskills-nested') || e.target.closest('.subskills-third-level')) {
return;
}
const L = getLabels();
const subName = (s.name || s.n || ).trim();
debugLog('Subicon clicked:', {
subName,
hasChildren: s.hasChildren,
hasNestedSubs: !!(s.subs && Array.isArray(s.subs) && s.subs.length > 0)
});
// Weapon logic
let subWeaponData = null;
if (item.dataset.weapon) {
try {
subWeaponData = JSON.parse(item.dataset.weapon);
} catch (e) {
subWeaponData = null;
}
}
const hasSubWeapon = !!subWeaponData;
const weaponOn = isWeaponModeOn();
const weaponEquipped = weaponOn && hasSubWeapon && subWeaponData;
// Descrição
const raw = (document.documentElement.lang || 'pt').toLowerCase();
const lang = raw === 'pt-br' ? 'pt' : (raw.split('-')[0] || 'pt');
let chosen = ;
if (weaponEquipped && subWeaponData) {
const weaponDescPack = subWeaponData.desc_i18n || subWeaponData.desc || {};
chosen = weaponDescPack[lang] || weaponDescPack.pt || weaponDescPack.en || ;
} else {
const base = s.desc_i18n || s.desc || {};
chosen = base[lang] || base.pt || base.en || (s.descPt || );
}
// Atributos
let attrsObj = {
powerpve: s.powerpve,
powerpvp: s.powerpvp,
energy: s.energy,
cooldown: s.cooldown
};
if (weaponEquipped && subWeaponData) {
attrsObj = {
powerpve: subWeaponData.powerpve || s.powerpve,
powerpvp: subWeaponData.powerpvp || s.powerpvp,
energy: subWeaponData.energy || s.energy,
cooldown: subWeaponData.cooldown || s.cooldown
};
}
// Level
const level = (weaponEquipped && subWeaponData && subWeaponData.level)
? subWeaponData.level.toString().trim()
: (s.level || ).toString().trim();
let flagsHTML = ;
if (Array.isArray(s.flags) && s.flags.length > 0) {
flagsHTML = renderFlagsRow(s.flags);
}
// Atualiza descrição
if (descBox) {
descBox.innerHTML = `
${s.name || nm}
${level ? `
${L.level} ${level}
` : }${renderSubAttrs(attrsObj, L)}
${chosen.replace(/(.*?)/g, '$1')}
`;
}
if (videoBox) {
const oldFlags = videoBox.querySelector('.skill-flags');
if (oldFlags) oldFlags.remove();
if (flagsHTML) {
videoBox.insertAdjacentHTML('beforeend', flagsHTML);
applyFlagTooltips(videoBox);
}
}
// Vídeo
const effectiveVideo = getEffectiveVideo(s);
if (!effectiveVideo || effectiveVideo.trim() === || effectiveVideo === 'Nada.png' || effectiveVideo.toLowerCase().includes('nada.png')) {
if (videoBox) videoBox.style.display = 'none';
} else {
showSubVideo(effectiveVideo, videoBox);
}
// Marca como ativa e limpa outras
Array.from(subBar.children).forEach(c => {
c.classList.remove('active');
c.classList.remove('weapon-equipped');
const nested = c.querySelector('.subskills-nested');
if (nested) nested.remove();
const thirdLevel = c.querySelector('.subskills-third-level');
if (thirdLevel) thirdLevel.remove();
});
item.classList.add('active');
if (weaponEquipped) {
item.classList.add('weapon-equipped');
}
window.__lastActiveSkillIcon = item;
// LÓGICA PARA SUB-SUBSKILLS
if (s.hasChildren && s.subs && Array.isArray(s.subs) && s.subs.length > 0) {
debugLog('Rendering nested subs for:', subName);
renderNestedSubskills(s.subs, item, subName, parentIdx);
} else {
const nested = item.querySelector('.subskills-nested');
if (nested) nested.remove();
const thirdLevel = item.querySelector('.subskills-third-level');
if (thirdLevel) thirdLevel.remove();
}
});
if (window.__globalSkillTooltip) {
const { show, hide, measureAndPos, lockUntil } = window.__globalSkillTooltip;
const label = item.title || ;
item.setAttribute('aria-label', label);
if (item.hasAttribute('title')) item.removeAttribute('title');
item.addEventListener('mouseenter', () => show(item, label));
item.addEventListener('mousemove', () => { if (performance.now() >= lockUntil.value) measureAndPos(item); });
item.addEventListener('click', () => { lockUntil.value = performance.now() + 240; measureAndPos(item); });
item.addEventListener('mouseleave', hide);
}
subBar.appendChild(item);
});
// Weapon classes
const applyWeaponClassesToSubskills = () => {
const weaponOn = isWeaponModeOn();
let weaponSubs = document.querySelectorAll('.subicon[data-weapon]');
weaponSubs.forEach(el => {
if (weaponOn) {
el.classList.add('has-weapon-available');
if (el.classList.contains('active')) {
el.classList.add('weapon-equipped');
}
} else {
el.classList.remove('has-weapon-available');
el.classList.remove('weapon-equipped');
}
});
};
applyWeaponClassesToSubskills();
window.dispatchEvent(new CustomEvent('gla:subskills:ready', { detail: { count: order.length } }));
if (subBar._weaponToggleListener) {
window.removeEventListener('gla:weaponToggled', subBar._weaponToggleListener);
}
subBar._weaponToggleListener = (e) => {
const enabled = e.detail?.enabled ?? false;
applyWeaponClassesToSubskills();
setTimeout(() => {
const activeSub = subBar.querySelector('.subicon[data-weapon].active')
|| subBar.querySelector('.subicon.active');
if (activeSub) {
activeSub.dispatchEvent(new Event('click', { bubbles: true }));
} else {
api.refreshCurrentSubSafe();
}
}, 50);
};
window.addEventListener('gla:weaponToggled', subBar._weaponToggleListener);
requestAnimationFrame(() => {
subRail.classList.remove('collapsed');
subRail.classList.remove('hidden');
const h = subRail.offsetHeight || 48;
if (spacer) spacer.style.height = h + 'px';
});
};
// Renderiza
Promise.all(iconPreloadPromises).then(() => {
setTimeout(() => {
renderSubskillsBar();
}, 10);
}).catch(() => {
setTimeout(() => {
renderSubskillsBar();
}, 10);
});
};
api.refreshCurrentSubSafe = function () {
const btn = document.querySelector('.subskills-rail .subicon.active');
if (!btn) return false;
const had = document.body.dataset.suppressSkillPlay;
document.body.dataset.suppressSkillPlay = '1';
try {
btn.dispatchEvent(new Event('click', { bubbles: true }));
} finally {
if (had) document.body.dataset.suppressSkillPlay = had;
else delete document.body.dataset.suppressSkillPlay;
}
return true;
};
api.hideAll = function (videoBox) {
const videos = videoBox?.querySelectorAll('.skill-video[data-sub="1"]') || [];
logSubVideo('api.hideAll called', { videoBoxExists: !!videoBox, videoCount: videos.length });
videos.forEach(v => {
try { v.pause(); } catch { }
v.style.display = 'none';
});
};
window.renderSubskillsBarFrom = function (el, ctx) { api.renderBarFrom(el, ctx); };
// ===== INICIALIZAÇÃO =====
function init() {
debugLog('Initializing subskills system');
getMainSkillsMap();
// Escuta mudanças no localStorage
window.addEventListener('storage', (e) => {
if (e.key === 'glaWeaponEnabled') {
setTimeout(() => api.refreshCurrentSubSafe(), 50);
}
});
window.addEventListener('gla:weaponToggled', (e) => {
const enabled = e.detail?.enabled ?? false;
setTimeout(() => {
const activeSub = document.querySelector('.subicon[data-weapon].active')
|| document.querySelector('.subicon.active');
if (activeSub) {
activeSub.dispatchEvent(new Event('click', { bubbles: true }));
} else {
api.refreshCurrentSubSafe();
}
}, 50);
});
window.addEventListener('gla:subskills:ready', (e) => {
debugLog('Subskills ready:', e.detail);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setTimeout(init, 100);
});
} else {
setTimeout(init, 100);
}
})();
</script> <style>
/* ESTILOS EXISTENTES */
.subicon-bar {
display: flex;
gap: 10px;
padding: 6px 6px;
overflow-x: auto;
scrollbar-width: thin;
scrollbar-color: #ababab transparent;
}
.subicon-bar::-webkit-scrollbar {
height: 6px;
}
.subicon-bar::-webkit-scrollbar-thumb {
background: #151515;
border-radius: 3px;
}
.subicon {
width: var(--icon-size, 42px);
height: var(--icon-size, 42px);
border-radius: var(--icon-radius, 10px);
overflow: hidden;
position: relative;
flex: 0 0 auto;
cursor: pointer;
isolation: isolate;
transition: transform 0.2s ease;
}
.subicon img {
width: 100%;
height: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
border-radius: inherit;
}
.subicon::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-idle, #cfcfcf);
pointer-events: none;
z-index: 2;
transition: box-shadow .12s ease;
}
.subicon:hover::after {
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) #e6e6e6;
}
.subicon.active::after {
box-shadow: inset 0 0 0 var(--icon-ring-w, 2px) var(--icon-active, #FFD95A);
}
.subicon.active {
transform: scale(1.10);
z-index: 5;
}
.subicon.active::before {
content: "";
position: absolute;
inset: -4px;
border-radius: calc(var(--icon-radius, 10px) + 4px);
pointer-events: none;
z-index: 1;
opacity: 1;
box-shadow: 0 0 12px 3px var(--icon-active-glow, rgba(255, 217, 90, .30)),
0 0 0 calc(var(--icon-ring-w, 2px) * 2) var(--icon-active-ring, rgba(255, 217, 90, .50));
}
.top-rail.skills {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
overflow: visible;
}
.subskills-rail {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: calc(100% - 1px);
z-index: 3;
display: inline-flex;
justify-content: center;
align-items: center;
width: auto;
max-width: 100%;
padding: 3px 5px;
background: rgba(0, 0, 0, .38);
border: 1px solid rgba(255, 255, 255, .10);
border-top: 1px solid rgba(255, 255, 255, .08);
border-radius: 0 0 10px 10px;
box-shadow: 0 3px 9px rgba(0, 0, 0, .22);
-webkit-backdrop-filter: blur(2px);
backdrop-filter: blur(2px);
overflow: hidden;
transition: opacity .14s ease, transform .14s ease;
opacity: 1;
}
.subskills-rail.collapsed {
opacity: 0;
pointer-events: none;
transform: translate(-50%, -6px);
}
.subskills-rail.hidden {
visibility: hidden;
}
.subskills-spacer {
height: 0;
transition: height .2s ease;
}
/* NOVOS ESTILOS PARA SUB-SUBSKILLS */
.subicon.has-children {
position: relative;
}
.subskill-indicator {
position: absolute;
bottom: -3px;
right: -3px;
background: var(--icon-active, #FFD95A);
color: #000;
font-size: 9px;
width: 14px;
height: 14px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
border: 1px solid #000;
z-index: 10;
}
.nested-indicator {
background: #FF7043 !important;
font-size: 8px;
width: 12px;
height: 12px;
}
.subskills-nested {
animation: fadeIn 0.2s ease;
z-index: 1000;
}
.subskills-third-level {
animation: fadeIn 0.2s ease;
z-index: 1001;
}
.nested-subicon {
width: 36px !important;
height: 36px !important;
border-radius: 6px !important;
position: relative;
}
.nested-subicon.active::after {
box-shadow: inset 0 0 0 2px var(--icon-active, #FFD95A) !important;
}
.third-level-subicon:hover {
background: rgba(255, 217, 90, 0.15) !important;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
}
/* Responsivo */
@media (max-width: 900px) {
.subskills-rail {
position: static;
transform: none;
margin-top: -2px;
border-top: 0;
border-radius: 0 0 10px 10px;
}
.subskills-spacer {
height: 0 !important;
}
.subskills-nested,
.subskills-third-level {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
max-width: 90vw !important;
z-index: 9999 !important;
}
}
/* Weapon styles (mantidos do original) */
.character-box .top-rail.skills .subicon.has-weapon-available:not(.active)::after {
box-shadow: inset 0 0 0 2px rgba(220, 70, 70, 0.85) !important;
}
@keyframes weapon-icon-border-scan {
0% { background-position: 0% 0%; }
100% { background-position: 400% 0%; }
}
@keyframes weapon-subicon-border-scan {
0% { background-position: 0% 0%; }
100% { background-position: 400% 0%; }
}
</style>