wynnbuilder-idk/js/damage_calc.js

409 lines
19 KiB
JavaScript
Raw Normal View History

2021-01-15 05:47:38 +00:00
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';
2022-06-24 10:35:03 +00:00
function get_base_dps(item) {
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
//SUPER JANK @HPP PLS FIX
if (item.get("tier") !== "Crafted") {
let total_damage = 0;
for (const damage_k of damage_keys) {
damages = item.get(damage_k);
total_damage += damages[0] + damages[1];
}
return total_damage * attack_speed_mult / 2;
}
else {
let total_damage_min = 0;
let total_damage_max = 0;
for (const damage_k of damage_keys) {
damages = item.get(damage_k);
total_damage_min += damages[0][0] + damages[0][1];
total_damage_max += damages[1][0] + damages[1][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];
}
}
// 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];
2022-06-24 10:35:03 +00:00
for (const i in damage_keys) {
item.set(damage_keys[i], damages[i]);
2022-06-24 10:35:03 +00:00
}
} 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];
2022-06-24 10:35:03 +00:00
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];
2022-06-24 10:35:03 +00:00
for (const i in damage_keys) {
item.set(damage_keys[i], [damage_low[i], damage_high[i]]);
2022-06-24 10:35:03 +00:00
}
}
console.log(item);
item.set(damage_present_key, present);
2022-06-24 10:35:03 +00:00
}
/**
* weapon: Weapon to apply powder to
* damageBases: used by crafted
*/
function calc_weapon_powder(weapon, damageBases) {
let powders = weapon.get("powders").slice();
2022-06-24 10:35:03 +00:00
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
2022-06-24 10:35:03 +00:00
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)
];
2021-02-17 18:29:41 +00:00
// Applying spell conversions
let neutralBase = damages[0].slice();
let neutralRemainingRaw = damages[0].slice();
2021-03-27 22:10:41 +00:00
2021-04-30 03:11:26 +00:00
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
2021-03-27 22:10:41 +00:00
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
//1st round - apply each as ingred, 2nd round - apply as normal
2022-06-24 10:35:03 +00:00
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
2021-03-27 22:09:16 +00:00
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
2022-06-24 10:35:03 +00:00
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();
2021-02-22 23:24:24 +00:00
}
2021-03-27 22:10:41 +00:00
//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;
2021-01-11 22:25:55 +00:00
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;
}
2021-03-27 22:10:41 +00:00
2022-06-24 10:35:03 +00:00
// 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;
2022-06-24 10:35:03 +00:00
return [damages, present_elements];
}
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
// TODO: Roll all the loops together maybe
2022-06-24 10:35:03 +00:00
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
// 1. Get weapon damage (with powders).
let weapon_damages;
if (weapon.get('tier') === 'Crafted') {
weapon_damages = damage_keys.map(x => weapon.get(x)[1]);
}
else {
weapon_damages = damage_keys.map(x => weapon.get(x));
}
let present = weapon.get(damage_present_key);
2022-06-24 10:35:03 +00:00
// 2. Conversions.
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
let damages = [];
const neutral_convert = conversions[0] / 100;
let weapon_min = 0;
let weapon_max = 0;
for (const damage of weapon_damages) {
let min_dmg = damage[0] * neutral_convert;
let max_dmg = damage[1] * neutral_convert;
damages.push([min_dmg, max_dmg]);
weapon_min += damage[0];
weapon_max += damage[1];
}
2022-06-24 10:35:03 +00:00
// 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;
}
}
2022-06-24 10:35:03 +00:00
// 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';
}
2022-06-24 10:35:03 +00:00
// 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)));
}
2022-06-24 10:35:03 +00:00
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) {
let damage_prefix = damage_elements[i] + specific_boost_str;
let damageBoost = 1 + skill_boost[i] + static_boost + (stats.get(damage_prefix+'Pct')/100);
damages[i][0] *= Math.max(damageBoost, 0);
damages[i][1] *= Math.max(damageBoost, 0);
// Collect total damage post %boost
total_min += damages[i][0];
total_max += damages[i][1];
}
let total_elem_min = total_min - damages[0][0];
let total_elem_max = total_max - damages[0][1];
2022-06-24 10:35:03 +00:00
// 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) {
2022-06-24 10:35:03 +00:00
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;
}
2022-06-24 10:35:03 +00:00
// 6. Strength boosters
// str/dex, as well as any other mutually multiplicative effects
let strBoost = 1 + skill_boost[1];
let total_dam_norm = [0, 0];
let total_dam_crit = [0, 0];
let damages_results = [];
const damage_mult = stats.get("damageMultiplier");
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];
}
2022-06-24 10:35:03 +00:00
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;
2022-06-24 10:35:03 +00:00
return [total_dam_norm, total_dam_crit, damages_results];
}
2021-01-09 08:52:58 +00:00
2021-01-09 08:52:58 +00:00
const spell_table = {
"wand": [
{ title: "Heal", cost: 6, parts: [
2021-01-11 12:36:24 +00:00
{ subtitle: "First Pulse", type: "heal", strength: 0.12 },
{ subtitle: "Second and Third Pulses", type: "heal", strength: 0.06 },
2021-01-30 12:03:40 +00:00
{ subtitle: "Total Heal", type: "heal", strength: 0.24, summary: true },
{ subtitle: "First Pulse (Ally)", type: "heal", strength: 0.20 },
{ subtitle: "Second and Third Pulses (Ally)", type: "heal", strength: 0.1 },
{ subtitle: "Total Heal (Ally)", type: "heal", strength: 0.4 }
2021-01-09 08:52:58 +00:00
] },
{ title: "Teleport", cost: 4, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 40, 0, 0, 0], summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "Meteor", cost: 8, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Blast Damage", type: "damage", multiplier: 500, conversion: [40, 30, 0, 0, 30, 0], summary: true },
2021-01-10 21:01:59 +00:00
{ subtitle: "Burn Damage", type: "damage", multiplier: 125, conversion: [100, 0, 0, 0, 0, 0] },
2021-01-09 08:52:58 +00:00
] },
{ title: "Ice Snake", cost: 4, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "damage", multiplier: 70, conversion: [50, 0, 0, 50, 0, 0], summary: true },
2021-01-09 08:52:58 +00:00
] },
],
"spear": [
{ title: "Bash", cost: 6, parts: [
{ subtitle: "First Damage", type: "damage", multiplier: 130, conversion: [60, 40, 0, 0, 0, 0]},
{ subtitle: "Explosion Damage", type: "damage", multiplier: 130, conversion: [100, 0, 0, 0, 0, 0]},
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "total", factors: [1, 1], summary: true },
2021-01-09 08:52:58 +00:00
] },
2021-01-30 12:03:40 +00:00
{ title: "Charge", cost: 4, variants: {
DEFAULT: [
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0], summary: true }
],
RALLY: [
{ subtitle: "Self Heal", type: "heal", strength: 0.07, summary: true },
2021-01-30 12:03:40 +00:00
{ subtitle: "Ally Heal", type: "heal", strength: 0.15 }
]
} },
{ title: "Uppercut", cost: 9, parts: [
2021-01-09 08:52:58 +00:00
{ subtitle: "First Damage", type: "damage", multiplier: 300, conversion: [70, 20, 10, 0, 0, 0] },
{ subtitle: "Fireworks Damage", type: "damage", multiplier: 50, conversion: [60, 0, 40, 0, 0, 0] },
{ subtitle: "Crash Damage", type: "damage", multiplier: 50, conversion: [80, 0, 20, 0, 0, 0] },
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "total", factors: [1, 1, 1], summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "War Scream", cost: 6, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Area Damage", type: "damage", multiplier: 50, conversion: [0, 0, 0, 0, 75, 25], summary: true },
2021-01-09 08:52:58 +00:00
{ subtitle: "Air Shout (Per Hit)", type: "damage", multiplier: 30, conversion: [0, 0, 0, 0, 75, 25] },
] },
],
"bow": [
2021-01-30 12:03:40 +00:00
{ title: "Arrow Storm", cost: 6, variants: {
DEFAULT: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [60, 0, 25, 0, 15, 0], summary: true },
2021-01-30 12:03:40 +00:00
{ subtitle: "Per Arrow (60)", type: "damage", multiplier: 10, conversion: [60, 0, 25, 0, 15, 0]}
],
HAWKEYE: [
{ subtitle: "Total Damage (Hawkeye)", type: "damage", multiplier: 400, conversion: [60, 0, 25, 0, 15, 0], summary: true },
{ subtitle: "Per Arrow (5)", type: "damage", multiplier: 80, conversion: [60, 0, 25, 0, 15, 0]}
],
} },
2021-01-09 08:52:58 +00:00
{ title: "Escape", cost: 3, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Landing Damage", type: "damage", multiplier: 100, conversion: [50, 0, 0, 0, 0, 50], summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "Bomb Arrow", cost: 8, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "damage", multiplier: 250, conversion: [60, 25, 0, 0, 15, 0], summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "Arrow Shield", cost: 10, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Shield Damage", type: "damage", multiplier: 100, conversion: [70, 0, 0, 0, 0, 30], summary: true },
2021-01-09 08:52:58 +00:00
{ subtitle: "Arrow Rain Damage", type: "damage", multiplier: 200, conversion: [70, 0, 0, 0, 0, 30] },
] },
],
"dagger": [
{ title: "Spin Attack", cost: 6, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [70, 0, 30, 0, 0, 0], summary: true},
2021-01-09 08:52:58 +00:00
] },
{ title: "Vanish", cost: 2, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "No Damage", type: "none", summary: true }
2021-01-09 08:52:58 +00:00
] },
{ title: "Multihit", cost: 8, parts: [
2021-01-09 09:35:18 +00:00
{ subtitle: "1st to 10th Hit", type: "damage", multiplier: 27, conversion: [100, 0, 0, 0, 0, 0] },
{ subtitle: "Fatality", type: "damage", multiplier: 120, conversion: [20, 0, 30, 50, 0, 0] },
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "total", factors: [10, 1], summary: true },
2021-01-09 08:52:58 +00:00
] },
2021-01-30 12:03:40 +00:00
{ title: "Smoke Bomb", cost: 8, variants: {
DEFAULT: [
{ subtitle: "Tick Damage (10 max)", type: "damage", multiplier: 60, conversion: [50, 25, 0, 0, 0, 25] },
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [50, 25, 0, 0, 0, 25], summary: true },
2021-01-30 12:03:40 +00:00
],
CHERRY_BOMBS: [
{ subtitle: "Total Damage (Cherry Bombs)", type: "damage", multiplier: 330, conversion: [50, 25, 0, 0, 0, 25], summary: true },
{ subtitle: "Per Bomb", type: "damage", multiplier: 110, conversion: [50, 25, 0, 0, 0, 25] }
2021-01-30 12:03:40 +00:00
]
} },
2021-01-09 08:52:58 +00:00
],
"relik": [
{ title: "Totem", cost: 4, parts: [
{ subtitle: "Smash Damage", type: "damage", multiplier: 100, conversion: [80, 0, 0, 0, 20, 0]},
{ subtitle: "Damage Tick", type: "damage", multiplier: 20, conversion: [80, 0, 0, 0, 0, 20]},
{ subtitle: "Heal Tick", type: "heal", strength: 0.03, summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "Haul", cost: 1, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [80, 0, 20, 0, 0, 0], summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "Aura", cost: 8, parts: [
2021-01-10 22:16:22 +00:00
{ subtitle: "One Wave", type: "damage", multiplier: 200, conversion: [70, 0, 0, 30, 0, 0], summary: true },
2021-01-09 08:52:58 +00:00
] },
{ title: "Uproot", cost: 6, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [70, 30, 0, 0, 0, 0], summary: true },
2021-01-09 08:52:58 +00:00
] },
],
"sword": [
{ title: "Successive Strikes", cost: 5, parts: [
{ subtitle: "Damage", type: "damage", multiplier: 65, conversion: [70, 0, 15, 0, 0, 15]},
{ subtitle: "Final Strike", type: "damage", multiplier: 120, conversion: [70, 0, 15, 0, 0, 15]},
{ subtitle: "Total Damage (Normal)", type: "total", factors: [2, 0], summary: true },
] },
{ title: "Dash", cost: 3, parts: [
{ subtitle: "Damage", type: "damage", multiplier: 120, conversion: [60, 0, 0, 0, 0, 40], summary: true },
] },
{ title: "Execute", cost: 8, parts: [
{ subtitle: "Minimum Damage", type: "damage", multiplier: 100, conversion: [60, 0, 20, 0, 20, 0]},
{ subtitle: "Maximum Damage", type: "damage", multiplier: 1200, conversion: [60, 0, 20, 0, 20, 0], summary: true },
] },
{ title: "Blade Echo", cost: 4, parts: [
{ subtitle: "Damage", type: "damage", multiplier: 125, conversion: [60, 0, 0, 20, 0, 20], summary: true },
] },
],
2021-01-28 00:52:34 +00:00
"powder": [ //This is how instant-damage powder specials are implemented.
{ title: "Quake", cost: 0, parts:[
{ subtitle: "Total Damage", type: "damage", multiplier: [155, 220, 285, 350, 415], conversion: [0,100,0,0,0,0], summary: true},
] },
{ title: "Chain Lightning", cost: 0, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: [200, 225, 250, 275, 300], conversion: [0,0,100,0,0,0], summary: true},
]},
{ title: "Courage", cost: 0, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: [75, 87.5, 100, 112.5, 125], conversion: [0,0,0,0,100,0], summary: true},
]}, //[75, 87.5, 100, 112.5, 125]
2021-01-09 08:52:58 +00:00
]
2021-01-09 12:47:25 +00:00
};