Almost working spell damage calculation

This commit is contained in:
hppeng 2022-06-19 09:49:04 -07:00
parent 62a9a4f0c2
commit 8db34d68a7
10 changed files with 212 additions and 114 deletions

View file

@ -127,7 +127,9 @@ class Build{
this.weapon = weapon;
this.items = this.equipment.concat([this.weapon]).concat(this.tomes);
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon);
// calc skillpoints requires statmaps only
let result = calculate_skillpoints(this.equipment.concat(this.tomes).map((x) => x.statMap), this.weapon.statMap);
console.log(result);
this.equip_order = result[0];
// How many skillpoints the player had to assign (5 number)
@ -165,8 +167,9 @@ class Build{
*/
getMeleeStats(){
const stats = this.statMap;
if (this.weapon.get("tier") === "Crafted") {
stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]);
const weapon_stats = this.weapon.statMap;
if (weapon_stats.get("tier") === "Crafted") {
stats.set("damageBases", [weapon_stats.get("nDamBaseHigh"),weapon_stats.get("eDamBaseHigh"),weapon_stats.get("tDamBaseHigh"),weapon_stats.get("wDamBaseHigh"),weapon_stats.get("fDamBaseHigh"),weapon_stats.get("aDamBaseHigh")]);
}
let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier");
if(adjAtkSpd > 6){
@ -176,13 +179,13 @@ class Build{
}
let damage_mult = 1;
if (this.weapon.get("type") === "relik") {
if (weapon_stats.get("type") === "relik") {
damage_mult = 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.
}
// 0spellmult for melee damage.
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats);
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct"), 0, this.weapon.statMap, this.total_skillpoints, damage_mult * this.damageMultiplier);
let dex = this.total_skillpoints[1];
@ -217,8 +220,8 @@ class Build{
defenseStats.push(totalHp);
//EHP
let ehp = [totalHp, totalHp];
let defMult = classDefenseMultipliers.get(this.weapon.get("type"));
ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
let defMult = classDefenseMultipliers.get(this.weapon.statMap.get("type"));
ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
defenseStats.push(ehp);
//HPR
@ -259,26 +262,27 @@ class Build{
let major_ids = new Set();
for (const item of this.items){
for (let [id, value] of item.get("maxRolls")) {
const item_stats = item.statMap;
for (let [id, value] of item_stats.get("maxRolls")) {
if (staticIDs.includes(id)) {
continue;
}
statMap.set(id,(statMap.get(id) || 0)+value);
}
for (const staticID of staticIDs) {
if (item.get(staticID)) {
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
if (item_stats.get(staticID)) {
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
}
}
if (item.get("majorIds")) {
for (const major_id of item.get("majorIds")) {
if (item_stats.get("majorIds")) {
for (const major_id of item_stats.get("majorIds")) {
major_ids.add(major_id);
}
}
}
statMap.set("activeMajorIDs", major_ids);
for (const [setName, count] of this.activeSetCounts) {
const bonus = sets[setName].bonuses[count-1];
const bonus = sets.get(setName).bonuses[count-1];
for (const id in bonus) {
if (skp_order.includes(id)) {
// pass. Don't include skillpoints in ids
@ -291,16 +295,8 @@ class Build{
statMap.set("poisonPct", 100);
// The stuff relevant for damage calculation!!! @ferricles
statMap.set("atkSpd", this.weapon.get("atkSpd"));
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
for (const x of skp_elements) {
this.externalStats.set(x + "DamPct", 0);
}
this.externalStats.set("mdPct", 0);
this.externalStats.set("sdPct", 0);
this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]);
this.externalStats.set("defBonus",[0, 0, 0, 0, 0]);
this.externalStats.set("poisonPct", 0);
this.statMap = statMap;
this.aggregateStats();
@ -308,10 +304,11 @@ class Build{
aggregateStats() {
let statMap = this.statMap;
statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]);
let weapon_stats = this.weapon.statMap;
statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]);
statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]);
statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]);
statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type")));
statMap.set("defMult", classDefenseMultipliers.get(weapon_stats.get("type")));
}
}

View file

@ -88,7 +88,7 @@ let equipmentInputs = equipment_fields.map(x => x + "-choice");
let buildFields = equipment_fields.map(x => x+"-tooltip").concat(tome_fields.map(x => x + "-tooltip"));
let tomeInputs = tome_fields.map(x => x + "-choice");
let powderInputs = [
let powder_inputs = [
"helmet-powder",
"chestplate-powder",
"leggings-powder",

View file

@ -1,3 +1,25 @@
function parsePowdering(powder_info) {
// TODO: Make this run in linear instead of quadratic time... ew
let powdering = [];
for (let i = 0; i < 5; ++i) {
let powders = "";
let n_blocks = Base64.toInt(powder_info.charAt(0));
// console.log(n_blocks + " blocks");
powder_info = powder_info.slice(1);
for (let j = 0; j < n_blocks; ++j) {
let block = powder_info.slice(0,5);
console.log(block);
let six_powders = Base64.toInt(block);
for (let k = 0; k < 6 && six_powders != 0; ++k) {
powders += powderNames.get((six_powders & 0x1f) - 1);
six_powders >>>= 5;
}
powder_info = powder_info.slice(5);
}
powdering[i] = powders;
}
return [powdering, powder_info];
}
/*
* Populate fields based on url, and calculate build.
@ -107,8 +129,8 @@ function decodeBuild(url_tag) {
info[1] = info[1].slice(7);
}
for (let i in powderInputs) {
setValue(powderInputs[i], powdering[i]);
for (let i in powder_inputs) {
setValue(powder_inputs[i], powdering[i]);
}
}
}

View file

@ -12,29 +12,6 @@ function getTomeNameFromID(id) {
return tomeIDMap.get(id);
}
function parsePowdering(powder_info) {
// TODO: Make this run in linear instead of quadratic time... ew
let powdering = [];
for (let i = 0; i < 5; ++i) {
let powders = "";
let n_blocks = Base64.toInt(powder_info.charAt(0));
// console.log(n_blocks + " blocks");
powder_info = powder_info.slice(1);
for (let j = 0; j < n_blocks; ++j) {
let block = powder_info.slice(0,5);
console.log(block);
let six_powders = Base64.toInt(block);
for (let k = 0; k < 6 && six_powders != 0; ++k) {
powders += powderNames.get((six_powders & 0x1f) - 1);
six_powders >>>= 5;
}
powder_info = powder_info.slice(5);
}
powdering[i] = powders;
}
return [powdering, powder_info];
}
function populateBuildList() {
const buildList = document.getElementById("build-choice");
const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
@ -139,6 +116,21 @@ function toggle_tab(tab) {
}
}
let tabs = ['overall-stats', 'offensive-stats', 'defensive-stats'];
function show_tab(tab) {
//console.log(itemFilters)
//hide all tabs, then show the tab of the div clicked and highlight the correct button
for (const i in tabs) {
document.querySelector("#" + tabs[i]).style.display = "none";
document.getElementById("tab-" + tabs[i].split("-")[0] + "-btn").classList.remove("selected-btn");
}
document.querySelector("#" + tab).style.display = "";
document.getElementById("tab-" + tab.split("-")[0] + "-btn").classList.add("selected-btn");
}
// TODO: Learn and use await
function init() {
console.log("builder.js init");

View file

@ -42,14 +42,71 @@ class BuildAssembleNode extends ComputeNode {
];
let weapon = input_map.get('weapon-input');
let level = input_map.get('level-input');
console.log('build node run');
let all_none = weapon.statMap.has('NONE');
for (const item of equipments) {
all_none = all_none && item.statMap.has('NONE');
}
if (all_none) {
return null;
}
return new Build(level, equipments, [], weapon);
}
}
class PowderInputNode extends InputNode {
constructor(name, input_field) {
super(name, input_field);
}
compute_func(input_map) {
// TODO: haha improve efficiency to O(n) dumb
// also, error handling is missing
let input = this.input_field.value.trim();
let powdering = [];
let errorederrors = [];
while (input) {
let first = input.slice(0, 2);
let powder = powderIDs.get(first);
if (powder === undefined) {
return null;
} else {
powdering.push(powder);
}
input = input.slice(2);
}
//console.log("POWDERING: " + powdering);
return powdering;
}
}
class SpellDamageCalcNode extends ComputeNode {
constructor(spell_num) {
super("builder-spell"+spell_num+"-calc");
this.spell_idx = spell_num;
}
compute_func(input_map) {
// inputs:
let weapon = new Map(input_map.get('weapon-input').statMap);
let build = input_map.get('build');
let weapon_powder = input_map.get('weapon-powder');
weapon.set("powders", weapon_powder);
const i = this.spell_idx;
let spell = spell_table[weapon.get("type")][i];
let parent_elem = document.getElementById("spell"+i+"-info");
let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, i+1, weapon);
}
}
let item_nodes = [];
let powder_nodes = [];
let spell_nodes = [];
document.addEventListener('DOMContentLoaded', function() {
// Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff).
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");
@ -57,28 +114,70 @@ document.addEventListener('DOMContentLoaded', function() {
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);
//new PrintNode(eq+'-debug').link_to(item_input);
//document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm');
}
// weapon image changer node.
let weapon_image = document.getElementById("weapon-img");
new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]);
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
new PrintNode('lvl-debug').link_to(level_input);
// Level input node.
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
let build_node = new BuildAssembleNode();
for (const input of item_nodes) {
build_node.link_to(input);
}
build_node.link_to(level_input);
for (const input of powder_inputs) {
powder_nodes.push(new PowderInputNode(input, document.getElementById(input)));
}
for (let i = 0; i < 4; ++i) {
let spell_node = new SpellDamageCalcNode(i);
spell_node.link_to(item_nodes[8], 'weapon-input');
spell_node.link_to(build_node, 'build');
spell_node.link_to(powder_nodes[4], 'weapon-powder');
spell_nodes.push(spell_node);
}
console.log("Set up graph");
let masonry = Macy({
container: "#masonry-container",
columns: 1,
mobileFirst: true,
breakAt: {
1200: 4,
},
margin: {
x: 20,
y: 20,
}
});
let search_masonry = Macy({
container: "#search-results",
columns: 1,
mobileFirst: true,
breakAt: {
1200: 4,
},
margin: {
x: 20,
y: 20,
}
});
});
// autocomplete initialize
function init_autocomplete() {
console.log("autocomplete init");
console.log(itemLists)
let dropdowns = new Map();
for (const eq of equipment_keys) {
if (tome_keys.includes(eq)) {

View file

@ -7,13 +7,14 @@ class ComputeNode {
*/
constructor(name) {
this.inputs = []; // parent nodes
this.input_translation = new Map();
this.children = [];
this.value = 0;
this.value = null;
this.name = name;
this.update_task = null;
this.update_time = Date.now();
this.fail_cb = false; // Set to true to force updates even if parent failed.
this.dirty = false;
this.dirty = true;
this.inputs_dirty = new Map();
this.inputs_dirty_count = 0;
}
@ -32,7 +33,7 @@ class ComputeNode {
}
let calc_inputs = new Map();
for (const input of this.inputs) {
calc_inputs.set(input.name, input.value);
calc_inputs.set(this.input_translation.get(input.name), input.value);
}
this.value = this.compute_func(calc_inputs);
this.dirty = false;
@ -41,18 +42,6 @@ class ComputeNode {
}
}
/**
* 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) {
child.set_input(this.name, this.value, timestamp);
}
}
/**
* Mark parent as not dirty. Propagates calculation if all inputs are present.
*/
@ -99,8 +88,10 @@ class ComputeNode {
throw "no compute func specified";
}
link_to(parent_node) {
link_to(parent_node, link_name) {
this.inputs.push(parent_node)
link_name = (link_name !== undefined) ? link_name : parent_node.name;
this.input_translation.set(parent_node.name, link_name);
this.inputs_dirty.set(parent_node.name, parent_node.dirty);
if (parent_node.dirty) {
this.inputs_dirty_count += 1;
@ -147,6 +138,7 @@ class InputNode extends ComputeNode {
super(name);
this.input_field = input_field;
this.input_field.addEventListener("input", () => calcSchedule(this));
calcSchedule(this);
}
compute_func(input_map) {
@ -168,6 +160,7 @@ class ItemInputNode extends InputNode {
constructor(name, item_input_field, none_item) {
super(name, item_input_field);
this.none_item = new Item(none_item);
this.none_item.statMap.set('NONE', true);
}
compute_func(input_map) {
@ -231,9 +224,13 @@ class ItemInputDisplayNode extends ComputeNode {
return null;
}
if (item.statMap.has('NONE')) {
return null;
}
const tier = item.statMap.get('tier');
this.input_field.classList.add(tier);
this.image.classList.add(tier + "-shadow");
return null;
}
}

View file

@ -1,29 +1,12 @@
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]);
// Calculate spell damage given a spell elemental conversion table, and a spell multiplier.
// If spell mult is 0, its melee damage and we don't multiply by attack speed.
// externalStats should be a map
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier, externalStats) {
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) {
let buildStats = new Map(stats);
let tooltipinfo = new Map();
//6x for damages, normal min normal max crit min crit max
let damageformulas = [["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "]];
if(externalStats) { //if nothing is passed in, then this hopefully won't trigger
for (const entry of externalStats) {
const key = entry[0];
const value = entry[1];
if (typeof value === "number") {
buildStats.set(key, buildStats.get(key) + value);
} else if (Array.isArray(value)) {
arr = [];
for (let j = 0; j < value.length; j++) {
arr[j] = buildStats.get(key)[j] + value[j];
}
buildStats.set(key, arr);
}
}
}
let powders = weapon.get("powders").slice();
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).

View file

@ -6,7 +6,7 @@ let reload = false;
let load_complete = false;
let load_in_progress = false;
let items;
let sets;
let sets = new Map();
let itemMap;
let idMap;
let redirectMap;
@ -20,7 +20,6 @@ async function load_local() {
let sets_store = get_tx.objectStore('set_db');
let get_store = get_tx.objectStore('item_db');
let request = get_store.getAll();
let request2 = sets_store.getAll();
request.onerror = function(event) {
reject("Could not read local item db...");
}
@ -28,15 +27,27 @@ async function load_local() {
console.log("Successfully read local item db.");
}
// key-value iteration (hpp don't break this again)
// https://stackoverflow.com/questions/47931595/indexeddb-getting-all-data-with-keys
let request2 = sets_store.openCursor();
request2.onerror = function(event) {
reject("Could not read local set db...");
}
request2.onsuccess = function(event) {
console.log("Successfully read local set db.");
}
let cursor = event.target.result;
if (cursor) {
let key = cursor.primaryKey;
let value = cursor.value;
sets.set(key, value);
cursor.continue();
}
else {
// no more results
console.log("Successfully read local set db.");
}
};
get_tx.oncomplete = function(event) {
items = request.result;
sets = request2.result;
init_maps();
load_complete = true;
db.close();

View file

@ -31,14 +31,15 @@ function calculate_skillpoints(equipment, weapon) {
let setCount = activeSetCounts.get(setName);
let old_bonus = {};
if (setCount) {
old_bonus = sets[setName].bonuses[setCount-1];
old_bonus = sets.get(setName).bonuses[setCount-1];
activeSetCounts.set(setName, setCount + 1);
}
else {
setCount = 0;
activeSetCounts.set(setName, 1);
}
const new_bonus = sets[setName].bonuses[setCount];
console.log(sets);
const new_bonus = sets.get(setName).bonuses[setCount];
//let skp_order = ["str","dex","int","def","agi"];
for (const i in skp_order) {
const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
@ -74,8 +75,8 @@ function calculate_skillpoints(equipment, weapon) {
if (setName) { // undefined/null means no set.
const setCount = activeSetCounts.get(setName);
if (setCount) {
const old_bonus = sets[setName].bonuses[setCount-1];
const new_bonus = sets[setName].bonuses[setCount];
const old_bonus = sets.get(setName).bonuses[setCount-1];
const new_bonus = sets.get(setName).bonuses[setCount];
//let skp_order = ["str","dex","int","def","agi"];
for (const i in skp_order) {
const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);

View file

@ -41,7 +41,7 @@ function displaysq2BuildStats(parent_id,build,command_group){
// id instruction
else {
let id = command;
if (stats.get(id) || build.externalStats.get(id)) {
if (stats.get(id)) {
let style = null;
// TODO: add pos and neg style
@ -54,10 +54,6 @@ function displaysq2BuildStats(parent_id,build,command_group){
// ignore
let id_val = stats.get(id);
if (build.externalStats.has(id)) {
id_val += build.externalStats.get(id);
}
if (reversedIDs.includes(id)) {
style === "positive" ? style = "negative" : style = "positive";
}
@ -650,7 +646,7 @@ function displaysq2PoisonDamage(overallparent_elem, build) {
let overallpoisonDamage = document.createElement("p");
let overallpoisonDamageFirst = document.createElement("span");
let overallpoisonDamageSecond = document.createElement("span");
let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct") + build.externalStats.get("poisonPct"))/100 /3);
let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct"))/100 /3);
overallpoisonDamageFirst.textContent = "Poison Tick: ";
overallpoisonDamageSecond.textContent = Math.max(poison_tick,0);
overallpoisonDamageSecond.classList.add("Damage");
@ -1126,8 +1122,8 @@ function displaysq2PowderSpecials(parent_elem, powderSpecials, build, overall=fa
let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
let part = spell["parts"][0];
let _results = calculateSpellDamage(stats, part.conversion,
stats.get("mdRaw"), stats.get("mdPct") + build.externalStats.get("mdPct"),
0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)), build.externalStats);//part.multiplier[power] / 100
stats.get("mdRaw"), stats.get("mdPct"),
0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100
let critChance = skillPointsToPercentage(build.total_skillpoints[1]);
let save_damages = [];
@ -1215,7 +1211,7 @@ function displaysq2PowderSpecials(parent_elem, powderSpecials, build, overall=fa
}
}
function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx) {
function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx, weapon) {
parent_elem.textContent = "";
@ -1264,7 +1260,7 @@ function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, sp
parent_elem.append(title_elem);
overallparent_elem.append(title_elemavg);
overallparent_elem.append(displaysq2NextCosts(spell, build));
overallparent_elem.append(displaysq2NextCosts(spell, build, weapon));
let critChance = skillPointsToPercentage(build.total_skillpoints[1]);
@ -1300,8 +1296,8 @@ function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, sp
if (part.type === "damage") {
//console.log(build.expandedStats);
let _results = calculateSpellDamage(stats, part.conversion,
stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct") + build.externalStats.get("sdPct"),
part.multiplier / 100, build.weapon, build.total_skillpoints, build.damageMultiplier, build.externalStats);
stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct"),
part.multiplier / 100, weapon, build.total_skillpoints, build.damageMultiplier);
let totalDamNormal = _results[0];
let totalDamCrit = _results[1];
let results = _results[2];
@ -1427,9 +1423,9 @@ function displaysq2EquipOrder(parent_elem, buildOrder){
}
}
function displaysq2NextCosts(spell, build) {
function displaysq2NextCosts(spell, build, weapon) {
let int = build.total_skillpoints[2];
let spells = spell_table[build.weapon.get("type")];
let spells = spell_table[weapon.get("type")];
let row = document.createElement("div");
row.classList.add("spellcost-tooltip");