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 }
}
}
return [ damage _boost , def _boost ] ;
}
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 ) {
let name = ( buttonId ) . split ( "-" ) [ 0 ] ;
let power = ( buttonId ) . split ( "-" ) [ 1 ] ; // [1, 5]
let elem = document . getElementById ( buttonId ) ;
if ( elem . classList . contains ( "toggleOn" ) ) { //toggle the pressed button off
elem . classList . remove ( "toggleOn" ) ;
} else {
for ( let i = 1 ; i < 6 ; i ++ ) { //toggle all pressed buttons of the same powder special off
//name is same, power is i
if ( document . getElementById ( name . replace ( " " , "_" ) + "-" + i ) . classList . contains ( "toggleOn" ) ) {
document . getElementById ( name . replace ( " " , "_" ) + "-" + i ) . classList . remove ( "toggleOn" ) ;
}
}
//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 {
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' ) ;
const weapon = input _map . get ( 'weapon' ) ;
displayPowderSpecials ( document . getElementById ( "powder-special-stats" ) , powder _specials , stats , weapon . statMap , true ) ;
}
}
/ * *
* Apply armor powders .
* Encoding shortcut assumes that all powders give + def to one element
* and - def to the element "behind" it in cycle ETWFA , which is true
* as of now and unlikely to change in the near future .
* /
function applyArmorPowders ( expandedItem , powders ) {
for ( const id of powders ) {
let powder = powderStats [ id ] ;
let name = powderNames . get ( id ) . charAt ( 0 ) ;
let prevName = skp _elements [ ( skp _elements . indexOf ( name ) + 4 ) % 5 ] ;
expandedItem . set ( name + "Def" , ( expandedItem . get ( name + "Def" ) || 0 ) + powder [ "defPlus" ] ) ;
expandedItem . set ( prevName + "Def" , ( expandedItem . get ( prevName + "Def" ) || 0 ) - powder [ "defMinus" ] ) ;
}
}
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 ) ;
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 ) {
item . statMap . set ( 'powders' , powdering ) ;
}
2022-06-19 20:44:02 +00:00
let type _match ;
if ( this . none _item . statMap . get ( 'category' ) === 'weapon' ) {
type _match = item . statMap . get ( 'category' ) === 'weapon' ;
} else {
type _match = item . statMap . get ( 'type' ) === this . none _item . statMap . get ( 'type' ) ;
}
2022-06-20 17:51:17 +00:00
if ( type _match ) {
if ( item . statMap . get ( 'category' ) === 'armor' && powdering !== undefined ) {
applyArmorPowders ( item . statMap , powdering ) ;
}
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" ) ;
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 ;
}
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 {
constructor ( name , image _field ) {
super ( name ) ;
this . image = image _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-23 11:24:12 +00:00
//as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff.
if ( ! document . getElementById ( "toggle-atree" ) . classList . contains ( "toggleOn" ) ) {
toggle _tab ( 'atree-dropdown' ) ;
toggleButton ( 'toggle-atree' ) ;
}
//for some reason we have to cast to string
construct _AT ( document . getElementById ( "atree-ui" ) , atrees [ wep _to _class . get ( type ) ] ) ;
if ( document . getElementById ( "toggle-atree" ) . classList . contains ( "toggleOn" ) ) {
toggle _tab ( 'atree-dropdown' ) ;
toggleButton ( 'toggle-atree' ) ;
}
console . log ( document . getElementById ( "toggle-atree" ) . classList . contains ( "toggleOn" ) ) ;
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' ) ;
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-20 13:12:22 +00:00
return encodeBuild ( build , powders , skillpoints ) ;
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-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 {
constructor ( spell _num ) {
super ( "builder-spell" + spell _num + "-select" ) ;
this . spell _idx = spell _num ;
}
compute _func ( input _map ) {
const build = input _map . get ( 'build' ) ;
const i = this . spell _idx ;
let spell = spell _table [ build . weapon . statMap . get ( "type" ) ] [ i ] ;
let stats = build . statMap ;
let spell _parts ;
if ( spell . parts ) {
spell _parts = spell . parts ;
}
else {
spell _parts = spell . variants . DEFAULT ;
for ( const majorID of stats . get ( "activeMajorIDs" ) ) {
if ( majorID in spell . variants ) {
spell _parts = spell . variants [ majorID ] ;
break ;
}
}
}
return [ spell , spell _parts ] ;
}
}
2022-06-20 13:12:22 +00:00
/ *
* Get all defensive stats for this build .
* /
function getDefenseStats ( stats ) {
let defenseStats = [ ] ;
let def _pct = skillPointsToPercentage ( stats . get ( 'def' ) ) ;
let agi _pct = skillPointsToPercentage ( stats . get ( 'agi' ) ) ;
//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-22 05:39:10 +00:00
let defMult = ( 2 - stats . get ( "classDef" ) ) * ( 2 - stats . get ( "defMultiplier" ) ) ;
2022-06-20 17:51:17 +00:00
ehp [ 0 ] /= ( 1 - def _pct ) * ( 1 - agi _pct ) * defMult ;
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-19 18:02:28 +00:00
const weapon = new Map ( input _map . get ( 'weapon-input' ) . statMap ) ;
const spell _info = input _map . get ( 'spell-info' ) ;
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 = [ ]
for ( const part of spell _parts ) {
if ( part . type === "damage" ) {
let results = calculateSpellDamage ( stats , part . conversion ,
stats . get ( "sdRaw" ) + stats . get ( "rainbowRaw" ) , stats . get ( "sdPct" ) ,
2022-06-20 13:12:22 +00:00
part . multiplier / 100 , weapon , skillpoints , damage _mult ) ;
2022-06-19 18:02:28 +00:00
spell _results . push ( results ) ;
} else if ( part . type === "heal" ) {
// TODO: wynn2 formula
2022-06-20 13:12:22 +00:00
let heal _amount = ( part . strength * getDefenseStats ( stats ) [ 0 ] * Math . max ( 0.5 , Math . min ( 1.75 , 1 + 0.5 * stats . get ( "wDamPct" ) / 100 ) ) ) . toFixed ( 2 ) ;
2022-06-19 18:02:28 +00:00
spell _results . push ( heal _amount ) ;
} else if ( part . type === "total" ) {
// TODO: remove "total" type
spell _results . push ( null ) ;
}
}
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 ] ;
const spell _parts = spell _info [ 1 ] ;
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-20 14:37:14 +00:00
displaySpellDamage ( parent _elem , overallparent _elem , stats , spell , i + 1 , spell _parts , 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 ) {
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 ;
}
2022-06-20 17:51:17 +00:00
let damage _mult = stats . get ( "damageMultiplier" ) ;
2022-06-20 14:37:14 +00:00
if ( weapon _stats . get ( "type" ) === "relik" ) {
damage _mult = 0.99 ; // CURSE YOU WYNNCRAFT
//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.
}
// 0spellmult for melee damage.
let results = calculateSpellDamage ( stats , [ 100 , 0 , 0 , 0 , 0 , 0 ] , stats . get ( "mdRaw" ) , stats . get ( "mdPct" ) , 0 , weapon _stats , skillpoints , damage _mult ) ;
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 ]
setText ( skp _order [ i ] + "-skp-assign" , "Assign: " + assigned ) ;
setValue ( skp _order [ i ] + "-skp" , skillpoints [ i ] ) ;
let linebreak = document . createElement ( "br" ) ;
linebreak . classList . add ( "itemp" ) ;
setText ( skp _order [ i ] + "-skp-pct" , ( skillPointsToPercentage ( skillpoints [ i ] ) * 100 ) . toFixed ( 1 ) . concat ( skp _effects [ i ] ) ) ;
document . getElementById ( skp _order [ i ] + "-warnings" ) . textContent = ''
if ( assigned > 100 ) {
let skp _warning = document . createElement ( "p" ) ;
skp _warning . classList . add ( "warning" ) ; skp _warning . classList . add ( "small-text" ) ;
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' ) ;
const weapon = input _map . get ( 'weapon' ) ; input _map . delete ( 'weapon' ) ;
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-20 13:12:22 +00:00
output _stats . set ( 'classDef' , classDefenseMultipliers . get ( weapon . statMap . get ( "type" ) ) ) ;
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 ( ) ) {
setText ( elem + "-skp-base" , "Original: " + build . base _skillpoints [ idx ] ) ;
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 ) {
const value = this . input _field . value ;
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-19 18:02:28 +00:00
let spelldmg _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-05-22 10:21:34 +00:00
2022-06-19 20:44:02 +00:00
function builder _graph _init ( ) {
2022-06-20 03:20:22 +00:00
// Phase 1/2: Set up item input, propagate updates, etc.
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
console . log ( none _tomes ) ;
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-19 20:44:02 +00:00
new WeaponInputDisplayNode ( 'weapon-type' , weapon _image ) . 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-19 07:42:49 +00:00
let build _node = new BuildAssembleNode ( ) ;
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-20 03:20:22 +00:00
// Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage
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-20 13:12:22 +00:00
let stat _agg _node = new AggregateStatsNode ( ) ;
2022-06-22 05:39:10 +00:00
let edit _agg _node = new AggregateEditableIDNode ( ) ;
edit _agg _node . link _to ( build _node , 'build' ) . link _to ( item _nodes [ 8 ] , 'weapon' ) ;
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-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-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' )
. link _to ( stat _agg _node , 'stats' ) . link _to ( item _nodes [ 8 ] , 'weapon' ) ;
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-19 16:49:04 +00:00
for ( let i = 0 ; i < 4 ; ++ i ) {
2022-06-19 18:02:28 +00:00
let spell _node = new SpellSelectNode ( i ) ;
2022-06-19 16:49:04 +00:00
spell _node . link _to ( build _node , 'build' ) ;
2022-06-20 13:12:22 +00:00
// TODO: link and rewrite spell_node to the stat agg node
spell _node . link _to ( stat _agg _node , 'stats' )
2022-06-19 18:02:28 +00:00
let calc _node = new SpellDamageCalcNode ( i ) ;
2022-06-20 13:12:22 +00:00
calc _node . link _to ( item _nodes [ 8 ] , 'weapon-input' ) . link _to ( stat _agg _node , 'stats' )
2022-06-20 17:51:17 +00:00
. link _to ( spell _node , 'spell-info' ) ;
2022-06-19 18:02:28 +00:00
spelldmg _nodes . push ( calc _node ) ;
let display _node = new SpellDisplayNode ( i ) ;
2022-06-20 14:37:14 +00:00
display _node . link _to ( stat _agg _node , 'stats' ) ; // TODO: same here..
2022-06-19 18:02:28 +00:00
display _node . link _to ( spell _node , 'spell-info' ) ;
display _node . link _to ( calc _node , 'spell-damage' ) ;
2022-06-19 16:49:04 +00:00
}
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