From 0f4dba258f3cc65ce07ff81c5ad0f80c84555f1a Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 22 May 2022 03:21:34 -0700 Subject: [PATCH] Proof of concept (compute nodes to get items from names and highlight) --- builder/index.html | 3 +- js/builder.js | 691 +--------------------------------------- js/builder_graph.js | 21 ++ js/computation_graph.js | 71 ++++- js/load.js | 86 ++--- js/optimize.js | 101 ++++++ js/utils.js | 27 +- 7 files changed, 227 insertions(+), 773 deletions(-) create mode 100644 js/builder_graph.js create mode 100644 js/optimize.js diff --git a/builder/index.html b/builder/index.html index 7a89a26..d7a802c 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1402,10 +1402,11 @@ + - + diff --git a/js/builder.js b/js/builder.js index 3455ee2..1b18b96 100644 --- a/js/builder.js +++ b/js/builder.js @@ -1,4 +1,3 @@ - function getItemNameFromID(id) { if (redirectMap.has(id)) { return getItemNameFromID(redirectMap.get(id)); @@ -51,7 +50,7 @@ function decodeBuild(url_tag) { let skillpoints = [0, 0, 0, 0, 0]; let level = 106; - version_number = parseInt(version) + let version_number = parseInt(version) //equipment (items) // TODO: use filters if (version_number < 4) { @@ -147,8 +146,6 @@ function decodeBuild(url_tag) { for (let i in powderInputs) { setValue(powderInputs[i], powdering[i]); } - - calculateBuild(save_skp, skillpoints); } } @@ -213,589 +210,6 @@ function encodeBuild() { } } -function calculateBuild(save_skp, skp){ - try { - resetEditableIDs(); - if (player_build) { - reset_powder_specials(); - updateBoosts("skip", false); - updatePowderSpecials("skip", false); - } - let weaponName = getValue(equipmentInputs[8]); - //bruh @hpp - if (weaponName.startsWith("Morph-")) { - let equipment = [ "Morph-Stardust", "Morph-Steel", "Morph-Iron", "Morph-Gold", "Morph-Topaz", "Morph-Emerald", "Morph-Amethyst", "Morph-Ruby", weaponName.substring(6) ]; - for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); - } - } - - //updatePowderSpecials("skip"); //jank pt 1 - save_skp = (typeof save_skp !== 'undefined') ? save_skp : false; - /* TODO: implement level changing - Make this entire function prettier - */ - let equipment = [ null, null, null, null, null, null, null, null, null ]; - for (let i in equipment) { - let equip = getValue(equipmentInputs[i]).trim(); - if (equip === "") { - equip = "No " + equipment_names[i] - } - else { - setValue(equipmentInputs[i], equip); - } - equipment[i] = equip; - } - let powderings = []; - let errors = []; - for (const i in powderInputs) { - // read in two characters at a time. - // TODO: make this more robust. - let input = getValue(powderInputs[i]).trim(); - let powdering = []; - let errorederrors = []; - while (input) { - let first = input.slice(0, 2); - let powder = powderIDs.get(first); - if (powder === undefined) { - errorederrors.push(first); - } else { - powdering.push(powder); - } - input = input.slice(2); - } - if (errorederrors.length > 0) { - if (errorederrors.length > 1) - errors.push(new IncorrectInput(errorederrors.join(""), "t6w6", powderInputs[i])); - else - errors.push(new IncorrectInput(errorederrors[0], "t6 or e3", powderInputs[i])); - } - //console.log("POWDERING: " + powdering); - powderings.push(powdering); - } - let tomes = [ null, null, null, null, null, null, null]; - for (let i in tomes) { - let equip = getValue(tomeInputs[i]).trim(); - if (equip === "") { - equip = "No " + tome_names[i] - } - else { - setValue(tomeInputs[i], equip); - } - tomes[i] = equip; - } - - - let level = document.getElementById("level-choice").value; - player_build = new Build(level, equipment, powderings, new Map(), errors, tomes); - console.log(player_build); - - //isn't this deprecated? - for (let i of document.getElementsByClassName("hide-container-block")) { - i.style.display = "block"; - } - for (let i of document.getElementsByClassName("hide-container-grid")) { - i.style.display = "grid"; - } - - console.log(player_build.toString()); - displaysq2EquipOrder(document.getElementById("build-order"),player_build.equip_order); - - const assigned = player_build.base_skillpoints; - const skillpoints = player_build.total_skillpoints; - for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-base", "Original: " + skillpoints[i]); - } - - if (save_skp) { - // TODO: reduce duplicated code, @updateStats - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let manual_assigned = skp[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - } - - updateEditableIDs(); - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - console.log(error); - } -} - -function handleBuilderError(error) { - if (error instanceof ListError) { - for (let i of error.errors) { - if (i instanceof ItemNotFound) { - i.element.textContent = i.message; - } else if (i instanceof IncorrectInput) { - if (document.getElementById(i.id) !== null) { - document.getElementById(i.id).parentElement.querySelectorAll("p.error")[0].textContent = i.message; - } - } else { - let msg = i.stack; - let lines = msg.split("\n"); - let header = document.getElementById("header"); - header.textContent = ""; - for (const line of lines) { - let p = document.createElement("p"); - p.classList.add("itemp"); - p.textContent = line; - header.appendChild(p); - } - let p2 = document.createElement("p"); - p2.textContent = "If you believe this is an error, contact hppeng on forums or discord."; - header.appendChild(p2); - } - } - } else { - let msg = error.stack; - let lines = msg.split("\n"); - let header = document.getElementById("header"); - header.textContent = ""; - for (const line of lines) { - let p = document.createElement("p"); - p.classList.add("itemp"); - p.textContent = line; - header.appendChild(p); - } - let p2 = document.createElement("p"); - p2.textContent = "If you believe this is an error, contact hppeng on forums or discord."; - header.appendChild(p2); - } -} - -/* Updates all build statistics based on (for now) the skillpoint input fields and then calculates build stats. -*/ -function updateStats() { - - let specialNames = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"]; - for (const sName of specialNames) { - for (let i = 1; i < 6; i++) { - let elem = document.getElementById(sName + "-" + i); - let name = sName.replace("_", " "); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(sName)]; - console.log(special); - if (special["weaponSpecialEffects"].has("Damage Boost")) { - if (name === "Courage" || name === "Curse") { //courage is universal damage boost - //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100; - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1]; - } - } - } - } - } - - - - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let value = document.getElementById(skp_order[i] + "-skp").value; - if (value === ""){value = 0; setValue(skp_order[i] + "-skp", value)} - let manual_assigned = 0; - if (value.includes("+")) { - let skp = value.split("+"); - for (const s of skp) { - manual_assigned += parseInt(s,10); - } - } else { - manual_assigned = parseInt(value,10); - } - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - if(player_build){ - updatePowderSpecials("skip", false); - updateArmorPowderSpecials("skip", false); - updateBoosts("skip", false); - for (let id of editable_item_fields) { - player_build.statMap.set(id, parseInt(getValue(id))); - } - } - player_build.aggregateStats(); - console.log(player_build.statMap); - calculateBuildStats(); -} - - -/* Updates all IDs in the edit IDs section. Resets each input and original value text to the correct text according to the current build. -*/ -function updateEditableIDs() { - if (player_build) { - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - let val = player_build.statMap.get(id); - edit_input.value = val; - edit_input.placeholder = val; - - let value_label = document.getElementById(id + "-base"); - value_label.textContent = "Original Value: " + val; - //a hack to make resetting easier - value_label.value = val; - } - } -} - -/* Resets all IDs in the edit IDs section to their "original" values. -*/ -function resetEditableIDs() { - if (player_build) { - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - let value_label = document.getElementById(id + "-base"); - - edit_input.value = value_label.value; - edit_input.placeholder = value_label.value; - } - } else { - //no player build, reset to 0 - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - - edit_input.value = 0; - edit_input.placeholder = 0; - } - } -} - -/* Updates all spell boosts -*/ -function updateBoosts(buttonId, recalcStats) { - let elem = document.getElementById(buttonId); - let name = buttonId.split("-")[0]; - if(buttonId !== "skip") { - if (elem.classList.contains("toggleOn")) { - player_build.damageMultiplier -= damageMultipliers.get(name); - if (name === "warscream") { - player_build.defenseMultiplier -= .20; - } - if (name === "vanish") { - player_build.defenseMultiplier -= .15; - } - elem.classList.remove("toggleOn"); - }else{ - player_build.damageMultiplier += damageMultipliers.get(name); - if (name === "warscream") { - player_build.defenseMultiplier += .20; - } - if (name === "vanish") { - player_build.defenseMultiplier += .15; - } - elem.classList.add("toggleOn"); - } - updatePowderSpecials("skip", false); //jank pt 1 - } else { - for (const [key, value] of damageMultipliers) { - let elem = document.getElementById(key + "-boost") - if (elem.classList.contains("toggleOn")) { - elem.classList.remove("toggleOn"); - player_build.damageMultiplier -= value; - if (key === "warscream") { player_build.defenseMultiplier -= .20 } - if (key === "vanish") { player_build.defenseMultiplier -= .15 } - } - } - } - if (recalcStats) { - calculateBuildStats(); - } -} - -/* Updates ACTIVE powder special boosts (weapons) -*/ -function updatePowderSpecials(buttonId, recalcStats) { - //console.log(player_build.statMap); - - let name = (buttonId).split("-")[0]; - let power = (buttonId).split("-")[1]; // [1, 5] - let specialNames = ["Quake", "Chain Lightning", "Curse", "Courage", "Wind Prison"]; - let powderSpecials = []; // [ [special, power], [special, power]] - - - if(name !== "skip"){ - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special.weaponSpecialEffects.has("Damage Boost")) { - name = name.replace("_", " "); - if (name === "Courage" || name === "Curse") { //courage and curse are universal damage boost - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - //poison? - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[power-1]; - } - } - } else { - for (let i = 1;i < 6; i++) { //toggle all pressed buttons of the same powder special off - //name is same, power is i - if(document.getElementById(name.replace(" ", "_") + "-" + i).classList.contains("toggleOn")) { - document.getElementById(name.replace(" ", "_") + "-" + i).classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special.weaponSpecialEffects.has("Damage Boost")) { - name = name.replace("_", " "); //might be redundant - if (name === "Courage" || name === "Curse") { //courage is universal damage boost - //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100; - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1]; - } - } - } - } - //toggle the pressed button on - elem.classList.add("toggleOn"); - } - } - - for (const sName of specialNames) { - for (let i = 1;i < 6; i++) { - if (document.getElementById(sName.replace(" ","_") + "-" + i).classList.contains("toggleOn")) { - let powderSpecial = powderSpecialStats[specialNames.indexOf(sName.replace("_"," "))]; - powderSpecials.push([powderSpecial, i]); - break; - } - } - } - - - if (name !== "skip") { - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special["weaponSpecialEffects"].has("Damage Boost")) { - let name = special["weaponSpecialName"]; - if (name === "Courage" || name === "Curse") { //courage and curse are is universal damage boost - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.get("damageBonus")[4] += special.weaponSpecialEffects.get("Damage Boost")[power-1]; - } - } - } - } - - if (recalcStats) { - calculateBuildStats(); - } - displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); -} - - -/* Updates PASSIVE powder special boosts (armors) -*/ -function updateArmorPowderSpecials(elem_id, recalc_stats) { - //we only update the powder special + external stats if the player has a build - if (elem_id !== "skip") { - if (player_build !== undefined && player_build.weapon !== undefined && player_build.weapon.get("name") !== "No Weapon") { - let wynn_elem = elem_id.split("_")[0]; //str, dex, int, def, agi - - //update the label associated w/ the slider - let elem = document.getElementById(elem_id); - let label = document.getElementById(elem_id + "_label"); - let prev_label = document.getElementById(elem_id + "_prev"); - - let value = elem.value; - - //for use in editing build stats - let prev_value = prev_label.value; - let value_diff = value - prev_value; - - //update the "previous" label - prev_label.value = value; - - label.textContent = label.textContent.split(":")[0] + ": " + value; - - let dmg_id = elem_chars[skp_names.indexOf(wynn_elem)] + "DamPct"; - let new_dmgboost = player_build.externalStats.get(dmg_id) + value_diff; - - //update build external stats - the second one is the relevant one for damage calc purposes - player_build.externalStats.set(dmg_id, new_dmgboost); - player_build.externalStats.get("damageBonus")[skp_names.indexOf(wynn_elem)] = new_dmgboost; - - //update the slider's graphics - let bg_color = elem_colors[skp_names.indexOf(wynn_elem)]; - let pct = Math.round(100 * value / powderSpecialStats[skp_names.indexOf(wynn_elem)].cap); - elem.style.background = `linear-gradient(to right, ${bg_color}, ${bg_color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`; - - } - } else { - if (player_build !== undefined) { - for (let i = 0; i < skp_names.length; ++i) { - skp_name = skp_names[i]; - skp_char = elem_chars[i]; - player_build.externalStats.set(skp_char + "DamPct", player_build.externalStats.get(skp_char + "DamPct") - document.getElementById(skp_name+"_boost_armor").value); - player_build.externalStats.get("damageBonus")[i] -= document.getElementById(skp_name+"_boost_armor").value; - } - } - } - - - if (recalc_stats && player_build) { - //calc build stats and display powder special - calculateBuildStats(); - // displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); - } - -} - -function resetArmorPowderSpecials() { - for (const skp of skp_names) { - document.getElementById(skp + "_boost_armor").value = 0; - document.getElementById(skp + "_boost_armor_prev").value = 0; - document.getElementById(skp + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`; - document.getElementById(skp + "_boost_armor_label").textContent = `% ${capitalizeFirst(elem_names[skp_names.indexOf(skp)])} Damage Boost: 0` - } -} - -/* Calculates all build statistics and updates the entire display. -*/ -function calculateBuildStats() { - const assigned = player_build.base_skillpoints; - const skillpoints = player_build.total_skillpoints; - let skp_effects = ["% more damage dealt.","% chance to crit.","% spell cost reduction.","% less damage taken.","% chance to dodge."]; - for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-assign", "Assign: " + assigned[i]); - setValue(skp_order[i] + "-skp", skillpoints[i]); - let linebreak = document.createElement("br"); - linebreak.classList.add("itemp"); - document.getElementById(skp_order[i] + "-skp-label"); - setText(skp_order[i] + "-skp-pct", (skillPointsToPercentage(skillpoints[i])*100).toFixed(1).concat(skp_effects[i])); - document.getElementById(skp_order[i]+"-warnings").textContent = '' - if (assigned[i] > 100) { - let skp_warning = document.createElement("p"); - skp_warning.classList.add("warning"); - skp_warning.classList.add("small-text") - skp_warning.textContent += "Cannot assign " + assigned[i] + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually."; - document.getElementById(skp_order[i]+"-warnings").textContent = '' - document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning); - } - } - - let summarybox = document.getElementById("summary-box"); - summarybox.textContent = ""; - let skpRow = document.createElement("p"); - - let remainingSkp = document.createElement("p"); - remainingSkp.classList.add("scaled-font"); - let remainingSkpTitle = document.createElement("b"); - remainingSkpTitle.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: "; - let remainingSkpContent = document.createElement("b"); - remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints); - remainingSkpContent.classList.add(levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints < 0 ? "negative" : "positive"); - - remainingSkp.appendChild(remainingSkpTitle); - remainingSkp.appendChild(remainingSkpContent); - - - summarybox.append(skpRow); - summarybox.append(remainingSkp); - if(player_build.assigned_skillpoints > levelToSkillPoints(player_build.level)){ - let skpWarning = document.createElement("span"); - //skpWarning.classList.add("itemp"); - skpWarning.classList.add("warning"); - skpWarning.textContent = "WARNING: Too many skillpoints need to be assigned!"; - let skpCount = document.createElement("p"); - skpCount.classList.add("warning"); - skpCount.textContent = "For level " + (player_build.level>101 ? "101+" : player_build.level) + ", there are only " + levelToSkillPoints(player_build.level) + " skill points available."; - summarybox.append(skpWarning); - summarybox.append(skpCount); - } - let lvlWarning; - for (const item of player_build.items) { - let item_lvl; - if (item.get("crafted")) { - //item_lvl = item.get("lvlLow") + "-" + item.get("lvl"); - item_lvl = item.get("lvlLow"); - } - else { - item_lvl = item.get("lvl"); - } - - if (player_build.level < item_lvl) { - if (!lvlWarning) { - lvlWarning = document.createElement("p"); - lvlWarning.classList.add("itemp"); - lvlWarning.classList.add("warning"); - lvlWarning.textContent = "WARNING: A level " + player_build.level + " player cannot use some piece(s) of this build." - } - let baditem = document.createElement("p"); - baditem.classList.add("nocolor"); - baditem.classList.add("itemp"); - baditem.textContent = item.get("displayName") + " requires level " + item.get("lvl") + " to use."; - lvlWarning.appendChild(baditem); - } - } - if(lvlWarning){ - summarybox.append(lvlWarning); - } - for (const [setName, count] of player_build.activeSetCounts) { - const bonus = sets[setName].bonuses[count-1]; - // console.log(setName); - if (bonus["illegal"]) { - let setWarning = document.createElement("p"); - setWarning.classList.add("itemp"); - setWarning.classList.add("warning"); - setWarning.textContent = "WARNING: illegal item combination: " + setName - summarybox.append(setWarning); - } - } - - for (let i in player_build.items) { - displaysq2ExpandedItem(player_build.items[i], buildFields[i]); - collapse_element("#"+equipment_keys[i]+"-tooltip"); - } - - displaysq2ArmorStats(player_build); - displaysq2BuildStats('overall-stats', player_build, build_all_display_commands); - displaysq2BuildStats("offensive-stats",player_build, build_offensive_display_commands); - displaysq2SetBonuses("set-info",player_build); - displaysq2WeaponStats(player_build); - - let meleeStats = player_build.getMeleeStats(); - displaysq2MeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); - - displaysq2DefenseStats(document.getElementById("defensive-stats"),player_build); - - displaysq2PoisonDamage(document.getElementById("build-poison-stats"),player_build); - - let spells = spell_table[player_build.weapon.get("type")]; - for (let i = 0; i < 4; ++i) { - let parent_elem = document.getElementById("spell"+i+"-info"); - let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaysq2SpellDamage(parent_elem, overallparent_elem, player_build, spells[i], i+1); - } - - location.hash = encodeBuild(); - clear_highlights(); -} - function copyBuild() { if (player_build) { copyTextToClipboard(url_base+location.hash); @@ -916,107 +330,6 @@ function toggleButton(button_id) { } } -function optimizeStrDex() { - if (!player_build) { - return; - } - const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; - const base_skillpoints = player_build.base_skillpoints; - const max_str_boost = 100 - base_skillpoints[0]; - const max_dex_boost = 100 - base_skillpoints[1]; - if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable - - const base_total_skillpoints = player_build.total_skillpoints; - let str_bonus = remaining; - let dex_bonus = 0; - let best_skillpoints = player_build.total_skillpoints; - let best_damage = 0; - for (let i = 0; i <= remaining; ++i) { - let total_skillpoints = base_total_skillpoints.slice(); - total_skillpoints[0] += Math.min(max_str_boost, str_bonus); - total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); - - // Calculate total 3rd spell damage - let spell = spell_table[player_build.weapon.get("type")][2]; - const stats = player_build.statMap; - let critChance = skillPointsToPercentage(total_skillpoints[1]); - let save_damages = []; - let spell_parts; - if (spell.parts) { - spell_parts = spell.parts; - } - else { - spell_parts = spell.variants.DEFAULT; - for (const majorID of stats.get("activeMajorIDs")) { - if (majorID in spell.variants) { - spell_parts = spell.variants[majorID]; - break; - } - } - } - let total_damage = 0; - for (const part of spell_parts) { - if (part.type === "damage") { - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw"), stats.get("sdPct") + player_build.externalStats.get("sdPct"), - part.multiplier / 100, player_build.weapon, total_skillpoints, - player_build.damageMultiplier, player_build.externalStats); - let totalDamNormal = _results[0]; - let totalDamCrit = _results[1]; - let results = _results[2]; - let tooltipinfo = _results[3]; - - for (let i = 0; i < 6; ++i) { - for (let j in results[i]) { - results[i][j] = results[i][j].toFixed(2); - } - } - let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; - let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; - let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; - - save_damages.push(averageDamage); - if (part.summary == true) { - total_damage = averageDamage; - } - } else if (part.type === "total") { - total_damage = 0; - for (let i in part.factors) { - total_damage += save_damages[i] * part.factors[i]; - } - } - } // END Calculate total 3rd spell damage (total_damage) - if (total_damage > best_damage) { - best_damage = total_damage; - best_skillpoints = total_skillpoints.slice(); - } - - str_bonus -= 1; - dex_bonus += 1; - - } - // TODO: reduce duplicated code, @calculateBuild - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let manual_assigned = best_skillpoints[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - - try { - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - handleBuilderError(error); - } -} - // TODO: Learn and use await function init() { console.log("builder.js init"); @@ -1026,6 +339,7 @@ function init() { update_field(i); } } + function init2() { load_ing_init(init); } @@ -1033,5 +347,4 @@ function init3() { load_tome_init(init2) } - load_init(init3); diff --git a/js/builder_graph.js b/js/builder_graph.js new file mode 100644 index 0000000..7b652ba --- /dev/null +++ b/js/builder_graph.js @@ -0,0 +1,21 @@ + + +let item_nodes = []; + +document.addEventListener('DOMContentLoaded', function() { + for (const [eq, none_item] of zip(equipment_fields, none_items)) { + let input_field = document.getElementById(eq+"-choice"); + let item_image = document.getElementById(eq+"-img"); + + let item_input = new ItemInputNode(eq+'-input', input_field, none_item); + item_nodes.push(item_input); + new ItemInputDisplayNode(eq+'-display', input_field, item_image).link_to(item_input); + new PrintNode(eq+'-debug').link_to(item_input); + //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); + + } + let weapon_image = document.getElementById("weapon-img"); + new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); + console.log("Set up graph"); + +}); diff --git a/js/computation_graph.js b/js/computation_graph.js index 9039685..417c9bf 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -13,6 +13,7 @@ class ComputeNode { this.update_task = null; this.update_time = Date.now(); this.fail_cb = false; // Set to true to force updates even if parent failed. + this.calc_inputs = new Map(); } /** @@ -23,19 +24,40 @@ class ComputeNode { return; } this.update_time = timestamp; + this.set_value(this.compute_func(this.calc_inputs)); + } - let value_map = Map(); - for (const input of this.inputs) { - value_map.set(input.name, input.get_value()); - } - this.value = this.compute_func(value_map); + /** + * Set this node's value directly. Notifies children. + */ + set_value(value) { + let timestamp = Date.now(); + this.update_time = timestamp; + this.value = value; for (const child of this.children) { - if (this.value || child.fail_cb) { - child.update(); + child.set_input(this.name, this.value, timestamp); + } + } + + /** + * Set an input value. Propagates calculation if all inputs are present. + */ + set_input(input_name, value, timestamp) { + if (value || this.fail_cb) { + this.calc_inputs.set(input_name, value) + if (this.calc_inputs.size === this.inputs.length) { + this.update(timestamp) } } } + /** + * Remove cached input values to this calculation. + */ + clear_cache() { + this.calc_inputs = new Map(); + } + /** * Get value of this compute node. Can't trigger update cascades (push based update, not pull based.) */ @@ -72,6 +94,19 @@ function calcSchedule(node) { }, 500); } +class PrintNode extends ComputeNode { + + constructor(name) { + super(name); + this.fail_cb = true; + } + + compute_func(input_map) { + console.log([this.name, input_map]); + return null; + } +} + /** * Node for getting an item's stats from an item input field. */ @@ -85,9 +120,9 @@ class ItemInputNode extends ComputeNode { */ constructor(name, item_input_field, none_item) { super(name); - this.input_field.setAttribute("input", () => calcSchedule(this)); this.input_field = item_input_field; - this.none_item = expandItem(none_item); + this.input_field.addEventListener("input", () => calcSchedule(this)); + this.none_item = new Item(none_item); } compute_func(input_map) { @@ -107,16 +142,22 @@ class ItemInputNode extends ComputeNode { item = getCraftFromHash(item_text); } else if (itemMap.has(item_text)) { - item = Item(itemMap.get(item_text)); + item = new Item(itemMap.get(item_text)); } else if (tomeMap.has(item_text)) { - item = Item(tomeMap.get(item_text)); + item = new Item(tomeMap.get(item_text)); } - if (!item || item.statMap.get('type') !== this.none_item.statMap.get('type')) { - return null; + if (item) { + let type_match; + if (this.none_item.statMap.get('category') === 'weapon') { + type_match = item.statMap.get('category') === 'weapon'; + } else { + type_match = item.statMap.get('type') === this.none_item.statMap.get('type'); + } + if (type_match) { return item; } } - return item; + return null; } } @@ -166,6 +207,6 @@ class WeaponDisplayNode extends ComputeNode { const [item] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element const type = item.statMap.get('type'); - this.image_field.setAttribute('src', '../media/items/new/generic-'+type+'.png'); + this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png'); } } diff --git a/js/load.js b/js/load.js index 265cc3c..461e1d7 100644 --- a/js/load.js +++ b/js/load.js @@ -201,59 +201,61 @@ function load_init(init_func) { } } +// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon. +for (const it of itemTypes) { + itemLists.set(it, []); +} + +let none_items = [ + ["armor", "helmet", "No Helmet"], + ["armor", "chestplate", "No Chestplate"], + ["armor", "leggings", "No Leggings"], + ["armor", "boots", "No Boots"], + ["accessory", "ring", "No Ring 1"], + ["accessory", "ring", "No Ring 2"], + ["accessory", "bracelet", "No Bracelet"], + ["accessory", "necklace", "No Necklace"], + ["weapon", "dagger", "No Weapon"], +]; +for (let i = 0; i < none_items.length; i++) { + let item = Object(); + item.slots = 0; + item.category = none_items[i][0]; + item.type = none_items[i][1]; + item.name = none_items[i][2]; + item.displayName = item.name; + item.set = null; + item.quest = null; + item.skillpoints = [0, 0, 0, 0, 0]; + item.has_negstat = false; + item.reqs = [0, 0, 0, 0, 0]; + item.fixID = true; + item.tier = "Normal"; + item.id = 10000 + i; + item.nDam = "0-0"; + item.eDam = "0-0"; + item.tDam = "0-0"; + item.wDam = "0-0"; + item.fDam = "0-0"; + item.aDam = "0-0"; + clean_item(item); + + none_items[i] = item; +} + function init_maps() { //warp itemMap = new Map(); /* Mapping from item names to set names. */ idMap = new Map(); redirectMap = new Map(); - for (const it of itemTypes) { - itemLists.set(it, []); - } - - let noneItems = [ - ["armor", "helmet", "No Helmet"], - ["armor", "chestplate", "No Chestplate"], - ["armor", "leggings", "No Leggings"], - ["armor", "boots", "No Boots"], - ["accessory", "ring", "No Ring 1"], - ["accessory", "ring", "No Ring 2"], - ["accessory", "bracelet", "No Bracelet"], - ["accessory", "necklace", "No Necklace"], - ["weapon", "dagger", "No Weapon"], - ]; - for (let i = 0; i < noneItems.length; i++) { - let item = Object(); - item.slots = 0; - item.category = noneItems[i][0]; - item.type = noneItems[i][1]; - item.name = noneItems[i][2]; - item.displayName = item.name; - item.set = null; - item.quest = null; - item.skillpoints = [0, 0, 0, 0, 0]; - item.has_negstat = false; - item.reqs = [0, 0, 0, 0, 0]; - item.fixID = true; - item.tier = "Normal"; - item.id = 10000 + i; - item.nDam = "0-0"; - item.eDam = "0-0"; - item.tDam = "0-0"; - item.wDam = "0-0"; - item.fDam = "0-0"; - item.aDam = "0-0"; - clean_item(item); - - noneItems[i] = item; - } - items = items.concat(noneItems); + items = items.concat(none_items); //console.log(items); for (const item of items) { if (item.remapID === undefined) { itemLists.get(item.type).push(item.displayName); itemMap.set(item.displayName, item); - if (noneItems.includes(item)) { + if (none_items.includes(item)) { idMap.set(item.id, ""); } else { diff --git a/js/optimize.js b/js/optimize.js new file mode 100644 index 0000000..6185edd --- /dev/null +++ b/js/optimize.js @@ -0,0 +1,101 @@ +function optimizeStrDex() { + if (!player_build) { + return; + } + const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; + const base_skillpoints = player_build.base_skillpoints; + const max_str_boost = 100 - base_skillpoints[0]; + const max_dex_boost = 100 - base_skillpoints[1]; + if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable + + const base_total_skillpoints = player_build.total_skillpoints; + let str_bonus = remaining; + let dex_bonus = 0; + let best_skillpoints = player_build.total_skillpoints; + let best_damage = 0; + for (let i = 0; i <= remaining; ++i) { + let total_skillpoints = base_total_skillpoints.slice(); + total_skillpoints[0] += Math.min(max_str_boost, str_bonus); + total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); + + // Calculate total 3rd spell damage + let spell = spell_table[player_build.weapon.get("type")][2]; + const stats = player_build.statMap; + let critChance = skillPointsToPercentage(total_skillpoints[1]); + let save_damages = []; + let spell_parts; + if (spell.parts) { + spell_parts = spell.parts; + } + else { + spell_parts = spell.variants.DEFAULT; + for (const majorID of stats.get("activeMajorIDs")) { + if (majorID in spell.variants) { + spell_parts = spell.variants[majorID]; + break; + } + } + } + let total_damage = 0; + for (const part of spell_parts) { + if (part.type === "damage") { + let _results = calculateSpellDamage(stats, part.conversion, + stats.get("sdRaw"), stats.get("sdPct") + player_build.externalStats.get("sdPct"), + part.multiplier / 100, player_build.weapon, total_skillpoints, + player_build.damageMultiplier, player_build.externalStats); + let totalDamNormal = _results[0]; + let totalDamCrit = _results[1]; + let results = _results[2]; + let tooltipinfo = _results[3]; + + for (let i = 0; i < 6; ++i) { + for (let j in results[i]) { + results[i][j] = results[i][j].toFixed(2); + } + } + let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; + let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; + let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; + + save_damages.push(averageDamage); + if (part.summary == true) { + total_damage = averageDamage; + } + } else if (part.type === "total") { + total_damage = 0; + for (let i in part.factors) { + total_damage += save_damages[i] * part.factors[i]; + } + } + } // END Calculate total 3rd spell damage (total_damage) + if (total_damage > best_damage) { + best_damage = total_damage; + best_skillpoints = total_skillpoints.slice(); + } + + str_bonus -= 1; + dex_bonus += 1; + + } + // TODO: reduce duplicated code, @calculateBuild + let skillpoints = player_build.total_skillpoints; + let delta_total = 0; + for (let i in skp_order) { + let manual_assigned = best_skillpoints[i]; + let delta = manual_assigned - skillpoints[i]; + skillpoints[i] = manual_assigned; + player_build.base_skillpoints[i] += delta; + delta_total += delta; + } + player_build.assigned_skillpoints += delta_total; + + try { + calculateBuildStats(); + if (player_build.errored) + throw new ListError(player_build.errors); + } + catch (error) { + handleBuilderError(error); + } +} + diff --git a/js/utils.js b/js/utils.js index fed7df8..164543d 100644 --- a/js/utils.js +++ b/js/utils.js @@ -3,31 +3,6 @@ const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.sp const zip = (a, b) => a.map((k, i) => [k, b[i]]); -//updates all the OGP tags for a webpage. Should be called when build changes -function updateOGP() { - //update the embed URL - let url_elem = document.getElementById("ogp-url"); - if (url_elem) { - url_elem.content = url_base+location.hash; - } - - //update the embed text content - let build_elem = document.getElementById("ogp-build-list"); - if (build_elem && player_build) { - let text = "WynnBuilder build:\n"+ - "> "+player_build.helmet.get("displayName")+"\n"+ - "> "+player_build.chestplate.get("displayName")+"\n"+ - "> "+player_build.leggings.get("displayName")+"\n"+ - "> "+player_build.boots.get("displayName")+"\n"+ - "> "+player_build.ring1.get("displayName")+"\n"+ - "> "+player_build.ring2.get("displayName")+"\n"+ - "> "+player_build.bracelet.get("displayName")+"\n"+ - "> "+player_build.necklace.get("displayName")+"\n"+ - "> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]"; - build_elem.content = text; - } -} - function clamp(num, low, high){ return Math.min(Math.max(num, low), high); } @@ -412,4 +387,4 @@ async function hardReload() { function capitalizeFirst(str) { return str[0].toUpperCase() + str.substring(1); -} \ No newline at end of file +}