MediaWiki:Gadget-hero-comparison.js: Difference between revisionsGive feedback
Changed Spirit Power to Bonus Spirit Power |
Toggleable alt fire rows for heroes that have it, bunch of other changes. |
||
| Line 1: | Line 1: | ||
/** | /** | ||
* Hero Comparison Table | * Hero Comparison Table – interactive controls. | ||
* | * Adds Boons / Bonus Spirit Power inputs that recalculate hero stats in real time, | ||
* expandable alt‑fire sub‑rows for heroes with alternate firing modes, | |||
* and a button to show/hide all alt‑fire rows at once. | |||
*/ | */ | ||
(function() { | (function() { | ||
'use strict'; | 'use strict'; | ||
if (!document.getElementById('hero-comparison-container')) return; | if (!document.getElementById('hero-comparison-container')) return; | ||
const TABLE_ID = 'hero-comparison-table'; | const TABLE_ID = 'hero-comparison-table'; | ||
| Line 18: | Line 18: | ||
const magnitude = Math.pow(10, power); | const magnitude = Math.pow(10, power); | ||
const rounded = Math.round(num * magnitude) / magnitude; | const rounded = Math.round(num * magnitude) / magnitude; | ||
return parseFloat(rounded.toPrecision(sig)); | return parseFloat(rounded.toPrecision(sig)); | ||
} | } | ||
function calculateLinearStat(cell, spiritPower, powerIncreases) { | function calculateLinearStat(cell, spiritPower, powerIncreases) { | ||
if (!cell) return 0; | if (!cell) return 0; | ||
const baseRaw = cell.dataset.base; | const baseRaw = cell.dataset.base; | ||
if (baseRaw === "true" || baseRaw === "false") return baseRaw; | |||
if (baseRaw === "true" || baseRaw === "false") | |||
const base = parseFloat(baseRaw || 0); | const base = parseFloat(baseRaw || 0); | ||
const spiritScale = parseFloat(cell.dataset.spiritScale || 0); | const spiritScale = parseFloat(cell.dataset.spiritScale || 0); | ||
const levelScale = parseFloat(cell.dataset.levelScale || 0); | const levelScale = parseFloat(cell.dataset.levelScale || 0); | ||
const innateSpiritScale = parseFloat(cell.dataset.innateSpiritScale || 0); | const innateSpiritScale = parseFloat(cell.dataset.innateSpiritScale || 0); | ||
let value = base; | let value = base; | ||
if (levelScale && powerIncreases > 0) { | if (levelScale && powerIncreases > 0) { | ||
value = base + (powerIncreases * levelScale); | value = base + (powerIncreases * levelScale); | ||
} | } | ||
const innateSpirit = powerIncreases * innateSpiritScale; | const innateSpirit = powerIncreases * innateSpiritScale; | ||
const totalSpirit = innateSpirit + spiritPower; | const totalSpirit = innateSpirit + spiritPower; | ||
if (spiritScale && totalSpirit > 0) { | if (spiritScale && totalSpirit > 0) { | ||
value += totalSpirit * spiritScale; | value += totalSpirit * spiritScale; | ||
} | } | ||
return value; | return value; | ||
} | } | ||
function calculateStat(cell, spiritPower, powerIncreases) { | function calculateStat(cell, spiritPower, powerIncreases) { | ||
return calculateLinearStat(cell, spiritPower, powerIncreases); | |||
} | |||
function toggleAltFireRow(heroKey) { | |||
const subRow = document.querySelector(`tr.alt-fire-sub[data-parent="${heroKey}"]`); | |||
if (!subRow) return; | |||
subRow.classList.toggle('visible'); | |||
const toggleLink = document.querySelector(`a.toggle-alt-fire[data-hero-key="${heroKey}"]`); | |||
if (toggleLink) toggleLink.textContent = subRow.classList.contains('visible') ? '−' : '+'; | |||
} | } | ||
const flashTimeouts = new Map(); | const flashTimeouts = new Map(); | ||
function forceResetDisplay() { | function forceResetDisplay() { | ||
const table = document.getElementById(TABLE_ID); | const table = document.getElementById(TABLE_ID); | ||
if (!table) return; | if (!table) return; | ||
table.querySelectorAll('td[data-base]').forEach((cell, index) => { | |||
const numSpan = cell.querySelector('.stat-num'); | const numSpan = cell.querySelector('.stat-num'); | ||
if (!numSpan) return; | if (!numSpan) return; | ||
const baseVal = cell.dataset.base; | const baseVal = cell.dataset.base; | ||
numSpan.textContent = baseVal; | numSpan.textContent = baseVal; | ||
cell.setAttribute('data-sort-value', baseVal); | cell.setAttribute('data-sort-value', baseVal); | ||
if (window.jQuery) jQuery(cell).data('sortValue', baseVal); | |||
if (window.jQuery) | |||
numSpan.classList.remove('stat-changed'); | numSpan.classList.remove('stat-changed'); | ||
numSpan.removeAttribute('style'); | numSpan.removeAttribute('style'); | ||
if (flashTimeouts.has(index)) { clearTimeout(flashTimeouts.get(index)); flashTimeouts.delete(index); } | |||
if (flashTimeouts.has(index)) { | |||
}); | }); | ||
} | } | ||
| Line 136: | Line 73: | ||
const table = document.getElementById(TABLE_ID); | const table = document.getElementById(TABLE_ID); | ||
if (!table) return; | if (!table) return; | ||
if (isReset || (spiritPower === 0 && powerIncreases === 0)) { | if (isReset || (spiritPower === 0 && powerIncreases === 0)) { | ||
forceResetDisplay(); | forceResetDisplay(); | ||
if (window.jQuery) jQuery(table).trigger('update'); | |||
if (window.jQuery) | |||
return; | return; | ||
} | } | ||
const cells = table.querySelectorAll('td[data-base]'); | const cells = table.querySelectorAll('td[data-base]'); | ||
cells.forEach((cell, index) => { | cells.forEach((cell, index) => { | ||
const numSpan = cell.querySelector('.stat-num'); | const numSpan = cell.querySelector('.stat-num'); | ||
if (!numSpan) return; | if (!numSpan) return; | ||
const newValue = calculateStat(cell, spiritPower, powerIncreases); | const newValue = calculateStat(cell, spiritPower, powerIncreases); | ||
const baseRaw = cell.dataset.base; | const baseRaw = cell.dataset.base; | ||
// | // Only update if the underlying numeric value has genuinely changed | ||
let | let hasNumericChange = false; | ||
if (baseRaw === "true" || baseRaw === "false") { | if (baseRaw === "true" || baseRaw === "false") { | ||
hasNumericChange = (String(newValue) !== baseRaw); | |||
} else { | } else { | ||
const baseVal = parseFloat(baseRaw || 0); | const baseVal = parseFloat(baseRaw || 0); | ||
const | const newValNum = (typeof newValue === 'number') ? newValue : parseFloat(newValue); | ||
hasNumericChange = Math.abs(newValNum - baseVal) > 0.001; | |||
} | } | ||
// | if (!hasNumericChange) return; | ||
numSpan.textContent = | |||
const displayValue = (typeof newValue === 'number') | |||
? roundToSigFigs(newValue, 3) | |||
cell.setAttribute('data-sort-value', | : newValue; | ||
// Skip update if the displayed text would be unchanged | |||
if (window.jQuery) | const currentText = numSpan.textContent.trim(); | ||
if (currentText === String(displayValue)) return; | |||
numSpan.textContent = displayValue; | |||
// | cell.setAttribute('data-sort-value', displayValue); | ||
if (window.jQuery) jQuery(cell).data('sortValue', displayValue); | |||
// Brief green flash to draw attention to changed values | |||
if (flashTimeouts.has(index)) { | if (flashTimeouts.has(index)) { | ||
clearTimeout(flashTimeouts.get(index)); | clearTimeout(flashTimeouts.get(index)); | ||
flashTimeouts.delete(index); | flashTimeouts.delete(index); | ||
} | } | ||
numSpan.classList.add('stat-changed'); | |||
numSpan.style.transition = 'none'; | |||
numSpan.style.color = '#4CAF50'; | |||
const timeoutId = setTimeout(() => { | |||
if (numSpan.parentElement) { | |||
numSpan.style.transition = 'color 0.3s ease'; | |||
numSpan.style.color = ''; | |||
} | |||
flashTimeouts.delete(index); | |||
}, 150); | |||
flashTimeouts.set(index, timeoutId); | |||
}); | }); | ||
if (window.jQuery) jQuery(table).trigger('update'); | |||
if (window.jQuery) | |||
} | } | ||
function createControls() { | function createControls() { | ||
const container = document.getElementById( | const container = document.getElementById('hero-comparison-container'); | ||
if (!container | if (!container || document.getElementById('hero-controls')) return; | ||
const maxPower = parseInt(container.dataset.maxPower) || 25; | const maxPower = parseInt(container.dataset.maxPower) || 25; | ||
const maxSpirit = parseInt(container.dataset.maxSpirit) || 500; | const maxSpirit = parseInt(container.dataset.maxSpirit) || 500; | ||
container.insertAdjacentHTML('afterbegin', ` | |||
<div id="hero-controls" style="margin-bottom: 15px; padding: 12px; background: #1e1e1e; border: 1px solid #333; border-radius: 4px; display: flex; align-items: center; flex-wrap: wrap; gap: 15px;"> | <div id="hero-controls" style="margin-bottom: 15px; padding: 12px; background: #1e1e1e; border: 1px solid #333; border-radius: 4px; display: flex; align-items: center; flex-wrap: wrap; gap: 15px;"> | ||
<label style="color: #fff; display: flex; align-items: center; gap: 5px;"> | <label style="color: #fff; display: flex; align-items: center; gap: 5px;"> | ||
<span style="color: # | <span style="color: #fff4df; font-weight: bold;">Boons:</span> | ||
<input type="number" id="hero-input-power" min="0" max="${maxPower}" value="0" | <input type="number" id="hero-input-power" min="0" max="${maxPower}" value="0" | ||
style="width: 60px; background: #2a2a2a; color: #fff; border: 1px solid #555; padding: 4px;"> | style="width: 60px; background: #2a2a2a; color: #fff; border: 1px solid #555; padding: 4px;"> | ||
| Line 230: | Line 150: | ||
style="width: 70px; background: #2a2a2a; color: #fff; border: 1px solid #555; padding: 4px;"> | style="width: 70px; background: #2a2a2a; color: #fff; border: 1px solid #555; padding: 4px;"> | ||
</label> | </label> | ||
<button id="hero-reset-btn" style="padding: 4px 12px; cursor: pointer; background: #444; color: # | <button id="hero-reset-btn" style="padding: 4px 12px; cursor: pointer; background: #444; color: #fff4df; border: 1px solid #666; border-radius: 3px;">Reset</button> | ||
<button id="hero-max-btn" style="padding: 4px 12px; cursor: pointer; background: #444; color: # | <button id="hero-max-btn" style="padding: 4px 12px; cursor: pointer; background: #444; color: #fff4df; border: 1px solid #666; border-radius: 3px;">Max Boons</button> | ||
<button id="hero-toggle-all-alt" style="padding: 4px 12px; cursor: pointer; background: #444; color: #fff4df; border: 1px solid #666; border-radius: 3px;">Show Alt‑Fire</button> | |||
<span style="color: #888; font-size: 0.85em;">Values update automatically</span> | <span style="color: #888; font-size: 0.85em;">Values update automatically</span> | ||
</div> | </div> | ||
` | `); | ||
const powerInput = document.getElementById('hero-input-power'); | const powerInput = document.getElementById('hero-input-power'); | ||
const spiritInput = document.getElementById('hero-input-spirit'); | const spiritInput = document.getElementById('hero-input-spirit'); | ||
const resetBtn = document.getElementById('hero-reset-btn'); | const resetBtn = document.getElementById('hero-reset-btn'); | ||
const maxBtn = document.getElementById('hero-max-btn'); | const maxBtn = document.getElementById('hero-max-btn'); | ||
let updateTimeout; | let updateTimeout; | ||
const scheduleUpdate = () => { | const scheduleUpdate = () => { | ||
| Line 250: | Line 166: | ||
const power = Math.min(parseInt(powerInput.value) || 0, maxPower); | const power = Math.min(parseInt(powerInput.value) || 0, maxPower); | ||
const spirit = Math.min(parseInt(spiritInput.value) || 0, maxSpirit); | const spirit = Math.min(parseInt(spiritInput.value) || 0, maxSpirit); | ||
if (powerInput.value > maxPower) powerInput.value = maxPower; | if (powerInput.value > maxPower) powerInput.value = maxPower; | ||
if (spiritInput.value > maxSpirit) spiritInput.value = maxSpirit; | if (spiritInput.value > maxSpirit) spiritInput.value = maxSpirit; | ||
| Line 256: | Line 171: | ||
}, 50); | }, 50); | ||
}; | }; | ||
powerInput.addEventListener('input', scheduleUpdate); | powerInput.addEventListener('input', scheduleUpdate); | ||
spiritInput.addEventListener('input', scheduleUpdate); | spiritInput.addEventListener('input', scheduleUpdate); | ||
resetBtn.addEventListener('click', () => { powerInput.value = 0; spiritInput.value = 0; updateTable(0, 0, true); }); | |||
resetBtn.addEventListener('click', () => { | maxBtn.addEventListener('click', () => { powerInput.value = maxPower; updateTable(parseInt(spiritInput.value) || 0, maxPower); }); | ||
powerInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') scheduleUpdate(); }); | |||
spiritInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') scheduleUpdate(); }); | |||
// Toggle all alt‑fire sub‑rows at once | |||
let allExpanded = false; | |||
const toggleAllBtn = document.getElementById('hero-toggle-all-alt'); | |||
if (toggleAllBtn) { | |||
toggleAllBtn.addEventListener('click', () => { | |||
const rows = document.querySelectorAll('tr.alt-fire-sub'); | |||
const links = document.querySelectorAll('a.toggle-alt-fire'); | |||
if (allExpanded) { | |||
rows.forEach(row => row.classList.remove('visible')); | |||
links.forEach(link => link.textContent = '+'); | |||
toggleAllBtn.textContent = 'Show Alt‑Fire'; | |||
allExpanded = false; | |||
} else { | |||
rows.forEach(row => row.classList.add('visible')); | |||
links.forEach(link => link.textContent = '−'); | |||
toggleAllBtn.textContent = 'Hide Alt‑Fire'; | |||
allExpanded = true; | |||
} | |||
}); | |||
} | |||
} | |||
function enhanceToggleLinks() { | |||
document.querySelectorAll('a[href^="#alt-fire-"]').forEach(link => { | |||
link.classList.add('toggle-alt-fire'); | |||
const heroKey = link.getAttribute('href').replace('#alt-fire-', ''); | |||
link.setAttribute('data-hero-key', heroKey); | |||
link.setAttribute('title', 'Show alt‑fire weapon stats'); | |||
}); | }); | ||
} | |||
function attachToggleListeners() { | |||
const table = document.getElementById(TABLE_ID); | |||
if (!table) return; | |||
table.addEventListener('click', function(e) { | |||
const link = e.target.closest('a[href^="#alt-fire-"]'); | |||
if (link) { | |||
e.preventDefault(); | |||
const heroKey = link.getAttribute('href').replace('#alt-fire-', ''); | |||
toggleAltFireRow(heroKey); | |||
} | |||
}); | }); | ||
} | } | ||
createControls(); | createControls(); | ||
enhanceToggleLinks(); | |||
attachToggleListeners(); | |||
if (window.jQuery) { | if (window.jQuery) { | ||
$(document).on('tabberchange', | $(document).on('tabberchange', () => { setTimeout(() => { createControls(); enhanceToggleLinks(); attachToggleListeners(); }, 100); }); | ||
} | } | ||
})(); | })(); | ||