Improve clarity in text content across templates, enhance weapon matching logic in filterWeapons.js, and update designs for better consistency.
This commit is contained in:
+77
-22
@@ -29,6 +29,16 @@ let allWeapons = [];
|
|||||||
let currentWeaponTypes = [];
|
let currentWeaponTypes = [];
|
||||||
let currentOperativeCategory = null;
|
let currentOperativeCategory = null;
|
||||||
|
|
||||||
|
const normalizeToArray = (value) => {
|
||||||
|
if (!value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.filter(Boolean);
|
||||||
|
}
|
||||||
|
return [value].filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
const numberOrNull = (value) => {
|
const numberOrNull = (value) => {
|
||||||
const parsed = Number.parseFloat(value);
|
const parsed = Number.parseFloat(value);
|
||||||
return Number.isFinite(parsed) ? parsed : null;
|
return Number.isFinite(parsed) ? parsed : null;
|
||||||
@@ -43,23 +53,39 @@ const formatValue = (value, suffix = '') => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const matchesOperative = (modifierOperative) => {
|
const matchesOperative = (modifierOperative) => {
|
||||||
if (!modifierOperative) {
|
const required = normalizeToArray(modifierOperative);
|
||||||
|
if (!required.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!currentOperativeCategory) {
|
if (!currentOperativeCategory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return modifierOperative.toLowerCase() === currentOperativeCategory.toLowerCase();
|
const operative = currentOperativeCategory.toLowerCase();
|
||||||
|
return required.some((entry) => entry.toLowerCase() === operative);
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchesWeaponType = (appliesTo) => {
|
const matchesWeaponType = (appliesTo) => {
|
||||||
if (!appliesTo) {
|
const required = normalizeToArray(appliesTo);
|
||||||
|
if (!required.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!currentWeaponTypes.length) {
|
if (!currentWeaponTypes.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return currentWeaponTypes.map((type) => type.toUpperCase()).includes(appliesTo.toUpperCase());
|
const available = currentWeaponTypes.map((type) => type.toUpperCase());
|
||||||
|
return required.some((type) => available.includes(type.toUpperCase()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchesWeaponExclusion = (appliesNotTo) => {
|
||||||
|
const excluded = normalizeToArray(appliesNotTo);
|
||||||
|
if (!excluded.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!currentWeaponTypes.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const available = currentWeaponTypes.map((type) => type.toUpperCase());
|
||||||
|
return !excluded.some((type) => available.includes(type.toUpperCase()));
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyBuffValue = (current, value, buffType) => {
|
const applyBuffValue = (current, value, buffType) => {
|
||||||
@@ -79,7 +105,11 @@ const applyModifierToStats = (modifier, stats) => {
|
|||||||
if (!modifier) {
|
if (!modifier) {
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
if (!matchesOperative(modifier.operatives) || !matchesWeaponType(modifier.appliesTo)) {
|
if (
|
||||||
|
!matchesOperative(modifier.operatives)
|
||||||
|
|| !matchesWeaponType(modifier.appliesTo)
|
||||||
|
|| !matchesWeaponExclusion(modifier.appliesNotTo)
|
||||||
|
) {
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,17 +171,18 @@ const updateActiveModifierList = (modifiers) => {
|
|||||||
const calculate = () => {
|
const calculate = () => {
|
||||||
const data = new FormData(form);
|
const data = new FormData(form);
|
||||||
const baseDamage = numberOrNull(data.get('baseDamage'));
|
const baseDamage = numberOrNull(data.get('baseDamage'));
|
||||||
const projectiles = numberOrNull(data.get('projectiles')) ?? 1;
|
const projectilesRaw = numberOrNull(data.get('projectiles'));
|
||||||
|
const projectiles = projectilesRaw !== null ? Math.max(projectilesRaw, 1) : 1;
|
||||||
const rpm = numberOrNull(data.get('rpm'));
|
const rpm = numberOrNull(data.get('rpm'));
|
||||||
const magazine = numberOrNull(data.get('magazine'));
|
const magazine = numberOrNull(data.get('magazine'));
|
||||||
const reload = numberOrNull(data.get('reload'));
|
const reload = numberOrNull(data.get('reload'));
|
||||||
const targetHealth = numberOrNull(data.get('targetHealth'));
|
const targetHealth = numberOrNull(data.get('targetHealth'));
|
||||||
|
|
||||||
if (!baseDamage || !rpm) {
|
if (baseDamage === null || rpm === null || baseDamage < 0 || rpm <= 0) {
|
||||||
Object.values(outputs).forEach((node) => {
|
Object.values(outputs).forEach((node) => {
|
||||||
node.textContent = '--';
|
node.textContent = '--';
|
||||||
});
|
});
|
||||||
updateActiveModifierList([]);
|
updateActiveModifierList(getSelectedModifiers());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,19 +196,31 @@ const calculate = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const shotDamage = stats.damage * projectiles;
|
const safeStats = {
|
||||||
const shotsPerSecond = stats.rpm / 60;
|
damage: Math.max(stats.damage, 0),
|
||||||
|
rpm: Math.max(stats.rpm, 0),
|
||||||
|
reload: Number.isFinite(stats.reload) ? Math.max(stats.reload, 0) : stats.reload
|
||||||
|
};
|
||||||
|
|
||||||
|
const magazineSize = Number.isFinite(magazine) && magazine > 0 ? magazine : null;
|
||||||
|
const shotDamage = safeStats.damage * projectiles;
|
||||||
|
const shotsPerSecond = safeStats.rpm / 60;
|
||||||
const burstDps = shotDamage * shotsPerSecond;
|
const burstDps = shotDamage * shotsPerSecond;
|
||||||
const magDamage = magazine ? shotDamage * magazine : null;
|
const magDamage = magazineSize ? shotDamage * magazineSize : null;
|
||||||
const timeToEmpty = magazine ? magazine / shotsPerSecond : null;
|
const timeToEmpty = magazineSize ? magazineSize / shotsPerSecond : null;
|
||||||
|
|
||||||
let sustainedDps = null;
|
let sustainedDps = null;
|
||||||
if (magDamage !== null && stats.reload !== null && timeToEmpty !== null && (timeToEmpty + stats.reload) > 0) {
|
if (
|
||||||
sustainedDps = magDamage / (timeToEmpty + stats.reload);
|
magDamage !== null
|
||||||
|
&& Number.isFinite(safeStats.reload)
|
||||||
|
&& timeToEmpty !== null
|
||||||
|
&& (timeToEmpty + safeStats.reload) > 0
|
||||||
|
) {
|
||||||
|
sustainedDps = magDamage / (timeToEmpty + safeStats.reload);
|
||||||
}
|
}
|
||||||
|
|
||||||
let ttk = null;
|
let ttk = null;
|
||||||
if (targetHealth && burstDps > 0) {
|
if (targetHealth !== null && targetHealth >= 0 && burstDps > 0) {
|
||||||
ttk = targetHealth / burstDps;
|
ttk = targetHealth / burstDps;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +263,8 @@ const normalizeModifier = (modifier, fallbackLabel) => {
|
|||||||
id: modifier.id,
|
id: modifier.id,
|
||||||
label: modifier.label ?? fallbackLabel ?? 'Unnamed modifier',
|
label: modifier.label ?? fallbackLabel ?? 'Unnamed modifier',
|
||||||
operatives: modifier.operatives,
|
operatives: modifier.operatives,
|
||||||
appliesTo: modifier.appliesTo
|
appliesTo: modifier.appliesTo,
|
||||||
|
appliesNotTo: modifier.appliesNotTo
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Number.isFinite(modifier.value) && modifier.type) {
|
if (Number.isFinite(modifier.value) && modifier.type) {
|
||||||
@@ -241,7 +285,7 @@ const inferSlot = (weapon) => {
|
|||||||
return directSlot;
|
return directSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
const types = weapon.types ?? weapon.guntype ?? [];
|
const types = normalizeToArray(weapon.types ?? weapon.guntype ?? []);
|
||||||
if (types.includes('SECONDARY')) {
|
if (types.includes('SECONDARY')) {
|
||||||
return 'SECONDARY';
|
return 'SECONDARY';
|
||||||
}
|
}
|
||||||
@@ -251,7 +295,9 @@ const inferSlot = (weapon) => {
|
|||||||
return 'PRIMARY';
|
return 'PRIMARY';
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeWeapon = (weapon) => ({
|
const normalizeWeapon = (weapon) => {
|
||||||
|
const types = normalizeToArray(weapon.types ?? weapon.guntype ?? []);
|
||||||
|
return {
|
||||||
id: weapon.id ?? weapon.name,
|
id: weapon.id ?? weapon.name,
|
||||||
name: weapon.name,
|
name: weapon.name,
|
||||||
damage: weapon.damage,
|
damage: weapon.damage,
|
||||||
@@ -259,10 +305,11 @@ const normalizeWeapon = (weapon) => ({
|
|||||||
rpm: weapon.rpm,
|
rpm: weapon.rpm,
|
||||||
magazine: weapon.magazine ?? weapon.mag_size,
|
magazine: weapon.magazine ?? weapon.mag_size,
|
||||||
reload: weapon.reload ?? weapon.reload_time,
|
reload: weapon.reload ?? weapon.reload_time,
|
||||||
types: weapon.types ?? weapon.guntype ?? [],
|
types,
|
||||||
slot: inferSlot(weapon),
|
slot: inferSlot(weapon),
|
||||||
operatives: Array.isArray(weapon.operatives) ? weapon.operatives : []
|
operatives: Array.isArray(weapon.operatives) ? weapon.operatives : []
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const selectedOperativeId = () => operativeSelect?.value ?? '';
|
const selectedOperativeId = () => operativeSelect?.value ?? '';
|
||||||
const selectedSlot = () => (loadoutSlotSelect?.value ?? 'primary').toUpperCase();
|
const selectedSlot = () => (loadoutSlotSelect?.value ?? 'primary').toUpperCase();
|
||||||
@@ -355,7 +402,10 @@ const createModifierOption = (modifier, container) => {
|
|||||||
if (!modifier || !container) {
|
if (!modifier || !container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = modifier.id ?? `${modifier.label}-${Math.random().toString(36).slice(2)}`;
|
let id = modifier.id ?? `${modifier.label}-${Math.random().toString(36).slice(2)}`;
|
||||||
|
while (modifiersById.has(id)) {
|
||||||
|
id = `${id}-${Math.random().toString(36).slice(2)}`;
|
||||||
|
}
|
||||||
modifiersById.set(id, modifier);
|
modifiersById.set(id, modifier);
|
||||||
|
|
||||||
const wrapper = document.createElement('label');
|
const wrapper = document.createElement('label');
|
||||||
@@ -370,6 +420,9 @@ const createModifierOption = (modifier, container) => {
|
|||||||
if (modifier.appliesTo) {
|
if (modifier.appliesTo) {
|
||||||
checkbox.dataset.appliesTo = modifier.appliesTo;
|
checkbox.dataset.appliesTo = modifier.appliesTo;
|
||||||
}
|
}
|
||||||
|
if (modifier.appliesNotTo) {
|
||||||
|
checkbox.dataset.appliesNotTo = modifier.appliesNotTo;
|
||||||
|
}
|
||||||
|
|
||||||
const label = document.createElement('span');
|
const label = document.createElement('span');
|
||||||
label.textContent = modifier.label ?? 'Unnamed modifier';
|
label.textContent = modifier.label ?? 'Unnamed modifier';
|
||||||
@@ -479,10 +532,12 @@ const updateModifierAvailability = () => {
|
|||||||
checkboxes.forEach((checkbox) => {
|
checkboxes.forEach((checkbox) => {
|
||||||
const requiredOperative = checkbox.dataset.operatives;
|
const requiredOperative = checkbox.dataset.operatives;
|
||||||
const requiredType = checkbox.dataset.appliesTo;
|
const requiredType = checkbox.dataset.appliesTo;
|
||||||
|
const excludedType = checkbox.dataset.appliesNotTo;
|
||||||
const wrapper = checkbox.closest('.af-checkbox');
|
const wrapper = checkbox.closest('.af-checkbox');
|
||||||
const operativeMatch = requiredOperative ? matchesOperative(requiredOperative) : true;
|
const operativeMatch = requiredOperative ? matchesOperative(requiredOperative) : true;
|
||||||
const typeMatch = requiredType ? matchesWeaponType(requiredType) : true;
|
const typeMatch = requiredType ? matchesWeaponType(requiredType) : true;
|
||||||
const isEnabled = operativeMatch && typeMatch;
|
const exclusionMatch = excludedType ? matchesWeaponExclusion(excludedType) : true;
|
||||||
|
const isEnabled = operativeMatch && typeMatch && exclusionMatch;
|
||||||
checkbox.disabled = !isEnabled;
|
checkbox.disabled = !isEnabled;
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
wrapper.classList.toggle('is-disabled', !isEnabled);
|
wrapper.classList.toggle('is-disabled', !isEnabled);
|
||||||
|
|||||||
+1
-1
@@ -862,7 +862,7 @@ body.menu-open {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.af-details summary::before {
|
.af-details summary::before {
|
||||||
content: "▸";
|
content: ">";
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,12 +58,12 @@
|
|||||||
<div class="af-card af-card-builder">
|
<div class="af-card af-card-builder">
|
||||||
<div class="af-card-header">
|
<div class="af-card-header">
|
||||||
<h2>Loadout builder</h2>
|
<h2>Loadout builder</h2>
|
||||||
<p>Follow this flow: operative → slot → weapon → buffs. Results update right below.</p>
|
<p>Follow this flow: operative -> slot -> weapon -> buffs. Results update right below.</p>
|
||||||
</div>
|
</div>
|
||||||
<form class="af-form" id="af-calculator">
|
<form class="af-form" id="af-calculator">
|
||||||
<div class="af-flow-grid">
|
<div class="af-flow-grid">
|
||||||
<label>
|
<label>
|
||||||
Step 1 · Operative
|
Step 1 - Operative
|
||||||
<select id="af-operative-select" name="operativeSelect"
|
<select id="af-operative-select" name="operativeSelect"
|
||||||
data-source="{{ url_for('static', filename='json/operative.json') }}">
|
data-source="{{ url_for('static', filename='json/operative.json') }}">
|
||||||
<option value="">No operative selected</option>
|
<option value="">No operative selected</option>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Step 2 · Loadout slot
|
Step 2 - Loadout slot
|
||||||
<select id="af-loadout-slot" name="loadoutSlot" disabled>
|
<select id="af-loadout-slot" name="loadoutSlot" disabled>
|
||||||
<option value="primary">Primary</option>
|
<option value="primary">Primary</option>
|
||||||
<option value="secondary">Secondary</option>
|
<option value="secondary">Secondary</option>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
Step 3 · Weapon
|
Step 3 - Weapon
|
||||||
<select name="weaponSelect" id="af-weapon-select"
|
<select name="weaponSelect" id="af-weapon-select"
|
||||||
data-source="{{ url_for('static', filename='json/data.json') }}">
|
data-source="{{ url_for('static', filename='json/data.json') }}">
|
||||||
<option value="">Custom entry</option>
|
<option value="">Custom entry</option>
|
||||||
@@ -253,3 +253,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user