Widget:Calculator/Combat/Level: Difference between revisions
No edit summary |
Refactored |
||
(8 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
< | <style> | ||
#combat-calc-widget { max-width: 360px; font-size: 14px; } | |||
.combat-calc__lookup { display: flex; gap: 8px; margin-bottom: 15px; } | |||
.combat-calc__table { border-collapse: collapse; width: 100%; } | |||
.combat-calc__table td { padding: 6px 4px; } | |||
.combat-calc__stat-label { display: flex; align-items: center; gap: 8px; } | |||
.combat-calc__stat-label a { color: var(--theme-color-link, #5c97e6); text-decoration: none; } | |||
.combat-calc__stat-label a:hover { text-decoration: underline; } | |||
.combat-calc__stat-label img { vertical-align: middle; } | |||
.combat-calc__results { margin-top: 15px; padding: 12px; background-color: var(--bg-secondary, #1e1e1e); color: var(--text-main, #ccc); border: 1px solid var(--border-color-base, #333); border-radius: 5px; } | |||
.combat-calc__results b { color: var(--text-bright, #fff); } | |||
.combat-calc__hints ul { list-style: none; padding-left: 0; margin: 6px 0 0; } | |||
.combat-calc__hints li { display: flex; align-items: center; gap: 6px; margin-top: 4px; } | |||
#combat-calc-widget input[type="text"], | |||
#combat-calc-widget input[type="number"] { width: 100%; background-color: var(--bg-main, #1e1f22); color: var(--text-main, #b6b4b2); border: 1px solid var(--border-color-base, #2d2d2d); border-radius: 4px; padding: 6px 8px; box-sizing: border-box; } | |||
#combat-calc-widget button { background-color: var(--theme-color-accent-base, #2ecc71); color: white; border: none; padding: 6px 12px; cursor: pointer; border-radius: 4px; white-space: nowrap; } | |||
#combat-calc-widget button:hover { background-color: var(--theme-color-accent-hover, #27ae60); } | |||
#combat-calc-widget button:disabled { background-color: #555; cursor: not-allowed; } | |||
</style> | |||
< | <div id="combat-calc-widget"> | ||
< | <div class="combat-calc__lookup"> | ||
<input type="text" id="username-input" placeholder="Enter HighSpell Username..."> | |||
<button id="lookup-btn">Lookup</button> | |||
< | </div> | ||
<table class="combat-calc__table"> | |||
< | <tbody id="stats-tbody"></tbody> | ||
</table> | |||
<div class="combat-calc__results"> | |||
<b>Combat Level:</b> <span id="combat-result">—</span> | |||
</ | <div class="combat-calc__hints" id="level-hints" style="font-style: italic;">—</div> | ||
</div> | |||
</div> | |||
<script> | <script> | ||
function | (function() { | ||
const hp | const STAT_CONFIG = [ | ||
const | { id: 'hp', name: 'Hitpoints', icon: '/w/images/thumb/9/96/Hitpoints_icon.png/20px-Hitpoints_icon.png' }, | ||
const | { id: 'accuracy', name: 'Accuracy', icon: '/w/images/thumb/d/df/Accuracy_icon.png/20px-Accuracy_icon.png' }, | ||
const | { id: 'strength', name: 'Strength', icon: '/w/images/thumb/1/1b/Strength_icon.png/20px-Strength_icon.png' }, | ||
const | { id: 'defense', name: 'Defense', icon: '/w/images/thumb/e/e5/Defense_icon.png/20px-Defense_icon.png' }, | ||
const | { id: 'magic', name: 'Magic', icon: '/w/images/thumb/5/5c/Magic_icon.png/20px-Magic_icon.png' }, | ||
{ id: 'range', name: 'Range', icon: '/w/images/thumb/1/13/Range_icon.png/20px-Range_icon.png' }, | |||
]; | |||
const tbody = document.getElementById('stats-tbody'); | |||
const combatResultEl = document.getElementById('combat-result'); | |||
const levelHintsEl = document.getElementById('level-hints'); | |||
const lookupBtn = document.getElementById('lookup-btn'); | |||
const usernameInput = document.getElementById('username-input'); | |||
function generateStatRows() { | |||
tbody.innerHTML = STAT_CONFIG.map(stat => ` | |||
<tr> | |||
<td> | |||
<span class="combat-calc__stat-label"> | |||
<img src="${stat.icon}" alt="${stat.name} icon" width="20" height="20"> | |||
<a href="/w/${stat.name}" title="${stat.name}">${stat.name}</a> | |||
</span> | |||
</td> | |||
<td style="width: 80px;"> | |||
<input type="number" id="stat-${stat.id}" value="${stat.id === 'hp' ? 10 : 1}" min="1" max="100" data-stat-id="${stat.id}"> | |||
</td> | |||
</tr> | |||
`).join(''); | |||
} | |||
function getStatValue(id) { | |||
return; | const input = document.getElementById(`stat-${id}`); | ||
let value = parseInt(input.value, 10) || 1; | |||
value = Math.max(1, Math.min(100, value)); | |||
input.value = value; | |||
return value; | |||
} | } | ||
function renderHints(combatLvl, base) { | |||
const nextLevel = Math.floor(combatLvl) + 1; | |||
const needed = (nextLevel * 3.75) - base; | |||
if (needed <= 0) { | |||
levelHintsEl.innerHTML = "You are at the threshold for the next level!"; | |||
return; | |||
if ( | |||
} | } | ||
const neededPrimary = Math.ceil(needed); | |||
const neededSecondary = Math.ceil(needed * 4); | |||
const icon = (name) => `<img src="${STAT_CONFIG.find(s=>s.name===name).icon}" alt="${name}" width="20" height="20">`; | |||
levelHintsEl.innerHTML = ` | |||
<div>To reach level ${nextLevel}, gain one of:</div> | |||
<ul> | |||
<li><b>+${neededPrimary}</b> ${icon('Hitpoints')} Hitpoints</li> | |||
<li><b>+${neededPrimary}</b> ${icon('Accuracy')} Accuracy, ${icon('Strength')} Strength, or ${icon('Defense')} Defense</li> | |||
<li><b>+${neededSecondary}</b> ${icon('Magic')} Magic or ${icon('Range')} Range</li> | |||
</ul>`; | |||
} | } | ||
function updateCombatLevel() { | |||
const stats = { | |||
hp: getStatValue('hp'), accuracy: getStatValue('accuracy'), strength: getStatValue('strength'), | |||
defense: getStatValue('defense'), magic: getStatValue('magic'), range: getStatValue('range') | |||
}; | |||
const base = stats.hp + stats.accuracy + stats.strength + stats.defense + (stats.magic / 4) + (stats.range / 4); | |||
const combat = base / 3.75; | |||
combatResultEl.textContent = combat.toFixed(2); | |||
levelHintsEl.style.fontStyle = 'normal'; | |||
renderHints(combat, base); | |||
} | } | ||
async function lookupStats() { | |||
const username = usernameInput.value.trim(); | |||
if (!username) { levelHintsEl.innerHTML = 'Please enter a username.'; return; } | |||
if (!response.ok) throw new Error( | lookupBtn.disabled = true; | ||
lookupBtn.textContent = '...'; | |||
levelHintsEl.textContent = `Searching for ${username}...`; | |||
try { | |||
const response = await fetch(`https://highspell.com/hiscores/player/${encodeURIComponent(username)}`); | |||
if (!response.ok) throw new Error(`Player not found or server error.`); | |||
const html = await response.text(); | |||
const doc = new DOMParser().parseFromString(html, "text/html"); | const doc = new DOMParser().parseFromString(html, "text/html"); | ||
let found = false; | |||
doc.querySelectorAll('.hs_data__row').forEach(row => { | |||
const skillName = row.querySelector('.hs_data__row__name')?.textContent.trim(); | |||
const config = STAT_CONFIG.find(s => s.name === skillName); | |||
if (config) { | |||
const level = row.querySelector('.hs_data__row__level')?.textContent.trim(); | |||
document.getElementById(`stat-${config.id}`).value = parseInt(level, 10) || 1; | |||
found = true; | |||
} | |||
} | }); | ||
if (found) updateCombatLevel(); | |||
else throw new Error(`Could not parse stats for ${username}.`); | |||
} catch (err) { | |||
} | levelHintsEl.style.fontStyle = 'italic'; | ||
} | levelHintsEl.innerHTML = err.message; | ||
combatResultEl.textContent = 'Error'; | |||
document. | } finally { | ||
lookupBtn.disabled = false; | |||
lookupBtn.textContent = 'Lookup'; | |||
} | |||
} | |||
document.addEventListener('DOMContentLoaded', () => { | |||
generateStatRows(); | |||
updateCombatLevel(); | |||
tbody.addEventListener('input', updateCombatLevel); | |||
lookupBtn.addEventListener('click', lookupStats); | |||
usernameInput.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); lookupStats(); } }); | |||
}); | |||
})(); | |||
</script> | </script> | ||
Latest revision as of 10:28, 15 July 2025
<style>
- combat-calc-widget { max-width: 360px; font-size: 14px; }
.combat-calc__lookup { display: flex; gap: 8px; margin-bottom: 15px; } .combat-calc__table { border-collapse: collapse; width: 100%; } .combat-calc__table td { padding: 6px 4px; } .combat-calc__stat-label { display: flex; align-items: center; gap: 8px; } .combat-calc__stat-label a { color: var(--theme-color-link, #5c97e6); text-decoration: none; } .combat-calc__stat-label a:hover { text-decoration: underline; } .combat-calc__stat-label img { vertical-align: middle; } .combat-calc__results { margin-top: 15px; padding: 12px; background-color: var(--bg-secondary, #1e1e1e); color: var(--text-main, #ccc); border: 1px solid var(--border-color-base, #333); border-radius: 5px; } .combat-calc__results b { color: var(--text-bright, #fff); } .combat-calc__hints ul { list-style: none; padding-left: 0; margin: 6px 0 0; } .combat-calc__hints li { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
- combat-calc-widget input[type="text"],
- combat-calc-widget input[type="number"] { width: 100%; background-color: var(--bg-main, #1e1f22); color: var(--text-main, #b6b4b2); border: 1px solid var(--border-color-base, #2d2d2d); border-radius: 4px; padding: 6px 8px; box-sizing: border-box; }
- combat-calc-widget button { background-color: var(--theme-color-accent-base, #2ecc71); color: white; border: none; padding: 6px 12px; cursor: pointer; border-radius: 4px; white-space: nowrap; }
- combat-calc-widget button:hover { background-color: var(--theme-color-accent-hover, #27ae60); }
- combat-calc-widget button:disabled { background-color: #555; cursor: not-allowed; }
</style>
<input type="text" id="username-input" placeholder="Enter HighSpell Username..."> <button id="lookup-btn">Lookup</button>
Combat Level: —
<script> (function() {
const STAT_CONFIG = [ { id: 'hp', name: 'Hitpoints', icon: '/w/images/thumb/9/96/Hitpoints_icon.png/20px-Hitpoints_icon.png' }, { id: 'accuracy', name: 'Accuracy', icon: '/w/images/thumb/d/df/Accuracy_icon.png/20px-Accuracy_icon.png' }, { id: 'strength', name: 'Strength', icon: '/w/images/thumb/1/1b/Strength_icon.png/20px-Strength_icon.png' }, { id: 'defense', name: 'Defense', icon: '/w/images/thumb/e/e5/Defense_icon.png/20px-Defense_icon.png' }, { id: 'magic', name: 'Magic', icon: '/w/images/thumb/5/5c/Magic_icon.png/20px-Magic_icon.png' }, { id: 'range', name: 'Range', icon: '/w/images/thumb/1/13/Range_icon.png/20px-Range_icon.png' }, ]; const tbody = document.getElementById('stats-tbody'); const combatResultEl = document.getElementById('combat-result'); const levelHintsEl = document.getElementById('level-hints'); const lookupBtn = document.getElementById('lookup-btn'); const usernameInput = document.getElementById('username-input');
function generateStatRows() { tbody.innerHTML = STAT_CONFIG.map(stat => `
<img src="${stat.icon}" alt="${stat.name} icon" width="20" height="20">
<a href="/w/${stat.name}" title="${stat.name}">${stat.name}</a>
<input type="number" id="stat-${stat.id}" value="${stat.id === 'hp' ? 10 : 1}" min="1" max="100" data-stat-id="${stat.id}">
`).join(); } function getStatValue(id) { const input = document.getElementById(`stat-${id}`); let value = parseInt(input.value, 10) || 1; value = Math.max(1, Math.min(100, value)); input.value = value; return value; } function renderHints(combatLvl, base) { const nextLevel = Math.floor(combatLvl) + 1; const needed = (nextLevel * 3.75) - base; if (needed <= 0) { levelHintsEl.innerHTML = "You are at the threshold for the next level!"; return; } const neededPrimary = Math.ceil(needed); const neededSecondary = Math.ceil(needed * 4); const icon = (name) => `<img src="${STAT_CONFIG.find(s=>s.name===name).icon}" alt="${name}" width="20" height="20">`; levelHintsEl.innerHTML = `
- +${neededPrimary} ${icon('Hitpoints')} Hitpoints
- +${neededPrimary} ${icon('Accuracy')} Accuracy, ${icon('Strength')} Strength, or ${icon('Defense')} Defense
- +${neededSecondary} ${icon('Magic')} Magic or ${icon('Range')} Range
`;
} function updateCombatLevel() { const stats = { hp: getStatValue('hp'), accuracy: getStatValue('accuracy'), strength: getStatValue('strength'), defense: getStatValue('defense'), magic: getStatValue('magic'), range: getStatValue('range') }; const base = stats.hp + stats.accuracy + stats.strength + stats.defense + (stats.magic / 4) + (stats.range / 4); const combat = base / 3.75; combatResultEl.textContent = combat.toFixed(2); levelHintsEl.style.fontStyle = 'normal'; renderHints(combat, base); } async function lookupStats() { const username = usernameInput.value.trim(); if (!username) { levelHintsEl.innerHTML = 'Please enter a username.'; return; } lookupBtn.disabled = true; lookupBtn.textContent = '...'; levelHintsEl.textContent = `Searching for ${username}...`; try { const response = await fetch(`https://highspell.com/hiscores/player/${encodeURIComponent(username)}`); if (!response.ok) throw new Error(`Player not found or server error.`); const html = await response.text(); const doc = new DOMParser().parseFromString(html, "text/html"); let found = false; doc.querySelectorAll('.hs_data__row').forEach(row => { const skillName = row.querySelector('.hs_data__row__name')?.textContent.trim(); const config = STAT_CONFIG.find(s => s.name === skillName); if (config) { const level = row.querySelector('.hs_data__row__level')?.textContent.trim(); document.getElementById(`stat-${config.id}`).value = parseInt(level, 10) || 1; found = true; } }); if (found) updateCombatLevel(); else throw new Error(`Could not parse stats for ${username}.`); } catch (err) { levelHintsEl.style.fontStyle = 'italic'; levelHintsEl.innerHTML = err.message; combatResultEl.textContent = 'Error'; } finally { lookupBtn.disabled = false; lookupBtn.textContent = 'Lookup'; } } document.addEventListener('DOMContentLoaded', () => { generateStatRows(); updateCombatLevel(); tbody.addEventListener('input', updateCombatLevel); lookupBtn.addEventListener('click', lookupStats); usernameInput.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); lookupStats(); } }); });
})(); </script>