From 295e8f3e364bbb820cf00871704b525a0ef1a686 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 03:14:31 -0700 Subject: [PATCH] Fix damage calc... somewhat unify powder format --- js/builder.js | 1 - js/builder_graph.js | 64 ++++++++-------------- js/damage_calc.js | 119 ++++------------------------------------ js/powders.js | 131 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 148 deletions(-) diff --git a/js/builder.js b/js/builder.js index 3b6cf33..96513d8 100644 --- a/js/builder.js +++ b/js/builder.js @@ -147,7 +147,6 @@ function toggle_tab(tab) { } else { document.querySelector("#"+tab).style.display = "none"; } - console.log(document.querySelector("#"+tab).style.display); } // toggle spell arrow diff --git a/js/builder_graph.js b/js/builder_graph.js index e54fd02..f079f40 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -22,7 +22,6 @@ function update_armor_powder_specials(elem_id) { //update the label associated w/ the slider let elem = document.getElementById(elem_id); let label = document.getElementById(elem_id + "_label"); - let value = elem.value; label.textContent = label.textContent.split(":")[0] + ": " + value @@ -86,23 +85,18 @@ let powder_special_input = new (class extends ComputeNode { })(); function updatePowderSpecials(buttonId) { - let name = (buttonId).split("-")[0]; - let power = (buttonId).split("-")[1]; // [1, 5] - + let prefix = (buttonId).split("-")[0].replace(' ', '_') + '-'; let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - } else { + if (elem.classList.contains("toggleOn")) { elem.classList.remove("toggleOn"); } + 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"); - } + const elem2 = document.getElementById(prefix + i); + if(elem2.classList.contains("toggleOn")) { elem2.classList.remove("toggleOn"); } } //toggle the pressed button on elem.classList.add("toggleOn"); } - powder_special_input.mark_dirty().update(); } @@ -129,6 +123,7 @@ class PowderSpecialCalcNode extends ComputeNode { } class PowderSpecialDisplayNode extends ComputeNode { + // TODO: Refactor this entirely to be adding more spells to the spell list constructor() { super('builder-powder-special-display'); this.fail_cb = true; @@ -137,27 +132,11 @@ class PowderSpecialDisplayNode extends ComputeNode { compute_func(input_map) { const powder_specials = input_map.get('powder-specials'); const stats = input_map.get('stats'); - const weapon = input_map.get('weapon'); + const weapon = input_map.get('build').weapon; displayPowderSpecials(document.getElementById("powder-special-stats"), powder_specials, stats, weapon.statMap, true); } } -/** - * Apply armor powders. - * Encoding shortcut assumes that all powders give +def to one element - * and -def to the element "behind" it in cycle ETWFA, which is true - * as of now and unlikely to change in the near future. - */ -function applyArmorPowders(expandedItem, powders) { - for(const id of powders){ - let powder = powderStats[id]; - let name = powderNames.get(id).charAt(0); - let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5]; - expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]); - expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]); - } -} - /** * Node for getting an item's stats from an item input field. * @@ -174,6 +153,11 @@ class ItemInputNode extends InputNode { constructor(name, item_input_field, none_item) { super(name, item_input_field); this.none_item = new Item(none_item); + this.category = this.none_item.statMap.get('category'); + if (this.category == 'armor' || this.category == 'weapon') { + this.none_item.statMap.set('powders', []); + apply_weapon_powders(this.none_item.statMap); // Needed to put in damagecalc zeros + } this.none_item.statMap.set('NONE', true); } @@ -197,17 +181,17 @@ class ItemInputNode extends InputNode { item.statMap.set('powders', powdering); } let type_match; - if (this.none_item.statMap.get('category') === 'weapon') { - type_match = item.statMap.get('category') === 'weapon'; + if (this.category == 'weapon') { + type_match = item.statMap.get('category') == 'weapon'; } else { - type_match = item.statMap.get('type') === this.none_item.statMap.get('type'); + type_match = item.statMap.get('type') == this.none_item.statMap.get('type'); } if (type_match) { - if (item.statMap.get('category') === 'armor') { - applyArmorPowders(item.statMap, powdering); + if (item.statMap.get('category') == 'armor') { + applyArmorPowders(item.statMap); } - else if (item.statMap.get('category') === 'weapon') { - apply_weapon_powders(item.statMap, powdering); + else if (item.statMap.get('category') == 'weapon') { + apply_weapon_powders(item.statMap); } return item; } @@ -579,7 +563,7 @@ class SpellDamageCalcNode extends ComputeNode { } compute_func(input_map) { - const weapon = new Map(input_map.get('weapon-input').statMap); + const weapon = input_map.get('build').weapon.statMap; const spell_info = input_map.get('spell-info'); const spell_parts = spell_info[1]; const stats = input_map.get('stats'); @@ -647,6 +631,7 @@ class SpellDisplayNode extends ComputeNode { Returns an array in the order: */ function getMeleeStats(stats, weapon) { + stats = new Map(stats); // Shallow copy const weapon_stats = weapon.statMap; const skillpoints = [ stats.get('str'), @@ -665,9 +650,8 @@ function getMeleeStats(stats, weapon) { adjAtkSpd = 0; } - let damage_mult = stats.get("damageMultiplier"); if (weapon_stats.get("type") === "relik") { - damage_mult = 0.99; // CURSE YOU WYNNCRAFT + stats.set('damageMultiplier', 0.99); // CURSE YOU WYNNCRAFT //One day we will create WynnWynn and no longer have shaman 99% melee injustice. //In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams. } @@ -1076,7 +1060,7 @@ function builder_graph_init() { // Powder specials. let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials'); new PowderSpecialDisplayNode().link_to(powder_special_input, 'powder-specials') - .link_to(stat_agg_node, 'stats').link_to(item_nodes[8], 'weapon'); + .link_to(stat_agg_node, 'stats').link_to(build_node, 'build'); stat_agg_node.link_to(powder_special_calc, 'powder-boost'); stat_agg_node.link_to(armor_powder_node, 'armor-powder'); powder_special_input.update(); @@ -1093,7 +1077,7 @@ function builder_graph_init() { spell_node.link_to(stat_agg_node, 'stats') let calc_node = new SpellDamageCalcNode(i); - calc_node.link_to(item_nodes[8], 'weapon-input').link_to(stat_agg_node, 'stats') + calc_node.link_to(build_node, 'build').link_to(stat_agg_node, 'stats') .link_to(spell_node, 'spell-info'); spelldmg_nodes.push(calc_node); diff --git a/js/damage_calc.js b/js/damage_calc.js index 856743d..e3b2f14 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -1,7 +1,5 @@ const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]); -const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; -const damage_present_key = 'damagePresent'; function get_base_dps(item) { const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))]; //SUPER JANK @HPP PLS FIX @@ -27,107 +25,6 @@ function get_base_dps(item) { } } -// THIS MUTATES THE ITEM -function apply_weapon_powders(item) { - let present; - if (item.get("tier") !== "Crafted") { - let weapon_result = calc_weapon_powder(item); - let damages = weapon_result[0]; - present = weapon_result[1]; - for (const i in damage_keys) { - item.set(damage_keys[i], damages[i]); - } - } else { - let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; - let results_low = calc_weapon_powder(item, base_low); - let damage_low = results_low[0]; - let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; - let results_high = calc_weapon_powder(item, base_high); - let damage_high = results_high[0]; - present = results_high[1]; - - for (const i in damage_keys) { - item.set(damage_keys[i], [damage_low[i], damage_high[i]]); - } - } - console.log(item); - item.set(damage_present_key, present); -} - -/** - * weapon: Weapon to apply powder to - * damageBases: used by crafted - */ -function calc_weapon_powder(weapon, damageBases) { - let powders = weapon.get("powders").slice(); - - // Array of neutral + ewtfa damages. Each entry is a pair (min, max). - let damages = [ - weapon.get('nDam').split('-').map(Number), - weapon.get('eDam').split('-').map(Number), - weapon.get('tDam').split('-').map(Number), - weapon.get('wDam').split('-').map(Number), - weapon.get('fDam').split('-').map(Number), - weapon.get('aDam').split('-').map(Number) - ]; - - // Applying spell conversions - let neutralBase = damages[0].slice(); - let neutralRemainingRaw = damages[0].slice(); - - //powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do. - - //Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA - //1st round - apply each as ingred, 2nd round - apply as normal - if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) { - for (const p of powders.concat(weapon.get("ingredPowders"))) { - let powder = powderStats[p]; //use min, max, and convert - let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error - let diff = Math.floor(damageBases[0] * powder.convert/100); - damageBases[0] -= diff; - damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 ); - } - //update all damages - for (let i = 0; i < damages.length; i++) { - damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)]; - } - neutralRemainingRaw = damages[0].slice(); - neutralBase = damages[0].slice(); - } - - //apply powders to weapon - for (const powderID of powders) { - const powder = powderStats[powderID]; - // Bitwise to force conversion to integer (integer division). - const element = (powderID/6) | 0; - let conversionRatio = powder.convert/100; - if (neutralRemainingRaw[1] > 0) { - let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); - let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]); - - //damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff)); - //damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff)); - //neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); - //neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); - damages[element+1][0] += min_diff; - damages[element+1][1] += max_diff; - neutralRemainingRaw[0] -= min_diff; - neutralRemainingRaw[1] -= max_diff; - } - damages[element+1][0] += powder.min; - damages[element+1][1] += powder.max; - } - - // The ordering of these two blocks decides whether neutral is present when converted away or not. - let present_elements = [] - for (const damage of damages) { - present_elements.push(damage[1] > 0); - } - - // The ordering of these two blocks decides whether neutral is present when converted away or not. - damages[0] = neutralRemainingRaw; - return [damages, present_elements]; -} function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) { // TODO: Roll all the loops together maybe @@ -227,10 +124,18 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw'); } // Next, rainraw and propRaw - let new_min = damages_obj[0] + raw_boost + (damages_obj[0] / total_min) * prop_raw; - let new_max = damages_obj[1] + raw_boost + (damages_obj[1] / total_max) * prop_raw; - if (i != 0) { // rainraw - new_min += (damages_obj[0] / total_elem_min) * rainbow_raw; + let new_min = damages_obj[0] + raw_boost; + let new_max = damages_obj[1] + raw_boost; + if (total_max > 0) { // TODO: what about total negative all raw? + if (total_elem_min > 0) { + new_min += (damages_obj[0] / total_min) * prop_raw; + } + new_max += (damages_obj[1] / total_max) * prop_raw; + } + if (i != 0 && total_elem_max > 0) { // rainraw TODO above + if (total_elem_min > 0) { + new_min += (damages_obj[0] / total_elem_min) * rainbow_raw; + } new_max += (damages_obj[1] / total_elem_max) * rainbow_raw; } damages_obj[0] = new_min; diff --git a/js/powders.js b/js/powders.js index db0d195..cd2ab00 100644 --- a/js/powders.js +++ b/js/powders.js @@ -61,3 +61,134 @@ let powderSpecialStats = [ _ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f _ps("Wind Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a ]; + +/** + * Apply armor powders. + * Encoding shortcut assumes that all powders give +def to one element + * and -def to the element "behind" it in cycle ETWFA, which is true + * as of now and unlikely to change in the near future. + */ +function applyArmorPowders(expandedItem) { + const powders = expandedItem.get('powders'); + for(const id of powders){ + let powder = powderStats[id]; + let name = powderNames.get(id).charAt(0); + let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5]; + expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]); + expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]); + } +} + +const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; +const damage_present_key = 'damagePresent'; +/** + * Apply weapon powders. MUTATES THE ITEM! + * Adds entries for `damage_keys` and `damage_present_key` + * For normal items, `damage_keys` is 6x2 list (elem: [min, max]) + * For crafted items, `damage_keys` is 6x2x2 list (elem: [minroll: [min, max], maxroll: [min, max]]) + */ +function apply_weapon_powders(item) { + let present; + if (item.get("tier") !== "Crafted") { + let weapon_result = calc_weapon_powder(item); + let damages = weapon_result[0]; + present = weapon_result[1]; + for (const i in damage_keys) { + item.set(damage_keys[i], damages[i]); + } + } else { + let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; + let results_low = calc_weapon_powder(item, base_low); + let damage_low = results_low[0]; + let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; + let results_high = calc_weapon_powder(item, base_high); + let damage_high = results_high[0]; + present = results_high[1]; + + for (const i in damage_keys) { + item.set(damage_keys[i], [damage_low[i], damage_high[i]]); + } + } + item.set(damage_present_key, present); +} + +/** + * Calculate weapon damage from powder. + * + * Params: + * weapon: Weapon to apply powder to + * damageBases: used by crafted + * + * Return: + * [damages, damage_present] + */ +function calc_weapon_powder(weapon, damageBases) { + let powders = weapon.get("powders").slice(); + + // Array of neutral + ewtfa damages. Each entry is a pair (min, max). + let damages = [ + weapon.get('nDam').split('-').map(Number), + weapon.get('eDam').split('-').map(Number), + weapon.get('tDam').split('-').map(Number), + weapon.get('wDam').split('-').map(Number), + weapon.get('fDam').split('-').map(Number), + weapon.get('aDam').split('-').map(Number) + ]; + + // Applying spell conversions + let neutralBase = damages[0].slice(); + let neutralRemainingRaw = damages[0].slice(); + + //powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do. + + //Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA + //1st round - apply each as ingred, 2nd round - apply as normal + if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) { + for (const p of powders.concat(weapon.get("ingredPowders"))) { + let powder = powderStats[p]; //use min, max, and convert + let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error + let diff = Math.floor(damageBases[0] * powder.convert/100); + damageBases[0] -= diff; + damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 ); + } + //update all damages + for (let i = 0; i < damages.length; i++) { + damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)]; + } + neutralRemainingRaw = damages[0].slice(); + neutralBase = damages[0].slice(); + } + + //apply powders to weapon + for (const powderID of powders) { + const powder = powderStats[powderID]; + // Bitwise to force conversion to integer (integer division). + const element = (powderID/6) | 0; + let conversionRatio = powder.convert/100; + if (neutralRemainingRaw[1] > 0) { + let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); + let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]); + + //damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff)); + //damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff)); + //neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); + //neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); + damages[element+1][0] += min_diff; + damages[element+1][1] += max_diff; + neutralRemainingRaw[0] -= min_diff; + neutralRemainingRaw[1] -= max_diff; + } + damages[element+1][0] += powder.min; + damages[element+1][1] += powder.max; + } + + // The ordering of these two blocks decides whether neutral is present when converted away or not. + let present_elements = [] + for (const damage of damages) { + present_elements.push(damage[1] > 0); + } + + // The ordering of these two blocks decides whether neutral is present when converted away or not. + damages[0] = neutralRemainingRaw; + return [damages, present_elements]; +}