103 lines
3.5 KiB
JavaScript
103 lines
3.5 KiB
JavaScript
const form = document.getElementById('af-calculator');
|
|
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 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');
|
|
};
|
|
|
|
form.addEventListener('input', calculate);
|
|
|
|
resetButton.addEventListener('click', () => {
|
|
form.reset();
|
|
calculate();
|
|
});
|
|
|
|
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();
|