const form = document.getElementById('af-calculator'); const weaponSelect = document.getElementById('af-weapon-select'); const outputs = { shotDamage: document.querySelector('[data-output="shotDamage"]'), burstDps: document.querySelector('[data-output="burstDps"]'), sustainedDps: document.querySelector('[data-output="sustainedDps"]'), timeToEmpty: document.querySelector('[data-output="timeToEmpty"]'), magDamage: document.querySelector('[data-output="magDamage"]'), ttk: document.querySelector('[data-output="ttk"]') }; const resetButton = document.getElementById('af-reset'); const copyButton = document.getElementById('af-copy'); const weaponsById = new Map(); const numberOrNull = (value) => { const parsed = Number.parseFloat(value); return Number.isFinite(parsed) ? parsed : null; }; const formatValue = (value, suffix = '') => { if (value === null) { return '--'; } const formatted = Number.isFinite(value) ? value.toFixed(2) : '--'; return `${formatted}${suffix}`; }; const calculate = () => { const data = new FormData(form); const baseDamage = numberOrNull(data.get('baseDamage')); const projectiles = numberOrNull(data.get('projectiles')) ?? 1; const rpm = numberOrNull(data.get('rpm')); const magazine = numberOrNull(data.get('magazine')); const reload = numberOrNull(data.get('reload')); const targetHealth = numberOrNull(data.get('targetHealth')); if (!baseDamage || !rpm) { Object.values(outputs).forEach((node) => { node.textContent = '--'; }); return; } const shotDamage = baseDamage * projectiles; const shotsPerSecond = rpm / 60; const burstDps = shotDamage * shotsPerSecond; const magDamage = magazine ? shotDamage * magazine : null; const timeToEmpty = magazine ? magazine / shotsPerSecond : null; let sustainedDps = null; if (magDamage !== null && reload !== null && timeToEmpty !== null && (timeToEmpty + reload) > 0) { sustainedDps = magDamage / (timeToEmpty + reload); } let ttk = null; if (targetHealth && burstDps > 0) { ttk = targetHealth / burstDps; } outputs.shotDamage.textContent = formatValue(shotDamage); outputs.burstDps.textContent = formatValue(burstDps); outputs.sustainedDps.textContent = formatValue(sustainedDps); outputs.timeToEmpty.textContent = formatValue(timeToEmpty, 's'); outputs.magDamage.textContent = formatValue(magDamage); outputs.ttk.textContent = formatValue(ttk, 's'); }; const setFormValue = (name, value) => { const field = form.elements[name]; if (!field) { return; } field.value = value ?? ''; }; const applyWeapon = (weapon) => { if (!weapon) { return; } setFormValue('weaponName', weapon.name ?? ''); setFormValue('baseDamage', weapon.damage ?? ''); setFormValue('projectiles', weapon.damage_multiplier ?? 1); setFormValue('rpm', weapon.rpm ?? ''); setFormValue('magazine', weapon.mag_size ?? ''); setFormValue('reload', weapon.reload_time ?? ''); calculate(); }; const loadWeaponLibrary = async () => { if (!weaponSelect) { return; } const source = weaponSelect.dataset.source; if (!source) { return; } try { const response = await fetch(source); if (!response.ok) { throw new Error('Failed to load weapons'); } const weapons = await response.json(); const normalized = weapons .filter((weapon) => weapon && weapon.name) .map((weapon) => ({ id: weapon.id ?? weapon.name, name: weapon.name, damage: weapon.damage, damage_multiplier: weapon.damage_multiplier ?? 1, rpm: weapon.rpm, mag_size: weapon.mag_size, reload_time: weapon.reload_time })) .sort((a, b) => a.name.localeCompare(b.name)); normalized.forEach((weapon) => { const key = String(weapon.id); weaponsById.set(key, weapon); const option = document.createElement('option'); option.value = key; option.textContent = weapon.name; weaponSelect.appendChild(option); }); } catch (error) { console.error('Weapon library load failed', error); const option = document.createElement('option'); option.textContent = 'Weapon library unavailable'; option.disabled = true; weaponSelect.appendChild(option); } }; form.addEventListener('input', calculate); resetButton.addEventListener('click', () => { form.reset(); calculate(); }); if (weaponSelect) { weaponSelect.addEventListener('change', (event) => { const selected = weaponsById.get(event.target.value); if (!selected) { return; } applyWeapon(selected); }); } copyButton.addEventListener('click', async () => { const data = new FormData(form); const name = data.get('weaponName') || 'Unnamed loadout'; const payload = [ `Loadout: ${name}`, `Damage per shot: ${outputs.shotDamage.textContent}`, `Burst DPS: ${outputs.burstDps.textContent}`, `Sustained DPS: ${outputs.sustainedDps.textContent}`, `Time to empty: ${outputs.timeToEmpty.textContent}`, `Magazine damage: ${outputs.magDamage.textContent}`, `Time to eliminate: ${outputs.ttk.textContent}` ].join('\n'); try { await navigator.clipboard.writeText(payload); copyButton.textContent = 'Copied!'; setTimeout(() => { copyButton.textContent = 'Copy summary'; }, 2000); } catch (error) { console.error('Clipboard copy failed', error); copyButton.textContent = 'Copy failed'; setTimeout(() => { copyButton.textContent = 'Copy summary'; }, 2000); } }); calculate(); loadWeaponLibrary();