2021-01-07 00:08:19 +00:00
|
|
|
/*Turns the input amount of skill points into a float precision percentage.
|
|
|
|
* @param skp - the integer skillpoint count to be converted
|
|
|
|
*/
|
|
|
|
function skillPointsToPercentage(skp){
|
|
|
|
if (skp<=0){
|
|
|
|
return 0.0;
|
|
|
|
}else if(skp>=150){
|
|
|
|
return 0.808;
|
|
|
|
}else{
|
|
|
|
return(-0.0000000066695* Math.pow(Math.E, -0.00924033 * skp + 18.9) + 1.0771);
|
|
|
|
//return(-0.0000000066695* Math.pow(Math.E, -0.00924033 * skp + 18.9) + 1.0771).toFixed(3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*Turns the input amount of levels into skillpoints available.
|
|
|
|
*
|
|
|
|
* @param level - the integer level count te be converted
|
|
|
|
*/
|
|
|
|
function levelToSkillPoints(level){
|
|
|
|
if(level < 1){
|
|
|
|
return 0;
|
|
|
|
}else if(level >= 101){
|
|
|
|
return 200;
|
|
|
|
}else{
|
|
|
|
return (level - 1) * 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*Turns the input amount of levels in to base HP.
|
|
|
|
* @param level - the integer level count to be converted
|
|
|
|
*/
|
|
|
|
function levelToHPBase(level){
|
|
|
|
if(level < 1){ //bad level
|
|
|
|
return this.levelToHPBase(1);
|
|
|
|
}else if (level > 106){ //also bad level
|
|
|
|
return this.levelToHPBase(106);
|
|
|
|
}else{ //good level
|
|
|
|
return 5*level + 5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-01-08 05:36:57 +00:00
|
|
|
const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ];
|
2021-01-08 04:31:29 +00:00
|
|
|
const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"];
|
|
|
|
|
2021-01-07 00:02:10 +00:00
|
|
|
/*Class that represents a wynn player's build.
|
|
|
|
*/
|
|
|
|
class Build{
|
|
|
|
|
2021-01-09 08:52:58 +00:00
|
|
|
/*
|
|
|
|
* Construct a build.
|
|
|
|
* @param level : Level of the player.
|
|
|
|
* @param equipment : List of equipment names that make up the build.
|
|
|
|
* In order: Helmet, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon.
|
|
|
|
* @param powders : Powder application. List of lists of integers (powder IDs).
|
|
|
|
* In order: Helmet, Chestplate, Leggings, Boots, Weapon.
|
|
|
|
*/
|
2021-01-09 03:53:57 +00:00
|
|
|
constructor(level,equipment, powders){
|
2021-01-08 20:17:37 +00:00
|
|
|
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
|
2021-01-09 19:50:36 +00:00
|
|
|
this.powders = powders;
|
2021-01-09 03:53:57 +00:00
|
|
|
if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") {
|
|
|
|
const helmet = itemMap.get(equipment[0]);
|
2021-01-08 20:17:37 +00:00
|
|
|
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
2021-01-09 00:56:07 +00:00
|
|
|
this.helmet = expandItem(helmet, this.powders[0]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such helmet named ", equipment[0]);
|
|
|
|
}
|
|
|
|
if(itemMap.get(equipment[1]).type === "chestplate") {
|
|
|
|
const chestplate = itemMap.get(equipment[1]);
|
2021-01-08 20:17:37 +00:00
|
|
|
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
2021-01-09 00:56:07 +00:00
|
|
|
this.chestplate = expandItem(chestplate, this.powders[1]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such chestplate named ", equipment[1]);
|
|
|
|
}
|
|
|
|
if(itemMap.get(equipment[2]).type === "leggings") {
|
|
|
|
const leggings = itemMap.get(equipment[2]);
|
2021-01-08 20:17:37 +00:00
|
|
|
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
2021-01-09 00:56:07 +00:00
|
|
|
this.leggings = expandItem(leggings, this.powders[2]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such leggings named ", equipment[2]);
|
|
|
|
}
|
|
|
|
if(itemMap.get(equipment[3]).type === "boots") {
|
|
|
|
const boots = itemMap.get(equipment[3]);
|
2021-01-08 20:17:37 +00:00
|
|
|
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
2021-01-09 00:56:07 +00:00
|
|
|
this.boots = expandItem(boots, this.powders[3]);
|
2021-01-09 03:53:57 +00:00
|
|
|
}else{
|
|
|
|
throw new TypeError("No such boots named ", equipment[3]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
2021-01-09 03:53:57 +00:00
|
|
|
if(itemMap.get(equipment[4]).type === "ring") {
|
|
|
|
const ring = itemMap.get(equipment[4]);
|
2021-01-09 19:50:36 +00:00
|
|
|
this.ring1 = expandItem(ring, []);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such ring named ", equipment[4]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
2021-01-09 03:53:57 +00:00
|
|
|
if(itemMap.get(equipment[5]).type === "ring") {
|
|
|
|
const ring = itemMap.get(equipment[5]);
|
2021-01-09 19:50:36 +00:00
|
|
|
this.ring2 = expandItem(ring, []);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such ring named ", equipment[5]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
2021-01-09 03:53:57 +00:00
|
|
|
if(itemMap.get(equipment[6]).type === "bracelet") {
|
|
|
|
const bracelet = itemMap.get(equipment[6]);
|
2021-01-09 19:50:36 +00:00
|
|
|
this.bracelet = expandItem(bracelet, []);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such bracelet named ", equipment[6]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
2021-01-09 03:53:57 +00:00
|
|
|
if(itemMap.get(equipment[7]).type === "necklace") {
|
|
|
|
const necklace = itemMap.get(equipment[7]);
|
2021-01-09 19:50:36 +00:00
|
|
|
this.necklace = expandItem(necklace, []);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such necklace named ", equipment[7]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
2021-01-09 03:53:57 +00:00
|
|
|
if(itemMap.get(equipment[8]).category === "weapon") {
|
|
|
|
const weapon = itemMap.get(equipment[8]);
|
2021-01-08 20:17:37 +00:00
|
|
|
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
2021-01-09 00:56:07 +00:00
|
|
|
this.weapon = expandItem(weapon, this.powders[4]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}else{
|
2021-01-09 03:53:57 +00:00
|
|
|
throw new TypeError("No such weapon named ", equipment[8]);
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
|
|
|
if(level < 1){ //Should these be constants?
|
|
|
|
this.level = 1;
|
|
|
|
}else if (level > 106){
|
|
|
|
this.level = 106;
|
|
|
|
}else{
|
|
|
|
this.level = level;
|
|
|
|
}
|
2021-01-07 06:41:41 +00:00
|
|
|
this.availableSkillpoints = levelToSkillPoints(this.level);
|
2021-01-09 00:56:07 +00:00
|
|
|
this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
|
|
|
this.items = this.equipment.concat([this.weapon]);
|
2021-01-07 06:41:41 +00:00
|
|
|
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
2021-01-09 00:56:07 +00:00
|
|
|
let result = calculate_skillpoints(this.equipment, this.weapon);
|
2021-01-07 06:41:41 +00:00
|
|
|
this.equip_order = result[0];
|
|
|
|
this.base_skillpoints = result[1];
|
|
|
|
this.total_skillpoints = result[2];
|
|
|
|
this.assigned_skillpoints = result[3];
|
2021-01-08 04:31:29 +00:00
|
|
|
|
2021-01-08 20:17:37 +00:00
|
|
|
// For strength boosts like warscream, vanish, etc.
|
2021-01-09 00:56:07 +00:00
|
|
|
this.damageMultiplier = 1.0;
|
2021-01-08 20:17:37 +00:00
|
|
|
|
2021-01-08 04:31:29 +00:00
|
|
|
this.initBuildStats();
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*Returns build in string format
|
2021-01-07 06:41:41 +00:00
|
|
|
*/
|
2021-01-07 00:02:10 +00:00
|
|
|
toString(){
|
2021-01-09 00:56:07 +00:00
|
|
|
return this.helmet.get("name") + ", " + this.chestplate.get("name") + ", " + this.leggings.get("name") + ", " + this.boots.get("name") + ", " + this.ring1.get("name") + ", " + this.ring2.get("name") + ", " + this.bracelet.get("name") + ", " + this.necklace.get("name") + ", " + this.weapon.get("name");
|
2021-01-07 00:02:10 +00:00
|
|
|
}
|
|
|
|
|
2021-01-07 06:41:41 +00:00
|
|
|
/* Getters */
|
2021-01-07 21:32:36 +00:00
|
|
|
|
|
|
|
/* Get total health for build.
|
|
|
|
*/
|
2021-01-07 00:02:10 +00:00
|
|
|
getHealth(){
|
2021-01-09 12:47:25 +00:00
|
|
|
let health = this.statMap.get("hp") + this.statMap.get("hpBonus");
|
2021-01-07 00:02:10 +00:00
|
|
|
if(health<5){
|
|
|
|
return 5;
|
|
|
|
}else{
|
|
|
|
return health;
|
|
|
|
}
|
|
|
|
}
|
2021-01-09 08:52:58 +00:00
|
|
|
|
|
|
|
getSpellCost(spellIdx, cost) {
|
|
|
|
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
|
|
|
cost += this.statMap.get("spRaw"+spellIdx);
|
2021-01-09 08:57:27 +00:00
|
|
|
return Math.max(1, Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)))
|
2021-01-09 08:52:58 +00:00
|
|
|
}
|
2021-01-09 00:56:07 +00:00
|
|
|
|
|
|
|
|
2021-01-08 02:34:07 +00:00
|
|
|
/* Get melee stats for build.
|
|
|
|
Returns an array in the order:
|
2021-01-07 21:32:36 +00:00
|
|
|
*/
|
2021-01-08 02:34:07 +00:00
|
|
|
getMeleeStats(){
|
2021-01-08 05:36:57 +00:00
|
|
|
const stats = this.statMap;
|
|
|
|
let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier");
|
|
|
|
if(adjAtkSpd > 6){
|
|
|
|
adjAtkSpd = 6;
|
|
|
|
}else if(adjAtkSpd < 0){
|
|
|
|
adjAtkSpd = 0;
|
|
|
|
}
|
|
|
|
|
2021-01-09 00:56:07 +00:00
|
|
|
// 0 for melee damage.
|
2021-01-09 03:53:57 +00:00
|
|
|
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct"), 0, this.weapon, this.total_skillpoints);
|
|
|
|
|
|
|
|
//TODO: Account for strength (this.damageMultiplier).
|
|
|
|
|
2021-01-09 00:56:07 +00:00
|
|
|
let totalDamNorm = results[0];
|
|
|
|
let totalDamCrit = results[1];
|
|
|
|
let damages_results = results[2];
|
|
|
|
|
2021-01-08 05:36:57 +00:00
|
|
|
let dex = this.total_skillpoints[1];
|
2021-01-08 04:31:29 +00:00
|
|
|
|
2021-01-08 02:34:07 +00:00
|
|
|
//Now do math
|
2021-01-08 05:36:57 +00:00
|
|
|
let normDPS = (totalDamNorm[0]+totalDamNorm[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
|
|
|
let critDPS = (totalDamCrit[0]+totalDamCrit[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
2021-01-09 00:56:07 +00:00
|
|
|
let avgDPS = (normDPS * (1 - skillPointsToPercentage(dex))) + (critDPS * (skillPointsToPercentage(dex)));
|
2021-01-08 02:34:07 +00:00
|
|
|
//console.log([nDamAdj,eDamAdj,tDamAdj,wDamAdj,fDamAdj,aDamAdj,totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS]);
|
2021-01-08 20:14:23 +00:00
|
|
|
return damages_results.concat([totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS,adjAtkSpd]);
|
2021-01-07 21:32:36 +00:00
|
|
|
}
|
|
|
|
|
2021-01-08 05:36:57 +00:00
|
|
|
/* Get all stats for this build. Stores in this.statMap.
|
2021-01-07 21:32:36 +00:00
|
|
|
@pre The build itself should be valid. No checking of validity of pieces is done here.
|
|
|
|
*/
|
2021-01-08 04:31:29 +00:00
|
|
|
initBuildStats(){
|
|
|
|
|
|
|
|
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"];
|
|
|
|
|
2021-01-07 21:32:36 +00:00
|
|
|
//Create a map of this build's stats
|
|
|
|
//This is universal for every possible build, so it's possible to move this elsewhere.
|
|
|
|
let statMap = new Map();
|
2021-01-08 04:31:29 +00:00
|
|
|
|
|
|
|
for (const staticID of staticIDs) {
|
|
|
|
statMap.set(staticID, 0);
|
2021-01-07 21:32:36 +00:00
|
|
|
}
|
2021-01-08 16:47:51 +00:00
|
|
|
statMap.set("hp", levelToHPBase(this.level)); //TODO: Add player base health
|
2021-01-08 02:34:07 +00:00
|
|
|
|
2021-01-09 00:56:07 +00:00
|
|
|
for (const item of this.items){
|
2021-01-08 04:31:29 +00:00
|
|
|
for (let [id, value] of item.get("maxRolls")) {
|
|
|
|
statMap.set(id,(statMap.get(id) || 0)+value);
|
|
|
|
}
|
|
|
|
for (const staticID of staticIDs) {
|
2021-01-09 12:47:25 +00:00
|
|
|
if (item.get(staticID)) { statMap.set(staticID, statMap.get(staticID) + item.get(staticID)); }
|
2021-01-07 21:32:36 +00:00
|
|
|
}
|
|
|
|
}
|
2021-01-07 06:41:41 +00:00
|
|
|
|
2021-01-08 04:31:29 +00:00
|
|
|
// The stuff relevant for damage calculation!!! @ferricles
|
2021-01-09 00:56:07 +00:00
|
|
|
statMap.set("atkSpd", this.weapon.get("atkSpd"));
|
|
|
|
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")]);
|
2021-01-08 05:36:57 +00:00
|
|
|
statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
|
|
|
statMap.set("defRaw", [statMap.get("eDam"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]);
|
|
|
|
statMap.set("defBonus", [statMap.get("eDamPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]);
|
2021-01-07 06:41:41 +00:00
|
|
|
|
2021-01-08 04:31:29 +00:00
|
|
|
this.statMap = statMap;
|
|
|
|
}
|
2021-01-07 00:02:10 +00:00
|
|
|
|
|
|
|
}
|