Wynn2 damage calculation

This commit is contained in:
hppeng 2022-06-24 03:35:03 -07:00
parent 14ab20c446
commit 2b18774395
6 changed files with 267 additions and 160 deletions

View file

@ -155,6 +155,17 @@ class Build{
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"]; let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"];
let must_ids = [
"eMdPct","eMdRaw","eSdPct","eSdRaw","eDamPct","eDamRaw","eDamAddMin","eDamAddMax",
"tMdPct","tMdRaw","tSdPct","tSdRaw","tDamPct","tDamRaw","tDamAddMin","tDamAddMax",
"wMdPct","wMdRaw","wSdPct","wSdRaw","wDamPct","wDamRaw","wDamAddMin","wDamAddMax",
"fMdPct","fMdRaw","fSdPct","fSdRaw","fDamPct","fDamRaw","fDamAddMin","fDamAddMax",
"aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax",
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
"mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
]
//Create a map of this build's stats //Create a map of this build's stats
let statMap = new Map(); let statMap = new Map();
statMap.set("damageMultiplier", 1); statMap.set("damageMultiplier", 1);
@ -163,6 +174,9 @@ class Build{
for (const staticID of staticIDs) { for (const staticID of staticIDs) {
statMap.set(staticID, 0); statMap.set(staticID, 0);
} }
for (const staticID of must_ids) {
statMap.set(staticID, 0);
}
statMap.set("hp", levelToHPBase(this.level)); statMap.set("hp", levelToHPBase(this.level));
let major_ids = new Set(); let major_ids = new Set();
@ -211,13 +225,5 @@ class Build{
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd")); statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
this.statMap = statMap; this.statMap = statMap;
this.aggregateStats();
}
aggregateStats() {
let statMap = this.statMap;
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")]);
} }
} }

View file

@ -66,12 +66,23 @@ let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tom
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ]; let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
let skpReqs = skp_order.map(x => x + "Req"); let skpReqs = skp_order.map(x => x + "Req");
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "dmgMobs", "defMobs"]; let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
// wynn2 damages.
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
];
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ] let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]
//File reading for ID translations for JSON purposes //File reading for ID translations for JSON purposes
let reversetranslations = new Map(); let reversetranslations = new Map();
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]); let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
//does not include dmgMobs (wep tomes) and defMobs (armor tomes) //does not include dmgMobs (wep tomes) and defMobs (armor tomes)
for (const [k, v] of translations) { for (const [k, v] of translations) {
reversetranslations.set(v, k); reversetranslations.set(v, k);
@ -103,7 +114,17 @@ let nonRolledIDs = [
"skillpoints", "skillpoints",
"reqs", "reqs",
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
"majorIds"]; "majorIds",
// wynn2 damages.
"eDamAddMin","eDamAddMax",
"tDamAddMin","tDamAddMax",
"wDamAddMin","wDamAddMax",
"fDamAddMin","fDamAddMax",
"aDamAddMin","aDamAddMax",
"nDamAddMin","nDamAddMax", // neutral which is now an element
"damAddMin","damAddMax", // all
"rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral).
];
let rolledIDs = [ let rolledIDs = [
"hprPct", "hprPct",
"mr", "mr",
@ -131,13 +152,22 @@ let rolledIDs = [
"spPct2", "spRaw2", "spPct2", "spRaw2",
"spPct3", "spRaw3", "spPct3", "spRaw3",
"spPct4", "spRaw4", "spPct4", "spRaw4",
"rainbowRaw", "pDamRaw",
"sprint", "sprint",
"sprintReg", "sprintReg",
"jh", "jh",
"lq", "lq",
"gXp", "gXp",
"gSpd" "gSpd",
// wynn2 damages.
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
]; ];
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];

View file

@ -313,9 +313,10 @@ class ItemDisplayNode extends ComputeNode {
*/ */
class WeaponInputDisplayNode extends ComputeNode { class WeaponInputDisplayNode extends ComputeNode {
constructor(name, image_field) { constructor(name, image_field, dps_field) {
super(name); super(name);
this.image = image_field; this.image = image_field;
this.dps_field = dps_field;
} }
compute_func(input_map) { compute_func(input_map) {
@ -324,6 +325,12 @@ class WeaponInputDisplayNode extends ComputeNode {
const type = item.statMap.get('type'); const type = item.statMap.get('type');
this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png'); this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png');
let dps = get_base_dps(item.statMap);
if (isNaN(dps)) {
dps = dps[1];
if (isNaN(dps)) dps = 0;
}
this.dps_field.textContent = dps;
//as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff. //as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff.
if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) { if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) {
@ -577,9 +584,11 @@ class SpellDamageCalcNode extends ComputeNode {
for (const part of spell_parts) { for (const part of spell_parts) {
if (part.type === "damage") { if (part.type === "damage") {
let results = calculateSpellDamage(stats, part.conversion, let tmp_conv = [];
stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct"), for (let i in part.conversion) {
part.multiplier / 100, weapon, skillpoints, damage_mult); tmp_conv.push(part.conversion[i] * part.multiplier/100);
}
let results = calculateSpellDamage(stats, weapon, tmp_conv, true);
spell_results.push(results); spell_results.push(results);
} else if (part.type === "heal") { } else if (part.type === "heal") {
// TODO: wynn2 formula // TODO: wynn2 formula
@ -651,8 +660,7 @@ function getMeleeStats(stats, weapon) {
//One day we will create WynnWynn and no longer have shaman 99% melee injustice. //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. //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, weapon_stats, [100, 0, 0, 0, 0, 0], false, true);
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct"), 0, weapon_stats, skillpoints, damage_mult);
let dex = skillpoints[1]; let dex = skillpoints[1];
@ -973,7 +981,6 @@ function builder_graph_init() {
//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'); //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm');
} }
console.log(none_tomes);
for (const [eq, none_item] of zip2(tome_fields, [none_tomes[0], none_tomes[0], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[2]])) { for (const [eq, none_item] of zip2(tome_fields, [none_tomes[0], none_tomes[0], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[2]])) {
let input_field = document.getElementById(eq+"-choice"); let input_field = document.getElementById(eq+"-choice");
let item_image = document.getElementById(eq+"-img"); let item_image = document.getElementById(eq+"-img");
@ -985,7 +992,8 @@ function builder_graph_init() {
// weapon image changer node. // weapon image changer node.
let weapon_image = document.getElementById("weapon-img"); let weapon_image = document.getElementById("weapon-img");
new WeaponInputDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); let weapon_dps = document.getElementById("weapon-dps");
new WeaponInputDisplayNode('weapon-type', weapon_image, weapon_dps).link_to(item_nodes[8]);
// Level input node. // Level input node.
let level_input = new InputNode('level-input', document.getElementById('level-choice')); let level_input = new InputNode('level-input', document.getElementById('level-choice'));

View file

@ -1,31 +1,67 @@
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]); 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.
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) {
let buildStats = new Map(stats);
//6x for damages, normal min normal max crit min crit max
let powders = weapon.get("powders").slice(); // GRR THIS MUTATES THE ITEM
function get_base_dps(item) {
// Array of neutral + ewtfa damages. Each entry is a pair (min, max). const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
let damages = []; //SUPER JANK @HPP PLS FIX
const rawDamages = buildStats.get("damageRaw"); let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
for (let i = 0; i < rawDamages.length; i++) { if (item.get("tier") !== "Crafted") {
const damage_vals = rawDamages[i].split("-").map(Number); let weapon_result = apply_weapon_powder(item);
damages.push(damage_vals); let damages = weapon_result[0];
let total_damage = 0;
for (const i in damage_keys) {
total_damage += damages[i][0] + damages[i][1];
item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]);
}
total_damage = total_damage / 2;
return total_damage * attack_speed_mult;
} 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 = apply_weapon_powder(item, base_low);
let damage_low = results_low[2];
let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")];
let results_high = apply_weapon_powder(item, base_high);
let damage_high = results_high[2];
let total_damage_min = 0;
let total_damage_max = 0;
for (const i in damage_keys) {
total_damage_min += damage_low[i][0] + damage_low[i][1];
total_damage_max += damage_high[i][0] + damage_high[i][1];
item.set(damage_keys[i], damage_low[i][0]+"-"+damage_low[i][1]+"\u279c"+damage_high[i][0]+"-"+damage_high[i][1]);
}
total_damage_min = attack_speed_mult * total_damage_min / 2;
total_damage_max = attack_speed_mult * total_damage_max / 2;
return [total_damage_min, total_damage_max];
} }
}
/**
* weapon: Weapon to apply powder to
* damageBases: used by crafted
*/
function apply_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 // Applying spell conversions
let neutralBase = damages[0].slice(); let neutralBase = damages[0].slice();
let neutralRemainingRaw = 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 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 //Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
//1st round - apply each as ingred, 2nd round - apply as normal //1st round - apply each as ingred, 2nd round - apply as normal
if (weapon.get("tier") === "Crafted") { if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) {
let damageBases = buildStats.get("damageBases").slice();
for (const p of powders.concat(weapon.get("ingredPowders"))) { for (const p of powders.concat(weapon.get("ingredPowders"))) {
let powder = powderStats[p]; //use min, max, and convert 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 element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
@ -34,26 +70,13 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 ); damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
} }
//update all damages //update all damages
if(!weapon.get("custom")) { for (let i = 0; i < damages.length; i++) {
for (let i = 0; i < damages.length; i++) { damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
}
} }
neutralRemainingRaw = damages[0].slice(); neutralRemainingRaw = damages[0].slice();
neutralBase = damages[0].slice(); neutralBase = damages[0].slice();
} }
for (let i = 0; i < 5; ++i) {
let conversionRatio = spellConversions[i+1]/100;
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
damages[i+1][0] = Math.floor(round_near(damages[i+1][0] + min_diff));
damages[i+1][1] = Math.floor(round_near(damages[i+1][1] + max_diff));
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
}
//apply powders to weapon //apply powders to weapon
for (const powderID of powders) { for (const powderID of powders) {
const powder = powderStats[powderID]; const powder = powderStats[powderID];
@ -72,69 +95,148 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
damages[element+1][1] += powder.max; 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; damages[0] = neutralRemainingRaw;
return [damages, present_elements];
}
let damageMult = damageMultiplier; function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
let melee = false; // TODO: Roll all the loops together maybe
// If we are doing melee calculations:
if (spellMultiplier == 0) { // Array of neutral + ewtfa damages. Each entry is a pair (min, max).
spellMultiplier = 1; // 1. Get weapon damage (with powders).
melee = true; let weapon_result = apply_weapon_powder(weapon);
} let weapon_damages = weapon_result[0];
else { let present = weapon_result[1];
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
} // 2. Conversions.
//console.log(damages); // 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
//console.log(damageMult); let damages = [];
rawModifier *= spellMultiplier * damageMultiplier; const neutral_convert = conversions[0] / 100;
let totalDamNorm = [0, 0]; let weapon_min = 0;
let totalDamCrit = [0, 0]; let weapon_max = 0;
let damages_results = []; for (const damage of weapon_damages) {
// 0th skillpoint is strength, 1st is dex. let min_dmg = damage[0] * neutral_convert;
let str = total_skillpoints[0]; let max_dmg = damage[1] * neutral_convert;
let strBoost = 1 + skillPointsToPercentage(str); damages.push([min_dmg, max_dmg]);
if(!melee){ weapon_min += damage[0];
let baseDam = rawModifier * strBoost; weapon_max += damage[1];
let baseDamCrit = rawModifier * (1 + strBoost);
totalDamNorm = [baseDam, baseDam];
totalDamCrit = [baseDamCrit, baseDamCrit];
}
let staticBoost = (pctModifier / 100.);
let skillBoost = [0];
for (let i in total_skillpoints) {
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get(skp_elements[i]+"DamPct") / 100.);
} }
// 2.2. Next, apply elemental conversions using damage computed in step 1.1.
// Also, track which elements are present. (Add onto those present in the weapon itself.)
for (let i = 1; i <= 5; ++i) {
if (conversions[i] > 0) {
damages[i][0] += conversions[i]/100 * weapon_min;
damages[i][1] += conversions[i]/100 * weapon_max;
present[i] = true;
}
}
// Also theres prop and rainbow!!
const damage_elements = ['n'].concat(skp_elements); // netwfa
if (!ignore_speed) {
// 3. Apply attack speed multiplier. Ignored for melee single hit
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(weapon.get("atkSpd"))];
for (let i = 0; i < 6; ++i) {
damages[i][0] *= attack_speed_mult;
damages[i][1] *= attack_speed_mult;
}
}
// 4. Add additive damage. TODO: Is there separate additive damage?
for (let i = 0; i < 6; ++i) {
if (present[i]) {
damages[i][0] += stats.get(damage_elements[i]+'DamAddMin');
damages[i][1] += stats.get(damage_elements[i]+'DamAddMax');
}
}
// 5. ID bonus.
let specific_boost_str = 'Md';
if (use_spell_damage) {
specific_boost_str = 'Sd';
}
// 5.1: %boost application
let skill_boost = [0]; // no neutral skillpoint booster
for (const skp of skp_order) {
skill_boost.push(skillPointsToPercentage(stats.get(skp)));
}
let static_boost = (stats.get(specific_boost_str.toLowerCase()+'Pct') + stats.get('damPct')) / 100;
// These do not count raw damage. I think. Easy enough to change
let total_min = 0;
let total_max = 0;
for (let i in damages) { for (let i in damages) {
let damageBoost = 1 + skillBoost[i] + staticBoost; let damage_prefix = damage_elements[i] + specific_boost_str;
damages_results.push([ let damageBoost = 1 + skill_boost[i] + static_boost + (stats.get(damage_prefix+'Pct')/100);
Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min damages[i][0] *= Math.max(damageBoost, 0);
Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max damages[i][1] *= Math.max(damageBoost, 0);
Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min // Collect total damage post %boost
Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max total_min += damages[i][0];
]); total_max += damages[i][1];
totalDamNorm[0] += damages_results[i][0];
totalDamNorm[1] += damages_results[i][1];
totalDamCrit[0] += damages_results[i][2];
totalDamCrit[1] += damages_results[i][3];
} }
if (melee) {
totalDamNorm[0] += Math.max(strBoost*rawModifier, -damages_results[0][0]); let total_elem_min = total_min - damages[0][0];
totalDamNorm[1] += Math.max(strBoost*rawModifier, -damages_results[0][1]); let total_elem_max = total_max - damages[0][1];
totalDamCrit[0] += Math.max((strBoost+1)*rawModifier, -damages_results[0][2]);
totalDamCrit[1] += Math.max((strBoost+1)*rawModifier, -damages_results[0][3]); // 5.2: Raw application.
let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw');
let rainbow_raw = stats.get('r'+specific_boost_str+'Raw') + stats.get('rDamRaw');
for (let i in damages) {
let damages_obj = damages[i];
let damage_prefix = damage_elements[i] + specific_boost_str;
// Normie raw
let raw_boost = 0;
if (present[i]) {
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;
new_max += (damages_obj[1] / total_elem_max) * rainbow_raw;
}
damages_obj[0] = new_min;
damages_obj[1] = new_max;
} }
damages_results[0][0] += strBoost*rawModifier;
damages_results[0][1] += strBoost*rawModifier;
damages_results[0][2] += (strBoost + 1)*rawModifier;
damages_results[0][3] += (strBoost + 1)*rawModifier;
if (totalDamNorm[0] < 0) totalDamNorm[0] = 0; // 6. Strength boosters
if (totalDamNorm[1] < 0) totalDamNorm[1] = 0; // str/dex, as well as any other mutually multiplicative effects
if (totalDamCrit[0] < 0) totalDamCrit[0] = 0; let strBoost = 1 + skill_boost[1];
if (totalDamCrit[1] < 0) totalDamCrit[1] = 0; let total_dam_norm = [0, 0];
let total_dam_crit = [0, 0];
let damages_results = [];
const damage_mult = stats.get("damageMultiplier");
return [totalDamNorm, totalDamCrit, damages_results]; for (const damage of damages) {
const res = [
damage[0] * strBoost * damage_mult, // Normal min
damage[1] * strBoost * damage_mult, // Normal max
damage[0] * (strBoost + 1) * damage_mult, // Crit min
damage[1] * (strBoost + 1) * damage_mult, // Crit max
];
damages_results.push(res);
total_dam_norm[0] += res[0];
total_dam_norm[1] += res[1];
total_dam_crit[0] += res[2];
total_dam_crit[1] += res[3];
}
if (total_dam_norm[0] < 0) total_dam_norm[0] = 0;
if (total_dam_norm[1] < 0) total_dam_norm[1] = 0;
if (total_dam_crit[0] < 0) total_dam_crit[0] = 0;
if (total_dam_crit[1] < 0) total_dam_crit[1] = 0;
return [total_dam_norm, total_dam_crit, damages_results];
} }

View file

@ -174,50 +174,7 @@ function displayExpandedItem(item, parent_id){
// !elemental is some janky hack for elemental damage. // !elemental is some janky hack for elemental damage.
// normals just display a thing. // normals just display a thing.
if (item.get("category") === "weapon") { if (item.get("category") === "weapon") {
let stats = new Map(); item.set('basedps', get_base_dps(item));
stats.set("atkSpd", item.get("atkSpd"));
stats.set("eDamPct", 0);
stats.set("tDamPct", 0);
stats.set("wDamPct", 0);
stats.set("fDamPct", 0);
stats.set("aDamPct", 0);
//SUPER JANK @HPP PLS FIX
let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
if (item.get("tier") !== "Crafted") {
stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
let damages = results[2];
let total_damage = 0;
for (const i in damage_keys) {
total_damage += damages[i][0] + damages[i][1];
item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]);
}
total_damage = total_damage / 2;
item.set("basedps", total_damage);
} else {
stats.set("damageRaw", [item.get("nDamLow"), item.get("eDamLow"), item.get("tDamLow"), item.get("wDamLow"), item.get("fDamLow"), item.get("aDamLow")]);
stats.set("damageBases", [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]);
let resultsLow = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
let damagesLow = resultsLow[2];
stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
stats.set("damageBases", [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]);
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
let damages = results[2];
console.log(damages);
let total_damage_min = 0;
let total_damage_max = 0;
for (const i in damage_keys) {
total_damage_min += damagesLow[i][0] + damagesLow[i][1];
total_damage_max += damages[i][0] + damages[i][1];
item.set(damage_keys[i], damagesLow[i][0]+"-"+damagesLow[i][1]+"\u279c"+damages[i][0]+"-"+damages[i][1]);
}
total_damage_min = total_damage_min / 2;
total_damage_max = total_damage_max / 2;
item.set("basedps", [total_damage_min, total_damage_max]);
}
} else if (item.get("category") === "armor") { } else if (item.get("category") === "armor") {
} }
@ -555,19 +512,18 @@ function displayExpandedItem(item, parent_id){
} }
if (item.get("category") === "weapon") { if (item.get("category") === "weapon") {
let damage_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
let total_damages = item.get("basedps"); let total_damages = item.get("basedps");
let base_dps_elem = document.createElement("p"); let base_dps_elem = document.createElement("p");
base_dps_elem.classList.add("left"); base_dps_elem.classList.add("left");
base_dps_elem.classList.add("itemp"); base_dps_elem.classList.add("itemp");
if (item.get("tier") === "Crafted") { if (item.get("tier") === "Crafted") {
let base_dps_min = total_damages[0] * damage_mult; let base_dps_min = total_damages[0];
let base_dps_max = total_damages[1] * damage_mult; let base_dps_max = total_damages[1];
base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3); base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3);
} }
else { else {
base_dps_elem.textContent = "Base DPS: "+(total_damages * damage_mult); base_dps_elem.textContent = "Base DPS: "+(total_damages);
} }
parent_div.appendChild(document.createElement("p")); parent_div.appendChild(document.createElement("p"));
parent_div.appendChild(base_dps_elem); parent_div.appendChild(base_dps_elem);
@ -1502,9 +1458,12 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage
let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]); let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
let part = spell["parts"][0]; let part = spell["parts"][0];
let _results = calculateSpellDamage(stats, part.conversion,
stats.get("mdRaw"), stats.get("mdPct"), let tmp_conv = [];
0, weapon, skillpoints, stats.get('damageMultiplier') * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100 for (let i in part.conversion) {
tmp_conv.push(part.conversion[i] * part.multiplier[power-1]);
}
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false);
let critChance = skillPointsToPercentage(skillpoints[1]); let critChance = skillPointsToPercentage(skillpoints[1]);
let save_damages = []; let save_damages = [];

View file

@ -39,9 +39,11 @@ function optimizeStrDex() {
let total_damage = 0; let total_damage = 0;
for (const part of spell_parts) { for (const part of spell_parts) {
if (part.type === "damage") { if (part.type === "damage") {
let _results = calculateSpellDamage(stats, part.conversion, let tmp_conv = [];
stats.get("sdRaw"), stats.get("sdPct"), for (let i in part.conversion) {
part.multiplier / 100, player_build.weapon.statMap, total_skillpoints, 1); tmp_conv.push(part.conversion[i] * part.multiplier);
}
let _results = calculateSpellDamage(stats, player_build.weapon.statMap, tmp_conv, true);
let totalDamNormal = _results[0]; let totalDamNormal = _results[0];
let totalDamCrit = _results[1]; let totalDamCrit = _results[1];
let results = _results[2]; let results = _results[2];