|
|
(3 intermediate revisions by the same user not shown) |
Line 1: |
Line 1: |
| <html>
| |
| <style> | | <style> |
| #level-hints ul { | | #combat-calc-widget { max-width: 360px; font-size: 14px; } |
| list-style: none;
| | .combat-calc__lookup { display: flex; gap: 8px; margin-bottom: 15px; } |
| padding-left: 0;
| | .combat-calc__table { border-collapse: collapse; width: 100%; } |
| margin-top: 6px;
| | .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; } |
| #level-hints li {
| | .combat-calc__stat-label a:hover { text-decoration: underline; } |
| margin: 2px 0;
| | .combat-calc__stat-label img { vertical-align: middle; } |
| display: flex;
| | .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; } |
| align-items: center;
| | .combat-calc__results b { color: var(--text-bright, #fff); } |
| gap: 6px;
| | .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-table {
| | #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; } |
| border-collapse: collapse;
| | #combat-calc-widget button:hover { background-color: var(--theme-color-accent-hover, #27ae60); } |
| margin-top: 10px;
| | #combat-calc-widget button:disabled { background-color: #555; cursor: not-allowed; } |
| }
| |
| | |
| #combat-table td {
| |
| padding: 6px 10px;
| |
| }
| |
| | |
| #combat-table input {
| |
| width: 60px;
| |
| }
| |
| | |
| .results-box {
| |
| margin-top: 20px;
| |
| padding: 10px;
| |
| background-color: #1e1e1e;
| |
| color: #ccc;
| |
| border: 1px solid #333;
| |
| border-radius: 5px;
| |
| max-width: 350px;
| |
| }
| |
| | |
| .results-box b {
| |
| color: #fff;
| |
| }
| |
| | |
| #lookup-form input[type="text"] {
| |
| width: 200px;
| |
| }
| |
| | |
| #lookup-form button {
| |
| background-color: #2ecc71;
| |
| color: white;
| |
| border: none;
| |
| padding: 4px 10px;
| |
| cursor: pointer;
| |
| margin-left: 5px;
| |
| border-radius: 3px;
| |
| }
| |
| | |
| #lookup-form button:hover {
| |
| background-color: #27ae60;
| |
| }
| |
| | |
| input[type="text"], #lookup-form input[type="text"] { | |
| 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;
| |
| font-size: 14px;
| |
| width: 200px;
| |
| box-sizing: border-box;
| |
| } | |
| | |
| </style> | | </style> |
|
| |
|
| <form id="lookup-form"> | | <div id="combat-calc-widget"> |
| <label>
| | <div class="combat-calc__lookup"> |
| HighSpell Username:
| | <input type="text" id="username-input" placeholder="Enter HighSpell Username..."> |
| <input type="text" id="username" placeholder="Enter player name" />
| | <button id="lookup-btn">Lookup</button> |
| <button type="button" onclick="lookupStats()">Lookup</button>
| | </div> |
| </label>
| | <table class="combat-calc__table"> |
| </form>
| | <tbody id="stats-tbody"></tbody> |
| | |
| <form id="combat-form">
| |
| <table id="combat-table"> | |
| <tr><td><span typeof="mw:File"><a href="/w/Hitpoints" title="Hitpoints"><img src="/w/images/thumb/9/96/Hitpoints_icon.png/20px-Hitpoints_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/9/96/Hitpoints_icon.png 1.5x" loading="lazy"></a></span> <a href="/w/Hitpoints" title="Hitpoints">Hitpoints</a>:</td><td><input type="number" id="stat-hp" value="10" min="1" max="100" /></td></tr> | |
| <tr><td><span typeof="mw:File"><a href="/w/Accuracy" title="Accuracy"><img src="/w/images/thumb/d/df/Accuracy_icon.png/20px-Accuracy_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/d/df/Accuracy_icon.png 1.5x" loading="lazy"></a></span> <a href="/w/Accuracy" title="Accuracy">Accuracy</a>:</td><td><input type="number" id="stat-accuracy" value="1" min="1" max="100" /></td></tr>
| |
| <tr><td><span typeof="mw:File"><a href="/w/Accuracy" title="Accuracy"><img src="/w/images/thumb/d/df/Accuracy_icon.png/20px-Accuracy_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/d/df/Accuracy_icon.png 1.5x" loading="lazy"></a></span> <a href="/w/Strength" title="Strength">Strength</a>:</td><td><input type="number" id="stat-strength" value="1" min="1" max="100" /></td></tr> | |
| <tr><td><span typeof="mw:File"><a href="/w/Defense" title="Defense"><img src="/w/images/thumb/e/e5/Defense_icon.png/20px-Defense_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/e/e5/Defense_icon.png 1.5x" loading="lazy"></a></span> <a href="/w/Defense" title="Defense">Defense</a>:</td><td><input type="number" id="stat-defense" value="1" min="1" max="100" /></td></tr>
| |
| <tr><td><span typeof="mw:File"><a href="/w/Magic" title="Magic"><img src="/w/images/thumb/5/5c/Magic_icon.png/20px-Magic_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/5/5c/Magic_icon.png 1.5x" loading="lazy"></a></span> <a href="/w/Magic" title="Magic">Magic</a>:</td><td><input type="number" id="stat-magic" value="1" min="1" max="100" /></td></tr>
| |
| <tr><td><span typeof="mw:File"><a href="/w/Range" title="Range"><img src="/w/images/thumb/1/13/Range_icon.png/20px-Range_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/1/13/Range_icon.png 1.5x" loading="lazy"></a></span> <a href="/w/Range" title="Range">Range</a>:</td><td><input type="number" id="stat-range" value="1" min="1" max="100" /></td></tr> | |
| </table> | | </table> |
| | | <div class="combat-calc__results"> |
| <div class="results-box"> | | <b>Combat Level:</b> <span id="combat-result">—</span> |
| <b>Combat Level:</b> <span id="combat-result">—</span><br /> | | <div class="combat-calc__hints" id="level-hints" style="font-style: italic;">—</div> |
| <div id="level-hints" style="margin-top: 6px; font-style: italic;">—</div> | |
| </div> | | </div> |
| </form> | | </div> |
|
| |
|
| <script> | | <script> |
| function calculateCombatLevel() { | | (function() { |
| const hp = Math.min(100, Math.max(1, parseInt(document.getElementById("stat-hp").value) || 1)); | | const STAT_CONFIG = [ |
| const acc = Math.min(100, Math.max(1, parseInt(document.getElementById("stat-accuracy").value) || 1)); | | { id: 'hp', name: 'Hitpoints', icon: '/w/images/thumb/9/96/Hitpoints_icon.png/20px-Hitpoints_icon.png' }, |
| const str = Math.min(100, Math.max(1, parseInt(document.getElementById("stat-strength").value) || 1)); | | { id: 'accuracy', name: 'Accuracy', icon: '/w/images/thumb/d/df/Accuracy_icon.png/20px-Accuracy_icon.png' }, |
| const def = Math.min(100, Math.max(1, parseInt(document.getElementById("stat-defense").value) || 1)); | | { id: 'strength', name: 'Strength', icon: '/w/images/thumb/1/1b/Strength_icon.png/20px-Strength_icon.png' }, |
| const mag = Math.min(100, Math.max(1, parseInt(document.getElementById("stat-magic").value) || 1)); | | { id: 'defense', name: 'Defense', icon: '/w/images/thumb/e/e5/Defense_icon.png/20px-Defense_icon.png' }, |
| const rng = Math.min(100, Math.max(1, parseInt(document.getElementById("stat-range").value) || 1)); | | { 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'); |
|
| |
|
| // Set clamped values back to inputs | | function generateStatRows() { |
| document.getElementById("stat-hp").value = hp;
| | tbody.innerHTML = STAT_CONFIG.map(stat => ` |
| document.getElementById("stat-accuracy").value = acc;
| | <tr> |
| document.getElementById("stat-strength").value = str;
| | <td> |
| document.getElementById("stat-defense").value = def;
| | <span class="combat-calc__stat-label"> |
| document.getElementById("stat-magic").value = mag;
| | <img src="${stat.icon}" alt="${stat.name} icon" width="20" height="20"> |
| document.getElementById("stat-range").value = rng;
| | <a href="/w/${stat.name}" title="${stat.name}">${stat.name}</a> |
| | | </span> |
| const base = hp + acc + str + def + mag / 4 + rng / 4; | | </td> |
| const combat = base / 3.75;
| | <td style="width: 80px;"> |
| document.getElementById("combat-result").textContent = combat.toFixed(2);
| | <input type="number" id="stat-${stat.id}" value="${stat.id === 'hp' ? 10 : 1}" min="1" max="100" data-stat-id="${stat.id}"> |
| | | </td> |
| const nextLevel = Math.floor(combat) + 1;
| | </tr> |
| | | `).join(''); |
| if (combat >= nextLevel) {
| | } |
| document.getElementById("level-hints").textContent = "You have reached or exceeded the next combat level!"; | | 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 needed = (nextLevel * 3.75) - base; | | const nextLevel = Math.floor(combatLvl) + 1; |
| | | const needed = (nextLevel * 3.75) - base; |
| // Calculate needed points per stat
| | if (needed <= 0) { |
| const neededHP = Math.max(1, Math.ceil(needed));
| | levelHintsEl.innerHTML = "You are at the threshold for the next level!"; |
| const neededAcc = neededHP;
| | return; |
| const neededStr = neededHP;
| |
| const neededDef = neededHP;
| |
| const neededMag = Math.max(1, Math.ceil(needed * 4));
| |
| const neededRng = neededMag;
| |
| | |
| const hints = [
| |
| `+${neededHP} <span typeof="mw:File"><a href="/w/Hitpoints" title="Hitpoints"><img src="/w/images/thumb/9/96/Hitpoints_icon.png/20px-Hitpoints_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/9/96/Hitpoints_icon.png 1.5x" loading="lazy"></a></span>`,
| |
| `+${neededAcc} <span typeof="mw:File"><a href="/w/Accuracy" title="Accuracy"><img src="/w/images/thumb/d/df/Accuracy_icon.png/20px-Accuracy_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/d/df/Accuracy_icon.png 1.5x" loading="lazy"></a></span>`, | |
| `+${neededStr} <span typeof="mw:File"><a href="/w/Strength" title="Strength"><img src="/w/images/thumb/1/1b/Strength_icon.png/20px-Strength_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/1/1b/Strength_icon.png 1.5x" loading="lazy"></a></span>`,
| |
| `+${neededDef} <span typeof="mw:File"><a href="/w/Defense" title="Defense"><img src="/w/images/thumb/e/e5/Defense_icon.png/20px-Defense_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/e/e5/Defense_icon.png 1.5x" loading="lazy"></a></span>`,
| |
| `+${neededMag} <span typeof="mw:File"><a href="/w/Magic" title="Magic"><img src="/w/images/thumb/5/5c/Magic_icon.png/20px-Magic_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/5/5c/Magic_icon.png 1.5x" loading="lazy"></a></span>`,
| |
| `+${neededRng} <span typeof="mw:File"><a href="/w/Range" title="Range"><img src="/w/images/thumb/1/13/Range_icon.png/20px-Range_icon.png" decoding="async" width="20" height="20" class="mw-file-element" srcset="/w/images/1/13/Range_icon.png 1.5x" loading="lazy"></a></span>`
| |
| ];
| |
| | |
| //document.getElementById("level-hints").innerHTML = `To reach level ${nextLevel}: ${hints.join(" or ")}`;
| |
| document.getElementById("level-hints").innerHTML = `<div>To reach level ${nextLevel}, you would need one of the following:</div><ul><li>${hints.join("</li><li>")}</li></ul>`;
| |
| }
| |
| | |
| function getStatFromDom(doc, skillName) {
| |
| const nameDivs = doc.querySelectorAll('.hs_data__row__name');
| |
| for (const nameDiv of nameDivs) {
| |
| if (nameDiv.textContent.toLowerCase().includes(skillName.toLowerCase())) { | |
| const levelDiv = nameDiv.parentElement.querySelector('.hs_data__row__level'); | |
| if (levelDiv) { | |
| const lvl = parseInt(levelDiv.textContent.trim());
| |
| if (!isNaN(lvl)) return lvl;
| |
| }
| |
| } | | } |
| | 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>`; |
| } | | } |
| return 1; // fallback | | function updateCombatLevel() { |
| }
| | const stats = { |
| | | hp: getStatValue('hp'), accuracy: getStatValue('accuracy'), strength: getStatValue('strength'), |
| function lookupStats() {
| | defense: getStatValue('defense'), magic: getStatValue('magic'), range: getStatValue('range') |
| const username = document.getElementById("username").value.trim();
| | }; |
| if (!username) {
| | const base = stats.hp + stats.accuracy + stats.strength + stats.defense + (stats.magic / 4) + (stats.range / 4); |
| alert("Please enter a username."); | | const combat = base / 3.75; |
| return; | | combatResultEl.textContent = combat.toFixed(2); |
| | levelHintsEl.style.fontStyle = 'normal'; |
| | renderHints(combat, base); |
| } | | } |
| | | async function lookupStats() { |
| fetch(`https://highspell.com/hiscores/player/${encodeURIComponent(username)}`)
| | const username = usernameInput.value.trim(); |
| .then(response => {
| | if (!username) { levelHintsEl.innerHTML = 'Please enter a username.'; return; } |
| if (!response.ok) throw new Error("Failed to fetch hiscores"); | | lookupBtn.disabled = true; |
| return response.text(); | | lookupBtn.textContent = '...'; |
| })
| | levelHintsEl.textContent = `Searching for ${username}...`; |
| .then(html => {
| | 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; |
| document.getElementById("stat-hp").value = getStatFromDom(doc, "Hitpoints"); | | doc.querySelectorAll('.hs_data__row').forEach(row => { |
| document.getElementById("stat-accuracy").value = getStatFromDom(doc, "Accuracy");
| | const skillName = row.querySelector('.hs_data__row__name')?.textContent.trim(); |
| document.getElementById("stat-strength").value = getStatFromDom(doc, "Strength");
| | const config = STAT_CONFIG.find(s => s.name === skillName); |
| document.getElementById("stat-defense").value = getStatFromDom(doc, "Defense");
| | if (config) { |
| document.getElementById("stat-magic").value = getStatFromDom(doc, "Magic");
| | const level = row.querySelector('.hs_data__row__level')?.textContent.trim(); |
| document.getElementById("stat-range").value = getStatFromDom(doc, "Range"); | | document.getElementById(`stat-${config.id}`).value = parseInt(level, 10) || 1; |
| | | found = true; |
| calculateCombatLevel(); | | } |
| }) | | }); |
| .catch(err => {
| | if (found) updateCombatLevel(); |
| alert("Lookup failed. Please check the username or try again later."); | | else throw new Error(`Could not parse stats for ${username}.`); |
| console.error(err); | | } catch (err) { |
| }); | | levelHintsEl.style.fontStyle = 'italic'; |
| } | | levelHintsEl.innerHTML = err.message; |
| | | combatResultEl.textContent = 'Error'; |
| document.querySelectorAll('#combat-form input').forEach(input => { | | } finally { |
| input.addEventListener('input', calculateCombatLevel);
| | lookupBtn.disabled = false; |
| });
| | lookupBtn.textContent = 'Lookup'; |
| | | } |
| document.addEventListener('DOMContentLoaded', calculateCombatLevel);
| | } |
| | 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> |
| </html>
| |