2021-01-10 02:02:23 -08:00
2021-03-30 23:44:56 -07:00
2021-08-07 16:15:44 -07:00
const classDefenseMultipliers = new Map ( [ [ "relik" , 0.50 ] , [ "bow" , 0.60 ] , [ "wand" , 0.80 ] , [ "dagger" , 1.0 ] , [ "spear" , 1.20 ] , [ "sword" , 1.10 ] ] ) ;
2021-01-10 02:02:23 -08:00
2021-01-18 07:31:20 +10:30
/ * *
* @ description Error to catch items that don ' t exist .
* @ module ItemNotFound
* /
class ItemNotFound {
/ * *
* @ class
* @ param { String } item the item name entered
* @ param { String } type the type of item
* @ param { Boolean } genElement whether to generate an element from inputs
* @ param { String } override override for item type
* /
constructor ( item , type , genElement , override ) {
/ * *
* @ public
* @ type { String }
* /
this . message = ` Cannot find ${ override || type } named ${ item } ` ;
if ( genElement )
/ * *
* @ public
* @ type { Element }
* /
this . element = document . getElementById ( ` ${ type } -choice ` ) . parentElement . querySelectorAll ( "p.error" ) [ 0 ] ;
else
this . element = document . createElement ( "div" ) ;
}
2021-01-06 18:08:19 -06:00
}
2021-01-18 07:31:20 +10:30
/ * *
* @ description Error to catch incorrect input .
* @ module IncorrectInput
* /
class IncorrectInput {
/ * *
* @ class
* @ param { String } input the inputted text
* @ param { String } format the correct format
* @ param { String } sibling the id of the error node ' s sibling
* /
constructor ( input , format , sibling ) {
/ * *
* @ public
* @ type { String }
* /
this . message = ` ${ input } is incorrect. Example: ${ format } ` ;
/ * *
* @ public
* @ type { String }
* /
this . id = sibling ;
2021-01-06 18:08:19 -06:00
}
}
2021-01-18 07:31:20 +10:30
/ * *
* @ description Error that inputs an array of items to generate errors of .
* @ module ListError
* @ extends Error
* /
class ListError extends Error {
/ * *
* @ class
* @ param { Array } errors array of errors
* /
constructor ( errors ) {
let ret = [ ] ;
if ( typeof errors [ 0 ] == "string" ) {
super ( errors [ 0 ] ) ;
} else {
super ( errors [ 0 ] . message ) ;
}
for ( let i of errors ) {
if ( typeof i == "string" ) {
ret . push ( new Error ( i ) ) ;
} else {
ret . push ( i ) ;
}
}
/ * *
* @ public
* @ type { Object [ ] }
* /
this . errors = ret ;
2021-01-06 18:08:19 -06:00
}
}
2021-01-06 18:02:10 -06:00
/ * C l a s s t h a t r e p r e s e n t s a w y n n p l a y e r ' s b u i l d .
* /
class Build {
2021-01-18 07:31:20 +10:30
/ * *
* @ description Construct a build .
* @ param { Number } level : Level of the player .
* @ param { String [ ] } equipment : List of equipment names that make up the build .
2021-03-13 23:55:08 -08:00
* In order : boots , Chestplate , Leggings , Boots , Ring1 , Ring2 , Brace , Neck , Weapon .
2021-01-18 07:31:20 +10:30
* @ param { Number [ ] } powders : Powder application . List of lists of integers ( powder IDs ) .
2021-03-13 23:55:08 -08:00
* In order : boots , Chestplate , Leggings , Boots , Weapon .
2021-01-18 07:31:20 +10:30
* @ param { Object [ ] } inputerrors : List of instances of error - like classes .
2022-06-19 00:42:49 -07:00
*
* @ param { Object [ ] } tomes : List of tomes .
* In order : 2 x Weapon Mastery Tome , 4 x Armor Mastery Tome , 1 x Guild Tome .
* 2 x Slaying Mastery Tome , 2 x Dungeoneering Mastery Tome , 2 x Gathering Mastery Tome are in game , but do not have "useful" stats ( those that affect damage calculations or building )
2021-01-09 02:52:58 -06:00
* /
2022-06-19 00:42:49 -07:00
constructor ( level , items , tomes , weapon ) {
2021-01-18 07:31:20 +10:30
if ( level < 1 ) { //Should these be constants?
2021-01-06 18:02:10 -06:00
this . level = 1 ;
2021-01-18 07:31:20 +10:30
} else if ( level > 106 ) {
2021-01-06 18:02:10 -06:00
this . level = 106 ;
2021-01-18 07:31:20 +10:30
} else if ( level <= 106 && level >= 1 ) {
2021-01-06 18:02:10 -06:00
this . level = level ;
2021-01-18 07:31:20 +10:30
} else if ( typeof level === "string" ) {
this . level = level ;
errors . push ( new IncorrectInput ( level , "a number" , "level-choice" ) ) ;
} else {
errors . push ( "Level is not a string or number." ) ;
2021-01-06 18:02:10 -06:00
}
2021-01-18 07:31:20 +10:30
document . getElementById ( "level-choice" ) . value = this . level ;
2021-01-07 00:41:41 -06:00
this . availableSkillpoints = levelToSkillPoints ( this . level ) ;
2022-06-19 00:42:49 -07:00
this . equipment = items ;
this . tomes = tomes ;
this . weapon = weapon ;
this . items = this . equipment . concat ( [ this . weapon ] ) . concat ( this . tomes ) ;
2021-01-07 00:41:41 -06:00
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
2022-06-19 09:49:04 -07:00
// calc skillpoints requires statmaps only
let result = calculate _skillpoints ( this . equipment . concat ( this . tomes ) . map ( ( x ) => x . statMap ) , this . weapon . statMap ) ;
2021-01-19 21:59:17 -08:00
console . log ( result ) ;
2021-01-07 00:41:41 -06:00
this . equip _order = result [ 0 ] ;
2021-07-27 03:04:12 -07:00
// How many skillpoints the player had to assign (5 number)
2021-01-07 00:41:41 -06:00
this . base _skillpoints = result [ 1 ] ;
2021-07-27 03:04:12 -07:00
// How many skillpoints the build ended up with (5 number)
2021-01-07 00:41:41 -06:00
this . total _skillpoints = result [ 2 ] ;
2021-07-27 03:04:12 -07:00
// How many skillpoints assigned (1 number, sum of base_skillpoints)
2021-01-07 00:41:41 -06:00
this . assigned _skillpoints = result [ 3 ] ;
2021-01-09 22:29:07 -06:00
this . activeSetCounts = result [ 4 ] ;
2021-01-13 19:17:01 -08:00
2021-01-07 22:31:29 -06:00
this . initBuildStats ( ) ;
2021-01-06 18:02:10 -06:00
}
/ * R e t u r n s b u i l d i n s t r i n g f o r m a t
2021-01-07 00:41:41 -06:00
* /
2021-01-06 18:02:10 -06:00
toString ( ) {
2022-06-19 00:42:49 -07:00
return [ this . equipment , this . weapon , this . tomes ] . flat ( ) ;
2021-01-06 18:02:10 -06:00
}
2021-01-07 00:41:41 -06:00
/* Getters */
2021-01-07 13:32:36 -08:00
2021-01-09 02:52:58 -06:00
getSpellCost ( spellIdx , cost ) {
2021-09-13 12:31:43 -07:00
return Math . max ( 1 , this . getBaseSpellCost ( spellIdx , cost ) ) ;
2021-09-13 12:29:15 -07:00
}
getBaseSpellCost ( spellIdx , cost ) {
2022-06-19 00:42:49 -07:00
// old intelligence: cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
2021-01-09 02:52:58 -06:00
cost += this . statMap . get ( "spRaw" + spellIdx ) ;
2021-09-13 12:29:15 -07:00
return Math . floor ( cost * ( 1 + this . statMap . get ( "spPct" + spellIdx ) / 100 ) ) ;
2021-01-09 02:52:58 -06:00
}
2021-01-08 18:56:07 -06:00
2021-01-07 18:34:07 -08:00
/ * G e t m e l e e s t a t s f o r b u i l d .
Returns an array in the order :
2021-01-07 13:32:36 -08:00
* /
2021-01-07 18:34:07 -08:00
getMeleeStats ( ) {
2021-01-07 23:36:57 -06:00
const stats = this . statMap ;
2022-06-19 09:49:04 -07:00
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" ) ] ) ;
2021-03-13 23:55:08 -08:00
}
2021-01-07 23:36:57 -06:00
let adjAtkSpd = attackSpeeds . indexOf ( stats . get ( "atkSpd" ) ) + stats . get ( "atkTier" ) ;
if ( adjAtkSpd > 6 ) {
adjAtkSpd = 6 ;
} else if ( adjAtkSpd < 0 ) {
adjAtkSpd = 0 ;
}
2021-03-25 00:18:33 -07:00
let damage _mult = 1 ;
2022-06-19 09:49:04 -07:00
if ( weapon _stats . get ( "type" ) === "relik" ) {
2021-03-25 00:18:33 -07:00
damage _mult = 0.99 ; // CURSE YOU WYNNCRAFT
2021-03-30 23:44:56 -07:00
//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.
2021-03-25 00:18:33 -07:00
}
// 0spellmult for melee damage.
2022-06-19 09:49:04 -07:00
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 ) ;
2021-01-10 18:58:39 -08:00
let dex = this . total _skillpoints [ 1 ] ;
2021-01-08 21:53:57 -06:00
2021-01-08 18:56:07 -06:00
let totalDamNorm = results [ 0 ] ;
let totalDamCrit = results [ 1 ] ;
2021-01-10 18:58:39 -08:00
totalDamNorm . push ( 1 - skillPointsToPercentage ( dex ) ) ;
totalDamCrit . push ( skillPointsToPercentage ( dex ) ) ;
2021-01-08 18:56:07 -06:00
let damages _results = results [ 2 ] ;
2021-01-10 18:58:39 -08:00
2021-01-12 16:49:57 -06:00
let singleHitTotal = ( ( totalDamNorm [ 0 ] + totalDamNorm [ 1 ] ) * ( totalDamNorm [ 2 ] )
+ ( totalDamCrit [ 0 ] + totalDamCrit [ 1 ] ) * ( totalDamCrit [ 2 ] ) ) / 2 ;
2021-01-07 22:31:29 -06:00
2021-01-07 18:34:07 -08:00
//Now do math
2021-01-07 23:36:57 -06:00
let normDPS = ( totalDamNorm [ 0 ] + totalDamNorm [ 1 ] ) / 2 * baseDamageMultiplier [ adjAtkSpd ] ;
let critDPS = ( totalDamCrit [ 0 ] + totalDamCrit [ 1 ] ) / 2 * baseDamageMultiplier [ adjAtkSpd ] ;
2021-01-08 18:56:07 -06:00
let avgDPS = ( normDPS * ( 1 - skillPointsToPercentage ( dex ) ) ) + ( critDPS * ( skillPointsToPercentage ( dex ) ) ) ;
2021-01-12 16:49:57 -06:00
//[[n n n n] [e e e e] [t t t t] [w w w w] [f f f f] [a a a a] [lowtotal hightotal normalChance] [critlowtotal crithightotal critChance] normalDPS critCPS averageDPS adjAttackSpeed, singleHit]
2021-03-30 23:44:56 -07:00
return damages _results . concat ( [ totalDamNorm , totalDamCrit , normDPS , critDPS , avgDPS , adjAtkSpd , singleHitTotal ] ) . concat ( results [ 3 ] ) ;
2021-01-07 13:32:36 -08:00
}
2021-01-10 02:02:23 -08:00
/ *
Get all defensive stats for this build .
* /
getDefenseStats ( ) {
const stats = this . statMap ;
let defenseStats = [ ] ;
let def _pct = skillPointsToPercentage ( this . total _skillpoints [ 3 ] ) ;
let agi _pct = skillPointsToPercentage ( this . total _skillpoints [ 4 ] ) ;
//total hp
let totalHp = stats . get ( "hp" ) + stats . get ( "hpBonus" ) ;
2021-01-10 06:54:25 -06:00
if ( totalHp < 5 ) totalHp = 5 ;
2021-01-10 02:02:23 -08:00
defenseStats . push ( totalHp ) ;
//EHP
2021-01-10 16:46:30 -08:00
let ehp = [ totalHp , totalHp ] ;
2022-06-19 09:49:04 -07:00
let defMult = classDefenseMultipliers . get ( this . weapon . statMap . get ( "type" ) ) ;
ehp [ 0 ] /= ( ( 1 - def _pct ) * ( 1 - agi _pct ) * ( 2 - defMult ) * ( 2 - this . defenseMultiplier ) ) ;
2021-01-13 22:58:32 -08:00
ehp [ 1 ] /= ( ( 1 - def _pct ) * ( 2 - defMult ) * ( 2 - this . defenseMultiplier ) ) ;
2021-01-10 02:02:23 -08:00
defenseStats . push ( ehp ) ;
//HPR
let totalHpr = rawToPct ( stats . get ( "hprRaw" ) , stats . get ( "hprPct" ) / 100. ) ;
defenseStats . push ( totalHpr ) ;
//EHPR
2021-01-10 16:46:30 -08:00
let ehpr = [ totalHpr , totalHpr ] ;
2021-01-13 22:58:32 -08:00
ehpr [ 0 ] /= ( ( 1 - def _pct ) * ( 1 - agi _pct ) * ( 2 - defMult ) * ( 2 - this . defenseMultiplier ) ) ;
ehpr [ 1 ] /= ( ( 1 - def _pct ) * ( 2 - defMult ) * ( 2 - this . defenseMultiplier ) ) ;
2021-01-10 02:02:23 -08:00
defenseStats . push ( ehpr ) ;
//skp stats
2021-01-13 22:58:32 -08:00
defenseStats . push ( [ ( 1 - ( ( 1 - def _pct ) * ( 2 - this . defenseMultiplier ) ) ) * 100 , agi _pct * 100 ] ) ;
2021-01-10 02:02:23 -08:00
//eledefs - TODO POWDERS
let eledefs = [ 0 , 0 , 0 , 0 , 0 ] ;
for ( const i in skp _elements ) { //kinda jank but ok
eledefs [ i ] = rawToPct ( stats . get ( skp _elements [ i ] + "Def" ) , stats . get ( skp _elements [ i ] + "DefPct" ) / 100. ) ;
}
defenseStats . push ( eledefs ) ;
2021-01-10 16:46:30 -08:00
//[total hp, [ehp w/ agi, ehp w/o agi], total hpr, [ehpr w/ agi, ehpr w/o agi], [def%, agi%], [edef,tdef,wdef,fdef,adef]]
2021-01-10 02:02:23 -08:00
return defenseStats ;
}
2021-01-07 23:36:57 -06:00
/ * G e t a l l s t a t s f o r t h i s b u i l d . S t o r e s i n t h i s . s t a t M a p .
2021-01-07 13:32:36 -08:00
@ pre The build itself should be valid . No checking of validity of pieces is done here .
* /
2021-01-07 22:31:29 -06:00
initBuildStats ( ) {
2021-07-06 20:36:12 -07:00
let staticIDs = [ "hp" , "eDef" , "tDef" , "wDef" , "fDef" , "aDef" , "str" , "dex" , "int" , "def" , "agi" ] ;
2021-01-07 22:31:29 -06:00
2021-01-07 13:32:36 -08:00
//Create a map of this build's stats
let statMap = new Map ( ) ;
2021-01-07 22:31:29 -06:00
for ( const staticID of staticIDs ) {
statMap . set ( staticID , 0 ) ;
2021-01-07 13:32:36 -08:00
}
2021-01-30 00:50:25 -08:00
statMap . set ( "hp" , levelToHPBase ( this . level ) ) ;
2021-01-13 22:58:32 -08:00
2021-01-30 06:03:40 -06:00
let major _ids = new Set ( ) ;
2021-01-08 18:56:07 -06:00
for ( const item of this . items ) {
2022-06-19 09:49:04 -07:00
const item _stats = item . statMap ;
for ( let [ id , value ] of item _stats . get ( "maxRolls" ) ) {
2021-07-06 20:36:12 -07:00
if ( staticIDs . includes ( id ) ) {
continue ;
}
2021-01-07 22:31:29 -06:00
statMap . set ( id , ( statMap . get ( id ) || 0 ) + value ) ;
}
for ( const staticID of staticIDs ) {
2022-06-19 09:49:04 -07:00
if ( item _stats . get ( staticID ) ) {
statMap . set ( staticID , statMap . get ( staticID ) + item _stats . get ( staticID ) ) ;
2021-01-10 07:18:53 -06:00
}
2021-01-07 13:32:36 -08:00
}
2022-06-19 09:49:04 -07:00
if ( item _stats . get ( "majorIds" ) ) {
for ( const major _id of item _stats . get ( "majorIds" ) ) {
2021-07-23 00:47:13 -07:00
major _ids . add ( major _id ) ;
}
2021-01-30 06:03:40 -06:00
}
2021-01-07 13:32:36 -08:00
}
2021-01-30 06:03:40 -06:00
statMap . set ( "activeMajorIDs" , major _ids ) ;
2021-01-09 22:29:07 -06:00
for ( const [ setName , count ] of this . activeSetCounts ) {
2022-06-19 09:49:04 -07:00
const bonus = sets . get ( setName ) . bonuses [ count - 1 ] ;
2021-01-09 21:40:15 -06:00
for ( const id in bonus ) {
2021-01-09 22:29:07 -06:00
if ( skp _order . includes ( id ) ) {
// pass. Don't include skillpoints in ids
}
else {
statMap . set ( id , ( statMap . get ( id ) || 0 ) + bonus [ id ] ) ;
}
2021-01-09 21:40:15 -06:00
}
}
2021-01-18 08:18:14 -06:00
statMap . set ( "poisonPct" , 100 ) ;
2021-01-07 00:41:41 -06:00
2021-01-07 22:31:29 -06:00
// The stuff relevant for damage calculation!!! @ferricles
2022-06-19 09:49:04 -07:00
statMap . set ( "atkSpd" , this . weapon . statMap . get ( "atkSpd" ) ) ;
2021-01-13 22:58:32 -08:00
2021-01-07 22:31:29 -06:00
this . statMap = statMap ;
2021-01-18 08:18:14 -06:00
this . aggregateStats ( ) ;
2021-01-07 22:31:29 -06:00
}
2021-01-06 18:02:10 -06:00
2021-01-18 08:18:14 -06:00
aggregateStats ( ) {
let statMap = this . statMap ;
2022-06-19 09:49:04 -07:00
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" ) ] ) ;
2021-01-18 08:18:14 -06:00
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" ) ] ) ;
2022-06-19 09:49:04 -07:00
statMap . set ( "defMult" , classDefenseMultipliers . get ( weapon _stats . get ( "type" ) ) ) ;
2021-01-18 08:18:14 -06:00
}
2021-01-06 18:02:10 -06:00
}