diff --git a/src/armor.html b/src/armor.html index 7187cb8..bb9ba80 100644 --- a/src/armor.html +++ b/src/armor.html @@ -1,275 +1,230 @@ - - - - - - - - - Erdtree - Armor Optimizer - - - - - - - - - - - - -
-

Armor Optimizer

-
- -
-
- -
- Settings - -
+ + + + + + + + Erdtree - Armor Optimizer + + + + + + + + + +
+

Armor Optimizer

+
+
+
+ +
+ Settings +
+
+ + +
+
+ + +
+
+ + +
+
+ Breakpoints +
- - + +
+
+
- - + +
+
+
- - + +
- -
- - Breakpoints - +
+
+ Sort by +
-
- - -
+ +
- +
+
-
- - -
+ +
- +
+
-
- - -
+ +
- -
- - Sort by - +
+
-
- - -
+ +
- +
+
-
- - -
+ +
- +
+
+ Extras +
-
- - -
+ +
- +
+
-
- - -
+ +
+
+
+ Locked Items + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+ Results +
+
+
+
+ +
+ +
+
    +
  • + Allowed Armor +
  • +
+
+
+
+ + -
-
- - -
-
- -
- - Extras - -
-
- - -
-
- -
-
- - -
-
- -
- - Locked Items - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
- - -
- Results -
- -
-
    -
    - - -
    - - -
    -
      -
    • - Allowed Armor -
    • -
    -
    -
    -
    - - - - + \ No newline at end of file diff --git a/src/data/helmets.json b/src/data/helmets.json index 5c091f0..dfb3f49 100644 --- a/src/data/helmets.json +++ b/src/data/helmets.json @@ -58,6 +58,7 @@ }, "ash-of-war-scarab": { "name": "Ash-of-War Scarab", + "id": "ash-of-war-scarab", "defenses": [-5.8, -5.6, -5.8, -5.8, -4.9, -4.9, -4.9, -5.1], "resistances": [42, 22, 27, 26], "poise": 2, @@ -553,6 +554,7 @@ "weight": 1.7 }, "godskin-noble-hood": { + "id": "godskin-noble-hood", "name": "Godskin Noble Hood", "defenses": [1.4, 2.8, 1.8, 1.4, 4.5, 4, 4.2, 4.8], "resistances": [16, 10, 27, 29], @@ -1200,6 +1202,7 @@ "weight": 2.2 }, "sanguine-noble-hood": { + "id": "sanguine-noble-hood", "name": "Sanguine Noble Hood", "defenses": [1.4, 0.9, 0.9, 0.9, 4.6, 3.8, 4.5, 4.6], "resistances": [18, 5, 29, 27], diff --git a/src/script/armor.js b/src/script/armor.js index b3c5745..f4f6f8f 100644 --- a/src/script/armor.js +++ b/src/script/armor.js @@ -1,80 +1,94 @@ -const HELMETS = fetch("/data/helmets.json") - .then((response) => response.json()) - .catch((error) => console.log(error)); -const CHESTPIECES = fetch("/data/chestpieces.json") - .then((response) => response.json()) - .catch((error) => console.log(error)); -const GAUNTLETS = fetch("/data/gauntlets.json") - .then((response) => response.json()) - .catch((error) => console.log(error)); -const LEGGINGS = fetch("/data/leggings.json") - .then((response) => response.json()) - .catch((error) => console.log(error)); +let HELMETS; +let CHESTPIECES; +let GAUNTLETS; +let LEGGINGS; +let EQUIPMENT; -var helmets; -var chestpieces; -var gauntlets; -var leggings; +const populate = (select, items) => items.forEach(item => select.options.add(new Option(item.name, item.id))); +const selected = select => select.options[select.selectedIndex]; async function init() { - // populate filter selects - populateSelect("locked-option", "select-helmet", Object.values(await HELMETS)); - populateSelect("locked-option", "select-chestpiece", Object.values(await CHESTPIECES)); - populateSelect("locked-option", "select-gauntlets", Object.values(await GAUNTLETS)); - populateSelect("locked-option", "select-leggings", Object.values(await LEGGINGS)); + HELMETS = await fetch("/data/helmets.json").then(response => response.json()); + CHESTPIECES = await fetch("/data/chestpieces.json").then(response => response.json()); + GAUNTLETS = await fetch("/data/gauntlets.json").then(response => response.json()); + LEGGINGS = await fetch("/data/leggings.json").then(response => response.json()); + EQUIPMENT = [HELMETS, CHESTPIECES, GAUNTLETS, LEGGINGS]; - update(true); + // populate filter selects + populate(document.getElementById("locked-helmet"), Object.values(HELMETS)); + populate(document.getElementById("locked-chestpiece"), Object.values(CHESTPIECES)); + populate(document.getElementById("locked-gauntlets"), Object.values(GAUNTLETS)); + populate(document.getElementById("locked-leggings"), Object.values(LEGGINGS)); + + update(); } -async function update() { +function update() { // remove any previous results - Array.from(document.getElementsByClassName("sort-result")).forEach((elem) => elem.parentNode.removeChild(elem)); + document.getElementById("results").innerHTML = ""; - // clamp equip load values to reasonable values - // [...document.getElementsByName("equip-load")].forEach(el => el.value = Math.max(el.value, 0.0)); - - // update budget + // get budget & sort order let budget = equipLoadBudget(); document.getElementById("equip-load-budget").value = budget.toFixed(1); - let sortBy = [...document.getElementsByName("sorting-order")].find((elem) => elem.checked).id; + let sortBy = [...document.getElementsByName("sorting-order")].find(elem => elem.checked).id; // get locked items - let lockedItems = await Promise.all([HELMETS, CHESTPIECES, GAUNTLETS, LEGGINGS]).then((allItems) => { - return [...document.getElementsByName("locked-items")] - .map((select, i) => Object.values(allItems[i])[select.selectedIndex]) - .filter((item) => !item.id.startsWith("no-")); - }); + let lockedItems = [...document.getElementsByName("locked-items")] + .map((select, i) => Object.values(EQUIPMENT[i])[select.selectedIndex]) + .filter(item => !item.id.startsWith("no-")); // pre-sort and eliminate some equipment - helmets = eliminate(Object.values(await HELMETS), sortBy, lockedItems); - chestpieces = eliminate(Object.values(await CHESTPIECES), sortBy, lockedItems); - gauntlets = eliminate(Object.values(await GAUNTLETS), sortBy, lockedItems); - leggings = eliminate(Object.values(await LEGGINGS), sortBy, lockedItems); - let selection = permutations(budget, lockedItems); + let helmets = dominated(Object.values(HELMETS), sortBy, lockedItems); + let chestpieces = dominated(Object.values(CHESTPIECES), sortBy, lockedItems); + let gauntlets = dominated(Object.values(GAUNTLETS), sortBy, lockedItems); + let leggings = dominated(Object.values(LEGGINGS), sortBy, lockedItems); + + let selection = permutations([helmets, chestpieces, gauntlets, leggings], budget, lockedItems); // find best sets to display let best = knapSack(selection, sortBy); // show best sets under budget - populateResults("sort-result", "sort-results", best); + let template = document.getElementById("result"); + let destination = document.getElementById("results"); + best.forEach(set => { + let clone = template.content.cloneNode(true); + let tbody = clone.children[1]; + let rows = tbody.children; + + [...rows].slice(1).forEach((row, i) => { + row.children[0].innerText = set[i].name; + row.children[1].innerHTML = itemStatsToString(set[i]); + }); + rows[0].children[1].innerHTML = setStatsToString(set); + + destination.appendChild(clone); + }); + + // display error message if sets is empty + if (best.length == 0) { + let error = document.createElement("b"); + error.innerHTML = "No sets found under budget"; + destination.appendChild(error); + } } -function reset() { - [...document.getElementsByName("locked-items")].forEach((select) => (select.selectedIndex = 0)); +function resetAll() { + [...document.getElementsByName("locked-items")].forEach(select => (select.selectedIndex = 0)); update(); } -function eliminate(list, sortBy, lockedItems) { - if (lockedItems.some((item) => list.includes(item))) { - return [list.find((item) => lockedItems.includes(item))]; +function dominated(itemList, sortBy, lockedItems) { + if (lockedItems.some(item => itemList.includes(item))) { + return [itemList.find(item => lockedItems.includes(item))]; } - let sorted = [...list]; + let sorted = [...itemList]; sorted.sort((a, b) => a.weight - b.weight); let approved = []; - sorted.forEach((item) => { - if (!approved.some((other) => fitness(item, sortBy) <= fitness(other, sortBy))) { + sorted.forEach(item => { + if (!approved.some(other => fitness(item, sortBy) <= fitness(other, sortBy))) { approved.push(item); } }); @@ -82,14 +96,14 @@ function eliminate(list, sortBy, lockedItems) { return approved; } -function permutations(budget, lockedItems) { - return helmets.flatMap((h) => { - return chestpieces.flatMap((c) => { - return gauntlets.flatMap((g) => { +function permutations([helmets, chestpieces, gauntlets, leggings], budget, lockedItems) { + return helmets.flatMap(h => { + return chestpieces.flatMap(c => { + return gauntlets.flatMap(g => { return leggings - .filter((l) => isAllowedSet([h, c, g, l], lockedItems)) - .filter((l) => budget > setWeight([h, c, g, l])) - .map((l) => [h, c, g, l]); + .filter(l => isAllowedSet([h, c, g, l], lockedItems)) + .filter(l => budget > setWeight([h, c, g, l])) + .map(l => [h, c, g, l]); }); }); }); @@ -121,12 +135,12 @@ function fitness(item, sortBy) { } } -const setWeight = (set) => (set.weight ??= set.reduce((total, item) => total + item.weight, 0)); +const setWeight = set => (set.weight ??= set.reduce((total, item) => total + item.weight, 0)); const setFitness = (set, sortBy) => (set.fitness ??= set.reduce((total, item) => total + fitness(item, sortBy), 0.0)); -const isAllowedSet = (set, lockedItems) => lockedItems.every((item) => set.includes(item)); +const isAllowedSet = (set, lockedItems) => lockedItems.every(item => set.includes(item)); function equipLoadBudget() { - let rollModifier = parseFloat([...document.getElementsByName("roll-type")].find((elem) => elem.checked).value); + let rollModifier = parseFloat([...document.getElementsByName("roll-type")].find(elem => elem.checked).value); let max = document.getElementById("max-equip-load").value || 0; let current = document.getElementById("current-equip-load").value || 0; @@ -134,42 +148,6 @@ function equipLoadBudget() { return parseFloat(Math.max((max - current) * rollModifier, 0.0)); } -// site rendering functions -function populateSelect(templateId, destinationId, items) { - let destination = document.getElementById(destinationId); - items.forEach((item) => { - destination.options.add(new Option(item.name, item.id)); - }); -} - -async function populateResults(templateId, destinationId, sets) { - let template = document.getElementById(templateId); - let destination = document.getElementById(destinationId); - - (await sets).forEach((set) => { - let clone = template.content.cloneNode(true); - - let li = clone.children[0]; - let table = li.children[0]; - let tbody = table.children[1]; - let rows = tbody.children; - - rows[1].children[0].innerText = set[0].name; - rows[2].children[0].innerText = set[1].name; - rows[3].children[0].innerText = set[2].name; - rows[4].children[0].innerText = set[3].name; - - rows[1].children[1].innerHTML = itemStatsToString(set[0]); - rows[2].children[1].innerHTML = itemStatsToString(set[1]); - rows[3].children[1].innerHTML = itemStatsToString(set[2]); - rows[4].children[1].innerHTML = itemStatsToString(set[3]); - - rows[0].children[1].innerHTML = setStatsToString(set); - - destination.appendChild(clone); - }); -} - function itemStatsToString(item) { let weight = item.weight.toFixed(1) + " wgt., "; let poise = item.poise + " poise, "; @@ -187,7 +165,7 @@ function itemStatsToString(item) { // let elemental = item.defenses.slice(4, 8).reduce((total, defense, i) => total + defense.toFixed(1) + " " + DEFENSE_NAMES[i + 4] + ", ", ""); let resistances = item.resistances.reduce( (total, res, i) => total + res + " " + ["immunity", "robustness", "focus", "vitality"][i] + ", ", - "" + "", ); return weight + poise + physical + elemental + "
    " + resistances; @@ -199,7 +177,7 @@ function setStatsToString(set) { poise: set[0].poise + set[1].poise + set[2].poise + set[3].poise, defenses: set[0].defenses.map((stat, i) => stat + set[1].defenses[i] + set[2].defenses[i] + set[3].defenses[i]), resistances: set[0].resistances.map( - (stat, i) => stat + set[1].resistances[i] + set[2].resistances[i] + set[3].resistances[i] + (stat, i) => stat + set[1].resistances[i] + set[2].resistances[i] + set[3].resistances[i], ), };