2022-06-23 08:25:19 +00:00
let armor _powder _node = new ( class extends ComputeNode {
constructor ( ) { super ( 'builder-armor-powder-input' ) ; }
compute _func ( input _map ) {
let damage _boost = 0 ;
let def _boost = 0 ;
let statMap = new Map ( ) ;
for ( const [ e , elem ] of zip2 ( skp _elements , skp _order ) ) {
let val = parseInt ( document . getElementById ( elem + "_boost_armor" ) . value ) ;
statMap . set ( e + 'DamPct' , val ) ;
}
return statMap ;
2022-06-20 17:51:17 +00:00
}
2022-06-23 08:25:19 +00:00
} ) ( ) . update ( ) ;
/ * U p d a t e s P A S S I V E p o w d e r s p e c i a l b o o s t s ( a r m o r s )
* /
function update _armor _powder _specials ( elem _id ) {
//we only update the powder special + external stats if the player has a build
let wynn _elem = elem _id . split ( "_" ) [ 0 ] ; //str, dex, int, def, agi
//update the label associated w/ the slider
let elem = document . getElementById ( elem _id ) ;
let label = document . getElementById ( elem _id + "_label" ) ;
let value = elem . value ;
label . textContent = label . textContent . split ( ":" ) [ 0 ] + ": " + value
//update the slider's graphics
let bg _color = elem _colors [ skp _order . indexOf ( wynn _elem ) ] ;
let pct = Math . round ( 100 * value / powderSpecialStats [ skp _order . indexOf ( wynn _elem ) ] . cap ) ;
elem . style . background = ` linear-gradient(to right, ${ bg _color } , ${ bg _color } ${ pct } %, #AAAAAA ${ pct } %, #AAAAAA 100%) ` ;
armor _powder _node . mark _dirty ( ) . update ( ) ;
2022-06-20 17:51:17 +00:00
}
2022-06-23 08:25:19 +00:00
let boosts _node = new ( class extends ComputeNode {
2022-06-20 17:51:17 +00:00
constructor ( ) { super ( 'builder-boost-input' ) ; }
compute _func ( input _map ) {
let damage _boost = 0 ;
let def _boost = 0 ;
for ( const [ key , value ] of damageMultipliers ) {
let elem = document . getElementById ( key + "-boost" )
if ( elem . classList . contains ( "toggleOn" ) ) {
damage _boost += value ;
if ( key === "warscream" ) { def _boost += . 20 }
if ( key === "vanish" ) { def _boost += . 15 }
}
}
2022-06-27 08:41:13 +00:00
let res = new Map ( ) ;
res . set ( 'damageMultiplier' , 1 + damage _boost ) ;
2022-07-04 19:32:36 +00:00
res . set ( 'defMultiplier' , 1 - def _boost ) ;
2022-06-27 08:41:13 +00:00
return res ;
2022-06-20 17:51:17 +00:00
}
2022-06-23 08:25:19 +00:00
} ) ( ) . update ( ) ;
2022-06-20 17:51:17 +00:00
2022-06-23 08:25:19 +00:00
/ * U p d a t e s a l l s p e l l b o o s t s
* /
function update _boosts ( buttonId ) {
2022-06-20 17:51:17 +00:00
let elem = document . getElementById ( buttonId ) ;
2022-06-23 08:25:19 +00:00
if ( elem . classList . contains ( "toggleOn" ) ) {
2022-06-20 17:51:17 +00:00
elem . classList . remove ( "toggleOn" ) ;
} else {
2022-06-23 08:25:19 +00:00
elem . classList . add ( "toggleOn" ) ;
2022-06-20 17:51:17 +00:00
}
2022-06-23 08:25:19 +00:00
boosts _node . mark _dirty ( ) . update ( ) ;
2022-06-20 17:51:17 +00:00
}
2022-06-23 08:25:19 +00:00
let specialNames = [ "Quake" , "Chain Lightning" , "Curse" , "Courage" , "Wind Prison" ] ;
let powder _special _input = new ( class extends ComputeNode {
2022-06-20 17:51:17 +00:00
constructor ( ) { super ( 'builder-powder-special-input' ) ; }
compute _func ( input _map ) {
let powder _specials = [ ] ; // [ [special, power], [special, power]]
for ( const sName of specialNames ) {
for ( let i = 1 ; i < 6 ; i ++ ) {
if ( document . getElementById ( sName . replace ( " " , "_" ) + "-" + i ) . classList . contains ( "toggleOn" ) ) {
let powder _special = powderSpecialStats [ specialNames . indexOf ( sName . replace ( "_" , " " ) ) ] ;
powder _specials . push ( [ powder _special , i ] ) ;
break ;
}
}
}
return powder _specials ;
}
2022-06-23 08:25:19 +00:00
} ) ( ) ;
function updatePowderSpecials ( buttonId ) {
2022-06-26 10:14:31 +00:00
let prefix = ( buttonId ) . split ( "-" ) [ 0 ] . replace ( ' ' , '_' ) + '-' ;
2022-06-23 08:25:19 +00:00
let elem = document . getElementById ( buttonId ) ;
2022-06-26 10:14:31 +00:00
if ( elem . classList . contains ( "toggleOn" ) ) { elem . classList . remove ( "toggleOn" ) ; }
else {
2022-06-23 08:25:19 +00:00
for ( let i = 1 ; i < 6 ; i ++ ) { //toggle all pressed buttons of the same powder special off
//name is same, power is i
2022-06-26 10:14:31 +00:00
const elem2 = document . getElementById ( prefix + i ) ;
if ( elem2 . classList . contains ( "toggleOn" ) ) { elem2 . classList . remove ( "toggleOn" ) ; }
2022-06-23 08:25:19 +00:00
}
//toggle the pressed button on
elem . classList . add ( "toggleOn" ) ;
}
powder _special _input . mark _dirty ( ) . update ( ) ;
2022-06-20 17:51:17 +00:00
}
class PowderSpecialCalcNode extends ComputeNode {
constructor ( ) { super ( 'builder-powder-special-apply' ) ; }
compute _func ( input _map ) {
const powder _specials = input _map . get ( 'powder-specials' ) ;
let stats = new Map ( ) ;
for ( const [ special , power ] of powder _specials ) {
if ( special [ "weaponSpecialEffects" ] . has ( "Damage Boost" ) ) {
let name = special [ "weaponSpecialName" ] ;
if ( name === "Courage" || name === "Curse" ) { //courage and curse are is universal damage boost
stats . set ( "sdPct" , special . weaponSpecialEffects . get ( "Damage Boost" ) [ power - 1 ] ) ;
stats . set ( "mdPct" , special . weaponSpecialEffects . get ( "Damage Boost" ) [ power - 1 ] ) ;
stats . set ( "poisonPct" , special . weaponSpecialEffects . get ( "Damage Boost" ) [ power - 1 ] ) ;
} else if ( name === "Wind Prison" ) {
stats . set ( "aDamPct" , special . weaponSpecialEffects . get ( "Damage Boost" ) [ power - 1 ] ) ;
}
}
}
return stats ;
}
}
class PowderSpecialDisplayNode extends ComputeNode {
2022-06-26 10:14:31 +00:00
// TODO: Refactor this entirely to be adding more spells to the spell list
2022-06-20 17:51:17 +00:00
constructor ( ) {
super ( 'builder-powder-special-display' ) ;
this . fail _cb = true ;
}
compute _func ( input _map ) {
const powder _specials = input _map . get ( 'powder-specials' ) ;
const stats = input _map . get ( 'stats' ) ;
2022-06-26 10:14:31 +00:00
const weapon = input _map . get ( 'build' ) . weapon ;
2022-06-20 17:51:17 +00:00
displayPowderSpecials ( document . getElementById ( "powder-special-stats" ) , powder _specials , stats , weapon . statMap , true ) ;
}
}
2022-06-19 20:44:02 +00:00
/ * *
* Node for getting an item ' s stats from an item input field .
*
2022-06-20 17:51:17 +00:00
* Signature : ItemInputNode ( powdering : Optional [ list [ powder ] ] ) => Item | null
2022-06-19 20:44:02 +00:00
* /
class ItemInputNode extends InputNode {
/ * *
* Make an item stat pulling compute node .
*
* @ param name : Name of this node .
* @ param item _input _field : Input field ( html element ) to listen for item names from .
* @ param none _item : Item object to use as the "none" for this field .
* /
constructor ( name , item _input _field , none _item ) {
super ( name , item _input _field ) ;
this . none _item = new Item ( none _item ) ;
2022-06-26 10:14:31 +00:00
this . category = this . none _item . statMap . get ( 'category' ) ;
if ( this . category == 'armor' || this . category == 'weapon' ) {
this . none _item . statMap . set ( 'powders' , [ ] ) ;
apply _weapon _powders ( this . none _item . statMap ) ; // Needed to put in damagecalc zeros
}
2022-06-19 20:44:02 +00:00
this . none _item . statMap . set ( 'NONE' , true ) ;
}
2022-05-22 10:21:34 +00:00
2022-06-19 20:44:02 +00:00
compute _func ( input _map ) {
2022-06-20 17:51:17 +00:00
const powdering = input _map . get ( 'powdering' ) ;
2022-05-22 10:21:34 +00:00
2022-06-20 17:51:17 +00:00
// built on the assumption of no one will type in CI/CR letter by letter
2022-06-19 20:44:02 +00:00
let item _text = this . input _field . value ;
if ( ! item _text ) {
return this . none _item ;
}
let item ;
2022-06-20 17:51:17 +00:00
if ( item _text . slice ( 0 , 3 ) == "CI-" ) { item = getCustomFromHash ( item _text ) ; }
else if ( item _text . slice ( 0 , 3 ) == "CR-" ) { item = getCraftFromHash ( item _text ) ; }
else if ( itemMap . has ( item _text ) ) { item = new Item ( itemMap . get ( item _text ) ) ; }
else if ( tomeMap . has ( item _text ) ) { item = new Item ( tomeMap . get ( item _text ) ) ; }
2022-06-19 20:44:02 +00:00
if ( item ) {
2022-06-20 17:51:17 +00:00
if ( powdering !== undefined ) {
2022-07-06 08:06:24 +00:00
const max _slots = item . statMap . get ( 'slots' ) ;
item . statMap . set ( 'powders' , powdering . slice ( 0 , max _slots ) ) ;
2022-06-20 17:51:17 +00:00
}
2022-06-19 20:44:02 +00:00
let type _match ;
2022-06-26 10:14:31 +00:00
if ( this . category == 'weapon' ) {
type _match = item . statMap . get ( 'category' ) == 'weapon' ;
2022-06-19 20:44:02 +00:00
} else {
2022-06-26 10:14:31 +00:00
type _match = item . statMap . get ( 'type' ) == this . none _item . statMap . get ( 'type' ) ;
2022-06-19 20:44:02 +00:00
}
2022-06-20 17:51:17 +00:00
if ( type _match ) {
2022-06-26 10:14:31 +00:00
if ( item . statMap . get ( 'category' ) == 'armor' ) {
applyArmorPowders ( item . statMap ) ;
2022-06-26 08:07:43 +00:00
}
2022-06-26 10:14:31 +00:00
else if ( item . statMap . get ( 'category' ) == 'weapon' ) {
apply _weapon _powders ( item . statMap ) ;
2022-06-20 17:51:17 +00:00
}
return item ;
}
2022-06-19 20:44:02 +00:00
}
2022-06-22 05:53:04 +00:00
else if ( this . none _item . statMap . get ( 'category' ) === 'weapon' && item _text . startsWith ( "Morph-" ) ) {
let replace _items = [ "Morph-Stardust" ,
"Morph-Steel" ,
"Morph-Iron" ,
"Morph-Gold" ,
"Morph-Topaz" ,
"Morph-Emerald" ,
"Morph-Amethyst" ,
"Morph-Ruby" ,
item _text . substring ( 6 )
]
for ( const [ i , x ] of zip2 ( equipment _inputs , replace _items ) ) { setValue ( i , x ) ; }
2022-06-23 10:55:13 +00:00
for ( const node of item _nodes ) { calcSchedule ( node , 10 ) ; }
return this . compute _func ( input _map ) ;
2022-06-22 05:53:04 +00:00
}
2022-06-19 20:44:02 +00:00
return null ;
}
}
/ * *
* Node for updating item input fields from parsed items .
*
* Signature : ItemInputDisplayNode ( item : Item ) => null
* /
class ItemInputDisplayNode extends ComputeNode {
constructor ( name , eq , item _image ) {
super ( name ) ;
this . input _field = document . getElementById ( eq + "-choice" ) ;
this . health _field = document . getElementById ( eq + "-health" ) ;
this . level _field = document . getElementById ( eq + "-lv" ) ;
2022-06-26 08:07:43 +00:00
this . powder _field = document . getElementById ( eq + "-powder" ) ; // possibly None
2022-06-19 20:44:02 +00:00
this . image = item _image ;
this . fail _cb = true ;
2022-06-19 07:42:49 +00:00
}
compute _func ( input _map ) {
2022-06-19 20:44:02 +00:00
if ( input _map . size !== 1 ) { throw "ItemInputDisplayNode accepts exactly one input (item)" ; }
const [ item ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
this . input _field . classList . remove ( "text-light" , "is-invalid" , 'Normal' , 'Unique' , 'Rare' , 'Legendary' , 'Fabled' , 'Mythic' , 'Set' , 'Crafted' , 'Custom' ) ;
this . input _field . classList . add ( "text-light" ) ;
this . image . classList . remove ( 'Normal-shadow' , 'Unique-shadow' , 'Rare-shadow' , 'Legendary-shadow' , 'Fabled-shadow' , 'Mythic-shadow' , 'Set-shadow' , 'Crafted-shadow' , 'Custom-shadow' ) ;
2022-06-22 05:39:10 +00:00
if ( this . health _field ) {
// Doesn't exist for weapons.
this . health _field . textContent = "0" ;
}
2022-06-23 09:23:56 +00:00
if ( this . level _field ) {
// Doesn't exist for tomes.
this . level _field . textContent = "0" ;
}
2022-06-19 20:44:02 +00:00
if ( ! item ) {
this . input _field . classList . add ( "is-invalid" ) ;
return null ;
}
2022-06-26 08:07:43 +00:00
if ( item . statMap . has ( 'powders' ) ) {
this . powder _field . placeholder = "powders" ;
}
2022-06-19 20:44:02 +00:00
if ( item . statMap . has ( 'NONE' ) ) {
return null ;
}
2022-06-26 08:07:43 +00:00
if ( item . statMap . has ( 'powders' ) ) {
this . powder _field . placeholder = item . statMap . get ( 'slots' ) + ' slots' ;
}
2022-06-19 20:44:02 +00:00
const tier = item . statMap . get ( 'tier' ) ;
this . input _field . classList . add ( tier ) ;
if ( this . health _field ) {
// Doesn't exist for weapons.
this . health _field . textContent = item . statMap . get ( 'hp' ) ;
}
2022-06-23 09:23:56 +00:00
if ( this . level _field ) {
// Doesn't exist for tomes.
this . level _field . textContent = item . statMap . get ( 'lvl' ) ;
}
2022-06-19 20:44:02 +00:00
this . image . classList . add ( tier + "-shadow" ) ;
return null ;
2022-06-19 07:42:49 +00:00
}
}
2022-06-19 20:44:02 +00:00
/ * *
* Node for rendering an item .
*
* Signature : ItemDisplayNode ( item : Item ) => null
* /
class ItemDisplayNode extends ComputeNode {
constructor ( name , target _elem ) {
super ( name ) ;
this . target _elem = target _elem ;
}
compute _func ( input _map ) {
if ( input _map . size !== 1 ) { throw "ItemInputDisplayNode accepts exactly one input (item)" ; }
const [ item ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
displayExpandedItem ( item . statMap , this . target _elem ) ;
collapse _element ( "#" + this . target _elem ) ;
}
}
/ * *
* Change the weapon to match correct type .
*
* Signature : WeaponInputDisplayNode ( item : Item ) => null
* /
class WeaponInputDisplayNode extends ComputeNode {
2022-06-24 10:35:03 +00:00
constructor ( name , image _field , dps _field ) {
2022-06-19 20:44:02 +00:00
super ( name ) ;
this . image = image _field ;
2022-06-24 10:35:03 +00:00
this . dps _field = dps _field ;
2022-06-19 07:42:49 +00:00
}
2022-06-19 20:44:02 +00:00
compute _func ( input _map ) {
if ( input _map . size !== 1 ) { throw "WeaponDisplayNode accepts exactly one input (item)" ; }
const [ item ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
const type = item . statMap . get ( 'type' ) ;
this . image . setAttribute ( 'src' , '../media/items/new/generic-' + type + '.png' ) ;
2022-06-24 10:35:03 +00:00
let dps = get _base _dps ( item . statMap ) ;
if ( isNaN ( dps ) ) {
dps = dps [ 1 ] ;
if ( isNaN ( dps ) ) dps = 0 ;
2022-06-23 11:24:12 +00:00
}
2022-06-26 07:08:02 +00:00
this . dps _field . textContent = Math . round ( dps ) ;
2022-06-19 20:44:02 +00:00
}
}
/ * *
* Encode the build into a url - able string .
*
* Signature : BuildEncodeNode ( build : Build ,
2022-06-20 17:51:17 +00:00
* helmet - powder : List [ powder ] ,
* chestplate - powder : List [ powder ] ,
* leggings - powder : List [ powder ] ,
* boots - powder : List [ powder ] ,
* weapon - powder : List [ powder ] ) => str
2022-06-19 20:44:02 +00:00
* /
class BuildEncodeNode extends ComputeNode {
constructor ( ) { super ( "builder-encode" ) ; }
compute _func ( input _map ) {
const build = input _map . get ( 'build' ) ;
2022-06-30 15:03:41 +00:00
const atree = input _map . get ( 'atree' ) ;
const atree _state = input _map . get ( 'atree-state' ) ;
2022-06-19 20:44:02 +00:00
let powders = [
input _map . get ( 'helmet-powder' ) ,
input _map . get ( 'chestplate-powder' ) ,
input _map . get ( 'leggings-powder' ) ,
input _map . get ( 'boots-powder' ) ,
input _map . get ( 'weapon-powder' )
] ;
2022-06-20 13:12:22 +00:00
const skillpoints = [
input _map . get ( 'str' ) ,
input _map . get ( 'dex' ) ,
input _map . get ( 'int' ) ,
input _map . get ( 'def' ) ,
input _map . get ( 'agi' )
] ;
2022-06-19 20:44:02 +00:00
// TODO: grr global state for copy button..
player _build = build ;
build _powders = powders ;
2022-06-30 15:03:41 +00:00
return encodeBuild ( build , powders , skillpoints , atree , atree _state ) ;
2022-06-19 20:44:02 +00:00
}
}
/ * *
* Update the window ' s URL .
*
* Signature : URLUpdateNode ( build _str : str ) => null
* /
class URLUpdateNode extends ComputeNode {
constructor ( ) { super ( "builder-url-update" ) ; }
2022-06-19 07:42:49 +00:00
compute _func ( input _map ) {
if ( input _map . size !== 1 ) { throw "URLUpdateNode accepts exactly one input (build_str)" ; }
const [ build _str ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
location . hash = build _str ;
}
}
2022-06-19 20:44:02 +00:00
/ * *
* Create a "build" object from a set of equipments .
* Returns a new Build object , or null if all items are NONE items .
*
* Signature : BuildAssembleNode ( helmet - input : Item ,
* chestplate - input : Item ,
* leggings - input : Item ,
* boots - input : Item ,
* ring1 - input : Item ,
* ring2 - input : Item ,
* bracelet - input : Item ,
* necklace - input : Item ,
* weapon - input : Item ,
* level - input : int ) => Build | null
* /
2022-06-19 07:42:49 +00:00
class BuildAssembleNode extends ComputeNode {
2022-06-19 20:44:02 +00:00
constructor ( ) { super ( "builder-make-build" ) ; }
2022-06-19 07:42:49 +00:00
compute _func ( input _map ) {
let equipments = [
input _map . get ( 'helmet-input' ) ,
input _map . get ( 'chestplate-input' ) ,
input _map . get ( 'leggings-input' ) ,
input _map . get ( 'boots-input' ) ,
input _map . get ( 'ring1-input' ) ,
input _map . get ( 'ring2-input' ) ,
input _map . get ( 'bracelet-input' ) ,
2022-06-23 09:23:56 +00:00
input _map . get ( 'necklace-input' ) ,
input _map . get ( 'weaponTome1-input' ) ,
input _map . get ( 'weaponTome2-input' ) ,
input _map . get ( 'armorTome1-input' ) ,
input _map . get ( 'armorTome2-input' ) ,
input _map . get ( 'armorTome3-input' ) ,
input _map . get ( 'armorTome4-input' ) ,
input _map . get ( 'guildTome1-input' )
2022-06-19 07:42:49 +00:00
] ;
let weapon = input _map . get ( 'weapon-input' ) ;
let level = input _map . get ( 'level-input' ) ;
2022-06-19 16:49:04 +00:00
let all _none = weapon . statMap . has ( 'NONE' ) ;
for ( const item of equipments ) {
all _none = all _none && item . statMap . has ( 'NONE' ) ;
}
2022-06-20 17:51:17 +00:00
if ( all _none && ! location . hash ) {
2022-06-19 16:49:04 +00:00
return null ;
}
2022-06-23 10:42:18 +00:00
return new Build ( level , equipments , weapon ) ;
2022-06-19 07:42:49 +00:00
}
}
2022-07-01 05:22:15 +00:00
class PlayerClassNode extends ValueCheckComputeNode {
constructor ( name ) { super ( name ) ; }
compute _func ( input _map ) {
if ( input _map . size !== 1 ) { throw "PlayerClassNode accepts exactly one input (build)" ; }
const [ build ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
return wep _to _class . get ( build . weapon . statMap . get ( 'type' ) ) ;
}
}
2022-06-19 20:44:02 +00:00
/ * *
* Read an input field and parse into a list of powderings .
* Every two characters makes one powder . If parsing fails , NULL is returned .
*
* Signature : PowderInputNode ( ) => List [ powder ] | null
* /
2022-06-19 16:49:04 +00:00
class PowderInputNode extends InputNode {
2022-06-19 20:44:02 +00:00
constructor ( name , input _field ) { super ( name , input _field ) ; }
2022-06-19 16:49:04 +00:00
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 ) ;
}
return powdering ;
}
}
2022-06-19 20:44:02 +00:00
/ * *
* Select a spell + spell "variation" based on a build / spell idx .
* Right now this isn ' t much logic and is only used to abstract away major id interactions
* but will become significantly more complex in wynn2 .
*
* Signature : SpellSelectNode < int > ( build : Build ) => [ Spell , SpellParts ]
* /
2022-06-19 18:02:28 +00:00
class SpellSelectNode extends ComputeNode {
2022-06-29 06:23:27 +00:00
constructor ( spell ) {
super ( "builder-spell" + spell . base _spell + "-select" ) ;
this . spell = spell ;
2022-06-19 18:02:28 +00:00
}
compute _func ( input _map ) {
const build = input _map . get ( 'build' ) ;
let stats = build . statMap ;
2022-06-29 06:23:27 +00:00
// TODO: apply major ids... DOOM.....
2022-06-19 18:02:28 +00:00
2022-06-29 06:23:27 +00:00
return [ this . spell , this . spell . parts ] ;
2022-06-19 18:02:28 +00:00
}
}
2022-06-20 13:12:22 +00:00
/ *
* Get all defensive stats for this build .
* /
function getDefenseStats ( stats ) {
let defenseStats = [ ] ;
2022-07-06 03:40:32 +00:00
let def _pct = skillPointsToPercentage ( stats . get ( 'def' ) ) * skillpoint _final _mult [ 3 ] ;
let agi _pct = skillPointsToPercentage ( stats . get ( 'agi' ) ) * skillpoint _final _mult [ 4 ] ;
2022-06-20 13:12:22 +00:00
//total hp
let totalHp = stats . get ( "hp" ) + stats . get ( "hpBonus" ) ;
if ( totalHp < 5 ) totalHp = 5 ;
defenseStats . push ( totalHp ) ;
//EHP
let ehp = [ totalHp , totalHp ] ;
2022-06-26 11:12:04 +00:00
let defMult = ( 2 - stats . get ( "classDef" ) ) * stats . get ( "defMultiplier" ) ;
2022-07-05 07:51:02 +00:00
// newehp = oldehp / [0.1 * A(x) + (1 - A(x)) * (1 - D(x))]
ehp [ 0 ] = ehp [ 0 ] / ( 0.1 * agi _pct + ( 1 - agi _pct ) * ( 1 - def _pct ) ) ;
ehp [ 0 ] /= defMult ;
// ehp[0] /= (1-def_pct)*(1-agi_pct)*defMult;
2022-06-20 17:51:17 +00:00
ehp [ 1 ] /= ( 1 - def _pct ) * defMult ;
2022-06-20 13:12:22 +00:00
defenseStats . push ( ehp ) ;
//HPR
let totalHpr = rawToPct ( stats . get ( "hprRaw" ) , stats . get ( "hprPct" ) / 100. ) ;
defenseStats . push ( totalHpr ) ;
//EHPR
let ehpr = [ totalHpr , totalHpr ] ;
2022-06-20 17:51:17 +00:00
ehpr [ 0 ] /= ( 1 - def _pct ) * ( 1 - agi _pct ) * defMult ;
ehpr [ 1 ] /= ( 1 - def _pct ) * defMult ;
2022-06-20 13:12:22 +00:00
defenseStats . push ( ehpr ) ;
//skp stats
defenseStats . push ( [ def _pct * 100 , agi _pct * 100 ] ) ;
//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 ) ;
//[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]]
return defenseStats ;
}
2022-06-19 20:44:02 +00:00
/ * *
* Compute spell damage of spell parts .
* Currently kinda janky / TODO while we rework the internal rep . of spells .
*
* Signature : SpellDamageCalcNode ( weapon - input : Item ,
2022-06-20 13:12:22 +00:00
* stats : StatMap ,
2022-06-19 20:44:02 +00:00
* spell - info : [ Spell , SpellParts ] ) => List [ SpellDamage ]
* /
2022-06-19 16:49:04 +00:00
class SpellDamageCalcNode extends ComputeNode {
constructor ( spell _num ) {
super ( "builder-spell" + spell _num + "-calc" ) ;
}
compute _func ( input _map ) {
2022-06-26 10:14:31 +00:00
const weapon = input _map . get ( 'build' ) . weapon . statMap ;
2022-06-19 18:02:28 +00:00
const spell _info = input _map . get ( 'spell-info' ) ;
2022-06-27 06:42:00 +00:00
const spell = spell _info [ 0 ] ;
2022-06-19 18:02:28 +00:00
const spell _parts = spell _info [ 1 ] ;
2022-06-20 13:12:22 +00:00
const stats = input _map . get ( 'stats' ) ;
2022-06-20 17:51:17 +00:00
const damage _mult = stats . get ( 'damageMultiplier' ) ;
2022-06-20 13:12:22 +00:00
const skillpoints = [
stats . get ( 'str' ) ,
stats . get ( 'dex' ) ,
stats . get ( 'int' ) ,
stats . get ( 'def' ) ,
stats . get ( 'agi' )
] ;
2022-06-19 18:02:28 +00:00
let spell _results = [ ]
2022-06-27 06:42:00 +00:00
let spell _result _map = new Map ( ) ;
const use _speed = ( ( 'use_atkspd' in spell ) ? spell . use _atkspd : true ) ;
const use _spell = ( ( 'scaling' in spell ) ? spell . scaling === 'spell' : true ) ;
2022-06-19 18:02:28 +00:00
2022-06-27 06:42:00 +00:00
// TODO: move preprocessing to separate node/node chain
2022-06-19 18:02:28 +00:00
for ( const part of spell _parts ) {
2022-06-27 06:42:00 +00:00
let spell _result ;
if ( 'multipliers' in part ) { // damage type spell
let results = calculateSpellDamage ( stats , weapon , part . multipliers , use _spell , ! use _speed ) ;
spell _result = {
type : "damage" ,
normal _min : results [ 2 ] . map ( x => x [ 0 ] ) ,
normal _max : results [ 2 ] . map ( x => x [ 1 ] ) ,
normal _total : results [ 0 ] ,
crit _min : results [ 2 ] . map ( x => x [ 2 ] ) ,
crit _max : results [ 2 ] . map ( x => x [ 3 ] ) ,
crit _total : results [ 1 ] ,
2022-06-24 10:35:03 +00:00
}
2022-06-27 06:42:00 +00:00
} else if ( 'power' in part ) {
2022-06-19 18:02:28 +00:00
// TODO: wynn2 formula
2022-06-27 06:54:05 +00:00
let _heal _amount = ( part . power * getDefenseStats ( stats ) [ 0 ] * Math . max ( 0.5 , Math . min ( 1.75 , 1 + 0.5 * stats . get ( "wDamPct" ) / 100 ) ) ) ;
2022-06-27 06:42:00 +00:00
spell _result = {
type : "heal" ,
heal _amount : _heal _amount
}
2022-07-01 03:44:26 +00:00
}
else {
continue ;
}
spell _result . name = part . name ;
spell _results . push ( spell _result ) ;
spell _result _map . set ( part . name , spell _result ) ;
}
for ( const part of spell _parts ) {
if ( 'hits' in part ) {
let spell _result = {
2022-06-27 06:42:00 +00:00
normal _min : [ 0 , 0 , 0 , 0 , 0 , 0 ] ,
normal _max : [ 0 , 0 , 0 , 0 , 0 , 0 ] ,
normal _total : [ 0 , 0 ] ,
crit _min : [ 0 , 0 , 0 , 0 , 0 , 0 ] ,
crit _max : [ 0 , 0 , 0 , 0 , 0 , 0 ] ,
2022-06-27 06:54:05 +00:00
crit _total : [ 0 , 0 ] ,
heal _amount : 0
2022-06-27 06:42:00 +00:00
}
const dam _res _keys = [ 'normal_min' , 'normal_max' , 'normal_total' , 'crit_min' , 'crit_max' , 'crit_total' ] ;
for ( const [ subpart _name , hits ] of Object . entries ( part . hits ) ) {
const subpart = spell _result _map . get ( subpart _name ) ;
2022-07-05 04:39:35 +00:00
if ( ! subpart ) { continue ; }
2022-06-27 06:42:00 +00:00
if ( spell _result . type ) {
if ( subpart . type !== spell _result . type ) {
throw "SpellCalc total subpart type mismatch" ;
}
}
else {
spell _result . type = subpart . type ;
}
if ( spell _result . type === 'damage' ) {
for ( const key of dam _res _keys ) {
for ( let i in spell _result . normal _min ) {
spell _result [ key ] [ i ] += subpart [ key ] [ i ] * hits ;
}
}
}
else {
spell _result . heal _amount += subpart . heal _amount ;
}
}
2022-07-01 03:44:26 +00:00
spell _result . name = part . name ;
spell _results . push ( spell _result ) ;
spell _result _map . set ( part . name , spell _result ) ;
2022-06-19 18:02:28 +00:00
}
}
return spell _results ;
}
}
2022-06-19 20:44:02 +00:00
/ * *
* Display spell damage from spell parts .
* Currently kinda janky / TODO while we rework the internal rep . of spells .
*
2022-06-20 13:12:22 +00:00
* Signature : SpellDisplayNode ( stats : StatMap ,
2022-06-19 20:44:02 +00:00
* spell - info : [ Spell , SpellParts ] ,
* spell - damage : List [ SpellDamage ] ) => null
* /
2022-06-19 18:02:28 +00:00
class SpellDisplayNode extends ComputeNode {
constructor ( spell _num ) {
super ( "builder-spell" + spell _num + "-display" ) ;
this . spell _idx = spell _num ;
}
compute _func ( input _map ) {
2022-06-20 14:37:14 +00:00
const stats = input _map . get ( 'stats' ) ;
2022-06-19 18:02:28 +00:00
const spell _info = input _map . get ( 'spell-info' ) ;
const damages = input _map . get ( 'spell-damage' ) ;
const spell = spell _info [ 0 ] ;
2022-06-19 16:49:04 +00:00
const i = this . spell _idx ;
let parent _elem = document . getElementById ( "spell" + i + "-info" ) ;
let overallparent _elem = document . getElementById ( "spell" + i + "-infoAvg" ) ;
2022-06-27 06:42:00 +00:00
displaySpellDamage ( parent _elem , overallparent _elem , stats , spell , i + 1 , damages ) ;
2022-06-19 16:49:04 +00:00
}
}
2022-06-20 14:37:14 +00: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 :
* /
function getMeleeStats ( stats , weapon ) {
2022-06-26 10:14:31 +00:00
stats = new Map ( stats ) ; // Shallow copy
2022-06-20 14:37:14 +00:00
const weapon _stats = weapon . statMap ;
const skillpoints = [
stats . get ( 'str' ) ,
stats . get ( 'dex' ) ,
stats . get ( 'int' ) ,
stats . get ( 'def' ) ,
stats . get ( 'agi' )
] ;
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 ) {
adjAtkSpd = 6 ;
} else if ( adjAtkSpd < 0 ) {
adjAtkSpd = 0 ;
}
if ( weapon _stats . get ( "type" ) === "relik" ) {
2022-06-26 10:14:31 +00:00
stats . set ( 'damageMultiplier' , 0.99 ) ; // CURSE YOU WYNNCRAFT
2022-06-20 14:37:14 +00: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.
}
2022-06-24 10:35:03 +00:00
let results = calculateSpellDamage ( stats , weapon _stats , [ 100 , 0 , 0 , 0 , 0 , 0 ] , false , true ) ;
2022-06-20 14:37:14 +00:00
let dex = skillpoints [ 1 ] ;
let totalDamNorm = results [ 0 ] ;
let totalDamCrit = results [ 1 ] ;
totalDamNorm . push ( 1 - skillPointsToPercentage ( dex ) ) ;
totalDamCrit . push ( skillPointsToPercentage ( dex ) ) ;
let damages _results = results [ 2 ] ;
let singleHitTotal = ( ( totalDamNorm [ 0 ] + totalDamNorm [ 1 ] ) * ( totalDamNorm [ 2 ] )
+ ( totalDamCrit [ 0 ] + totalDamCrit [ 1 ] ) * ( totalDamCrit [ 2 ] ) ) / 2 ;
//Now do math
let normDPS = ( totalDamNorm [ 0 ] + totalDamNorm [ 1 ] ) / 2 * baseDamageMultiplier [ adjAtkSpd ] ;
let critDPS = ( totalDamCrit [ 0 ] + totalDamCrit [ 1 ] ) / 2 * baseDamageMultiplier [ adjAtkSpd ] ;
let avgDPS = ( normDPS * ( 1 - skillPointsToPercentage ( dex ) ) ) + ( critDPS * ( skillPointsToPercentage ( dex ) ) ) ;
//[[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]
return damages _results . concat ( [ totalDamNorm , totalDamCrit , normDPS , critDPS , avgDPS , adjAtkSpd , singleHitTotal ] ) . concat ( results [ 3 ] ) ;
}
2022-06-19 20:44:02 +00:00
/ * *
* Display build stats .
*
* Signature : BuildDisplayNode ( build : Build ) => null
* /
class BuildDisplayNode extends ComputeNode {
2022-06-20 13:12:22 +00:00
constructor ( ) { super ( "builder-stats-display" ) ; }
2022-06-19 20:44:02 +00:00
compute _func ( input _map ) {
2022-06-20 13:12:22 +00:00
const build = input _map . get ( 'build' ) ;
const stats = input _map . get ( 'stats' ) ;
displayBuildStats ( 'overall-stats' , build , build _all _display _commands , stats ) ;
displayBuildStats ( "offensive-stats" , build , build _offensive _display _commands , stats ) ;
2022-06-19 20:44:02 +00:00
displaySetBonuses ( "set-info" , build ) ;
2022-06-20 14:37:14 +00:00
let meleeStats = getMeleeStats ( stats , build . weapon ) ;
// TODO: move weapon out?
2022-06-19 20:59:16 +00:00
displayMeleeDamage ( document . getElementById ( "build-melee-stats" ) , document . getElementById ( "build-melee-statsAvg" ) , meleeStats ) ;
2022-06-20 13:12:22 +00:00
displayDefenseStats ( document . getElementById ( "defensive-stats" ) , stats ) ;
2022-06-19 20:59:16 +00:00
displayPoisonDamage ( document . getElementById ( "build-poison-stats" ) , build ) ;
2022-06-20 02:07:59 +00:00
displayEquipOrder ( document . getElementById ( "build-order" ) , build . equip _order ) ;
2022-06-19 20:44:02 +00:00
}
}
2022-06-20 13:12:22 +00:00
/ * *
* Show warnings for skillpoints , level , set bonus for a build
* Also shosw skill point remaining and other misc . info
*
* Signature : DisplayBuildWarningNode ( build : Build , str : int , dex : int , int : int , def : int , agi : int ) => null
* /
class DisplayBuildWarningsNode extends ComputeNode {
constructor ( ) { super ( "builder-show-warnings" ) ; }
compute _func ( input _map ) {
const build = input _map . get ( 'build' ) ;
const min _assigned = build . base _skillpoints ;
const base _totals = build . total _skillpoints ;
const skillpoints = [
input _map . get ( 'str' ) ,
input _map . get ( 'dex' ) ,
input _map . get ( 'int' ) ,
input _map . get ( 'def' ) ,
input _map . get ( 'agi' )
] ;
let skp _effects = [ "% more damage dealt." , "% chance to crit." , "% spell cost reduction." , "% less damage taken." , "% chance to dodge." ] ;
let total _assigned = 0 ;
for ( let i in skp _order ) { //big bren
const assigned = skillpoints [ i ] - base _totals [ i ] + min _assigned [ i ]
2022-06-26 12:44:10 +00:00
setText ( skp _order [ i ] + "-skp-base" , "Original: " + base _totals [ i ] ) ;
2022-06-20 13:12:22 +00:00
setText ( skp _order [ i ] + "-skp-assign" , "Assign: " + assigned ) ;
setValue ( skp _order [ i ] + "-skp" , skillpoints [ i ] ) ;
let linebreak = document . createElement ( "br" ) ;
linebreak . classList . add ( "itemp" ) ;
2022-07-06 03:40:32 +00:00
setText ( skp _order [ i ] + "-skp-pct" , ( skillPointsToPercentage ( skillpoints [ i ] ) * 100 * skillpoint _final _mult [ i ] ) . toFixed ( 1 ) . concat ( skp _effects [ i ] ) ) ;
2022-06-20 13:12:22 +00:00
document . getElementById ( skp _order [ i ] + "-warnings" ) . textContent = ''
if ( assigned > 100 ) {
let skp _warning = document . createElement ( "p" ) ;
2022-06-30 12:27:35 +00:00
skp _warning . classList . add ( "warning" , "small-text" ) ;
2022-06-20 13:12:22 +00:00
skp _warning . textContent += "Cannot assign " + assigned + " skillpoints in " + [ "Strength" , "Dexterity" , "Intelligence" , "Defense" , "Agility" ] [ i ] + " manually." ;
document . getElementById ( skp _order [ i ] + "-warnings" ) . appendChild ( skp _warning ) ;
}
total _assigned += assigned ;
}
let summarybox = document . getElementById ( "summary-box" ) ;
summarybox . textContent = "" ;
let skpRow = document . createElement ( "p" ) ;
let remainingSkp = document . createElement ( "p" ) ;
remainingSkp . classList . add ( "scaled-font" ) ;
let remainingSkpTitle = document . createElement ( "b" ) ;
remainingSkpTitle . textContent = "Assigned " + total _assigned + " skillpoints. Remaining skillpoints: " ;
let remainingSkpContent = document . createElement ( "b" ) ;
remainingSkpContent . textContent = "" + ( levelToSkillPoints ( build . level ) - total _assigned ) ;
remainingSkpContent . classList . add ( levelToSkillPoints ( build . level ) - total _assigned < 0 ? "negative" : "positive" ) ;
remainingSkp . appendChild ( remainingSkpTitle ) ;
remainingSkp . appendChild ( remainingSkpContent ) ;
summarybox . append ( skpRow ) ;
summarybox . append ( remainingSkp ) ;
if ( total _assigned > levelToSkillPoints ( build . level ) ) {
let skpWarning = document . createElement ( "span" ) ;
//skpWarning.classList.add("itemp");
skpWarning . classList . add ( "warning" ) ;
skpWarning . textContent = "WARNING: Too many skillpoints need to be assigned!" ;
let skpCount = document . createElement ( "p" ) ;
skpCount . classList . add ( "warning" ) ;
skpCount . textContent = "For level " + ( build . level > 101 ? "101+" : build . level ) + ", there are only " + levelToSkillPoints ( build . level ) + " skill points available." ;
summarybox . append ( skpWarning ) ;
summarybox . append ( skpCount ) ;
}
let lvlWarning ;
for ( const item of build . items ) {
let item _lvl ;
if ( item . statMap . get ( "crafted" ) ) {
//item_lvl = item.get("lvlLow") + "-" + item.get("lvl");
item _lvl = item . statMap . get ( "lvlLow" ) ;
}
else {
item _lvl = item . statMap . get ( "lvl" ) ;
}
if ( build . level < item _lvl ) {
if ( ! lvlWarning ) {
lvlWarning = document . createElement ( "p" ) ;
2022-06-20 17:51:17 +00:00
lvlWarning . classList . add ( "itemp" ) ; lvlWarning . classList . add ( "warning" ) ;
2022-06-20 13:12:22 +00:00
lvlWarning . textContent = "WARNING: A level " + build . level + " player cannot use some piece(s) of this build."
}
let baditem = document . createElement ( "p" ) ;
2022-06-20 17:51:17 +00:00
baditem . classList . add ( "nocolor" ) ; baditem . classList . add ( "itemp" ) ;
2022-06-23 09:23:56 +00:00
baditem . textContent = item . statMap . get ( "displayName" ) + " requires level " + item _lvl + " to use." ;
2022-06-20 13:12:22 +00:00
lvlWarning . appendChild ( baditem ) ;
}
}
if ( lvlWarning ) {
summarybox . append ( lvlWarning ) ;
}
for ( const [ setName , count ] of build . activeSetCounts ) {
const bonus = sets . get ( setName ) . bonuses [ count - 1 ] ;
if ( bonus [ "illegal" ] ) {
let setWarning = document . createElement ( "p" ) ;
2022-06-20 17:51:17 +00:00
setWarning . classList . add ( "itemp" ) ; setWarning . classList . add ( "warning" ) ;
2022-06-20 13:12:22 +00:00
setWarning . textContent = "WARNING: illegal item combination: " + setName
summarybox . append ( setWarning ) ;
}
}
}
}
/ * *
2022-06-22 05:39:10 +00:00
* Aggregate stats from all inputs ( merges statmaps ) .
2022-06-20 13:12:22 +00:00
*
2022-06-22 05:39:10 +00:00
* Signature : AggregateStatsNode ( * args ) => StatMap
2022-06-20 13:12:22 +00:00
* /
class AggregateStatsNode extends ComputeNode {
constructor ( ) { super ( "builder-aggregate-stats" ) ; }
2022-06-22 05:39:10 +00:00
compute _func ( input _map ) {
const output _stats = new Map ( ) ;
for ( const [ k , v ] of input _map . entries ( ) ) {
for ( const [ k2 , v2 ] of v . entries ( ) ) {
if ( output _stats . has ( k2 ) ) {
2022-06-23 09:23:56 +00:00
// TODO: ugly AF
if ( k2 === 'damageMultiplier' || k2 === 'defMultiplier' ) {
output _stats . set ( k2 , v2 * output _stats . get ( k2 ) ) ;
}
else {
output _stats . set ( k2 , v2 + output _stats . get ( k2 ) ) ;
}
2022-06-22 05:39:10 +00:00
}
else {
output _stats . set ( k2 , v2 ) ;
}
}
}
return output _stats ;
}
}
/ * *
* Aggregate editable ID stats with build and weapon type .
*
* Signature : AggregateEditableIDNode ( build : Build , weapon : Item , * args ) => StatMap
* /
class AggregateEditableIDNode extends ComputeNode {
constructor ( ) { super ( "builder-aggregate-inputs" ) ; }
2022-06-20 13:12:22 +00:00
compute _func ( input _map ) {
2022-06-20 17:51:17 +00:00
const build = input _map . get ( 'build' ) ; input _map . delete ( 'build' ) ;
2022-06-20 13:12:22 +00:00
const output _stats = new Map ( build . statMap ) ;
for ( const [ k , v ] of input _map . entries ( ) ) {
output _stats . set ( k , v ) ;
}
2022-06-20 17:51:17 +00:00
2022-06-27 03:24:38 +00:00
output _stats . set ( 'classDef' , classDefenseMultipliers . get ( build . weapon . statMap . get ( "type" ) ) ) ;
2022-06-20 13:12:22 +00:00
return output _stats ;
}
}
2022-06-22 03:41:40 +00:00
let edit _id _output ;
function resetEditableIDs ( ) {
edit _id _output . notify ( ) ;
}
2022-06-20 02:07:59 +00:00
/ * *
* Set the editble id fields .
2022-06-20 13:12:22 +00:00
*
* Signature : EditableIDSetterNode ( build : Build ) => null
2022-06-20 02:07:59 +00:00
* /
class EditableIDSetterNode extends ComputeNode {
2022-06-22 03:41:40 +00:00
constructor ( notify _nodes ) {
super ( "builder-id-setter" ) ;
this . notify _nodes = notify _nodes . slice ( ) ;
}
2022-06-20 13:12:22 +00:00
compute _func ( input _map ) {
if ( input _map . size !== 1 ) { throw "EditableIDSetterNode accepts exactly one input (build)" ; }
const [ build ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
for ( const id of editable _item _fields ) {
2022-06-22 03:41:40 +00:00
const val = build . statMap . get ( id ) ;
document . getElementById ( id ) . value = val ;
document . getElementById ( id + '-base' ) . textContent = 'Original Value: ' + val ;
}
}
notify ( ) {
this . mark _dirty ( ) ;
this . update ( ) ;
// NOTE: DO NOT merge these loops for performance reasons!!!
for ( const node of this . notify _nodes ) {
node . mark _dirty ( ) ;
}
for ( const node of this . notify _nodes ) {
node . update ( ) ;
2022-06-20 13:12:22 +00:00
}
}
}
/ * *
* Set skillpoint fields from build .
* This is separate because ... . . because of the way we work with edit ids vs skill points during the load sequence ... .
*
* Signature : SkillPointSetterNode ( build : Build ) => null
* /
class SkillPointSetterNode extends ComputeNode {
constructor ( notify _nodes ) {
super ( "builder-skillpoint-setter" ) ;
2022-06-22 03:41:40 +00:00
this . notify _nodes = notify _nodes . slice ( ) ;
2022-06-20 13:12:22 +00:00
}
2022-06-20 02:07:59 +00:00
2022-06-20 13:12:22 +00:00
compute _func ( input _map ) {
if ( input _map . size !== 1 ) { throw "SkillPointSetterNode accepts exactly one input (build)" ; }
const [ build ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
for ( const [ idx , elem ] of skp _order . entries ( ) ) {
document . getElementById ( elem + '-skp' ) . value = build . total _skillpoints [ idx ] ;
}
// NOTE: DO NOT merge these loops for performance reasons!!!
for ( const node of this . notify _nodes ) {
node . mark _dirty ( ) ;
}
for ( const node of this . notify _nodes ) {
node . update ( ) ;
}
}
}
/ * *
* Get number ( possibly summed ) from a text input .
*
* Signature : SumNumberInputNode ( ) => int
* /
class SumNumberInputNode extends InputNode {
compute _func ( input _map ) {
2022-06-26 12:35:04 +00:00
let value = this . input _field . value ;
2022-06-20 13:12:22 +00:00
if ( value === "" ) { value = 0 ; }
let input _num = 0 ;
if ( value . includes ( "+" ) ) {
let skp = value . split ( "+" ) ;
for ( const s of skp ) {
const val = parseInt ( s , 10 ) ;
if ( isNaN ( val ) ) {
return null ;
}
input _num += val ;
}
} else {
input _num = parseInt ( value , 10 ) ;
if ( isNaN ( input _num ) ) {
return null ;
}
}
return input _num ;
}
2022-06-20 02:07:59 +00:00
}
2022-05-22 10:21:34 +00:00
let item _nodes = [ ] ;
2022-06-19 16:49:04 +00:00
let powder _nodes = [ ] ;
2022-06-20 14:37:14 +00:00
let edit _input _nodes = [ ] ;
2022-06-23 10:55:13 +00:00
let skp _inputs = [ ] ;
2022-06-27 03:24:38 +00:00
let build _node ;
let stat _agg _node ;
let edit _agg _node ;
2022-06-29 06:23:27 +00:00
let atree _graph _creator ;
2022-05-22 10:21:34 +00:00
2022-06-19 20:44:02 +00:00
function builder _graph _init ( ) {
2022-06-29 06:23:27 +00:00
// Phase 1/3: Set up item input, propagate updates, etc.
2022-06-20 03:20:22 +00:00
2022-06-19 16:49:04 +00:00
// Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff).
2022-06-19 20:44:02 +00:00
for ( const [ eq , display _elem , none _item ] of zip3 ( equipment _fields , build _fields , none _items ) ) {
2022-05-22 10:21:34 +00:00
let input _field = document . getElementById ( eq + "-choice" ) ;
let item _image = document . getElementById ( eq + "-img" ) ;
let item _input = new ItemInputNode ( eq + '-input' , input _field , none _item ) ;
item _nodes . push ( item _input ) ;
2022-06-19 20:44:02 +00:00
new ItemInputDisplayNode ( eq + '-input-display' , eq , item _image ) . link _to ( item _input ) ;
new ItemDisplayNode ( eq + '-item-display' , display _elem ) . link _to ( item _input ) ;
2022-06-19 16:49:04 +00:00
//new PrintNode(eq+'-debug').link_to(item_input);
2022-05-22 10:21:34 +00:00
//document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm');
}
2022-06-23 09:23:56 +00:00
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 item _image = document . getElementById ( eq + "-img" ) ;
let item _input = new ItemInputNode ( eq + '-input' , input _field , none _item ) ;
item _nodes . push ( item _input ) ;
new ItemInputDisplayNode ( eq + '-input-display' , eq , item _image ) . link _to ( item _input ) ;
}
2022-06-19 16:49:04 +00:00
// weapon image changer node.
2022-05-22 10:21:34 +00:00
let weapon _image = document . getElementById ( "weapon-img" ) ;
2022-06-24 10:35:03 +00:00
let weapon _dps = document . getElementById ( "weapon-dps" ) ;
new WeaponInputDisplayNode ( 'weapon-type' , weapon _image , weapon _dps ) . link _to ( item _nodes [ 8 ] ) ;
2022-06-19 16:49:04 +00:00
// Level input node.
2022-06-19 07:42:49 +00:00
let level _input = new InputNode ( 'level-input' , document . getElementById ( 'level-choice' ) ) ;
2022-06-19 16:49:04 +00:00
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
2022-06-27 03:24:38 +00:00
build _node = new BuildAssembleNode ( ) ;
2022-06-19 07:42:49 +00:00
for ( const input of item _nodes ) {
build _node . link _to ( input ) ;
}
build _node . link _to ( level _input ) ;
2022-06-19 20:44:02 +00:00
let build _encode _node = new BuildEncodeNode ( ) ;
build _encode _node . link _to ( build _node , 'build' ) ;
let url _update _node = new URLUpdateNode ( ) ;
url _update _node . link _to ( build _encode _node , 'build-str' ) ;
2022-06-19 16:49:04 +00:00
for ( const input of powder _inputs ) {
2022-06-19 20:44:02 +00:00
let powder _node = new PowderInputNode ( input , document . getElementById ( input ) ) ;
powder _nodes . push ( powder _node ) ;
build _encode _node . link _to ( powder _node , input ) ;
2022-06-19 16:49:04 +00:00
}
2022-06-20 17:51:17 +00:00
item _nodes [ 0 ] . link _to ( powder _nodes [ 0 ] , 'powdering' ) ;
item _nodes [ 1 ] . link _to ( powder _nodes [ 1 ] , 'powdering' ) ;
item _nodes [ 2 ] . link _to ( powder _nodes [ 2 ] , 'powdering' ) ;
item _nodes [ 3 ] . link _to ( powder _nodes [ 3 ] , 'powdering' ) ;
item _nodes [ 8 ] . link _to ( powder _nodes [ 4 ] , 'powdering' ) ;
2022-06-29 06:23:27 +00:00
// Phase 2/3: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage
2022-06-20 03:20:22 +00:00
2022-06-20 13:12:22 +00:00
let build _disp _node = new BuildDisplayNode ( )
build _disp _node . link _to ( build _node , 'build' ) ;
2022-06-20 03:20:22 +00:00
// Create one node that will be the "aggregator node" (listen to all the editable id nodes, as well as the build_node (for non editable stats) and collect them into one statmap)
2022-06-27 03:24:38 +00:00
stat _agg _node = new AggregateStatsNode ( ) ;
edit _agg _node = new AggregateEditableIDNode ( ) ;
edit _agg _node . link _to ( build _node , 'build' ) ;
2022-06-20 13:12:22 +00:00
for ( const field of editable _item _fields ) {
2022-06-20 03:20:22 +00:00
// Create nodes that listens to each editable id input, the node name should match the "id"
2022-06-20 13:12:22 +00:00
const elem = document . getElementById ( field ) ;
const node = new SumNumberInputNode ( 'builder-' + field + '-input' , elem ) ;
2022-06-22 05:39:10 +00:00
edit _agg _node . link _to ( node , field ) ;
2022-06-20 13:12:22 +00:00
edit _input _nodes . push ( node ) ;
}
2022-06-22 03:41:40 +00:00
// Edit IDs setter declared up here to set ids so they will be populated by default.
edit _id _output = new EditableIDSetterNode ( edit _input _nodes ) ; // Makes shallow copy of list.
edit _id _output . link _to ( build _node ) ;
2022-06-20 13:12:22 +00:00
for ( const skp of skp _order ) {
const elem = document . getElementById ( skp + '-skp' ) ;
const node = new SumNumberInputNode ( 'builder-' + skp + '-input' , elem ) ;
2022-06-22 05:39:10 +00:00
edit _agg _node . link _to ( node , skp ) ;
2022-06-20 13:12:22 +00:00
build _encode _node . link _to ( node , skp ) ;
edit _input _nodes . push ( node ) ;
2022-06-23 10:42:18 +00:00
skp _inputs . push ( node ) ;
2022-06-20 13:12:22 +00:00
}
2022-06-22 05:39:10 +00:00
stat _agg _node . link _to ( edit _agg _node ) ;
2022-06-20 13:12:22 +00:00
build _disp _node . link _to ( stat _agg _node , 'stats' ) ;
2022-06-20 03:20:22 +00:00
2022-06-29 06:23:27 +00:00
// Phase 3/3: Set up atree stuff.
2022-07-01 05:22:15 +00:00
let class _node = new PlayerClassNode ( 'builder-class' ) . link _to ( build _node ) ;
2022-06-29 06:23:27 +00:00
// These two are defined in `atree.js`
2022-07-01 05:22:15 +00:00
atree _node . link _to ( class _node , 'player-class' ) ;
2022-06-28 18:43:35 +00:00
atree _merge . link _to ( build _node , 'build' ) ;
2022-06-29 06:23:27 +00:00
atree _graph _creator = new AbilityTreeEnsureNodesNode ( build _node , stat _agg _node )
. link _to ( atree _collect _spells , 'spells' ) ;
2022-07-06 06:43:44 +00:00
stat _agg _node . link _to ( atree _stats , 'atree-stats' ) ;
2022-06-20 03:20:22 +00:00
2022-06-30 15:03:41 +00:00
build _encode _node . link _to ( atree _node , 'atree' ) . link _to ( atree _state _node , 'atree-state' ) ;
2022-06-29 06:23:27 +00:00
// ---------------------------------------------------------------
// Trigger the update cascade for build!
// ---------------------------------------------------------------
2022-06-20 13:12:22 +00:00
for ( const input _node of item _nodes . concat ( powder _nodes ) ) {
input _node . update ( ) ;
2022-06-20 03:20:22 +00:00
}
2022-06-20 13:12:22 +00:00
level _input . update ( ) ;
2022-06-20 03:20:22 +00:00
2022-06-30 15:03:41 +00:00
// kinda janky, manually set atree and update. Some wasted compute here
if ( atree _data !== null && atree _node . value !== null ) { // janky check if atree is valid
const atree _state = atree _state _node . value ;
if ( atree _data . length > 0 ) {
const active _nodes = decode _atree ( atree _node . value , atree _data ) ;
for ( const node of active _nodes ) {
atree _set _state ( atree _state . get ( node . ability . id ) , true ) ;
}
atree _state _node . mark _dirty ( ) . update ( ) ;
}
}
2022-06-20 17:51:17 +00:00
// Powder specials.
let powder _special _calc = new PowderSpecialCalcNode ( ) . link _to ( powder _special _input , 'powder-specials' ) ;
new PowderSpecialDisplayNode ( ) . link _to ( powder _special _input , 'powder-specials' )
2022-06-26 10:14:31 +00:00
. link _to ( stat _agg _node , 'stats' ) . link _to ( build _node , 'build' ) ;
2022-06-20 17:51:17 +00:00
stat _agg _node . link _to ( powder _special _calc , 'powder-boost' ) ;
2022-06-23 08:25:19 +00:00
stat _agg _node . link _to ( armor _powder _node , 'armor-powder' ) ;
2022-06-20 17:51:17 +00:00
powder _special _input . update ( ) ;
// Potion boost.
stat _agg _node . link _to ( boosts _node , 'potion-boost' ) ;
2022-06-20 03:20:22 +00:00
// Also do something similar for skill points
2022-06-20 13:12:22 +00:00
for ( const node of edit _input _nodes ) {
node . update ( ) ;
}
2022-06-20 03:20:22 +00:00
2022-06-20 13:12:22 +00:00
let skp _output = new SkillPointSetterNode ( edit _input _nodes ) ;
skp _output . link _to ( build _node ) ;
2022-06-20 03:20:22 +00:00
2022-06-23 10:42:18 +00:00
let build _warnings _node = new DisplayBuildWarningsNode ( ) ;
build _warnings _node . link _to ( build _node , 'build' ) ;
for ( const [ skp _input , skp ] of zip2 ( skp _inputs , skp _order ) ) {
build _warnings _node . link _to ( skp _input , skp ) ;
}
build _warnings _node . update ( ) ;
2022-06-20 03:20:22 +00:00
// call node.update() for each skillpoint node and stat edit listener node manually
// NOTE: the text boxes for skill points are already filled out by decodeBuild() so this will fix them
// this will propagate the update to the `stat_agg_node`, and then to damage calc
2022-06-20 02:07:59 +00:00
2022-05-22 10:21:34 +00:00
console . log ( "Set up graph" ) ;
2022-06-12 14:45:48 +00:00
}
2022-06-19 20:44:02 +00:00