2022-07-29 17:37:09 +00:00
/ * *
* File containing compute graph structure of the builder page .
* /
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-07-07 02:44:51 +00:00
} ) ( ) ;
2022-06-23 08:25:19 +00:00
2023-02-17 11:40:23 +00:00
const damageMultipliers = new Map ( [ [ "totem" , 0.2 ] , [ "warscream" , 0.0 ] , [ "ragnarokkr" , 0.20 ] , [ "fortitude" , 0.60 ] , [ "radiance" , 0.0 ] ] ) ;
2022-08-11 10:13:53 +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 ;
2022-09-04 19:17:01 +00:00
if ( key === "warscream" ) { def _boost += . 20 }
2022-06-20 17:51:17 +00:00
}
}
2022-06-27 08:41:13 +00:00
let res = new Map ( ) ;
2022-07-08 05:28:46 +00:00
res . set ( 'damMult.Potion' , 100 * damage _boost ) ;
res . set ( 'defMult.Potion' , 100 * 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" ] ;
2022-09-04 19:41:42 +00:00
if ( name === "Courage" || name === "Curse" || name == "Wind Prison" ) { // Master mod all the way
stats . set ( "damMult." + name , special . weaponSpecialEffects . get ( "Damage Boost" ) [ power - 1 ] ) ;
// legacy
2022-06-20 17:51:17 +00:00
stats . set ( "poisonPct" , 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-07-27 06:18:55 +00:00
* Signature : ItemInputNode ( ) => 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
// 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 ) ; }
2022-07-17 21:34:54 +00:00
else if ( itemMap . has ( item _text ) ) { item = new Item ( itemMap . get ( item _text ) ) ; }
2022-06-20 17:51:17 +00:00
else if ( tomeMap . has ( item _text ) ) { item = new Item ( tomeMap . get ( item _text ) ) ; }
2022-06-19 20:44:02 +00:00
if ( item ) {
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 ) {
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 ) ; }
2023-01-03 01:21:45 +00:00
for ( const node of equip _inputs ) {
2022-07-13 07:37:31 +00:00
if ( node !== this ) {
// save a tiny bit of compute
calcSchedule ( node , 10 ) ;
}
}
// Needed to push the weapon node's updates forward
2022-06-23 10:55:13 +00:00
return this . compute _func ( input _map ) ;
2022-06-22 05:53:04 +00:00
}
2022-06-19 20:44:02 +00:00
return null ;
}
}
2022-07-27 06:18:55 +00:00
/ * *
* Node for updating item input fields from parsed items .
*
* Signature : ItemInputDisplayNode ( item : Item , powdering : List [ powder ] ) => Item
* /
class ItemPowderingNode extends ComputeNode {
constructor ( name ) { super ( name ) ; }
compute _func ( input _map ) {
const powdering = input _map . get ( 'powdering' ) ;
2022-07-29 05:48:01 +00:00
const input _item = input _map . get ( 'item' ) ;
const item = input _item . copy ( ) ; // TODO: performance
2022-07-27 06:18:55 +00:00
const max _slots = item . statMap . get ( 'slots' ) ;
item . statMap . set ( 'powders' , powdering . slice ( 0 , max _slots ) ) ;
if ( item . statMap . get ( 'category' ) == 'armor' ) {
applyArmorPowders ( item . statMap ) ;
}
else if ( item . statMap . get ( 'category' ) == 'weapon' ) {
apply _weapon _powders ( item . statMap ) ;
}
return item ;
}
}
2022-06-19 20:44:02 +00:00
/ * *
* 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" ) ;
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 ;
}
if ( item . statMap . has ( 'NONE' ) ) {
return null ;
}
2022-06-26 08:07:43 +00:00
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' ) ;
2022-07-24 21:31:23 +00:00
this . image . style . backgroundPosition = itemBGPositions [ type ] ;
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 .
*
2022-07-27 06:18:55 +00:00
* Signature : BuildAssembleNode ( helmet : Item ,
* chestplate : Item ,
* leggings : Item ,
* boots : Item ,
* ring1 : Item ,
* ring2 : Item ,
* bracelet : Item ,
* necklace : Item ,
* weapon : Item ,
* level : int ) => Build | null
2022-06-19 20:44:02 +00:00
* /
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 = [
2022-07-27 06:18:55 +00:00
input _map . get ( 'helmet' ) ,
input _map . get ( 'chestplate' ) ,
input _map . get ( 'leggings' ) ,
input _map . get ( 'boots' ) ,
input _map . get ( 'ring1' ) ,
input _map . get ( 'ring2' ) ,
input _map . get ( 'bracelet' ) ,
input _map . get ( 'necklace' ) ,
input _map . get ( 'weaponTome1' ) ,
input _map . get ( 'weaponTome2' ) ,
input _map . get ( 'armorTome1' ) ,
input _map . get ( 'armorTome2' ) ,
input _map . get ( 'armorTome3' ) ,
input _map . get ( 'armorTome4' ) ,
input _map . get ( 'guildTome1' )
2022-06-19 07:42:49 +00:00
] ;
2022-07-27 06:18:55 +00:00
let weapon = input _map . get ( 'weapon' ) ;
2022-07-18 03:44:00 +00:00
let level = parseInt ( input _map . get ( 'level-input' ) ) ;
if ( isNaN ( level ) ) {
level = 106 ;
}
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
2022-07-25 15:58:41 +00:00
if ( build . weapon . statMap . has ( 'NONE' ) ) { return null ; }
2022-07-01 05:22:15 +00:00
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 .
*
2022-07-27 06:18:55 +00:00
* Signature : PowderInputNode ( item : Item ) => List [ powder ] | null
2022-06-19 20:44:02 +00:00
* /
2022-06-19 16:49:04 +00:00
class PowderInputNode extends InputNode {
2022-07-27 06:18:55 +00:00
constructor ( name , input _field ) { super ( name , input _field ) ; this . fail _cb = true ; }
2022-06-19 16:49:04 +00:00
compute _func ( input _map ) {
2022-07-27 06:18:55 +00:00
if ( input _map . size !== 1 ) { throw "PowderInputNode accepts exactly one input (item)" ; }
const [ item ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
if ( item === null ) {
this . input _field . placeholder = 'powders' ;
return [ ] ;
}
if ( item . statMap . has ( 'slots' ) ) {
this . input _field . placeholder = item . statMap . get ( 'slots' ) + ' slots' ;
}
2022-06-19 16:49:04 +00:00
// TODO: haha improve efficiency to O(n) dumb
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 ) {
2022-07-26 21:18:27 +00:00
if ( first . length > 0 ) {
2022-07-25 05:42:51 +00:00
errorederrors . push ( first ) ;
2022-07-26 21:18:27 +00:00
} else {
2022-07-25 05:42:51 +00:00
break ;
2022-07-26 21:18:27 +00:00
}
2022-06-19 16:49:04 +00:00
} else {
powdering . push ( powder ) ;
}
input = input . slice ( 2 ) ;
}
2022-07-25 05:42:51 +00:00
if ( this . input _field . getAttribute ( "placeholder" ) != null ) {
2022-07-27 06:18:55 +00:00
if ( item . statMap . get ( 'slots' ) < powdering . length ) {
2022-07-25 05:42:51 +00:00
errorederrors . push ( "Too many powders: " + powdering . length ) ;
2022-07-27 06:18:55 +00:00
}
2022-07-25 05:42:51 +00:00
}
2022-07-26 21:18:27 +00:00
if ( errorederrors . length ) {
2022-07-25 05:42:51 +00:00
this . input _field . classList . add ( "is-invalid" ) ;
2022-07-26 21:18:27 +00:00
} else {
2022-07-25 05:42:51 +00:00
this . input _field . classList . remove ( "is-invalid" ) ;
2022-07-26 21:18:27 +00:00
}
2022-07-25 05:42:51 +00:00
2022-06-19 16:49:04 +00:00
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-07-08 05:28:46 +00:00
let defMult = ( 2 - stats . get ( "classDef" ) ) ;
for ( const [ k , v ] of stats . get ( "defMult" ) . entries ( ) ) {
defMult *= ( 1 - v / 100 ) ;
}
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-12-24 10:12:14 +00:00
ehpr [ 0 ] = ehpr [ 0 ] / ( 0.1 * agi _pct + ( 1 - agi _pct ) * ( 1 - def _pct ) ) ;
ehpr [ 0 ] /= defMult ;
2022-06-20 17:51:17 +00:00
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' ) ;
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 ;
2022-08-16 16:24:16 +00:00
const part _id = spell . base _spell + '.' + part . name
2022-06-27 06:42:00 +00:00
if ( 'multipliers' in part ) { // damage type spell
2022-08-16 16:24:16 +00:00
let results = calculateSpellDamage ( stats , weapon , part . multipliers , use _spell , ! use _speed , part _id ) ;
2022-06-27 06:42:00 +00:00
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-09-18 23:14:38 +00:00
let _heal _amount = ( part . power * getDefenseStats ( stats ) [ 0 ] * ( 1 + stats . get ( 'healPct' ) / 100 ) ) ;
2022-08-16 16:24:16 +00:00
if ( stats . has ( 'healPct:' + part _id ) ) {
_heal _amount *= 1 + ( stats . get ( 'healPct:' + part _id ) / 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 ;
}
2022-09-04 19:17:01 +00:00
const { name , display = true } = part ;
spell _result . name = name ;
spell _result . display = display ;
2022-07-01 03:44:26 +00:00
spell _results . push ( spell _result ) ;
2022-09-04 19:17:01 +00:00
spell _result _map . set ( name , spell _result ) ;
2022-07-01 03:44:26 +00:00
}
for ( const part of spell _parts ) {
2022-09-04 19:17:01 +00:00
if ( ! ( 'hits' in part ) ) { continue ; }
let spell _result = {
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 ] ,
crit _total : [ 0 , 0 ] ,
heal _amount : 0
}
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 ) ;
if ( ! subpart ) { continue ; }
if ( spell _result . type ) {
if ( subpart . type !== spell _result . type ) {
throw "SpellCalc total subpart type mismatch" ;
2022-06-27 06:42:00 +00:00
}
2022-09-04 19:17:01 +00:00
}
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 ;
2022-06-27 06:42:00 +00:00
}
}
}
2022-09-04 19:17:01 +00:00
else {
spell _result . heal _amount += subpart . heal _amount * hits ;
}
2022-06-19 18:02:28 +00:00
}
2022-09-04 19:17:01 +00:00
const { name , display = true } = part ;
spell _result . name = name ;
spell _result . display = display ;
spell _results . push ( spell _result ) ;
spell _result _map . set ( 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-07-19 07:33:05 +00:00
displaySpellDamage ( parent _elem , overallparent _elem , stats , spell , i , damages ) ;
2022-06-19 16:49:04 +00:00
}
}
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' ) ;
2022-07-28 04:46:14 +00:00
displayBuildStats ( 'summary-stats' , build , build _overall _display _commands , stats ) ;
displayBuildStats ( "detailed-stats" , build , build _detailed _display _commands , stats ) ;
2022-06-19 20:44:02 +00:00
displaySetBonuses ( "set-info" , build ) ;
2022-06-20 14:37:14 +00:00
// TODO: move weapon out?
2022-07-28 04:46:14 +00:00
// displayDefenseStats(document.getElementById("defensive-stats"), stats);
2022-06-19 20:59:16 +00:00
2022-08-15 02:55:31 +00:00
displayPoisonDamage ( document . getElementById ( "build-poison-stats" ) , stats ) ;
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' )
] ;
2022-07-24 19:35:06 +00:00
let skp _effects = [ "% damage" , "% crit" , "% cost red." , "% resist" , "% dodge" ] ;
2022-06-20 13:12:22 +00:00
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 = "" ;
2022-07-24 19:35:06 +00:00
let remainingSkp = make _elem ( "p" , [ 'scaled-font' , 'my-0' ] ) ;
let remainingSkpTitle = make _elem ( "b" , [ ] , { textContent : "Assigned " + total _assigned + " skillpoints. Remaining skillpoints: " } ) ;
2022-06-20 13:12:22 +00:00
let remainingSkpContent = document . createElement ( "b" ) ;
remainingSkpContent . textContent = "" + ( levelToSkillPoints ( build . level ) - total _assigned ) ;
remainingSkpContent . classList . add ( levelToSkillPoints ( build . level ) - total _assigned < 0 ? "negative" : "positive" ) ;
2022-07-24 19:35:06 +00:00
remainingSkp . append ( remainingSkpTitle ) ;
remainingSkp . append ( remainingSkpContent ) ;
2022-06-20 13:12:22 +00:00
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 {
2022-07-23 19:30:59 +00:00
constructor ( name ) { super ( name ) ; }
2022-06-20 13:12:22 +00:00
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 ( ) ) {
2022-07-08 05:28:46 +00:00
merge _stat ( output _stats , k2 , v2 ) ;
2022-06-22 05:39:10 +00:00
}
}
return output _stats ;
}
}
2022-09-18 23:14:38 +00:00
let radiance _affected = [ /*"hp"*/ , "fDef" , "wDef" , "aDef" , "tDef" , "eDef" , "hprPct" , "mr" , "sdPct" , "mdPct" , "ls" , "ms" , "xpb" , "lb" , "ref" ,
/*"str", "dex", "int", "agi", "def",*/
"thorns" , "expd" , "spd" , "atkTier" , "poison" , "hpBonus" , "spRegen" , "eSteal" , "hprRaw" , "sdRaw" , "mdRaw" , "fDamPct" , "wDamPct" , "aDamPct" , "tDamPct" , "eDamPct" , "fDefPct" , "wDefPct" , "aDefPct" , "tDefPct" , "eDefPct" , "fixID" , "category" , "spPct1" , "spRaw1" , "spPct2" , "spRaw2" , "spPct3" , "spRaw3" , "spPct4" , "spRaw4" , "rSdRaw" , "sprint" , "sprintReg" , "jh" , "lq" , "gXp" , "gSpd" ,
// wynn2 damages.
"eMdPct" , "eMdRaw" , "eSdPct" , "eSdRaw" , /*"eDamPct,"*/ "eDamRaw" , //"eDamAddMin","eDamAddMax",
"tMdPct" , "tMdRaw" , "tSdPct" , "tSdRaw" , /*"tDamPct,"*/ "tDamRaw" , //"tDamAddMin","tDamAddMax",
"wMdPct" , "wMdRaw" , "wSdPct" , "wSdRaw" , /*"wDamPct,"*/ "wDamRaw" , //"wDamAddMin","wDamAddMax",
"fMdPct" , "fMdRaw" , "fSdPct" , "fSdRaw" , /*"fDamPct,"*/ "fDamRaw" , //"fDamAddMin","fDamAddMax",
"aMdPct" , "aMdRaw" , "aSdPct" , "aSdRaw" , /*"aDamPct,"*/ "aDamRaw" , //"aDamAddMin","aDamAddMax",
"nMdPct" , "nMdRaw" , "nSdPct" , "nSdRaw" , "nDamPct" , "nDamRaw" , //"nDamAddMin","nDamAddMax", // neutral which is now an element
/*"mdPct","mdRaw","sdPct","sdRaw",*/ "damPct" , "damRaw" , //"damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct" , "rMdRaw" , "rSdPct" , /*"rSdRaw",*/ "rDamPct" , "rDamRaw" , //"rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
"critDamPct" ,
//"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final"
] ;
/ * *
* Scale stats if radiance is enabled .
2022-12-20 21:34:11 +00:00
* TODO : skillpoints ...
2022-09-18 23:14:38 +00:00
* /
const radiance _node = new ( class extends ComputeNode {
constructor ( ) { super ( 'radiance-node->:(' ) ; }
compute _func ( input _map ) {
const [ statmap ] = input _map . values ( ) ; // Extract values, pattern match it into size one list and bind to first element
let elem = document . getElementById ( 'radiance-boost' ) ;
if ( elem . classList . contains ( "toggleOn" ) ) {
const ret = new Map ( statmap ) ;
for ( const val of radiance _affected ) {
if ( reversedIDs . includes ( val ) ) {
if ( ( ret . get ( val ) || 0 ) < 0 ) {
ret . set ( val , Math . floor ( ( ret . get ( val ) || 0 ) * 1.2 ) ) ;
}
}
else {
if ( ( ret . get ( val ) || 0 ) > 0 ) {
ret . set ( val , Math . floor ( ( ret . get ( val ) || 0 ) * 1.2 ) ) ;
}
}
}
const dam _mults = new Map ( ret . get ( 'damMult' ) ) ;
dam _mults . set ( 'tome' , dam _mults . get ( 'tome' ) * 1.2 )
ret . set ( 'damMult' , dam _mults )
const def _mults = new Map ( ret . get ( 'defMult' ) ) ;
def _mults . set ( 'tome' , def _mults . get ( 'tome' ) * 1.2 )
ret . set ( 'defMult' , def _mults )
return ret ;
}
else {
return statmap ;
}
}
} ) ( ) ;
/ * U p d a t e s a l l s p e l l b o o s t s
* /
function update _radiance ( ) {
let elem = document . getElementById ( 'radiance-boost' ) ;
if ( elem . classList . contains ( "toggleOn" ) ) {
elem . classList . remove ( "toggleOn" ) ;
} else {
elem . classList . add ( "toggleOn" ) ;
}
radiance _node . mark _dirty ( ) . update ( ) ;
}
2022-06-22 05:39:10 +00:00
/ * *
* 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 ( ) {
2022-12-20 21:34:11 +00:00
edit _id _output . mark _dirty ( ) . update ( ) ;
2022-06-22 03:41:40 +00:00
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-12-20 21:34:11 +00:00
for ( const child of this . notify _nodes ) {
child . link _to ( this ) ;
2022-12-21 18:15:21 +00:00
child . fail _cb = true ;
2022-12-20 21:34:11 +00:00
}
2022-06-22 03:41:40 +00:00
}
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 ( ) {
// 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-12-20 21:34:11 +00:00
for ( const child of this . notify _nodes ) {
child . link _to ( this ) ;
2022-12-21 18:15:21 +00:00
child . fail _cb = true ;
2022-12-24 10:12:14 +00:00
// This is needed because initially there is a value mismatch possibly... due to setting skillpoints manually
2022-12-21 18:15:21 +00:00
child . mark _input _clean ( this . name , null ) ;
2022-12-20 21:34:11 +00:00
}
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 ] ;
}
}
}
/ * *
* 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-07-08 05:51:10 +00:00
if ( value === "" ) { value = "0" ; }
2022-06-20 13:12:22 +00:00
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
}
2023-01-03 01:21:45 +00:00
let item _final _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-07-27 06:18:55 +00:00
let equip _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-12-20 21:34:11 +00:00
/ * *
* Parameters :
* save _skp : bool True if skillpoints are modified away from skp engine defaults .
* /
function builder _graph _init ( save _skp ) {
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-07-27 06:18:55 +00:00
// Level input node.
let level _input = new InputNode ( 'level-input' , document . getElementById ( 'level-choice' ) ) ;
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
build _node = new BuildAssembleNode ( ) ;
build _node . link _to ( level _input ) ;
let build _encode _node = new BuildEncodeNode ( ) ;
build _encode _node . link _to ( build _node , 'build' ) ;
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 ) ;
2022-07-27 06:18:55 +00:00
equip _inputs . push ( item _input ) ;
if ( powder _inputs . includes ( eq + '-powder' ) ) { // TODO: fragile
const powder _name = eq + '-powder' ;
let powder _node = new PowderInputNode ( powder _name , document . getElementById ( powder _name ) )
. link _to ( item _input , 'item' ) ;
powder _nodes . push ( powder _node ) ;
build _encode _node . link _to ( powder _node , powder _name ) ;
let item _powdering = new ItemPowderingNode ( eq + '-powder-apply' )
. link _to ( powder _node , 'powdering' ) . link _to ( item _input , 'item' ) ;
item _input = item _powdering ;
}
2023-01-03 01:21:45 +00:00
item _final _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-07-27 06:18:55 +00:00
build _node . link _to ( item _input , eq ) ;
2022-05-22 10:21:34 +00:00
}
2022-07-27 06:18:55 +00:00
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 ) ;
2022-07-27 06:18:55 +00:00
equip _inputs . push ( item _input ) ;
2023-01-03 01:21:45 +00:00
item _final _nodes . push ( item _input ) ;
2022-06-23 09:23:56 +00:00
new ItemInputDisplayNode ( eq + '-input-display' , eq , item _image ) . link _to ( item _input ) ;
2022-07-27 06:18:55 +00:00
build _node . link _to ( item _input , eq ) ;
2022-06-23 09:23:56 +00:00
}
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" ) ;
2023-01-03 01:21:45 +00:00
new WeaponInputDisplayNode ( 'weapon-type' , weapon _image , weapon _dps ) . link _to ( item _final _nodes [ 8 ] ) ;
2022-07-29 05:48:01 +00:00
2022-07-11 02:56:15 +00:00
// linking to atree verification
2022-07-11 09:03:42 +00:00
atree _validate . link _to ( level _input , 'level' ) ;
2022-06-19 07:42:49 +00:00
2022-06-19 20:44:02 +00:00
let url _update _node = new URLUpdateNode ( ) ;
url _update _node . link _to ( build _encode _node , 'build-str' ) ;
2022-06-19 16:49:04 +00:00
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
// 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-07-23 19:30:59 +00:00
pre _scale _agg _node = new AggregateStatsNode ( 'pre-scale-stats' ) ;
stat _agg _node = new AggregateStatsNode ( 'final-stats' ) ;
2022-06-27 03:24:38 +00:00
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-12-20 21:34:11 +00:00
edit _agg _node . link _to ( edit _id _output , 'edit-id-setter' ) ;
2022-06-22 03:41:40 +00:00
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-07-23 19:30:59 +00:00
pre _scale _agg _node . link _to ( edit _agg _node ) ;
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-08-16 05:32:49 +00:00
// These two are defined in `builder/atree.js`
2022-07-01 05:22:15 +00:00
atree _node . link _to ( class _node , 'player-class' ) ;
2022-07-11 13:15:53 +00:00
atree _merge . link _to ( class _node , 'player-class' ) ;
2022-08-11 10:13:53 +00:00
pre _scale _agg _node . link _to ( atree _raw _stats , 'atree-raw-stats' ) ;
2022-09-18 23:14:38 +00:00
radiance _node . link _to ( pre _scale _agg _node , 'stats' ) ;
atree _scaling . link _to ( radiance _node , 'scale-stats' ) ;
stat _agg _node . link _to ( radiance _node , 'pre-scaling' ) ;
2022-08-11 10:13:53 +00:00
stat _agg _node . link _to ( atree _scaling _stats , 'atree-scaling' ) ;
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-07-27 06:18:55 +00:00
for ( const input _node of equip _inputs ) {
2022-06-20 13:12:22 +00:00
input _node . update ( ) ;
2022-06-20 03:20:22 +00:00
}
2022-07-07 02:44:51 +00:00
armor _powder _node . update ( ) ;
2022-06-20 13:12:22 +00:00
level _input . update ( ) ;
2022-06-20 03:20:22 +00:00
2022-07-13 06:16:13 +00:00
atree _graph _creator = new AbilityTreeEnsureNodesNode ( build _node , stat _agg _node )
. link _to ( atree _collect _spells , 'spells' ) ;
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 ) {
2022-12-16 10:48:00 +00:00
try {
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 ( ) ;
} catch ( e ) {
console . log ( "Failed to decode atree. This can happen when updating versions. Give up!" )
2022-06-30 15:03:41 +00:00
}
}
}
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-07-23 19:30:59 +00:00
pre _scale _agg _node . link _to ( powder _special _calc , 'powder-boost' ) ;
2022-07-24 04:22:28 +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.
2022-07-23 21:35:11 +00:00
stat _agg _node . link _to ( boosts _node , 'potion-boost' ) ;
2022-06-20 17:51:17 +00:00
2022-06-20 03:20:22 +00:00
// Also do something similar for skill points
2022-07-18 07:34:29 +00:00
let build _disp _node = new BuildDisplayNode ( )
build _disp _node . link _to ( build _node , 'build' ) ;
build _disp _node . link _to ( stat _agg _node , 'stats' ) ;
2022-06-20 13:12:22 +00:00
for ( const node of edit _input _nodes ) {
node . update ( ) ;
}
2022-07-18 07:34:29 +00:00
2022-12-20 21:34:11 +00:00
let skp _output = new SkillPointSetterNode ( skp _inputs ) ;
2022-06-20 13:12:22 +00:00
skp _output . link _to ( build _node ) ;
2022-12-20 21:34:11 +00:00
if ( ! save _skp ) {
skp _output . update ( ) . mark _dirty ( ) . update ( ) ;
}
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-07-18 07:34:29 +00:00
graph _live _update = true ;
2022-06-12 14:45:48 +00:00
}
2022-06-19 20:44:02 +00:00