From 1e863fd01560966d5ec714d076adc118b5f24651 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sat, 21 May 2022 22:38:43 -0700 Subject: [PATCH 001/155] Create common.js for more common stuff (Item class, expandItem func) --- js/common.js | 123 ++++++++++++++++++++++++++++++++++++++++ js/computation_graph.js | 48 ++++++++++++++-- js/display.js | 54 ------------------ js/display_constants.js | 95 ------------------------------- 4 files changed, 165 insertions(+), 155 deletions(-) create mode 100644 js/common.js diff --git a/js/common.js b/js/common.js new file mode 100644 index 0000000..079922c --- /dev/null +++ b/js/common.js @@ -0,0 +1,123 @@ +let nonRolledIDs = [ + "name", + "lore", + "displayName", + "tier", + "set", + "slots", + "type", + "material", + "drop", + "quest", + "restrict", + "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", + "atkSpd", + "hp", + "fDef", "wDef", "aDef", "tDef", "eDef", + "lvl", + "classReq", + "strReq", "dexReq", "intReq", "defReq", "agiReq", + "str", "dex", "int", "agi", "def", + "fixID", + "category", + "id", + "skillpoints", + "reqs", + "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", + "majorIds"]; +let rolledIDs = [ + "hprPct", + "mr", + "sdPct", + "mdPct", + "ls", + "ms", + "xpb", + "lb", + "ref", + "thorns", + "expd", + "spd", + "atkTier", + "poison", + "hpBonus", + "spRegen", + "eSteal", + "hprRaw", + "sdRaw", + "mdRaw", + "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", + "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", + "spPct1", "spRaw1", + "spPct2", "spRaw2", + "spPct3", "spRaw3", + "spPct4", "spRaw4", + "rainbowRaw", + "sprint", + "sprintReg", + "jh", + "lq", + "gXp", + "gSpd" +]; + +/** + * Take an item with id list and turn it into a set of minrolls and maxrolls. + */ +function expandItem(item) { + let minRolls = new Map(); + let maxRolls = new Map(); + let expandedItem = new Map(); + if (item.fixID) { //The item has fixed IDs. + expandedItem.set("fixID",true); + for (const id of rolledIDs) { //all rolled IDs are numerical + let val = (item[id] || 0); + minRolls.set(id,val); + maxRolls.set(id,val); + } + } else { //The item does not have fixed IDs. + for (const id of rolledIDs) { + let val = (item[id] || 0); + if (val > 0) { // positive rolled IDs + if (reversedIDs.includes(id)) { + maxRolls.set(id,idRound(val*0.3)); + minRolls.set(id,idRound(val*1.3)); + } else { + maxRolls.set(id,idRound(val*1.3)); + minRolls.set(id,idRound(val*0.3)); + } + } else if (val < 0) { //negative rolled IDs + if (reversedIDs.includes(id)) { + maxRolls.set(id,idRound(val*1.3)); + minRolls.set(id,idRound(val*0.7)); + } + else { + maxRolls.set(id,idRound(val*0.7)); + minRolls.set(id,idRound(val*1.3)); + } + } + else { // if val == 0 + // NOTE: DO NOT remove this case! idRound behavior does not round to 0! + maxRolls.set(id,0); + minRolls.set(id,0); + } + } + } + for (const id of nonRolledIDs) { + expandedItem.set(id,item[id]); + } + expandedItem.set("minRolls",minRolls); + expandedItem.set("maxRolls",maxRolls); + expandedItem.set("powders", powders); + return expandedItem; +} + +class Item { + constructor(item_obj) { + this.statMap = ; //can use the statMap as an expanded Item + this.atkSpd = attackSpeed; + this.hash = "CR-" + hash; + this.initCraftStats(); + this.statMap.set("hash", this.hash); + } +} diff --git a/js/computation_graph.js b/js/computation_graph.js index 4e193c6..975a22d 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -18,6 +18,7 @@ class ComputeNode { this.name = name; this.update_task = null; this.update_time = Date.now(); + this.fail_cb = false; // Set to true to force updates even if parent failed. } /*** @@ -33,9 +34,11 @@ class ComputeNode { for (const input of this.inputs) { value_map.set(input.name, input.get_value()); } - this.value = this.compute_func(); + this.value = this.compute_func(value_map); for (const child of this.children) { - child.update(); + if (this.value || child.fail_cb) { + child.update(); + } } } @@ -49,7 +52,7 @@ class ComputeNode { /*** * Abstract method for computing something. Return value is set into this.value */ - compute_func() { + compute_func(input_map) { throw "no compute func specified"; } @@ -94,10 +97,14 @@ class ItemStats extends ComputeNode { this.none_item = none_item; } - compute_func() { + compute_func(input_map) { // built on the assumption of no one will type in CI/CR letter by letter let item_text = this.input_field.value; + if (!item_text) { + return this.none_item; + } + let item; if (item_text.slice(0, 3) == "CI-") { @@ -113,10 +120,39 @@ class ItemStats extends ComputeNode { item = tomeMap.get(item_text); } - if (!item) { - return this.none_item; + if (!item || item.) { + return null; } return item; } } +/*** + * Node for updating item input fields from parsed items. + */ +class ItemInputDisplay extends ComputeNode { + + constructor(name, item_input_field, item_image) { + super(name); + this.input_field = item_input_field; + this.image = item_image; + this.fail_cb = true; + } + + compute_func(input_map) { + if (input_map.size !== 1) { + throw "ItemInputDisplay 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'); + + if (!item) { + this.input_field.classList.add("is-invalid"); + return null; + } + } +} diff --git a/js/display.js b/js/display.js index fcf9f5e..707efe3 100644 --- a/js/display.js +++ b/js/display.js @@ -27,60 +27,6 @@ function applyArmorPowdersOnce(expandedItem, powders) { } } -/** - * Take an item with id list and turn it into a set of minrolls and maxrolls. - * Also applies powders to armor. - */ -function expandItem(item, powders) { - let minRolls = new Map(); - let maxRolls = new Map(); - let expandedItem = new Map(); - if (item.fixID) { //The item has fixed IDs. - expandedItem.set("fixID",true); - for (const id of rolledIDs) { //all rolled IDs are numerical - let val = (item[id] || 0); - minRolls.set(id,val); - maxRolls.set(id,val); - } - } else { //The item does not have fixed IDs. - for (const id of rolledIDs) { - let val = (item[id] || 0); - if (val > 0) { // positive rolled IDs - if (reversedIDs.includes(id)) { - maxRolls.set(id,idRound(val*0.3)); - minRolls.set(id,idRound(val*1.3)); - } else { - maxRolls.set(id,idRound(val*1.3)); - minRolls.set(id,idRound(val*0.3)); - } - } else if (val < 0) { //negative rolled IDs - if (reversedIDs.includes(id)) { - maxRolls.set(id,idRound(val*1.3)); - minRolls.set(id,idRound(val*0.7)); - } - else { - maxRolls.set(id,idRound(val*0.7)); - minRolls.set(id,idRound(val*1.3)); - } - } - else { // if val == 0 - // NOTE: DO NOT remove this case! idRound behavior does not round to 0! - maxRolls.set(id,0); - minRolls.set(id,0); - } - } - } - for (const id of nonRolledIDs) { - expandedItem.set(id,item[id]); - } - expandedItem.set("minRolls",minRolls); - expandedItem.set("maxRolls",maxRolls); - expandedItem.set("powders", powders); - if (item.category === "armor") { - applyArmorPowders(expandedItem, powders); - } - return expandedItem; -} /* Takes in an ingredient object and returns an equivalent Map(). */ diff --git a/js/display_constants.js b/js/display_constants.js index f686899..d8c26dd 100644 --- a/js/display_constants.js +++ b/js/display_constants.js @@ -1,98 +1,3 @@ -let nonRolledIDs = [ - "name", - "lore", - "displayName", - "tier", - "set", - "slots", - "type", - "material", - "drop", - "quest", - "restrict", - "nDam", - "fDam", - "wDam", - "aDam", - "tDam", - "eDam", - "atkSpd", - "hp", - "fDef", - "wDef", - "aDef", - "tDef", - "eDef", - "lvl", - "classReq", - "strReq", - "dexReq", - "intReq", - "defReq", - "agiReq","str", - "dex", - "int", - "agi", - "def", - "fixID", - "category", - "id", - "skillpoints", - "reqs", - "nDam_", - "fDam_", - "wDam_", - "aDam_", - "tDam_", - "eDam_", - "majorIds"]; -let rolledIDs = [ - "hprPct", - "mr", - "sdPct", - "mdPct", - "ls", - "ms", - "xpb", - "lb", - "ref", - "thorns", - "expd", - "spd", - "atkTier", - "poison", - "hpBonus", - "spRegen", - "eSteal", - "hprRaw", - "sdRaw", - "mdRaw", - "fDamPct", - "wDamPct", - "aDamPct", - "tDamPct", - "eDamPct", - "fDefPct", - "wDefPct", - "aDefPct", - "tDefPct", - "eDefPct", - "spPct1", - "spRaw1", - "spPct2", - "spRaw2", - "spPct3", - "spRaw3", - "spPct4", - "spRaw4", - "rainbowRaw", - "sprint", - "sprintReg", - "jh", - "lq", - "gXp", - "gSpd" -]; let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; let colorMap = new Map( [ From 02341012628b52d46ab7b27f0c394368dcc6973a Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 22 May 2022 00:14:20 -0700 Subject: [PATCH 002/155] Remove common (we already have build_utils), edit instances of expandItems --- js/build_utils.js | 119 ++++++++++++++++++++++++++++++++++++++ js/common.js | 123 ---------------------------------------- js/computation_graph.js | 56 ++++++++++++------ js/customizer.js | 2 +- js/items_2.js | 2 +- js/sq2items.js | 2 +- 6 files changed, 160 insertions(+), 144 deletions(-) delete mode 100644 js/common.js diff --git a/js/build_utils.js b/js/build_utils.js index 16bacb5..e60474b 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -76,3 +76,122 @@ for (const [k, v] of translations) { reversetranslations.set(v, k); } +let nonRolledIDs = [ + "name", + "lore", + "displayName", + "tier", + "set", + "slots", + "type", + "material", + "drop", + "quest", + "restrict", + "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", + "atkSpd", + "hp", + "fDef", "wDef", "aDef", "tDef", "eDef", + "lvl", + "classReq", + "strReq", "dexReq", "intReq", "defReq", "agiReq", + "str", "dex", "int", "agi", "def", + "fixID", + "category", + "id", + "skillpoints", + "reqs", + "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", + "majorIds"]; +let rolledIDs = [ + "hprPct", + "mr", + "sdPct", + "mdPct", + "ls", + "ms", + "xpb", + "lb", + "ref", + "thorns", + "expd", + "spd", + "atkTier", + "poison", + "hpBonus", + "spRegen", + "eSteal", + "hprRaw", + "sdRaw", + "mdRaw", + "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", + "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", + "spPct1", "spRaw1", + "spPct2", "spRaw2", + "spPct3", "spRaw3", + "spPct4", "spRaw4", + "rainbowRaw", + "sprint", + "sprintReg", + "jh", + "lq", + "gXp", + "gSpd" +]; + +/** + * Take an item with id list and turn it into a set of minrolls and maxrolls. + */ +function expandItem(item) { + let minRolls = new Map(); + let maxRolls = new Map(); + let expandedItem = new Map(); + if (item.fixID) { //The item has fixed IDs. + expandedItem.set("fixID",true); + for (const id of rolledIDs) { //all rolled IDs are numerical + let val = (item[id] || 0); + minRolls.set(id,val); + maxRolls.set(id,val); + } + } else { //The item does not have fixed IDs. + for (const id of rolledIDs) { + let val = (item[id] || 0); + if (val > 0) { // positive rolled IDs + if (reversedIDs.includes(id)) { + maxRolls.set(id,idRound(val*0.3)); + minRolls.set(id,idRound(val*1.3)); + } else { + maxRolls.set(id,idRound(val*1.3)); + minRolls.set(id,idRound(val*0.3)); + } + } else if (val < 0) { //negative rolled IDs + if (reversedIDs.includes(id)) { + maxRolls.set(id,idRound(val*1.3)); + minRolls.set(id,idRound(val*0.7)); + } + else { + maxRolls.set(id,idRound(val*0.7)); + minRolls.set(id,idRound(val*1.3)); + } + } + else { // if val == 0 + // NOTE: DO NOT remove this case! idRound behavior does not round to 0! + maxRolls.set(id,0); + minRolls.set(id,0); + } + } + } + for (const id of nonRolledIDs) { + expandedItem.set(id,item[id]); + } + expandedItem.set("minRolls",minRolls); + expandedItem.set("maxRolls",maxRolls); + expandedItem.set("powders", powders); + return expandedItem; +} + +class Item { + constructor(item_obj) { + this.statMap = expandItem(item_obj); + } +} diff --git a/js/common.js b/js/common.js deleted file mode 100644 index 079922c..0000000 --- a/js/common.js +++ /dev/null @@ -1,123 +0,0 @@ -let nonRolledIDs = [ - "name", - "lore", - "displayName", - "tier", - "set", - "slots", - "type", - "material", - "drop", - "quest", - "restrict", - "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", - "atkSpd", - "hp", - "fDef", "wDef", "aDef", "tDef", "eDef", - "lvl", - "classReq", - "strReq", "dexReq", "intReq", "defReq", "agiReq", - "str", "dex", "int", "agi", "def", - "fixID", - "category", - "id", - "skillpoints", - "reqs", - "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", - "majorIds"]; -let rolledIDs = [ - "hprPct", - "mr", - "sdPct", - "mdPct", - "ls", - "ms", - "xpb", - "lb", - "ref", - "thorns", - "expd", - "spd", - "atkTier", - "poison", - "hpBonus", - "spRegen", - "eSteal", - "hprRaw", - "sdRaw", - "mdRaw", - "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", - "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", - "spPct1", "spRaw1", - "spPct2", "spRaw2", - "spPct3", "spRaw3", - "spPct4", "spRaw4", - "rainbowRaw", - "sprint", - "sprintReg", - "jh", - "lq", - "gXp", - "gSpd" -]; - -/** - * Take an item with id list and turn it into a set of minrolls and maxrolls. - */ -function expandItem(item) { - let minRolls = new Map(); - let maxRolls = new Map(); - let expandedItem = new Map(); - if (item.fixID) { //The item has fixed IDs. - expandedItem.set("fixID",true); - for (const id of rolledIDs) { //all rolled IDs are numerical - let val = (item[id] || 0); - minRolls.set(id,val); - maxRolls.set(id,val); - } - } else { //The item does not have fixed IDs. - for (const id of rolledIDs) { - let val = (item[id] || 0); - if (val > 0) { // positive rolled IDs - if (reversedIDs.includes(id)) { - maxRolls.set(id,idRound(val*0.3)); - minRolls.set(id,idRound(val*1.3)); - } else { - maxRolls.set(id,idRound(val*1.3)); - minRolls.set(id,idRound(val*0.3)); - } - } else if (val < 0) { //negative rolled IDs - if (reversedIDs.includes(id)) { - maxRolls.set(id,idRound(val*1.3)); - minRolls.set(id,idRound(val*0.7)); - } - else { - maxRolls.set(id,idRound(val*0.7)); - minRolls.set(id,idRound(val*1.3)); - } - } - else { // if val == 0 - // NOTE: DO NOT remove this case! idRound behavior does not round to 0! - maxRolls.set(id,0); - minRolls.set(id,0); - } - } - } - for (const id of nonRolledIDs) { - expandedItem.set(id,item[id]); - } - expandedItem.set("minRolls",minRolls); - expandedItem.set("maxRolls",maxRolls); - expandedItem.set("powders", powders); - return expandedItem; -} - -class Item { - constructor(item_obj) { - this.statMap = ; //can use the statMap as an expanded Item - this.atkSpd = attackSpeed; - this.hash = "CR-" + hash; - this.initCraftStats(); - this.statMap.set("hash", this.hash); - } -} diff --git a/js/computation_graph.js b/js/computation_graph.js index 975a22d..cdd1212 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -1,7 +1,7 @@ let _ALL_NODES = new Map(); class ComputeNode { - /*** + /** * Make a generic compute node. * Adds the node to the global map of nodenames to nodes (for calling from html listeners). * @@ -21,7 +21,7 @@ class ComputeNode { this.fail_cb = false; // Set to true to force updates even if parent failed. } - /*** + /** * Request update of this compute node. Pushes updates to children. */ update(timestamp) { @@ -42,14 +42,14 @@ class ComputeNode { } } - /*** + /** * Get value of this compute node. Can't trigger update cascades (push based update, not pull based.) */ get_value() { return this.value } - /*** + /** * Abstract method for computing something. Return value is set into this.value */ compute_func(input_map) { @@ -62,7 +62,7 @@ class ComputeNode { } } -/*** +/** * Schedule a ComputeNode to be updated. * * @param node_name : ComputeNode name to schedule an update for. @@ -79,11 +79,11 @@ function calcSchedule(node_name) { }, 500); } -/*** +/** * Node for getting an item's stats from an item input field. */ -class ItemStats extends ComputeNode { - /*** +class ItemInputNode extends ComputeNode { + /** * Make an item stat pulling compute node. * * @param name: Name of this node. @@ -94,7 +94,7 @@ class ItemStats extends ComputeNode { super(name); this.input_field.setAttribute("onInput", "calcSchedule('"+name+"');"); this.input_field = item_input_field; - this.none_item = none_item; + this.none_item = expandItem(none_item); } compute_func(input_map) { @@ -114,23 +114,23 @@ class ItemStats extends ComputeNode { item = getCraftFromHash(item_text); } else if (itemMap.has(item_text)) { - item = itemMap.get(item_text); + item = Item(itemMap.get(item_text)); } else if (tomeMap.has(item_text)) { - item = tomeMap.get(item_text); + item = Item(tomeMap.get(item_text)); } - if (!item || item.) { + if (!item || item.statMap.get('type') !== this.none_item.statMap.get('type')) { return null; } return item; } } -/*** +/** * Node for updating item input fields from parsed items. */ -class ItemInputDisplay extends ComputeNode { +class ItemInputDisplayNode extends ComputeNode { constructor(name, item_input_field, item_image) { super(name); @@ -140,10 +140,7 @@ class ItemInputDisplay extends ComputeNode { } compute_func(input_map) { - if (input_map.size !== 1) { - throw "ItemInputDisplay accepts exactly one input (item)"; - } - + 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'); @@ -154,5 +151,28 @@ class ItemInputDisplay extends ComputeNode { this.input_field.classList.add("is-invalid"); return null; } + + const tier = item.statMap.get('tier'); + this.input_field.classList.add(tier); + this.image.classList.add(tier + "-shadow"); + } +} + +/** + * Change the weapon to match correct type. + */ +class WeaponDisplayNode extends ComputeNode { + + constructor(name, image_field) { + super(name); + this.image = image_field; + } + + 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_field.setAttribute('src', '../media/items/new/generic-'+type+'.png'); } } diff --git a/js/customizer.js b/js/customizer.js index 84c665e..85ffbb0 100644 --- a/js/customizer.js +++ b/js/customizer.js @@ -369,7 +369,7 @@ function useBaseItem(elem) { //Check items db. for (const [name,itemObj] of itemMap) { if (itemName === name) { - baseItem = expandItem(itemObj, []); + baseItem = expandItem(itemObj); break; } } diff --git a/js/items_2.js b/js/items_2.js index a670ee3..220b447 100644 --- a/js/items_2.js +++ b/js/items_2.js @@ -239,7 +239,7 @@ function init_items2() { const itemListFooter = document.getElementById('item-list-footer'); // compile the search db from the item db - const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i, [])]); + const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i)]); // init item list elements const ITEM_LIST_SIZE = 64; diff --git a/js/sq2items.js b/js/sq2items.js index 59f16ba..8531721 100644 --- a/js/sq2items.js +++ b/js/sq2items.js @@ -250,7 +250,7 @@ function resetItemSearch() { } function init_items() { - items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i, []) ); + items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i) ); } load_init(init_items); From 06f745c158b8da65c0201bba1f85b3a6cbad47b5 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 22 May 2022 00:57:47 -0700 Subject: [PATCH 003/155] Consolidate constants/utils files, start working on new builder file --- builder/index.html | 4 +- js/build_constants.js | 106 +++++++ js/build_utils.js | 3 +- js/builder.js | 607 +++++++++++++++++++--------------------- js/computation_graph.js | 13 +- js/display_constants.js | 1 - js/sq2bs.js | 14 +- js/sq2builder.js | 21 +- 8 files changed, 405 insertions(+), 364 deletions(-) create mode 100644 js/build_constants.js diff --git a/builder/index.html b/builder/index.html index c49ccb4..7a89a26 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1379,6 +1379,7 @@ + @@ -1399,7 +1400,8 @@ - + + diff --git a/js/build_constants.js b/js/build_constants.js new file mode 100644 index 0000000..fbcbb3f --- /dev/null +++ b/js/build_constants.js @@ -0,0 +1,106 @@ +/** + * I kinda lied. Theres some listener stuff in here + * but its mostly constants for builder page specifically. + */ + +const url_tag = location.hash.slice(1); + +const BUILD_VERSION = "7.0.19"; + +let player_build; + + +// THIS IS SUPER DANGEROUS, WE SHOULD NOT BE KEEPING THIS IN SO MANY PLACES +let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison", + "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", + "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", + "hprRaw", "hprPct", "hpBonus", "atkTier", + "spPct1", "spRaw1", "spPct2", "spRaw2", + "spPct3", "spRaw3", "spPct4", "spRaw4" ]; + +let editable_elems = []; + +for (let i of editable_item_fields) { + let elem = document.getElementById(i); + elem.addEventListener("change", (event) => { + elem.classList.add("highlight"); + }); + editable_elems.push(elem); +} + +for (let i of skp_order) { + let elem = document.getElementById(i+"-skp"); + elem.addEventListener("change", (event) => { + elem.classList.add("highlight"); + }); + editable_elems.push(elem); +} + +function clear_highlights() { + for (let i of editable_elems) { + i.classList.remove("highlight"); + } +} + + +let equipment_fields = [ + "helmet", + "chestplate", + "leggings", + "boots", + "ring1", + "ring2", + "bracelet", + "necklace", + "weapon" +]; +let tome_fields = [ + "weaponTome1", + "weaponTome2", + "armorTome1", + "armorTome2", + "armorTome3", + "armorTome4", + "guildTome1", +] +let equipment_names = [ + "Helmet", + "Chestplate", + "Leggings", + "Boots", + "Ring 1", + "Ring 2", + "Bracelet", + "Necklace", + "Weapon" +]; + +let tome_names = [ + "Weapon Tome", + "Weapon Tome", + "Armor Tome", + "Armor Tome", + "Armor Tome", + "Armor Tome", + "Guild Tome", +] +let equipmentInputs = equipment_fields.map(x => x + "-choice"); +let buildFields = equipment_fields.map(x => x+"-tooltip").concat(tome_fields.map(x => x + "-tooltip")); +let tomeInputs = tome_fields.map(x => x + "-choice"); + +let powderInputs = [ + "helmet-powder", + "chestplate-powder", + "leggings-powder", + "boots-powder", + "weapon-powder", +]; + +let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear']; +let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots']; +let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace']; +let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon']; +let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'].concat(tome_keys); + +let spell_disp = ['spell0-info', 'spell1-info', 'spell2-info', 'spell3-info']; +let other_disp = ['build-order', 'set-info', 'int-info']; diff --git a/js/build_utils.js b/js/build_utils.js index e60474b..9381035 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -138,6 +138,7 @@ let rolledIDs = [ "gXp", "gSpd" ]; +let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; /** * Take an item with id list and turn it into a set of minrolls and maxrolls. @@ -186,7 +187,7 @@ function expandItem(item) { } expandedItem.set("minRolls",minRolls); expandedItem.set("maxRolls",maxRolls); - expandedItem.set("powders", powders); + expandedItem.set("powders", []); return expandedItem; } diff --git a/js/builder.js b/js/builder.js index a91ef19..3455ee2 100644 --- a/js/builder.js +++ b/js/builder.js @@ -1,182 +1,3 @@ -const url_tag = location.hash.slice(1); -// console.log(url_base); -// console.log(url_tag); - - -const BUILD_VERSION = "7.0.19"; - -function setTitle() { - let text; - if (url_base.includes("hppeng-wynn")) { - text = "WynnBuilder UNSTABLE version "+BUILD_VERSION+" (db version "+DB_VERSION+")"; - } - else { - text = "WynnBuilder version "+BUILD_VERSION+" (db version "+DB_VERSION+")"; - document.getElementById("header").classList.add("funnynumber"); - } - document.getElementById("header").textContent = text; -} - -setTitle(); - -let player_build; - -let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "hprRaw", "hprPct", "hpBonus", "atkTier", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; -let editable_elems = []; - -for (let i of editable_item_fields) { - let elem = document.getElementById(i); - elem.addEventListener("change", (event) => { - elem.classList.add("highlight"); - }); - editable_elems.push(elem); -} - -for (let i of skp_order) { - let elem = document.getElementById(i+"-skp"); - elem.addEventListener("change", (event) => { - elem.classList.add("highlight"); - }); - editable_elems.push(elem); -} - -function clear_highlights() { - for (let i of editable_elems) { - i.classList.remove("highlight"); - } -} - - -let equipment_fields = [ - "helmet", - "chestplate", - "leggings", - "boots", - "ring1", - "ring2", - "bracelet", - "necklace", - "weapon" -]; -let equipment_names = [ - "Helmet", - "Chestplate", - "Leggings", - "Boots", - "Ring 1", - "Ring 2", - "Bracelet", - "Necklace", - "Weapon" -]; -let equipmentInputs = equipment_fields.map(x => x + "-choice"); -let buildFields = equipment_fields.map(x => "build-"+x); - -let powderInputs = [ - "helmet-powder", - "chestplate-powder", - "leggings-powder", - "boots-powder", - "weapon-powder", -]; - - - -/* - * Function that takes an item list and populates its corresponding dropdown. - * Used for armors and bracelet/necklace. - */ -function populateItemList(type) { - let item_list = document.getElementById(type+"-items"); - for (const item of itemLists.get(type)) { - let item_obj = itemMap.get(item); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - let el = document.createElement("option"); - el.value = item; - item_list.appendChild(el); - } -} - -/* - * Populate dropdowns, add listeners, etc. - */ -function init() { - console.log("builder.js init"); - - for (const armorType of armorTypes) { - populateItemList(armorType); - // Add change listener to update armor slots. - document.getElementById(armorType+"-choice").addEventListener("change", (event) => { - let item_name = event.target.value; - let nSlots = undefined; - if (itemMap.has(item_name)) { - let item = itemMap.get(item_name); - nSlots = item["slots"]; - //console.log(item); - } - else { - let crafted_custom_item = getCraftFromHash(item_name) !== undefined ? getCraftFromHash(item_name) : (getCustomFromHash(item_name) !== undefined ? getCustomFromHash(item_name) : undefined); - if (crafted_custom_item !== undefined) { - nSlots = crafted_custom_item.statMap.get("slots"); - } - } - if (nSlots !== undefined) { - document.getElementById(armorType+"-slots").textContent = nSlots + " slots"; - } - else { - document.getElementById(armorType+"-slots").textContent = "X slots"; - } - }); - } - - let ring1_list = document.getElementById("ring1-items"); - let ring2_list = document.getElementById("ring2-items"); - for (const ring of itemLists.get("ring")) { - let item_obj = itemMap.get(ring); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - let el1 = document.createElement("option"); - let el2 = document.createElement("option"); - el1.value = ring; - el2.value = ring; - ring1_list.appendChild(el1); - ring2_list.appendChild(el2); - } - - populateItemList("bracelet"); - populateItemList("necklace"); - - let weapon_list = document.getElementById("weapon-items"); - for (const weaponType of weaponTypes) { - for (const weapon of itemLists.get(weaponType)) { - let item_obj = itemMap.get(weapon); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - let el = document.createElement("option"); - el.value = weapon; - weapon_list.appendChild(el); - } - } - - // Add change listener to update weapon slots. - document.getElementById("weapon-choice").addEventListener("change", (event) => { - let item_name = event.target.value; - let item = itemMap.has(item_name) ? itemMap.get(item_name) : (getCraftFromHash(item_name) ? getCraftFromHash(item_name) : (getCustomFromHash(item_name) ? getCustomFromHash(item_name) : undefined)); - if (item !== undefined && event.target.value !== "") { - document.getElementById("weapon-slots").textContent = (item["slots"] ? item["slots"] : (item.statMap !== undefined ? ( item.statMap.has("slots") ? item.statMap.get("slots") : 0): 0) )+ " slots"; - } else { - document.getElementById("weapon-slots").textContent = "X slots"; - } - }); - - decodeBuild(url_tag); - - populateBuildList(); -} function getItemNameFromID(id) { if (redirectMap.has(id)) { @@ -185,13 +6,20 @@ function getItemNameFromID(id) { return idMap.get(id); } +function getTomeNameFromID(id) { + if (tomeRedirectMap.has(id)) { + return getTomeNameFromID(tomeRedirectMap.get(id)); + } + return tomeIDMap.get(id); +} + function parsePowdering(powder_info) { // TODO: Make this run in linear instead of quadratic time... ew let powdering = []; for (let i = 0; i < 5; ++i) { let powders = ""; let n_blocks = Base64.toInt(powder_info.charAt(0)); - console.log(n_blocks + " blocks"); + // console.log(n_blocks + " blocks"); powder_info = powder_info.slice(1); for (let j = 0; j < n_blocks; ++j) { let block = powder_info.slice(0,5); @@ -205,7 +33,7 @@ function parsePowdering(powder_info) { } powdering[i] = powders; } - return powdering; + return [powdering, powder_info]; } /* @@ -213,14 +41,20 @@ function parsePowdering(powder_info) { */ function decodeBuild(url_tag) { if (url_tag) { + //default values let equipment = [null, null, null, null, null, null, null, null, null]; + let tomes = [null, null, null, null, null, null, null]; let powdering = ["", "", "", "", ""]; let info = url_tag.split("_"); let version = info[0]; let save_skp = false; let skillpoints = [0, 0, 0, 0, 0]; let level = 106; - if (version === "0" || version === "1" || version === "2" || version === "3") { + + version_number = parseInt(version) + //equipment (items) + // TODO: use filters + if (version_number < 4) { let equipments = info[1]; for (let i = 0; i < 9; ++i ) { let equipment_str = equipments.slice(i*3,i*3+3); @@ -228,7 +62,7 @@ function decodeBuild(url_tag) { } info[1] = equipments.slice(27); } - if (version === "4") { + else if (version_number == 4) { let info_str = info[1]; let start_idx = 0; for (let i = 0; i < 9; ++i ) { @@ -244,7 +78,7 @@ function decodeBuild(url_tag) { } info[1] = info_str.slice(start_idx); } - if (version === "5") { + else if (version_number <= 6) { let info_str = info[1]; let start_idx = 0; for (let i = 0; i < 9; ++i ) { @@ -263,10 +97,17 @@ function decodeBuild(url_tag) { } info[1] = info_str.slice(start_idx); } - if (version === "1") { + //constant in all versions + for (let i in equipment) { + setValue(equipmentInputs[i], equipment[i]); + } + + //level, skill point assignments, and powdering + if (version_number == 1) { let powder_info = info[1]; - powdering = parsePowdering(powder_info); - } else if (version === "2") { + let res = parsePowdering(powder_info); + powdering = res[0]; + } else if (version_number == 2) { save_skp = true; let skillpoint_info = info[1].slice(0, 10); for (let i = 0; i < 5; ++i ) { @@ -274,8 +115,9 @@ function decodeBuild(url_tag) { } let powder_info = info[1].slice(10); - powdering = parsePowdering(powder_info); - } else if (version === "3" || version === "4" || version === "5"){ + let res = parsePowdering(powder_info); + powdering = res[0]; + } else if (version_number <= 6){ level = Base64.toInt(info[1].slice(10,12)); setValue("level-choice",level); save_skp = true; @@ -286,126 +128,101 @@ function decodeBuild(url_tag) { let powder_info = info[1].slice(12); - powdering = parsePowdering(powder_info); + let res = parsePowdering(powder_info); + powdering = res[0]; + info[1] = res[1]; + } + // Tomes. + if (version == 6) { + //tome values do not appear in anything before v6. + for (let i = 0; i < 7; ++i) { + let tome_str = info[1].charAt(i); + for (let i in tomes) { + setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str))); + } + } + info[1] = info[1].slice(7); } for (let i in powderInputs) { setValue(powderInputs[i], powdering[i]); } - for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); - } + calculateBuild(save_skp, skillpoints); } } -/* Stores the entire build in a string using B64 encryption and adds it to the URL. +/* Stores the entire build in a string using B64 encoding and adds it to the URL. */ function encodeBuild() { if (player_build) { let build_string; - if (player_build.customItems.length > 0) { //v5 encoding - build_string = "5_"; - let crafted_idx = 0; - let custom_idx = 0; - for (const item of player_build.items) { - - if (item.get("custom")) { - let custom = "CI-"+encodeCustom(player_build.customItems[custom_idx],true); - build_string += Base64.fromIntN(custom.length, 3) + custom; - custom_idx += 1; - } else if (item.get("crafted")) { - build_string += "CR-"+encodeCraft(player_build.craftedItems[crafted_idx]); - crafted_idx += 1; - } else { - build_string += Base64.fromIntN(item.get("id"), 3); - } - } - - for (const skp of skp_order) { - build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 - } - build_string += Base64.fromIntN(player_build.level, 2); - for (const _powderset of player_build.powders) { - let n_bits = Math.ceil(_powderset.length / 6); - build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. - // Slice copy. - let powderset = _powderset.slice(); - while (powderset.length != 0) { - let firstSix = powderset.slice(0,6).reverse(); - let powder_hash = 0; - for (const powder of firstSix) { - powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first. - } - build_string += Base64.fromIntN(powder_hash, 5); - powderset = powderset.slice(6); - } - } - } else { //v4 encoding - build_string = "4_"; - let crafted_idx = 0; - for (const item of player_build.items) { - if (item.get("crafted")) { - build_string += "-"+encodeCraft(player_build.craftedItems[crafted_idx]); - crafted_idx += 1; - } else { - build_string += Base64.fromIntN(item.get("id"), 3); - } - } - - for (const skp of skp_order) { - build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 - } - build_string += Base64.fromIntN(player_build.level, 2); - for (const _powderset of player_build.powders) { - let n_bits = Math.ceil(_powderset.length / 6); - build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. - // Slice copy. - let powderset = _powderset.slice(); - while (powderset.length != 0) { - let firstSix = powderset.slice(0,6).reverse(); - let powder_hash = 0; - for (const powder of firstSix) { - powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first. - } - build_string += Base64.fromIntN(powder_hash, 5); - powderset = powderset.slice(6); + + //V6 encoding - Tomes + build_version = 4; + build_string = ""; + tome_string = ""; + + let crafted_idx = 0; + let custom_idx = 0; + for (const item of player_build.items) { + + if (item.get("custom")) { + let custom = "CI-"+encodeCustom(player_build.customItems[custom_idx],true); + build_string += Base64.fromIntN(custom.length, 3) + custom; + custom_idx += 1; + build_version = Math.max(build_version, 5); + } else if (item.get("crafted")) { + build_string += "CR-"+encodeCraft(player_build.craftedItems[crafted_idx]); + crafted_idx += 1; + } else if (item.get("category") === "tome") { + let tome_id = item.get("id"); + if (tome_id <= 60) { + // valid normal tome. ID 61-63 is for NONE tomes. + build_version = Math.max(build_version, 6); } + tome_string += Base64.fromIntN(tome_id, 1); + } else { + build_string += Base64.fromIntN(item.get("id"), 3); } } - return build_string; + + for (const skp of skp_order) { + build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 + } + build_string += Base64.fromIntN(player_build.level, 2); + for (const _powderset of player_build.powders) { + let n_bits = Math.ceil(_powderset.length / 6); + build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. + // Slice copy. + let powderset = _powderset.slice(); + while (powderset.length != 0) { + let firstSix = powderset.slice(0,6).reverse(); + let powder_hash = 0; + for (const powder of firstSix) { + powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first. + } + build_string += Base64.fromIntN(powder_hash, 5); + powderset = powderset.slice(6); + } + } + build_string += tome_string; + + return build_version.toString() + "_" + build_string; } - // this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ]; - // let build_string = "3_" + Base64.fromIntN(player_build.helmet.get("id"), 3) + - // Base64.fromIntN(player_build.chestplate.get("id"), 3) + - // Base64.fromIntN(player_build.leggings.get("id"), 3) + - // Base64.fromIntN(player_build.boots.get("id"), 3) + - // Base64.fromIntN(player_build.ring1.get("id"), 3) + - // Base64.fromIntN(player_build.ring2.get("id"), 3) + - // Base64.fromIntN(player_build.bracelet.get("id"), 3) + - // Base64.fromIntN(player_build.necklace.get("id"), 3) + - // Base64.fromIntN(player_build.weapon.get("id"), 3); - return ""; } function calculateBuild(save_skp, skp){ try { - let specialNames = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"]; - for (const sName of specialNames) { - for (let i = 1; i < 6; i++) { - let elem = document.getElementById(sName + "-" + i); - let name = sName.replace("_", " "); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - } - } - } - if(player_build){ + resetEditableIDs(); + if (player_build) { + reset_powder_specials(); updateBoosts("skip", false); updatePowderSpecials("skip", false); } let weaponName = getValue(equipmentInputs[8]); + //bruh @hpp if (weaponName.startsWith("Morph-")) { let equipment = [ "Morph-Stardust", "Morph-Steel", "Morph-Iron", "Morph-Gold", "Morph-Topaz", "Morph-Emerald", "Morph-Amethyst", "Morph-Ruby", weaponName.substring(6) ]; for (let i in equipment) { @@ -440,7 +257,6 @@ function calculateBuild(save_skp, skp){ while (input) { let first = input.slice(0, 2); let powder = powderIDs.get(first); - console.log(powder); if (powder === undefined) { errorederrors.push(first); } else { @@ -457,11 +273,24 @@ function calculateBuild(save_skp, skp){ //console.log("POWDERING: " + powdering); powderings.push(powdering); } + let tomes = [ null, null, null, null, null, null, null]; + for (let i in tomes) { + let equip = getValue(tomeInputs[i]).trim(); + if (equip === "") { + equip = "No " + tome_names[i] + } + else { + setValue(tomeInputs[i], equip); + } + tomes[i] = equip; + } let level = document.getElementById("level-choice").value; - player_build = new Build(level, equipment, powderings, new Map(), errors); + player_build = new Build(level, equipment, powderings, new Map(), errors, tomes); console.log(player_build); + + //isn't this deprecated? for (let i of document.getElementsByClassName("hide-container-block")) { i.style.display = "block"; } @@ -470,21 +299,14 @@ function calculateBuild(save_skp, skp){ } console.log(player_build.toString()); - displayEquipOrder(document.getElementById("build-order"),player_build.equip_order); - - + displaysq2EquipOrder(document.getElementById("build-order"),player_build.equip_order); const assigned = player_build.base_skillpoints; const skillpoints = player_build.total_skillpoints; for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-base", "Original Value: " + skillpoints[i]); + setText(skp_order[i] + "-skp-base", "Original: " + skillpoints[i]); } - for (let id of editable_item_fields) { - setValue(id, player_build.statMap.get(id)); - setText(id+"-base", "Original Value: " + player_build.statMap.get(id)); - } - if (save_skp) { // TODO: reduce duplicated code, @updateStats let skillpoints = player_build.total_skillpoints; @@ -499,14 +321,13 @@ function calculateBuild(save_skp, skp){ player_build.assigned_skillpoints += delta_total; } + updateEditableIDs(); calculateBuildStats(); - setTitle(); if (player_build.errored) throw new ListError(player_build.errors); - } catch (error) { - handleBuilderError(error); + console.log(error); } } @@ -586,7 +407,7 @@ function updateStats() { let delta_total = 0; for (let i in skp_order) { let value = document.getElementById(skp_order[i] + "-skp").value; - if (value === ""){value = "0"; setValue(skp_order[i] + "-skp", value)} + if (value === ""){value = 0; setValue(skp_order[i] + "-skp", value)} let manual_assigned = 0; if (value.includes("+")) { let skp = value.split("+"); @@ -604,15 +425,58 @@ function updateStats() { player_build.assigned_skillpoints += delta_total; if(player_build){ updatePowderSpecials("skip", false); + updateArmorPowderSpecials("skip", false); updateBoosts("skip", false); - } - for (let id of editable_item_fields) { - player_build.statMap.set(id, parseInt(getValue(id))); + for (let id of editable_item_fields) { + player_build.statMap.set(id, parseInt(getValue(id))); + } } player_build.aggregateStats(); console.log(player_build.statMap); calculateBuildStats(); } + + +/* Updates all IDs in the edit IDs section. Resets each input and original value text to the correct text according to the current build. +*/ +function updateEditableIDs() { + if (player_build) { + for (const id of editable_item_fields) { + let edit_input = document.getElementById(id); + let val = player_build.statMap.get(id); + edit_input.value = val; + edit_input.placeholder = val; + + let value_label = document.getElementById(id + "-base"); + value_label.textContent = "Original Value: " + val; + //a hack to make resetting easier + value_label.value = val; + } + } +} + +/* Resets all IDs in the edit IDs section to their "original" values. +*/ +function resetEditableIDs() { + if (player_build) { + for (const id of editable_item_fields) { + let edit_input = document.getElementById(id); + let value_label = document.getElementById(id + "-base"); + + edit_input.value = value_label.value; + edit_input.placeholder = value_label.value; + } + } else { + //no player build, reset to 0 + for (const id of editable_item_fields) { + let edit_input = document.getElementById(id); + + edit_input.value = 0; + edit_input.placeholder = 0; + } + } +} + /* Updates all spell boosts */ function updateBoosts(buttonId, recalcStats) { @@ -655,7 +519,7 @@ function updateBoosts(buttonId, recalcStats) { } } -/* Updates all powder special boosts +/* Updates ACTIVE powder special boosts (weapons) */ function updatePowderSpecials(buttonId, recalcStats) { //console.log(player_build.statMap); @@ -740,8 +604,76 @@ function updatePowderSpecials(buttonId, recalcStats) { if (recalcStats) { calculateBuildStats(); } - displayPowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build); + displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); } + + +/* Updates PASSIVE powder special boosts (armors) +*/ +function updateArmorPowderSpecials(elem_id, recalc_stats) { + //we only update the powder special + external stats if the player has a build + if (elem_id !== "skip") { + if (player_build !== undefined && player_build.weapon !== undefined && player_build.weapon.get("name") !== "No Weapon") { + 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 prev_label = document.getElementById(elem_id + "_prev"); + + let value = elem.value; + + //for use in editing build stats + let prev_value = prev_label.value; + let value_diff = value - prev_value; + + //update the "previous" label + prev_label.value = value; + + label.textContent = label.textContent.split(":")[0] + ": " + value; + + let dmg_id = elem_chars[skp_names.indexOf(wynn_elem)] + "DamPct"; + let new_dmgboost = player_build.externalStats.get(dmg_id) + value_diff; + + //update build external stats - the second one is the relevant one for damage calc purposes + player_build.externalStats.set(dmg_id, new_dmgboost); + player_build.externalStats.get("damageBonus")[skp_names.indexOf(wynn_elem)] = new_dmgboost; + + //update the slider's graphics + let bg_color = elem_colors[skp_names.indexOf(wynn_elem)]; + let pct = Math.round(100 * value / powderSpecialStats[skp_names.indexOf(wynn_elem)].cap); + elem.style.background = `linear-gradient(to right, ${bg_color}, ${bg_color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`; + + } + } else { + if (player_build !== undefined) { + for (let i = 0; i < skp_names.length; ++i) { + skp_name = skp_names[i]; + skp_char = elem_chars[i]; + player_build.externalStats.set(skp_char + "DamPct", player_build.externalStats.get(skp_char + "DamPct") - document.getElementById(skp_name+"_boost_armor").value); + player_build.externalStats.get("damageBonus")[i] -= document.getElementById(skp_name+"_boost_armor").value; + } + } + } + + + if (recalc_stats && player_build) { + //calc build stats and display powder special + calculateBuildStats(); + // displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); + } + +} + +function resetArmorPowderSpecials() { + for (const skp of skp_names) { + document.getElementById(skp + "_boost_armor").value = 0; + document.getElementById(skp + "_boost_armor_prev").value = 0; + document.getElementById(skp + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`; + document.getElementById(skp + "_boost_armor_label").textContent = `% ${capitalizeFirst(elem_names[skp_names.indexOf(skp)])} Damage Boost: 0` + } +} + /* Calculates all build statistics and updates the entire display. */ function calculateBuildStats() { @@ -749,27 +681,29 @@ function calculateBuildStats() { const skillpoints = player_build.total_skillpoints; let skp_effects = ["% more damage dealt.","% chance to crit.","% spell cost reduction.","% less damage taken.","% chance to dodge."]; for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-assign", "Manually Assigned: " + assigned[i]); + setText(skp_order[i] + "-skp-assign", "Assign: " + assigned[i]); setValue(skp_order[i] + "-skp", skillpoints[i]); let linebreak = document.createElement("br"); linebreak.classList.add("itemp"); document.getElementById(skp_order[i] + "-skp-label"); 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[i] > 100) { let skp_warning = document.createElement("p"); skp_warning.classList.add("warning"); - skp_warning.textContent += "WARNING: Cannot assign " + assigned[i] + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually."; - document.getElementById(skp_order[i]+"-skp-pct").appendChild(skp_warning); + skp_warning.classList.add("small-text") + skp_warning.textContent += "Cannot assign " + assigned[i] + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually."; + document.getElementById(skp_order[i]+"-warnings").textContent = '' + document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning); } } let summarybox = document.getElementById("summary-box"); summarybox.textContent = ""; let skpRow = document.createElement("p"); - let td = document.createElement("p"); let remainingSkp = document.createElement("p"); - remainingSkp.classList.add("center"); + remainingSkp.classList.add("scaled-font"); let remainingSkpTitle = document.createElement("b"); remainingSkpTitle.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: "; let remainingSkpContent = document.createElement("b"); @@ -783,13 +717,12 @@ function calculateBuildStats() { summarybox.append(skpRow); summarybox.append(remainingSkp); if(player_build.assigned_skillpoints > levelToSkillPoints(player_build.level)){ - let skpWarning = document.createElement("p"); + let skpWarning = document.createElement("span"); //skpWarning.classList.add("itemp"); skpWarning.classList.add("warning"); - skpWarning.classList.add("itemp"); skpWarning.textContent = "WARNING: Too many skillpoints need to be assigned!"; let skpCount = document.createElement("p"); - skpCount.classList.add("itemp"); + skpCount.classList.add("warning"); skpCount.textContent = "For level " + (player_build.level>101 ? "101+" : player_build.level) + ", there are only " + levelToSkillPoints(player_build.level) + " skill points available."; summarybox.append(skpWarning); summarybox.append(skpCount); @@ -826,7 +759,6 @@ function calculateBuildStats() { const bonus = sets[setName].bonuses[count-1]; // console.log(setName); if (bonus["illegal"]) { - console.log("mmmm"); let setWarning = document.createElement("p"); setWarning.classList.add("itemp"); setWarning.classList.add("warning"); @@ -836,30 +768,32 @@ function calculateBuildStats() { } for (let i in player_build.items) { - displayExpandedItem(player_build.items[i], buildFields[i]); + displaysq2ExpandedItem(player_build.items[i], buildFields[i]); + collapse_element("#"+equipment_keys[i]+"-tooltip"); } - displayBuildStats("build-overall-stats",player_build); - displaySetBonuses("set-info",player_build); - displayNextCosts("int-info",player_build); + displaysq2ArmorStats(player_build); + displaysq2BuildStats('overall-stats', player_build, build_all_display_commands); + displaysq2BuildStats("offensive-stats",player_build, build_offensive_display_commands); + displaysq2SetBonuses("set-info",player_build); + displaysq2WeaponStats(player_build); let meleeStats = player_build.getMeleeStats(); - displayMeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); + displaysq2MeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); - displayDefenseStats(document.getElementById("build-defense-stats"),player_build); + displaysq2DefenseStats(document.getElementById("defensive-stats"),player_build); - displayPoisonDamage(document.getElementById("build-poison-stats"),player_build); + displaysq2PoisonDamage(document.getElementById("build-poison-stats"),player_build); let spells = spell_table[player_build.weapon.get("type")]; for (let i = 0; i < 4; ++i) { let parent_elem = document.getElementById("spell"+i+"-info"); let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaySpellDamage(parent_elem, overallparent_elem, player_build, spells[i], i+1); + displaysq2SpellDamage(parent_elem, overallparent_elem, player_build, spells[i], i+1); } location.hash = encodeBuild(); clear_highlights(); - updateOGP(); } function copyBuild() { @@ -971,7 +905,21 @@ function toggleID() { } } +function toggleButton(button_id) { + let button = document.getElementById(button_id); + if (button) { + if (button.classList.contains("toggleOn")) { + button.classList.remove("toggleOn"); + } else { + button.classList.add("toggleOn"); + } + } +} + function optimizeStrDex() { + if (!player_build) { + return; + } const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; const base_skillpoints = player_build.base_skillpoints; const max_str_boost = 100 - base_skillpoints[0]; @@ -1061,7 +1009,6 @@ function optimizeStrDex() { try { calculateBuildStats(); - setTitle(); if (player_build.errored) throw new ListError(player_build.errors); } @@ -1071,8 +1018,20 @@ function optimizeStrDex() { } // TODO: Learn and use await +function init() { + console.log("builder.js init"); + init_autocomplete(); + decodeBuild(url_tag); + for (const i of equipment_keys) { + update_field(i); + } +} function init2() { load_ing_init(init); } -load_init(init2); -updateOGP(); \ No newline at end of file +function init3() { + load_tome_init(init2) +} + + +load_init(init3); diff --git a/js/computation_graph.js b/js/computation_graph.js index cdd1212..9039685 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -1,5 +1,3 @@ -let _ALL_NODES = new Map(); - class ComputeNode { /** * Make a generic compute node. @@ -8,10 +6,6 @@ class ComputeNode { * @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes). */ constructor(name) { - if (_ALL_NODES.has(name)) { - throw 'Duplicate node name: ' + name; - } - _ALL_NODES.set(name, this) this.inputs = []; this.children = []; this.value = 0; @@ -65,10 +59,9 @@ class ComputeNode { /** * Schedule a ComputeNode to be updated. * - * @param node_name : ComputeNode name to schedule an update for. + * @param node : ComputeNode to schedule an update for. */ -function calcSchedule(node_name) { - node = _ALL_NODES.get(node_name); +function calcSchedule(node) { if (node.update_task !== null) { clearTimeout(node.update_task); } @@ -92,7 +85,7 @@ class ItemInputNode extends ComputeNode { */ constructor(name, item_input_field, none_item) { super(name); - this.input_field.setAttribute("onInput", "calcSchedule('"+name+"');"); + this.input_field.setAttribute("input", () => calcSchedule(this)); this.input_field = item_input_field; this.none_item = expandItem(none_item); } diff --git a/js/display_constants.js b/js/display_constants.js index d8c26dd..93f77e6 100644 --- a/js/display_constants.js +++ b/js/display_constants.js @@ -1,4 +1,3 @@ -let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; let colorMap = new Map( [ ["Normal", "#fff"], diff --git a/js/sq2bs.js b/js/sq2bs.js index 25ac2d2..94f1c22 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -1,13 +1,3 @@ -let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear']; -let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots']; -let skp_keys = ['str', 'dex', 'int', 'def', 'agi']; -let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace']; -let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon']; -let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'].concat(tome_keys); -let powder_keys = ['e', 't', 'w', 'f', 'a']; - -let spell_disp = ['spell0-info', 'spell1-info', 'spell2-info', 'spell3-info']; -let other_disp = ['build-order', 'set-info', 'int-info']; document.addEventListener('DOMContentLoaded', function() { @@ -202,7 +192,7 @@ function update_field(field) { document.querySelector("#"+field+"-powder").classList.add("is-invalid"); } else { for (i = 0; i < powder_string.length / 2; i++) { - if (powder_keys.includes(powder_string.substring(i*2, i*2+2).split("")[0]) == false || isNaN(powder_string.substring(i*2, i*2+2).split("")[1]) || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) < 1 || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) > 6) { + if (skp_elements.includes(powder_string.substring(i*2, i*2+2).split("")[0]) == false || isNaN(powder_string.substring(i*2, i*2+2).split("")[1]) || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) < 1 || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) > 6) { document.querySelector("#"+field+"-powder").classList.add("is-invalid"); } } @@ -239,7 +229,7 @@ function toggle_spell_tab(tab) { } function toggle_boost_tab(tab) { - for (const i of skp_keys) { + for (const i of skp_order) { document.querySelector("#"+i+"-boost").style.display = "none"; document.getElementById(i + "-boost-tab").classList.remove("selected-btn"); } diff --git a/js/sq2builder.js b/js/sq2builder.js index fc42f6d..e63cdb0 100644 --- a/js/sq2builder.js +++ b/js/sq2builder.js @@ -5,25 +5,16 @@ const url_tag = location.hash.slice(1); const BUILD_VERSION = "7.0.19"; -// function setTitle() { -// let text; -// if (url_base.includes("hppeng-wynn")) { -// text = "WynnBuilder UNSTABLE version "+BUILD_VERSION+" (db version "+DB_VERSION+")"; -// } -// else { -// text = "WynnBuilder version "+BUILD_VERSION+" (db version "+DB_VERSION+")"; -// document.getElementById("header").classList.add("funnynumber"); -// } -// document.getElementById("header").textContent = text; -// } -// -// setTitle(); - let player_build; // THIS IS SUPER DANGEROUS, WE SHOULD NOT BE KEEPING THIS IN SO MANY PLACES -let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "hprRaw", "hprPct", "hpBonus", "atkTier", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; +let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison", + "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", + "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", + "hprRaw", "hprPct", "hpBonus", "atkTier", + "spPct1", "spRaw1", "spPct2", "spRaw2", + "spPct3", "spRaw3", "spPct4", "spRaw4" ]; let editable_elems = []; From 0f4dba258f3cc65ce07ff81c5ad0f80c84555f1a Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 22 May 2022 03:21:34 -0700 Subject: [PATCH 004/155] Proof of concept (compute nodes to get items from names and highlight) --- builder/index.html | 3 +- js/builder.js | 691 +--------------------------------------- js/builder_graph.js | 21 ++ js/computation_graph.js | 71 ++++- js/load.js | 86 ++--- js/optimize.js | 101 ++++++ js/utils.js | 27 +- 7 files changed, 227 insertions(+), 773 deletions(-) create mode 100644 js/builder_graph.js create mode 100644 js/optimize.js diff --git a/builder/index.html b/builder/index.html index 7a89a26..d7a802c 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1402,10 +1402,11 @@ + - + diff --git a/js/builder.js b/js/builder.js index 3455ee2..1b18b96 100644 --- a/js/builder.js +++ b/js/builder.js @@ -1,4 +1,3 @@ - function getItemNameFromID(id) { if (redirectMap.has(id)) { return getItemNameFromID(redirectMap.get(id)); @@ -51,7 +50,7 @@ function decodeBuild(url_tag) { let skillpoints = [0, 0, 0, 0, 0]; let level = 106; - version_number = parseInt(version) + let version_number = parseInt(version) //equipment (items) // TODO: use filters if (version_number < 4) { @@ -147,8 +146,6 @@ function decodeBuild(url_tag) { for (let i in powderInputs) { setValue(powderInputs[i], powdering[i]); } - - calculateBuild(save_skp, skillpoints); } } @@ -213,589 +210,6 @@ function encodeBuild() { } } -function calculateBuild(save_skp, skp){ - try { - resetEditableIDs(); - if (player_build) { - reset_powder_specials(); - updateBoosts("skip", false); - updatePowderSpecials("skip", false); - } - let weaponName = getValue(equipmentInputs[8]); - //bruh @hpp - if (weaponName.startsWith("Morph-")) { - let equipment = [ "Morph-Stardust", "Morph-Steel", "Morph-Iron", "Morph-Gold", "Morph-Topaz", "Morph-Emerald", "Morph-Amethyst", "Morph-Ruby", weaponName.substring(6) ]; - for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); - } - } - - //updatePowderSpecials("skip"); //jank pt 1 - save_skp = (typeof save_skp !== 'undefined') ? save_skp : false; - /* TODO: implement level changing - Make this entire function prettier - */ - let equipment = [ null, null, null, null, null, null, null, null, null ]; - for (let i in equipment) { - let equip = getValue(equipmentInputs[i]).trim(); - if (equip === "") { - equip = "No " + equipment_names[i] - } - else { - setValue(equipmentInputs[i], equip); - } - equipment[i] = equip; - } - let powderings = []; - let errors = []; - for (const i in powderInputs) { - // read in two characters at a time. - // TODO: make this more robust. - let input = getValue(powderInputs[i]).trim(); - let powdering = []; - let errorederrors = []; - while (input) { - let first = input.slice(0, 2); - let powder = powderIDs.get(first); - if (powder === undefined) { - errorederrors.push(first); - } else { - powdering.push(powder); - } - input = input.slice(2); - } - if (errorederrors.length > 0) { - if (errorederrors.length > 1) - errors.push(new IncorrectInput(errorederrors.join(""), "t6w6", powderInputs[i])); - else - errors.push(new IncorrectInput(errorederrors[0], "t6 or e3", powderInputs[i])); - } - //console.log("POWDERING: " + powdering); - powderings.push(powdering); - } - let tomes = [ null, null, null, null, null, null, null]; - for (let i in tomes) { - let equip = getValue(tomeInputs[i]).trim(); - if (equip === "") { - equip = "No " + tome_names[i] - } - else { - setValue(tomeInputs[i], equip); - } - tomes[i] = equip; - } - - - let level = document.getElementById("level-choice").value; - player_build = new Build(level, equipment, powderings, new Map(), errors, tomes); - console.log(player_build); - - //isn't this deprecated? - for (let i of document.getElementsByClassName("hide-container-block")) { - i.style.display = "block"; - } - for (let i of document.getElementsByClassName("hide-container-grid")) { - i.style.display = "grid"; - } - - console.log(player_build.toString()); - displaysq2EquipOrder(document.getElementById("build-order"),player_build.equip_order); - - const assigned = player_build.base_skillpoints; - const skillpoints = player_build.total_skillpoints; - for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-base", "Original: " + skillpoints[i]); - } - - if (save_skp) { - // TODO: reduce duplicated code, @updateStats - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let manual_assigned = skp[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - } - - updateEditableIDs(); - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - console.log(error); - } -} - -function handleBuilderError(error) { - if (error instanceof ListError) { - for (let i of error.errors) { - if (i instanceof ItemNotFound) { - i.element.textContent = i.message; - } else if (i instanceof IncorrectInput) { - if (document.getElementById(i.id) !== null) { - document.getElementById(i.id).parentElement.querySelectorAll("p.error")[0].textContent = i.message; - } - } else { - let msg = i.stack; - let lines = msg.split("\n"); - let header = document.getElementById("header"); - header.textContent = ""; - for (const line of lines) { - let p = document.createElement("p"); - p.classList.add("itemp"); - p.textContent = line; - header.appendChild(p); - } - let p2 = document.createElement("p"); - p2.textContent = "If you believe this is an error, contact hppeng on forums or discord."; - header.appendChild(p2); - } - } - } else { - let msg = error.stack; - let lines = msg.split("\n"); - let header = document.getElementById("header"); - header.textContent = ""; - for (const line of lines) { - let p = document.createElement("p"); - p.classList.add("itemp"); - p.textContent = line; - header.appendChild(p); - } - let p2 = document.createElement("p"); - p2.textContent = "If you believe this is an error, contact hppeng on forums or discord."; - header.appendChild(p2); - } -} - -/* Updates all build statistics based on (for now) the skillpoint input fields and then calculates build stats. -*/ -function updateStats() { - - let specialNames = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"]; - for (const sName of specialNames) { - for (let i = 1; i < 6; i++) { - let elem = document.getElementById(sName + "-" + i); - let name = sName.replace("_", " "); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(sName)]; - console.log(special); - if (special["weaponSpecialEffects"].has("Damage Boost")) { - if (name === "Courage" || name === "Curse") { //courage is universal damage boost - //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100; - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1]; - } - } - } - } - } - - - - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let value = document.getElementById(skp_order[i] + "-skp").value; - if (value === ""){value = 0; setValue(skp_order[i] + "-skp", value)} - let manual_assigned = 0; - if (value.includes("+")) { - let skp = value.split("+"); - for (const s of skp) { - manual_assigned += parseInt(s,10); - } - } else { - manual_assigned = parseInt(value,10); - } - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - if(player_build){ - updatePowderSpecials("skip", false); - updateArmorPowderSpecials("skip", false); - updateBoosts("skip", false); - for (let id of editable_item_fields) { - player_build.statMap.set(id, parseInt(getValue(id))); - } - } - player_build.aggregateStats(); - console.log(player_build.statMap); - calculateBuildStats(); -} - - -/* Updates all IDs in the edit IDs section. Resets each input and original value text to the correct text according to the current build. -*/ -function updateEditableIDs() { - if (player_build) { - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - let val = player_build.statMap.get(id); - edit_input.value = val; - edit_input.placeholder = val; - - let value_label = document.getElementById(id + "-base"); - value_label.textContent = "Original Value: " + val; - //a hack to make resetting easier - value_label.value = val; - } - } -} - -/* Resets all IDs in the edit IDs section to their "original" values. -*/ -function resetEditableIDs() { - if (player_build) { - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - let value_label = document.getElementById(id + "-base"); - - edit_input.value = value_label.value; - edit_input.placeholder = value_label.value; - } - } else { - //no player build, reset to 0 - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - - edit_input.value = 0; - edit_input.placeholder = 0; - } - } -} - -/* Updates all spell boosts -*/ -function updateBoosts(buttonId, recalcStats) { - let elem = document.getElementById(buttonId); - let name = buttonId.split("-")[0]; - if(buttonId !== "skip") { - if (elem.classList.contains("toggleOn")) { - player_build.damageMultiplier -= damageMultipliers.get(name); - if (name === "warscream") { - player_build.defenseMultiplier -= .20; - } - if (name === "vanish") { - player_build.defenseMultiplier -= .15; - } - elem.classList.remove("toggleOn"); - }else{ - player_build.damageMultiplier += damageMultipliers.get(name); - if (name === "warscream") { - player_build.defenseMultiplier += .20; - } - if (name === "vanish") { - player_build.defenseMultiplier += .15; - } - elem.classList.add("toggleOn"); - } - updatePowderSpecials("skip", false); //jank pt 1 - } else { - for (const [key, value] of damageMultipliers) { - let elem = document.getElementById(key + "-boost") - if (elem.classList.contains("toggleOn")) { - elem.classList.remove("toggleOn"); - player_build.damageMultiplier -= value; - if (key === "warscream") { player_build.defenseMultiplier -= .20 } - if (key === "vanish") { player_build.defenseMultiplier -= .15 } - } - } - } - if (recalcStats) { - calculateBuildStats(); - } -} - -/* Updates ACTIVE powder special boosts (weapons) -*/ -function updatePowderSpecials(buttonId, recalcStats) { - //console.log(player_build.statMap); - - let name = (buttonId).split("-")[0]; - let power = (buttonId).split("-")[1]; // [1, 5] - let specialNames = ["Quake", "Chain Lightning", "Curse", "Courage", "Wind Prison"]; - let powderSpecials = []; // [ [special, power], [special, power]] - - - if(name !== "skip"){ - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special.weaponSpecialEffects.has("Damage Boost")) { - name = name.replace("_", " "); - if (name === "Courage" || name === "Curse") { //courage and curse are universal damage boost - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - //poison? - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[power-1]; - } - } - } 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"); - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special.weaponSpecialEffects.has("Damage Boost")) { - name = name.replace("_", " "); //might be redundant - if (name === "Courage" || name === "Curse") { //courage is universal damage boost - //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100; - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1]; - } - } - } - } - //toggle the pressed button on - elem.classList.add("toggleOn"); - } - } - - for (const sName of specialNames) { - for (let i = 1;i < 6; i++) { - if (document.getElementById(sName.replace(" ","_") + "-" + i).classList.contains("toggleOn")) { - let powderSpecial = powderSpecialStats[specialNames.indexOf(sName.replace("_"," "))]; - powderSpecials.push([powderSpecial, i]); - break; - } - } - } - - - if (name !== "skip") { - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special["weaponSpecialEffects"].has("Damage Boost")) { - let name = special["weaponSpecialName"]; - if (name === "Courage" || name === "Curse") { //courage and curse are is universal damage boost - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.get("damageBonus")[4] += special.weaponSpecialEffects.get("Damage Boost")[power-1]; - } - } - } - } - - if (recalcStats) { - calculateBuildStats(); - } - displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); -} - - -/* Updates PASSIVE powder special boosts (armors) -*/ -function updateArmorPowderSpecials(elem_id, recalc_stats) { - //we only update the powder special + external stats if the player has a build - if (elem_id !== "skip") { - if (player_build !== undefined && player_build.weapon !== undefined && player_build.weapon.get("name") !== "No Weapon") { - 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 prev_label = document.getElementById(elem_id + "_prev"); - - let value = elem.value; - - //for use in editing build stats - let prev_value = prev_label.value; - let value_diff = value - prev_value; - - //update the "previous" label - prev_label.value = value; - - label.textContent = label.textContent.split(":")[0] + ": " + value; - - let dmg_id = elem_chars[skp_names.indexOf(wynn_elem)] + "DamPct"; - let new_dmgboost = player_build.externalStats.get(dmg_id) + value_diff; - - //update build external stats - the second one is the relevant one for damage calc purposes - player_build.externalStats.set(dmg_id, new_dmgboost); - player_build.externalStats.get("damageBonus")[skp_names.indexOf(wynn_elem)] = new_dmgboost; - - //update the slider's graphics - let bg_color = elem_colors[skp_names.indexOf(wynn_elem)]; - let pct = Math.round(100 * value / powderSpecialStats[skp_names.indexOf(wynn_elem)].cap); - elem.style.background = `linear-gradient(to right, ${bg_color}, ${bg_color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`; - - } - } else { - if (player_build !== undefined) { - for (let i = 0; i < skp_names.length; ++i) { - skp_name = skp_names[i]; - skp_char = elem_chars[i]; - player_build.externalStats.set(skp_char + "DamPct", player_build.externalStats.get(skp_char + "DamPct") - document.getElementById(skp_name+"_boost_armor").value); - player_build.externalStats.get("damageBonus")[i] -= document.getElementById(skp_name+"_boost_armor").value; - } - } - } - - - if (recalc_stats && player_build) { - //calc build stats and display powder special - calculateBuildStats(); - // displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); - } - -} - -function resetArmorPowderSpecials() { - for (const skp of skp_names) { - document.getElementById(skp + "_boost_armor").value = 0; - document.getElementById(skp + "_boost_armor_prev").value = 0; - document.getElementById(skp + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`; - document.getElementById(skp + "_boost_armor_label").textContent = `% ${capitalizeFirst(elem_names[skp_names.indexOf(skp)])} Damage Boost: 0` - } -} - -/* Calculates all build statistics and updates the entire display. -*/ -function calculateBuildStats() { - const assigned = player_build.base_skillpoints; - const skillpoints = player_build.total_skillpoints; - let skp_effects = ["% more damage dealt.","% chance to crit.","% spell cost reduction.","% less damage taken.","% chance to dodge."]; - for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-assign", "Assign: " + assigned[i]); - setValue(skp_order[i] + "-skp", skillpoints[i]); - let linebreak = document.createElement("br"); - linebreak.classList.add("itemp"); - document.getElementById(skp_order[i] + "-skp-label"); - 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[i] > 100) { - let skp_warning = document.createElement("p"); - skp_warning.classList.add("warning"); - skp_warning.classList.add("small-text") - skp_warning.textContent += "Cannot assign " + assigned[i] + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually."; - document.getElementById(skp_order[i]+"-warnings").textContent = '' - document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning); - } - } - - 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 " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: "; - let remainingSkpContent = document.createElement("b"); - remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints); - remainingSkpContent.classList.add(levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints < 0 ? "negative" : "positive"); - - remainingSkp.appendChild(remainingSkpTitle); - remainingSkp.appendChild(remainingSkpContent); - - - summarybox.append(skpRow); - summarybox.append(remainingSkp); - if(player_build.assigned_skillpoints > levelToSkillPoints(player_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 " + (player_build.level>101 ? "101+" : player_build.level) + ", there are only " + levelToSkillPoints(player_build.level) + " skill points available."; - summarybox.append(skpWarning); - summarybox.append(skpCount); - } - let lvlWarning; - for (const item of player_build.items) { - let item_lvl; - if (item.get("crafted")) { - //item_lvl = item.get("lvlLow") + "-" + item.get("lvl"); - item_lvl = item.get("lvlLow"); - } - else { - item_lvl = item.get("lvl"); - } - - if (player_build.level < item_lvl) { - if (!lvlWarning) { - lvlWarning = document.createElement("p"); - lvlWarning.classList.add("itemp"); - lvlWarning.classList.add("warning"); - lvlWarning.textContent = "WARNING: A level " + player_build.level + " player cannot use some piece(s) of this build." - } - let baditem = document.createElement("p"); - baditem.classList.add("nocolor"); - baditem.classList.add("itemp"); - baditem.textContent = item.get("displayName") + " requires level " + item.get("lvl") + " to use."; - lvlWarning.appendChild(baditem); - } - } - if(lvlWarning){ - summarybox.append(lvlWarning); - } - for (const [setName, count] of player_build.activeSetCounts) { - const bonus = sets[setName].bonuses[count-1]; - // console.log(setName); - if (bonus["illegal"]) { - let setWarning = document.createElement("p"); - setWarning.classList.add("itemp"); - setWarning.classList.add("warning"); - setWarning.textContent = "WARNING: illegal item combination: " + setName - summarybox.append(setWarning); - } - } - - for (let i in player_build.items) { - displaysq2ExpandedItem(player_build.items[i], buildFields[i]); - collapse_element("#"+equipment_keys[i]+"-tooltip"); - } - - displaysq2ArmorStats(player_build); - displaysq2BuildStats('overall-stats', player_build, build_all_display_commands); - displaysq2BuildStats("offensive-stats",player_build, build_offensive_display_commands); - displaysq2SetBonuses("set-info",player_build); - displaysq2WeaponStats(player_build); - - let meleeStats = player_build.getMeleeStats(); - displaysq2MeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); - - displaysq2DefenseStats(document.getElementById("defensive-stats"),player_build); - - displaysq2PoisonDamage(document.getElementById("build-poison-stats"),player_build); - - let spells = spell_table[player_build.weapon.get("type")]; - for (let i = 0; i < 4; ++i) { - let parent_elem = document.getElementById("spell"+i+"-info"); - let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaysq2SpellDamage(parent_elem, overallparent_elem, player_build, spells[i], i+1); - } - - location.hash = encodeBuild(); - clear_highlights(); -} - function copyBuild() { if (player_build) { copyTextToClipboard(url_base+location.hash); @@ -916,107 +330,6 @@ function toggleButton(button_id) { } } -function optimizeStrDex() { - if (!player_build) { - return; - } - const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; - const base_skillpoints = player_build.base_skillpoints; - const max_str_boost = 100 - base_skillpoints[0]; - const max_dex_boost = 100 - base_skillpoints[1]; - if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable - - const base_total_skillpoints = player_build.total_skillpoints; - let str_bonus = remaining; - let dex_bonus = 0; - let best_skillpoints = player_build.total_skillpoints; - let best_damage = 0; - for (let i = 0; i <= remaining; ++i) { - let total_skillpoints = base_total_skillpoints.slice(); - total_skillpoints[0] += Math.min(max_str_boost, str_bonus); - total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); - - // Calculate total 3rd spell damage - let spell = spell_table[player_build.weapon.get("type")][2]; - const stats = player_build.statMap; - let critChance = skillPointsToPercentage(total_skillpoints[1]); - let save_damages = []; - 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; - } - } - } - let total_damage = 0; - for (const part of spell_parts) { - if (part.type === "damage") { - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw"), stats.get("sdPct") + player_build.externalStats.get("sdPct"), - part.multiplier / 100, player_build.weapon, total_skillpoints, - player_build.damageMultiplier, player_build.externalStats); - let totalDamNormal = _results[0]; - let totalDamCrit = _results[1]; - let results = _results[2]; - let tooltipinfo = _results[3]; - - for (let i = 0; i < 6; ++i) { - for (let j in results[i]) { - results[i][j] = results[i][j].toFixed(2); - } - } - let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; - let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; - let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; - - save_damages.push(averageDamage); - if (part.summary == true) { - total_damage = averageDamage; - } - } else if (part.type === "total") { - total_damage = 0; - for (let i in part.factors) { - total_damage += save_damages[i] * part.factors[i]; - } - } - } // END Calculate total 3rd spell damage (total_damage) - if (total_damage > best_damage) { - best_damage = total_damage; - best_skillpoints = total_skillpoints.slice(); - } - - str_bonus -= 1; - dex_bonus += 1; - - } - // TODO: reduce duplicated code, @calculateBuild - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let manual_assigned = best_skillpoints[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - - try { - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - handleBuilderError(error); - } -} - // TODO: Learn and use await function init() { console.log("builder.js init"); @@ -1026,6 +339,7 @@ function init() { update_field(i); } } + function init2() { load_ing_init(init); } @@ -1033,5 +347,4 @@ function init3() { load_tome_init(init2) } - load_init(init3); diff --git a/js/builder_graph.js b/js/builder_graph.js new file mode 100644 index 0000000..7b652ba --- /dev/null +++ b/js/builder_graph.js @@ -0,0 +1,21 @@ + + +let item_nodes = []; + +document.addEventListener('DOMContentLoaded', function() { + for (const [eq, none_item] of zip(equipment_fields, none_items)) { + 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+'-display', input_field, item_image).link_to(item_input); + new PrintNode(eq+'-debug').link_to(item_input); + //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); + + } + let weapon_image = document.getElementById("weapon-img"); + new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); + console.log("Set up graph"); + +}); diff --git a/js/computation_graph.js b/js/computation_graph.js index 9039685..417c9bf 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -13,6 +13,7 @@ class ComputeNode { this.update_task = null; this.update_time = Date.now(); this.fail_cb = false; // Set to true to force updates even if parent failed. + this.calc_inputs = new Map(); } /** @@ -23,19 +24,40 @@ class ComputeNode { return; } this.update_time = timestamp; + this.set_value(this.compute_func(this.calc_inputs)); + } - let value_map = Map(); - for (const input of this.inputs) { - value_map.set(input.name, input.get_value()); - } - this.value = this.compute_func(value_map); + /** + * Set this node's value directly. Notifies children. + */ + set_value(value) { + let timestamp = Date.now(); + this.update_time = timestamp; + this.value = value; for (const child of this.children) { - if (this.value || child.fail_cb) { - child.update(); + child.set_input(this.name, this.value, timestamp); + } + } + + /** + * Set an input value. Propagates calculation if all inputs are present. + */ + set_input(input_name, value, timestamp) { + if (value || this.fail_cb) { + this.calc_inputs.set(input_name, value) + if (this.calc_inputs.size === this.inputs.length) { + this.update(timestamp) } } } + /** + * Remove cached input values to this calculation. + */ + clear_cache() { + this.calc_inputs = new Map(); + } + /** * Get value of this compute node. Can't trigger update cascades (push based update, not pull based.) */ @@ -72,6 +94,19 @@ function calcSchedule(node) { }, 500); } +class PrintNode extends ComputeNode { + + constructor(name) { + super(name); + this.fail_cb = true; + } + + compute_func(input_map) { + console.log([this.name, input_map]); + return null; + } +} + /** * Node for getting an item's stats from an item input field. */ @@ -85,9 +120,9 @@ class ItemInputNode extends ComputeNode { */ constructor(name, item_input_field, none_item) { super(name); - this.input_field.setAttribute("input", () => calcSchedule(this)); this.input_field = item_input_field; - this.none_item = expandItem(none_item); + this.input_field.addEventListener("input", () => calcSchedule(this)); + this.none_item = new Item(none_item); } compute_func(input_map) { @@ -107,16 +142,22 @@ class ItemInputNode extends ComputeNode { item = getCraftFromHash(item_text); } else if (itemMap.has(item_text)) { - item = Item(itemMap.get(item_text)); + item = new Item(itemMap.get(item_text)); } else if (tomeMap.has(item_text)) { - item = Item(tomeMap.get(item_text)); + item = new Item(tomeMap.get(item_text)); } - if (!item || item.statMap.get('type') !== this.none_item.statMap.get('type')) { - return null; + if (item) { + 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'); + } + if (type_match) { return item; } } - return item; + return null; } } @@ -166,6 +207,6 @@ class WeaponDisplayNode extends ComputeNode { 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_field.setAttribute('src', '../media/items/new/generic-'+type+'.png'); + this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png'); } } diff --git a/js/load.js b/js/load.js index 265cc3c..461e1d7 100644 --- a/js/load.js +++ b/js/load.js @@ -201,59 +201,61 @@ function load_init(init_func) { } } +// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon. +for (const it of itemTypes) { + itemLists.set(it, []); +} + +let none_items = [ + ["armor", "helmet", "No Helmet"], + ["armor", "chestplate", "No Chestplate"], + ["armor", "leggings", "No Leggings"], + ["armor", "boots", "No Boots"], + ["accessory", "ring", "No Ring 1"], + ["accessory", "ring", "No Ring 2"], + ["accessory", "bracelet", "No Bracelet"], + ["accessory", "necklace", "No Necklace"], + ["weapon", "dagger", "No Weapon"], +]; +for (let i = 0; i < none_items.length; i++) { + let item = Object(); + item.slots = 0; + item.category = none_items[i][0]; + item.type = none_items[i][1]; + item.name = none_items[i][2]; + item.displayName = item.name; + item.set = null; + item.quest = null; + item.skillpoints = [0, 0, 0, 0, 0]; + item.has_negstat = false; + item.reqs = [0, 0, 0, 0, 0]; + item.fixID = true; + item.tier = "Normal"; + item.id = 10000 + i; + item.nDam = "0-0"; + item.eDam = "0-0"; + item.tDam = "0-0"; + item.wDam = "0-0"; + item.fDam = "0-0"; + item.aDam = "0-0"; + clean_item(item); + + none_items[i] = item; +} + function init_maps() { //warp itemMap = new Map(); /* Mapping from item names to set names. */ idMap = new Map(); redirectMap = new Map(); - for (const it of itemTypes) { - itemLists.set(it, []); - } - - let noneItems = [ - ["armor", "helmet", "No Helmet"], - ["armor", "chestplate", "No Chestplate"], - ["armor", "leggings", "No Leggings"], - ["armor", "boots", "No Boots"], - ["accessory", "ring", "No Ring 1"], - ["accessory", "ring", "No Ring 2"], - ["accessory", "bracelet", "No Bracelet"], - ["accessory", "necklace", "No Necklace"], - ["weapon", "dagger", "No Weapon"], - ]; - for (let i = 0; i < noneItems.length; i++) { - let item = Object(); - item.slots = 0; - item.category = noneItems[i][0]; - item.type = noneItems[i][1]; - item.name = noneItems[i][2]; - item.displayName = item.name; - item.set = null; - item.quest = null; - item.skillpoints = [0, 0, 0, 0, 0]; - item.has_negstat = false; - item.reqs = [0, 0, 0, 0, 0]; - item.fixID = true; - item.tier = "Normal"; - item.id = 10000 + i; - item.nDam = "0-0"; - item.eDam = "0-0"; - item.tDam = "0-0"; - item.wDam = "0-0"; - item.fDam = "0-0"; - item.aDam = "0-0"; - clean_item(item); - - noneItems[i] = item; - } - items = items.concat(noneItems); + items = items.concat(none_items); //console.log(items); for (const item of items) { if (item.remapID === undefined) { itemLists.get(item.type).push(item.displayName); itemMap.set(item.displayName, item); - if (noneItems.includes(item)) { + if (none_items.includes(item)) { idMap.set(item.id, ""); } else { diff --git a/js/optimize.js b/js/optimize.js new file mode 100644 index 0000000..6185edd --- /dev/null +++ b/js/optimize.js @@ -0,0 +1,101 @@ +function optimizeStrDex() { + if (!player_build) { + return; + } + const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; + const base_skillpoints = player_build.base_skillpoints; + const max_str_boost = 100 - base_skillpoints[0]; + const max_dex_boost = 100 - base_skillpoints[1]; + if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable + + const base_total_skillpoints = player_build.total_skillpoints; + let str_bonus = remaining; + let dex_bonus = 0; + let best_skillpoints = player_build.total_skillpoints; + let best_damage = 0; + for (let i = 0; i <= remaining; ++i) { + let total_skillpoints = base_total_skillpoints.slice(); + total_skillpoints[0] += Math.min(max_str_boost, str_bonus); + total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); + + // Calculate total 3rd spell damage + let spell = spell_table[player_build.weapon.get("type")][2]; + const stats = player_build.statMap; + let critChance = skillPointsToPercentage(total_skillpoints[1]); + let save_damages = []; + 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; + } + } + } + let total_damage = 0; + for (const part of spell_parts) { + if (part.type === "damage") { + let _results = calculateSpellDamage(stats, part.conversion, + stats.get("sdRaw"), stats.get("sdPct") + player_build.externalStats.get("sdPct"), + part.multiplier / 100, player_build.weapon, total_skillpoints, + player_build.damageMultiplier, player_build.externalStats); + let totalDamNormal = _results[0]; + let totalDamCrit = _results[1]; + let results = _results[2]; + let tooltipinfo = _results[3]; + + for (let i = 0; i < 6; ++i) { + for (let j in results[i]) { + results[i][j] = results[i][j].toFixed(2); + } + } + let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; + let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; + let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; + + save_damages.push(averageDamage); + if (part.summary == true) { + total_damage = averageDamage; + } + } else if (part.type === "total") { + total_damage = 0; + for (let i in part.factors) { + total_damage += save_damages[i] * part.factors[i]; + } + } + } // END Calculate total 3rd spell damage (total_damage) + if (total_damage > best_damage) { + best_damage = total_damage; + best_skillpoints = total_skillpoints.slice(); + } + + str_bonus -= 1; + dex_bonus += 1; + + } + // TODO: reduce duplicated code, @calculateBuild + let skillpoints = player_build.total_skillpoints; + let delta_total = 0; + for (let i in skp_order) { + let manual_assigned = best_skillpoints[i]; + let delta = manual_assigned - skillpoints[i]; + skillpoints[i] = manual_assigned; + player_build.base_skillpoints[i] += delta; + delta_total += delta; + } + player_build.assigned_skillpoints += delta_total; + + try { + calculateBuildStats(); + if (player_build.errored) + throw new ListError(player_build.errors); + } + catch (error) { + handleBuilderError(error); + } +} + diff --git a/js/utils.js b/js/utils.js index fed7df8..164543d 100644 --- a/js/utils.js +++ b/js/utils.js @@ -3,31 +3,6 @@ const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.sp const zip = (a, b) => a.map((k, i) => [k, b[i]]); -//updates all the OGP tags for a webpage. Should be called when build changes -function updateOGP() { - //update the embed URL - let url_elem = document.getElementById("ogp-url"); - if (url_elem) { - url_elem.content = url_base+location.hash; - } - - //update the embed text content - let build_elem = document.getElementById("ogp-build-list"); - if (build_elem && player_build) { - let text = "WynnBuilder build:\n"+ - "> "+player_build.helmet.get("displayName")+"\n"+ - "> "+player_build.chestplate.get("displayName")+"\n"+ - "> "+player_build.leggings.get("displayName")+"\n"+ - "> "+player_build.boots.get("displayName")+"\n"+ - "> "+player_build.ring1.get("displayName")+"\n"+ - "> "+player_build.ring2.get("displayName")+"\n"+ - "> "+player_build.bracelet.get("displayName")+"\n"+ - "> "+player_build.necklace.get("displayName")+"\n"+ - "> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]"; - build_elem.content = text; - } -} - function clamp(num, low, high){ return Math.min(Math.max(num, low), high); } @@ -412,4 +387,4 @@ async function hardReload() { function capitalizeFirst(str) { return str[0].toUpperCase() + str.substring(1); -} \ No newline at end of file +} From adcf1ee3621393fb191acffa71b9c2ba816933bb Mon Sep 17 00:00:00 2001 From: reschan Date: Mon, 23 May 2022 13:20:03 +0700 Subject: [PATCH 005/155] re-enable melee breakdown --- builder/index.html | 2 +- js/sq2bs.js | 2 ++ js/sq2display.js | 26 +++++++------------------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/builder/index.html b/builder/index.html index 2569df8..806d3e5 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1240,7 +1240,7 @@
-
melee
+
melee
diff --git a/js/sq2bs.js b/js/sq2bs.js index dce7eba..cb571d3 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -29,6 +29,8 @@ document.addEventListener('DOMContentLoaded', function() { document.querySelector("#"+i+"Avg").setAttribute("onclick", "toggle_spell_tab('"+i+"')"); } + document.querySelector("#build-melee-statsAvg").setAttribute("onclick", "toggle_spell_tab('build-melee-stats')"); + document.querySelector("#level-choice").setAttribute("oninput", "calcBuildSchedule()") document.querySelector("#weapon-choice").setAttribute("oninput", document.querySelector("#weapon-choice").getAttribute("oninput") + "resetArmorPowderSpecials();"); // document.querySelector("#edit-IDs-button").setAttribute("onclick", "toggle_edit_id_tab()"); diff --git a/js/sq2display.js b/js/sq2display.js index 07715d5..7dd1d27 100644 --- a/js/sq2display.js +++ b/js/sq2display.js @@ -692,7 +692,6 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ title_elem.classList.add("title"); title_elem.textContent = "Melee Stats"; parent_elem.append(title_elem); - parent_elem.append(document.createElement("br")); //overall title let title_elemavg = document.createElement("b"); @@ -703,9 +702,6 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ let averageDamage = document.createElement("p"); averageDamage.classList.add("left"); averageDamage.textContent = "Average DPS: " + stats[10]; - tooltiptext = `= ((${stats[8]} * ${(stats[6][2]).toFixed(2)}) + (${stats[9]} * ${(stats[7][2]).toFixed(2)}))` - tooltip = createTooltip(tooltip, "p", tooltiptext, averageDamage, ["melee-tooltip"]); - averageDamage.appendChild(tooltip); parent_elem.append(averageDamage); //overall average DPS @@ -751,8 +747,6 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ dmg.textContent = stats[i][0] + " \u2013 " + stats[i][1]; dmg.classList.add(damageClasses[i]); dmg.classList.add("itemp"); - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(0,2).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]); nonCritStats.append(dmg); } } @@ -767,15 +761,11 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ arr2.push(stats[i][1]); } } - tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + "); - tooltip = createTooltip(tooltip, "p", tooltiptext, normalDamage, ["melee-tooltip"]); nonCritStats.append(normalDamage); let normalDPS = document.createElement("p"); normalDPS.textContent = "Normal DPS: " + stats[8]; normalDPS.classList.add("tooltip"); - tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, normalDPS, ["melee-tooltip"]); nonCritStats.append(normalDPS); //overall average DPS @@ -785,9 +775,6 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ let singleHitDamageSecond = document.createElement("span"); singleHitDamageSecond.classList.add("Damage"); singleHitDamageSecond.textContent = stats[12].toFixed(2); - tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${stats[6][2].toFixed(2)} + ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${stats[7][2].toFixed(2)}`; - // tooltip = createTooltip(tooltip, "p", tooltiptext, singleHitDamage, ["melee-tooltip", "summary-tooltip"]); - singleHitDamage.appendChild(singleHitDamageFirst); singleHitDamage.appendChild(singleHitDamageSecond); overallparent_elem.append(singleHitDamage); @@ -812,8 +799,6 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ dmg.textContent = stats[i][2] + " \u2013 " + stats[i][3]; dmg.classList.add(damageClasses[i]); dmg.classList.add("itemp"); - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(2,4).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]); critStats.append(dmg); } } @@ -827,15 +812,11 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ arr2.push(stats[i][3]); } } - tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + "); - tooltip = createTooltip(tooltip, "p", tooltiptext, critDamage, ["melee-tooltip"]); critStats.append(critDamage); let critDPS = document.createElement("p"); critDPS.textContent = "Crit DPS: " + stats[9]; - tooltiptext = ` = ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, critDPS, ["melee-tooltip"]); critStats.append(critDPS); let critChance = document.createElement("p"); @@ -845,6 +826,13 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ critStats.append(critChance); parent_elem.append(critStats); + + //up and down arrow - done ugly + let arrow = document.createElement("img"); + arrow.id = "arrow_" + overallparent_elem.id; + arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem"; + arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png"; + overallparent_elem.appendChild(arrow); } function displaysq2ArmorStats(build) { From 4f7f0f9cfc20a48b51bcb7f9aa4c1e63d753ffd4 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 12 Jun 2022 07:45:48 -0700 Subject: [PATCH 006/155] Clean up loading code, much cleaner invocation during init (and less prone to races) --- js/builder.js | 17 ++-- js/builder_graph.js | 194 ++++++++++++++++++++++++++++++++++++++++++++ js/load.js | 166 ++++++++++++++++--------------------- js/load_ing.js | 143 +++++++++++++++++--------------- js/load_tome.js | 128 ++++++++++++++--------------- 5 files changed, 411 insertions(+), 237 deletions(-) diff --git a/js/builder.js b/js/builder.js index 1b18b96..1e51e31 100644 --- a/js/builder.js +++ b/js/builder.js @@ -335,16 +335,11 @@ function init() { console.log("builder.js init"); init_autocomplete(); decodeBuild(url_tag); - for (const i of equipment_keys) { - update_field(i); - } } -function init2() { - load_ing_init(init); -} -function init3() { - load_tome_init(init2) -} - -load_init(init3); +//load_init(init3); +(async function() { + let load_promises = [ load_init(), load_ing_init(), load_tome_init() ]; + await Promise.all(load_promises); + init(); +})(); diff --git a/js/builder_graph.js b/js/builder_graph.js index 7b652ba..fd0de13 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -19,3 +19,197 @@ document.addEventListener('DOMContentLoaded', function() { console.log("Set up graph"); }); + +// autocomplete initialize +function init_autocomplete() { + console.log("autocomplete init"); + console.log(itemLists) + let dropdowns = new Map(); + for (const eq of equipment_keys) { + if (tome_keys.includes(eq)) { + continue; + } + // build dropdown + let item_arr = []; + if (eq == 'weapon') { + for (const weaponType of weapon_keys) { + for (const weapon of itemLists.get(weaponType)) { + let item_obj = itemMap.get(weapon); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { + continue; + } + item_arr.push(weapon); + } + } + } else { + for (const item of itemLists.get(eq.replace(/[0-9]/g, ''))) { + let item_obj = itemMap.get(item); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { + continue; + } + item_arr.push(item) + } + } + + // create dropdown + dropdowns.set(eq, new autoComplete({ + data: { + src: item_arr + }, + selector: "#"+ eq +"-choice", + wrapper: false, + resultsList: { + maxResults: 1000, + tabSelect: true, + noResults: true, + class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", + element: (list, data) => { + // dynamic result loc + let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); + list.style.top = position.bottom + window.scrollY +"px"; + list.style.left = position.x+"px"; + list.style.width = position.width+"px"; + list.style.maxHeight = position.height * 2 +"px"; + + if (!data.results.length) { + message = document.createElement('li'); + message.classList.add('scaled-font'); + message.textContent = "No results found!"; + list.prepend(message); + } + }, + }, + resultItem: { + class: "scaled-font search-item", + selected: "dark-5", + element: (item, data) => { + item.classList.add(itemMap.get(data.value).tier); + }, + }, + events: { + input: { + selection: (event) => { + if (event.detail.selection.value) { + event.target.value = event.detail.selection.value; + } + }, + }, + } + })); + } + + for (const eq of tome_keys) { + // build dropdown + let tome_arr = []; + for (const tome of tomeLists.get(eq.replace(/[0-9]/g, ''))) { + let tome_obj = tomeMap.get(tome); + if (tome_obj["restrict"] && tome_obj["restrict"] === "DEPRECATED") { + continue; + } + //this should suffice for tomes - jank + if (tome_obj["name"].includes('No ' + eq.charAt(0).toUpperCase())) { + continue; + } + let tome_name = tome; + tome_arr.push(tome_name); + } + + // create dropdown + dropdowns.set(eq, new autoComplete({ + data: { + src: tome_arr + }, + selector: "#"+ eq +"-choice", + wrapper: false, + resultsList: { + maxResults: 1000, + tabSelect: true, + noResults: true, + class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", + element: (list, data) => { + // dynamic result loc + let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); + list.style.top = position.bottom + window.scrollY +"px"; + list.style.left = position.x+"px"; + list.style.width = position.width+"px"; + list.style.maxHeight = position.height * 2 +"px"; + + if (!data.results.length) { + message = document.createElement('li'); + message.classList.add('scaled-font'); + message.textContent = "No results found!"; + list.prepend(message); + } + }, + }, + resultItem: { + class: "scaled-font search-item", + selected: "dark-5", + element: (tome, data) => { + tome.classList.add(tomeMap.get(data.value).tier); + }, + }, + events: { + input: { + selection: (event) => { + if (event.detail.selection.value) { + event.target.value = event.detail.selection.value; + } + }, + }, + } + })); + } + + let filter_loc = ["filter1", "filter2", "filter3", "filter4"]; + for (const i of filter_loc) { + dropdowns.set(i+"-choice", new autoComplete({ + data: { + src: sq2ItemFilters, + }, + selector: "#"+i+"-choice", + wrapper: false, + resultsList: { + tabSelect: true, + noResults: true, + class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", + element: (list, data) => { + // dynamic result loc + console.log(i); + list.style.zIndex = "100"; + let position = document.getElementById(i+"-dropdown").getBoundingClientRect(); + window_pos = document.getElementById("search-container").getBoundingClientRect(); + list.style.top = position.bottom - window_pos.top + 5 +"px"; + list.style.left = position.x - window_pos.x +"px"; + list.style.width = position.width+"px"; + + if (!data.results.length) { + message = document.createElement('li'); + message.classList.add('scaled-font'); + message.textContent = "No filters found!"; + list.prepend(message); + } + }, + }, + resultItem: { + class: "scaled-font search-item", + selected: "dark-5", + }, + events: { + input: { + selection: (event) => { + if (event.detail.selection.value) { + event.target.value = event.detail.selection.value; + } + }, + }, + } + })); + } +} diff --git a/js/load.js b/js/load.js index 461e1d7..4f6e4b2 100644 --- a/js/load.js +++ b/js/load.js @@ -14,42 +14,35 @@ let itemLists = new Map(); /* * Load item set from local DB. Calls init() on success. */ -async function load_local(init_func) { - let get_tx = db.transaction(['item_db', 'set_db'], 'readonly'); - let sets_store = get_tx.objectStore('set_db'); - let get_store = get_tx.objectStore('item_db'); - let request = get_store.getAll(); - request.onerror = function(event) { - console.log("Could not read local item db..."); - } - request.onsuccess = function(event) { - console.log("Successfully read local item db."); - items = request.result; - //console.log(items); - let request2 = sets_store.openCursor(); +async function load_local() { + return new Promise(function(resolve, reject) { + let get_tx = db.transaction(['item_db', 'set_db'], 'readonly'); + let sets_store = get_tx.objectStore('set_db'); + let get_store = get_tx.objectStore('item_db'); + let request = get_store.getAll(); + let request2 = sets_store.getAll(); + request.onerror = function(event) { + reject("Could not read local item db..."); + } + request.onsuccess = function(event) { + console.log("Successfully read local item db."); + } - sets = {}; request2.onerror = function(event) { - console.log("Could not read local set db..."); + reject("Could not read local set db..."); } - request2.onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - sets[cursor.primaryKey] = cursor.value; - cursor.continue(); - } - else { - console.log("Successfully read local set db."); - //console.log(sets); - init_maps(); - init_func(); - load_complete = true; - } + console.log("Successfully read local set db."); } - } - await get_tx.complete; - db.close(); + get_tx.oncomplete = function(event) { + items = request.result; + sets = request2.result; + init_maps(); + load_complete = true; + db.close(); + resolve(); + } + }); } /* @@ -91,7 +84,7 @@ function clean_item(item) { /* * Load item set from remote DB (aka a big json file). Calls init() on success. */ -async function load(init_func) { +async function load() { let getUrl = window.location; let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1]; @@ -101,16 +94,6 @@ async function load(init_func) { items = result.items; sets = result.sets; - - -// let clear_tx = db.transaction(['item_db', 'set_db'], 'readwrite'); -// let clear_items = clear_tx.objectStore('item_db'); -// let clear_sets = clear_tx.objectStore('item_db'); -// -// await clear_items.clear(); -// await clear_sets.clear(); -// await clear_tx.complete; - let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite'); add_tx.onabort = function(e) { console.log(e); @@ -131,74 +114,67 @@ async function load(init_func) { add_promises.push(sets_store.add(sets[set], set)); } add_promises.push(add_tx.complete); - Promise.all(add_promises).then((values) => { - init_maps(); - init_func(); - load_complete = true; - }); - // DB not closed? idfk man + + await Promise.all(add_promises); + init_maps(); + load_complete = true; + db.close(); } -function load_init(init_func) { - if (load_complete) { - console.log("Item db already loaded, skipping load sequence"); - init_func(); - return; - } - let request = window.indexedDB.open('item_db', DB_VERSION); +async function load_init() { + return new Promise((resolve, reject) => { + let request = window.indexedDB.open('item_db', DB_VERSION); - request.onerror = function() { - console.log("DB failed to open..."); - }; + request.onerror = function() { + reject("DB failed to open..."); + }; - request.onsuccess = function() { - (async function() { + request.onsuccess = async function() { db = request.result; - if (!reload) { - console.log("Using stored data...") - load_local(init_func); + if (load_in_progress) { + while (!load_complete) { + await sleep(100); + } + console.log("Skipping load...") } else { - if (load_in_progress) { - while (!load_complete) { - await sleep(100); - } - console.log("Skipping load...") - init_func(); + load_in_progress = true + if (reload) { + console.log("Using new data...") + await load(); } else { - // Not 100% safe... whatever! - load_in_progress = true - console.log("Using new data...") - load(init_func); + console.log("Using stored data...") + await load_local(); } } - })() - } + resolve(); + }; - request.onupgradeneeded = function(e) { - reload = true; + request.onupgradeneeded = function(e) { + reload = true; - let db = e.target.result; - - try { - db.deleteObjectStore('item_db'); - } - catch (error) { - console.log("Could not delete item DB. This is probably fine"); - } - try { - db.deleteObjectStore('set_db'); - } - catch (error) { - console.log("Could not delete set DB. This is probably fine"); - } + let db = e.target.result; + + try { + db.deleteObjectStore('item_db'); + } + catch (error) { + console.log("Could not delete item DB. This is probably fine"); + } + try { + db.deleteObjectStore('set_db'); + } + catch (error) { + console.log("Could not delete set DB. This is probably fine"); + } - db.createObjectStore('item_db'); - db.createObjectStore('set_db'); + db.createObjectStore('item_db'); + db.createObjectStore('set_db'); - console.log("DB setup complete..."); - } + console.log("DB setup complete..."); + }; + }); } // List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon. diff --git a/js/load_ing.js b/js/load_ing.js index 1a6144e..ffc4c05 100644 --- a/js/load_ing.js +++ b/js/load_ing.js @@ -4,6 +4,7 @@ const ING_DB_VERSION = 13; let idb; let ireload = false; +let iload_in_progress = false; let iload_complete = false; let ings; let recipes; @@ -20,32 +21,34 @@ let recipeIDMap; /* * Load item set from local DB. Calls init() on success. */ -async function ing_load_local(init_func) { - console.log("IngMap is: \n " + ingMap); - let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly'); - let ings_store = get_tx.objectStore('ing_db'); - let recipes_store = get_tx.objectStore('recipe_db'); - let request3 = ings_store.getAll(); - request3.onerror = function(event) { - console.log("Could not read local ingredient db..."); - } - request3.onsuccess = function(event) { - console.log("Successfully read local ingredient db."); - ings = request3.result; +async function ing_load_local() { + return new Promise(function(resolve, reject) { + let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly'); + let ings_store = get_tx.objectStore('ing_db'); + let recipes_store = get_tx.objectStore('recipe_db'); + let request3 = ings_store.getAll(); + request3.onerror = function(event) { + reject("Could not read local ingredient db..."); + } + request3.onsuccess = function(event) { + console.log("Successfully read local ingredient db."); + } let request4 = recipes_store.getAll(); request4.onerror = function(event) { - console.log("Could not read local recipe db..."); + reject("Could not read local recipe db..."); } request4.onsuccess = function(event) { console.log("Successfully read local recipe db."); + } + get_tx.oncomplete = function(event) { + ings = request3.result; recipes = request4.result; init_ing_maps(); - init_func(); iload_complete = true; + idb.close(); + resolve() } - } - await get_tx.complete; - idb.close(); + }); } function clean_ing(ing) { @@ -59,11 +62,12 @@ function clean_ing(ing) { /* * Load item set from remote DB (aka a big json file). Calls init() on success. */ -async function load_ings(init_func) { +async function load_ings() { let getUrl = window.location; - let baseUrl = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1]; - let url = baseUrl + "/ingreds_compress.json"; + let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1]; + // "Random" string to prevent caching! + let url = baseUrl + "/ingreds_compress.json?"+new Date(); url = url.replace(/\w+.html/, "") ; let result = await (await fetch(url)).json(); @@ -97,59 +101,65 @@ async function load_ings(init_func) { } add_promises.push(add_tx2.complete); add_promises.push(add_tx3.complete); - Promise.all(add_promises).then((values) => { - init_ing_maps(); - init_func(); - iload_complete = true; - }); - // DB not closed? idfk man + + await Promise.all(add_promises); + init_ing_maps(); + iload_complete = true; + idb.close(); } -function load_ing_init(init_func) { - if (iload_complete) { - console.log("Ingredient db already loaded, skipping load sequence"); - init_func(); - return; - } - let request = window.indexedDB.open("ing_db", ING_DB_VERSION) - request.onerror = function() { - console.log("DB failed to open..."); - } +async function load_ing_init() { + return new Promise((resolve, reject) => { + let request = window.indexedDB.open("ing_db", ING_DB_VERSION) + request.onerror = function() { + reject("DB failed to open..."); + } - request.onsuccess = function() { - idb = request.result; - if (!ireload) { - console.log("Using stored data...") - ing_load_local(init_func); + request.onsuccess = async function() { + idb = request.result; + if (iload_in_progress) { + while (!iload_complete) { + await sleep(100); + } + console.log("Skipping load...") + } + else { + iload_in_progress = true + if (ireload) { + console.log("Using new data...") + await load_ings(); + } + else { + console.log("Using stored data...") + await ing_load_local(); + } + } + resolve(); } - else { - console.log("Using new data...") - load_ings(init_func); - } - } - request.onupgradeneeded = function(e) { - ireload = true; + request.onupgradeneeded = function(e) { + ireload = true; - let idb = e.target.result; - - try { - idb.deleteObjectStore('ing_db'); - } - catch (error) { - console.log("Could not delete ingredient DB. This is probably fine"); - } - try { - idb.deleteObjectStore('recipe_db'); - } - catch (error) { - console.log("Could not delete recipe DB. This is probably fine"); - } - idb.createObjectStore('ing_db'); - idb.createObjectStore('recipe_db'); + let idb = e.target.result; + + try { + idb.deleteObjectStore('ing_db'); + } + catch (error) { + console.log("Could not delete ingredient DB. This is probably fine"); + } + try { + idb.deleteObjectStore('recipe_db'); + } + catch (error) { + console.log("Could not delete recipe DB. This is probably fine"); + } + idb.createObjectStore('ing_db'); + idb.createObjectStore('recipe_db'); - console.log("DB setup complete..."); - } + console.log("DB setup complete..."); + } + }); } function init_ing_maps() { @@ -222,4 +232,5 @@ function init_ing_maps() { recipeList.push(recipe["name"]); recipeIDMap.set(recipe["id"],recipe["name"]); } + console.log(ingMap); } diff --git a/js/load_tome.js b/js/load_tome.js index afd9315..2186a60 100644 --- a/js/load_tome.js +++ b/js/load_tome.js @@ -13,29 +13,33 @@ let tomeLists = new Map(); /* * Load tome set from local DB. Calls init() on success. */ -async function load_tome_local(init_func) { - let get_tx = tdb.transaction(['tome_db'], 'readonly'); - let get_store = get_tx.objectStore('tome_db'); - let request = get_store.getAll(); - request.onerror = function(event) { - console.log("Could not read local tome db..."); - } - request.onsuccess = function(event) { - console.log("Successfully read local tome db."); - tomes = request.result; - - init_tome_maps(); - init_func(); - tload_complete = true; - } - await get_tx.complete; - tdb.close(); +async function load_tome_local() { + return new Promise(function(resolve, reject) { + let get_tx = tdb.transaction(['tome_db'], 'readonly'); + let get_store = get_tx.objectStore('tome_db'); + let request = get_store.getAll(); + request.onerror = function(event) { + reject("Could not read local tome db..."); + } + request.onsuccess = function(event) { + console.log("Successfully read local tome db."); + } + get_tx.oncomplete = function(event) { + console.log('b'); + console.log(request.readyState); + tomes = request.result; + init_tome_maps(); + tload_complete = true; + tdb.close(); + resolve(); + } + }); } /* * Load tome set from remote DB (json). Calls init() on success. */ -async function load_tome(init_func) { +async function load_tome() { let getUrl = window.location; let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1]; @@ -60,67 +64,61 @@ async function load_tome(init_func) { }; add_promises.push(req); } - Promise.all(add_promises).then((values) => { - init_tome_maps(); - init_func(); - tload_complete = true; - }); - // DB not closed? idfk man + add_promises.push(add_tx.complete); + + await Promise.all(add_promises); + init_tome_maps(); + tload_complete = true; + tdb.close(); } -function load_tome_init(init_func) { - if (tload_complete) { - console.log("Tome db already loaded, skipping load sequence"); - init_func(); - return; - } - let request = window.indexedDB.open('tome_db', TOME_DB_VERSION); +async function load_tome_init() { + return new Promise((resolve, reject) => { + let request = window.indexedDB.open('tome_db', TOME_DB_VERSION); - request.onerror = function() { - console.log("DB failed to open..."); - }; + request.onerror = function() { + reject("DB failed to open..."); + }; - request.onsuccess = function() { - (async function() { + request.onsuccess = async function() { tdb = request.result; - if (!treload) { - console.log("Using stored data...") - load_tome_local(init_func); + if (tload_in_progress) { + while (!tload_complete) { + await sleep(100); + } + console.log("Skipping load...") } else { - if (tload_in_progress) { - while (!tload_complete) { - await sleep(100); - } - console.log("Skipping load...") - init_func(); + tload_in_progress = true + if (treload) { + console.log("Using new data...") + await load_tome(); } else { - // Not 100% safe... whatever! - tload_in_progress = true - console.log("Using new data...") - load_tome(init_func); + console.log("Using stored data...") + await load_tome_local(); } } - })() - } - - request.onupgradeneeded = function(e) { - treload = true; - - let tdb = e.target.result; - - try { - tdb.deleteObjectStore('tome_db'); - } - catch (error) { - console.log("Could not delete tome DB. This is probably fine"); + resolve(); } - tdb.createObjectStore('tome_db'); + request.onupgradeneeded = function(e) { + treload = true; - console.log("DB setup complete..."); - } + let tdb = e.target.result; + + try { + tdb.deleteObjectStore('tome_db'); + } + catch (error) { + console.log("Could not delete tome DB. This is probably fine"); + } + + tdb.createObjectStore('tome_db'); + + console.log("DB setup complete..."); + } + }); } function init_tome_maps() { From d4357d5d6b26068f092c07edca6902401680e0d0 Mon Sep 17 00:00:00 2001 From: ferricles Date: Sun, 12 Jun 2022 20:54:53 -0700 Subject: [PATCH 007/155] merge conflicts + first part of refactor --- js/builder_graph.js | 23 +++++++++++++++++++++++ py_script/ci_parse.py | 2 ++ py_script/ci_scrape.py | 2 ++ py_script/clean_json.py | 13 +++++++++++++ py_script/compress_json.py | 19 ++++++++++++------- py_script/dump.py | 22 ---------------------- py_script/get.py | 33 +++++++++++++++++++++++++++++++++ py_script/image_get.py | 4 ++++ 8 files changed, 89 insertions(+), 29 deletions(-) create mode 100644 js/builder_graph.js create mode 100644 py_script/clean_json.py delete mode 100644 py_script/dump.py create mode 100644 py_script/get.py diff --git a/js/builder_graph.js b/js/builder_graph.js new file mode 100644 index 0000000..34e6f9b --- /dev/null +++ b/js/builder_graph.js @@ -0,0 +1,23 @@ + + +let item_nodes = []; + +document.addEventListener('DOMContentLoaded', function() { + for (const [eq, none_item] of zip(equipment_fields, none_items)) { + let input_field = document.getElementById(eq+"-choice"); + let item_image = document.getElementById(eq+"-img"); + + // let item_dropdown + + let item_input = new ItemInputNode(eq+'-input', input_field, none_item); + item_nodes.push(item_input); + new ItemInputDisplayNode(eq+'-display', input_field, item_image).link_to(item_input); + new PrintNode(eq+'-debug').link_to(item_input); + //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); + + } + let weapon_image = document.getElementById("weapon-img"); + new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); + console.log("Set up graph"); + +}); diff --git a/py_script/ci_parse.py b/py_script/ci_parse.py index fc1772a..6318069 100644 --- a/py_script/ci_parse.py +++ b/py_script/ci_parse.py @@ -1,3 +1,5 @@ +#parses all CI and creates a json file with all of them + import os import re diff --git a/py_script/ci_scrape.py b/py_script/ci_scrape.py index a4ed7c6..92ab880 100644 --- a/py_script/ci_scrape.py +++ b/py_script/ci_scrape.py @@ -1,3 +1,5 @@ +#looks like something that hpp does with curl + import os with open("ci.txt.2") as infile: diff --git a/py_script/clean_json.py b/py_script/clean_json.py new file mode 100644 index 0000000..81276c3 --- /dev/null +++ b/py_script/clean_json.py @@ -0,0 +1,13 @@ +''' +A generic file used for turning a json into a compressed version of itself (minimal whitespaces). +Compressed files are useful for lowering the amount of data sent. + +Usage: python clean_json.py [infile rel path] [outfile rel path] +''' + +if __name__ == "__main__": + import sys + import json + infile = sys.argv[1] + outfile = sys.argv[2] + json.dump(json.load(open(infile)), open(outfile, "w"), indent = 2) diff --git a/py_script/compress_json.py b/py_script/compress_json.py index c4d4899..a826c36 100644 --- a/py_script/compress_json.py +++ b/py_script/compress_json.py @@ -1,8 +1,13 @@ -import sys -import json -infile = sys.argv[1] -outfile = sys.argv[2] -if len(sys.argv) > 3 and sys.argv[3] == "decompress": - json.dump(json.load(open(infile)), open(outfile, "w"), indent=4) -else: +''' +A generic file used for turning a json into a "clean" version of itself (human-friendly whitespace). +Clean files are useful for human reading and dev debugging. + +Usage: python compress_json.py [infile rel path] [outfile rel path] +''' + +if __name__ == "__main__": + import sys + import json + infile = sys.argv[1] + outfile = sys.argv[2] json.dump(json.load(open(infile)), open(outfile, "w")) diff --git a/py_script/dump.py b/py_script/dump.py deleted file mode 100644 index d649c7e..0000000 --- a/py_script/dump.py +++ /dev/null @@ -1,22 +0,0 @@ -import requests -import json -import numpy as np - -response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&category=all") - -with open("dump.json", "w") as outfile: - outfile.write(json.dumps(response.json())) - -arr = np.array([]) -for i in range(4): - response = requests.get("https://api.wynncraft.com/v2/ingredient/search/tier/" + str(i)) - arr = np.append(arr, np.array(response.json()['data'])) - -with open("../ingreds.json", "w") as outfile: - outfile.write(json.dumps(list(arr))) - -with open("../ingreds_compress.json", "w") as outfile: - outfile.write(json.dumps(list(arr))) - -with open("../ingreds_clean.json", "w") as outfile: - json.dump(list(arr), outfile, indent = 2) #needs further cleaning diff --git a/py_script/get.py b/py_script/get.py new file mode 100644 index 0000000..ae5ba2b --- /dev/null +++ b/py_script/get.py @@ -0,0 +1,33 @@ +""" +Used to GET data from the Wynncraft API. Has shorthand options and allows +for requesting from a specific url. + +Usage: python get.py [url or command] [outfile rel path] + +Relevant page: https://docs.wynncraft.com/ +""" + +if __name__ == "__main__": + import requests + import json + import numpy as np + import sys + + #req can either be a link to an API page OR a preset default + req = sys.argv[1] + outfile = sys.argv[2] + response = {} #default to empty file output + + if req.lower() is "items": + response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&category=all") + elif req.lower() is "ings": + response = requests.get("https://api.wynncraft.com/v2/ingredient/list") + elif req.lower() is "recipes": + response = requests.get("https://api.wynncraft.com/v2/recipe/list") + else: + response = requests.get(req) + + with open("dump.json", "w") as outfile: + outfile.write(json.dumps(response.json())) + + json.dump(response.json(), open(outfile, "w")) \ No newline at end of file diff --git a/py_script/image_get.py b/py_script/image_get.py index ebe22a3..9e3eced 100644 --- a/py_script/image_get.py +++ b/py_script/image_get.py @@ -1,3 +1,7 @@ +""" +Used for grabbing image files at some point. Not used recently. +""" + import os import json From 4387da07b0d21a7812e55559797799311a2429fe Mon Sep 17 00:00:00 2001 From: ferricles Date: Wed, 15 Jun 2022 11:55:04 -0700 Subject: [PATCH 008/155] python refactor pt 2 --- py_script/README.md | 10 +- py_script/get.py | 67 ++++-- py_script/id_map.json | 10 +- ing_map.json => py_script/ing_map.json | 0 py_script/json_diff.py | 6 +- py_script/parse_log.py | 6 + py_script/parse_set_individual.py | 6 + py_script/parse_sets.py | 5 + py_script/plot_dps.py | 4 + ...g_transform_combine.py => process_ings.py} | 41 ++-- ...transform_preserve.py => process_items.py} | 69 +++--- ...ransform_combine.py => process_recipes.py} | 35 +-- recipe_map.json => py_script/recipe_map.json | 0 py_script/recipes.py | 33 --- py_script/script.py | 9 - py_script/sets/_Adventurer%27s.json | 39 ---- py_script/sets/_Air+Relic.json | 30 --- py_script/sets/_Bandit%27s.json | 26 --- py_script/sets/_Beachside.json | 14 -- py_script/sets/_Bear.json | 15 -- py_script/sets/_Black+Catalyst.json | 19 -- py_script/sets/_Black.json | 29 --- py_script/sets/_Blue+Team.json | 14 -- py_script/sets/_Bony.json | 14 -- py_script/sets/_Builder%27s.json | 20 -- py_script/sets/_Champion.json | 21 -- py_script/sets/_Clock.json | 58 ----- py_script/sets/_Corrupted+Nii.json | 24 -- py_script/sets/_Corrupted+Uth.json | 24 -- py_script/sets/_Cosmic.json | 44 ---- py_script/sets/_Earth+Relic.json | 30 --- py_script/sets/_Elf.json | 33 --- py_script/sets/_Fire+Relic.json | 30 --- py_script/sets/_Flashfire.json | 22 -- py_script/sets/_GM%27s.json | 20 -- py_script/sets/_Ghostly.json | 35 --- py_script/sets/_Goblin.json | 30 --- py_script/sets/_Hallowynn+2016.json | 14 -- py_script/sets/_Horse.json | 16 -- py_script/sets/_Jester.json | 37 --- py_script/sets/_Kaerynn%27s.json | 17 -- py_script/sets/_Leaf.json | 29 --- py_script/sets/_Morph.json | 73 ------ py_script/sets/_Nether.json | 33 --- py_script/sets/_Outlaw.json | 29 --- py_script/sets/_Pigman.json | 13 -- py_script/sets/_Red+Team.json | 14 -- py_script/sets/_Relic.json | 46 ---- py_script/sets/_Saint%27s.json | 38 ---- py_script/sets/_Silverfish.json | 17 -- py_script/sets/_Skien%27s.json | 24 -- py_script/sets/_Slime.json | 17 -- py_script/sets/_Snail.json | 38 ---- py_script/sets/_Snow.json | 32 --- py_script/sets/_Spider.json | 24 -- py_script/sets/_Spore.json | 14 -- py_script/sets/_Thanos+Legionnaire.json | 42 ---- py_script/sets/_Thunder+Relic.json | 30 --- py_script/sets/_Tribal.json | 27 --- py_script/sets/_Ultramarine.json | 35 --- py_script/sets/_Veekhat%27s.json | 15 -- py_script/sets/_Vexing.json | 16 -- py_script/sets/_Villager.json | 14 -- py_script/sets/_Visceral.json | 35 --- py_script/sets/_Water+Relic.json | 30 --- py_script/skillpoint_test.py | 7 +- py_script/terrs.py | 63 ------ py_script/transform_combine.py | 179 --------------- py_script/transform_merge.py | 214 ------------------ py_script/update_sets_in_items.py | 25 -- py_script/update_tomes_in_items.py | 42 ---- py_script/validate.py | 9 + 72 files changed, 176 insertions(+), 2024 deletions(-) rename ing_map.json => py_script/ing_map.json (100%) rename py_script/{ing_transform_combine.py => process_ings.py} (89%) rename py_script/{transform_preserve.py => process_items.py} (76%) rename py_script/{recipe_transform_combine.py => process_recipes.py} (57%) rename recipe_map.json => py_script/recipe_map.json (100%) delete mode 100644 py_script/recipes.py delete mode 100644 py_script/script.py delete mode 100644 py_script/sets/_Adventurer%27s.json delete mode 100644 py_script/sets/_Air+Relic.json delete mode 100644 py_script/sets/_Bandit%27s.json delete mode 100644 py_script/sets/_Beachside.json delete mode 100644 py_script/sets/_Bear.json delete mode 100644 py_script/sets/_Black+Catalyst.json delete mode 100644 py_script/sets/_Black.json delete mode 100644 py_script/sets/_Blue+Team.json delete mode 100644 py_script/sets/_Bony.json delete mode 100644 py_script/sets/_Builder%27s.json delete mode 100644 py_script/sets/_Champion.json delete mode 100644 py_script/sets/_Clock.json delete mode 100644 py_script/sets/_Corrupted+Nii.json delete mode 100644 py_script/sets/_Corrupted+Uth.json delete mode 100644 py_script/sets/_Cosmic.json delete mode 100644 py_script/sets/_Earth+Relic.json delete mode 100644 py_script/sets/_Elf.json delete mode 100644 py_script/sets/_Fire+Relic.json delete mode 100644 py_script/sets/_Flashfire.json delete mode 100644 py_script/sets/_GM%27s.json delete mode 100644 py_script/sets/_Ghostly.json delete mode 100644 py_script/sets/_Goblin.json delete mode 100644 py_script/sets/_Hallowynn+2016.json delete mode 100644 py_script/sets/_Horse.json delete mode 100644 py_script/sets/_Jester.json delete mode 100644 py_script/sets/_Kaerynn%27s.json delete mode 100644 py_script/sets/_Leaf.json delete mode 100644 py_script/sets/_Morph.json delete mode 100644 py_script/sets/_Nether.json delete mode 100644 py_script/sets/_Outlaw.json delete mode 100644 py_script/sets/_Pigman.json delete mode 100644 py_script/sets/_Red+Team.json delete mode 100644 py_script/sets/_Relic.json delete mode 100644 py_script/sets/_Saint%27s.json delete mode 100644 py_script/sets/_Silverfish.json delete mode 100644 py_script/sets/_Skien%27s.json delete mode 100644 py_script/sets/_Slime.json delete mode 100644 py_script/sets/_Snail.json delete mode 100644 py_script/sets/_Snow.json delete mode 100644 py_script/sets/_Spider.json delete mode 100644 py_script/sets/_Spore.json delete mode 100644 py_script/sets/_Thanos+Legionnaire.json delete mode 100644 py_script/sets/_Thunder+Relic.json delete mode 100644 py_script/sets/_Tribal.json delete mode 100644 py_script/sets/_Ultramarine.json delete mode 100644 py_script/sets/_Veekhat%27s.json delete mode 100644 py_script/sets/_Vexing.json delete mode 100644 py_script/sets/_Villager.json delete mode 100644 py_script/sets/_Visceral.json delete mode 100644 py_script/sets/_Water+Relic.json delete mode 100644 py_script/terrs.py delete mode 100644 py_script/transform_combine.py delete mode 100644 py_script/transform_merge.py delete mode 100644 py_script/update_sets_in_items.py delete mode 100644 py_script/update_tomes_in_items.py diff --git a/py_script/README.md b/py_script/README.md index 656b911..89a1feb 100644 --- a/py_script/README.md +++ b/py_script/README.md @@ -1,8 +1,6 @@ Process for getting new data: -1. run `python3 dump.py`. This will overwrite `dump.json` and `../ingreds.json` -2. Copy `../old clean.json` or `../compress.json` into `updated.json` -3. Run `python3 transform_merge.py` -4. Run `python3 ing_transform_combine.py` -5. Check validity (json differ or whatever) -6. Copy `clean.json` and `compress.json` into toplevel for usage +1. Get new data from API with `get.py` +2. Clean the data (may have to do manually) with the `process` related py files +3. Check validity (json differ or whatever) +4. Create clean and compress versions and copy them into toplevel for usage (can use `clean_json.py` and `compress_json.py` for this). diff --git a/py_script/get.py b/py_script/get.py index ae5ba2b..9daa4fc 100644 --- a/py_script/get.py +++ b/py_script/get.py @@ -7,27 +7,54 @@ Usage: python get.py [url or command] [outfile rel path] Relevant page: https://docs.wynncraft.com/ """ -if __name__ == "__main__": - import requests - import json - import numpy as np - import sys +import requests +import json +import numpy as np +import sys - #req can either be a link to an API page OR a preset default - req = sys.argv[1] - outfile = sys.argv[2] - response = {} #default to empty file output +#req can either be a link to an API page OR a preset default +req, outfile = sys.argv[1], sys.argv[2] - if req.lower() is "items": - response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&category=all") - elif req.lower() is "ings": - response = requests.get("https://api.wynncraft.com/v2/ingredient/list") - elif req.lower() is "recipes": - response = requests.get("https://api.wynncraft.com/v2/recipe/list") - else: - response = requests.get(req) +CURR_WYNN_VERS = 2.0 - with open("dump.json", "w") as outfile: - outfile.write(json.dumps(response.json())) +#default to empty file output +response = {} - json.dump(response.json(), open(outfile, "w")) \ No newline at end of file +if req.lower() == "items": + response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&category=all") +elif req.lower() == "ings": + response = {"ings":[]} + for i in range(4): + response['ings'].extend(requests.get("https://api.wynncraft.com/v2/ingredient/search/tier/" + str(i)).json()['data']) +elif req.lower() == "recipes": + temp = requests.get("https://api.wynncraft.com/v2/recipe/list") + response = {"recipes":[]} + for i in range(len(temp['data'])): + response["recipes"].extend(requests.get("https://api.wynncraft.com/v2/recipe/get/" + temp['data'][i]).json()['data']) + print("" + str(i) + " / " + str(len(temp['data']))) +elif req.lower() == "terrs": + response = requests.get("https://api.wynncraft.com/public_api.php?action=territoryList").json()['territories'] + delkeys = ["territory","acquired","attacker"] + for t in response: + for key in delkeys: + del response[t][key] + response[t]["neighbors"] = [] + + #Dependency on a third-party manually-collected data source. May not update in sync with API. + terr_data = requests.get("https://gist.githubusercontent.com/kristofbolyai/87ae828ecc740424c0f4b3749b2287ed/raw/0735f2e8bb2d2177ba0e7e96ade421621070a236/territories.json").json() + for t in data: + response[t]["neighbors"] = data[t]["Routes"] + response[t]["resources"] = data[t]["Resources"] + response[t]["storage"] = data[t]["Storage"] + response[t]["emeralds"] = data[t]["Emeralds"] + response[t]["doubleemeralds"] = data[t]["DoubleEmerald"] + response[t]["doubleresource"] = data[t]["DoubleResource"] + +elif req.lower() == "maploc": + response = requests.get('https://api.wynncraft.com/public_api.php?action=mapLocations') +else: + response = requests.get(req) + +response['version'] = CURR_WYNN_VERS + +json.dump(response, open(outfile, "w+")) \ No newline at end of file diff --git a/py_script/id_map.json b/py_script/id_map.json index 4fd47dd..aedceff 100644 --- a/py_script/id_map.json +++ b/py_script/id_map.json @@ -3646,5 +3646,11 @@ "Narcissist": 3648, "Mask of the Spirits": 3649, "Inhibitor": 3650, - "Spear of Testiness": 3651 -} + "Spear of Testiness": 3651, + "Blue Wynnter Sweater": 3648, + "Green Wynnter Sweater": 3649, + "Purple Wynnter Sweater": 3650, + "Red Wynnter Sweater": 3651, + "Snowtread Boots": 3652, + "White Wynnter Sweater": 3653 +} \ No newline at end of file diff --git a/ing_map.json b/py_script/ing_map.json similarity index 100% rename from ing_map.json rename to py_script/ing_map.json diff --git a/py_script/json_diff.py b/py_script/json_diff.py index 6e47cea..11ce04e 100644 --- a/py_script/json_diff.py +++ b/py_script/json_diff.py @@ -1,4 +1,8 @@ -"""Json diff checker for manual testing.""" +""" +Json diff checker for manual testing - mainly debug + + +""" import argparse import json diff --git a/py_script/parse_log.py b/py_script/parse_log.py index cc2b6ed..946da03 100644 --- a/py_script/parse_log.py +++ b/py_script/parse_log.py @@ -1,3 +1,9 @@ +""" +Used to parse a changelog at some point in the past. Could be used in the future. + +Not a typically used file +""" + import json import difflib diff --git a/py_script/parse_set_individual.py b/py_script/parse_set_individual.py index 4b8d012..972506f 100644 --- a/py_script/parse_set_individual.py +++ b/py_script/parse_set_individual.py @@ -1,3 +1,9 @@ +""" +Parses a set from a single file. + +Usage: python parse_set_individual.py [infile] +""" + import sys set_infile = sys.argv[1] diff --git a/py_script/parse_sets.py b/py_script/parse_sets.py index bb61088..289578d 100644 --- a/py_script/parse_sets.py +++ b/py_script/parse_sets.py @@ -1,3 +1,8 @@ +""" +An old file. + +""" + with open("sets.txt", "r") as setsFile: sets_split = (x.split("'", 2)[1][2:] for x in setsFile.read().split("a href=")[1:]) with open("sets_list.txt", "w") as outFile: diff --git a/py_script/plot_dps.py b/py_script/plot_dps.py index 5768f1d..89bafe5 100644 --- a/py_script/plot_dps.py +++ b/py_script/plot_dps.py @@ -1,3 +1,7 @@ +""" +Plots the dps of all weapons on a neat graph. Used to generate graphics for dps_vis. +""" + import matplotlib.pyplot as plt import json import numpy as np diff --git a/py_script/ing_transform_combine.py b/py_script/process_ings.py similarity index 89% rename from py_script/ing_transform_combine.py rename to py_script/process_ings.py index 0dd7491..666281b 100644 --- a/py_script/ing_transform_combine.py +++ b/py_script/process_ings.py @@ -1,15 +1,25 @@ +""" +Used to process the raw data about ingredients pulled from the API. +Usage: +- python process_ings.py [infile] [outfile] +OR +- python process_ings.py [infile and outfile] +""" import json - -with open("../ingreds.json", "r") as infile: - ing_data = json.loads(infile.read()) -ings = ing_data -#this data does not have request :) - +import sys import os -if os.path.exists("../ing_map.json"): - with open("../ing_map.json","r") as ing_mapfile: +import base64 + +infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] + +with open(infile, "r") as in_file: + ing_data = json.loads(in_file.read()) +ings = ing_data['ings'] + +if os.path.exists("ing_map.json"): + with open("ing_map.json","r") as ing_mapfile: ing_map = json.load(ing_mapfile) else: ing_map = {ing["name"]: i for i, ing in enumerate(ings)} @@ -146,8 +156,6 @@ ing_delete_keys = [ "skin" ] -print("loaded all files.") - for ing in ings: for key in ing_delete_keys: if key in ing: @@ -202,13 +210,10 @@ for ing in ings: print(f'New Ingred: {ing["name"]}') ing["id"] = ing_map[ing["name"]] - -with open("../ingreds_clean.json", "w") as outfile: - json.dump(ing_data, outfile, indent = 2) -with open("../ingreds_compress.json", "w") as outfile: - json.dump(ing_data, outfile) -with open("../ing_map.json", "w") as ing_mapfile: +#save ing ids +with open("ing_map.json", "w+") as ing_mapfile: json.dump(ing_map, ing_mapfile, indent = 2) - -print('All ing jsons updated.') +#save ings +with open(outfile, "w+") as out_file: + json.dump(ing_data, out_file) diff --git a/py_script/transform_preserve.py b/py_script/process_items.py similarity index 76% rename from py_script/transform_preserve.py rename to py_script/process_items.py index 66c415f..27e6ed6 100644 --- a/py_script/transform_preserve.py +++ b/py_script/process_items.py @@ -1,44 +1,30 @@ """ +Used to process the raw item data pulled from the API. -NOTE!!!!!!! +Usage: +- python process_items.py [infile] [outfile] +OR +- python process_items.py [infile and outfile] -DEMON TIDE 1.20 IS HARD CODED! - -AMBIVALENCE IS REMOVED! +NOTE: id_map.json is due for change. Should be updated manually when Wynn2.0/corresponding WB version drops. """ import json +import sys +import os +import base64 -with open("dump.json", "r") as infile: - data = json.load(infile) +infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] + +with open(infile, "r") as in_file: + data = json.loads(in_file.read()) -with open("updated.json", "r") as oldfile: - old_data = json.load(oldfile) items = data["items"] -old_items = old_data["items"] if "request" in data: del data["request"] -# import os -# sets = dict() -# for filename in os.listdir('sets'): -# if "json" not in filename: -# continue -# set_name = filename[1:].split(".")[0].replace("+", " ").replace("%27", "'") -# with open("sets/"+filename) as set_info: -# set_obj = json.load(set_info) -# for item in set_obj["items"]: -# item_set_map[item] = set_name -# sets[set_name] = set_obj -# -# data["sets"] = sets -data["sets"] = old_data["sets"] -item_set_map = dict() -for set_name, set_data in data["sets"].items(): - for item_name in set_data["items"]: - item_set_map[item_name] = set_name translate_mappings = { #"name": "name", @@ -141,7 +127,12 @@ delete_keys = [ #"material" ] +with open("../clean.json", "r") as oldfile: + old_data = json.load(oldfile) +old_items = old_data['items'] id_map = {item["name"]: item["id"] for item in old_items} +with open("id_map.json", "r") as idmap_file: + id_map = json.load(idmap_file) used_ids = set([v for k, v in id_map.items()]) max_id = 0 @@ -150,8 +141,8 @@ known_item_names = set() for item in items: known_item_names.add(item["name"]) -old_items_map = dict() remap_items = [] +old_items_map = dict() for item in old_items: if "remapID" in item: remap_items.append(item) @@ -186,16 +177,18 @@ for item in items: item_name = item["displayName"] else: item_name = item["name"] - if item_name in item_set_map: - item["set"] = item_set_map[item_name] - if item["name"] in old_items_map: - old_item = old_items_map[item["name"]] - if "hideSet" in old_item: - item["hideSet"] = old_item["hideSet"] items.extend(remap_items) -with open("clean.json", "w") as outfile: - json.dump(data, outfile, indent=2) -with open("compress.json", "w") as outfile: - json.dump(data, outfile) +#write items back into data +data["items"] = items + +#save id map +with open("id_map.json","w") as id_mapfile: + json.dump(id_map, id_mapfile, indent=2) + + +#write the data back to the outfile +with open(outfile, "w+") as out_file: + json.dump(data, out_file) + diff --git a/py_script/recipe_transform_combine.py b/py_script/process_recipes.py similarity index 57% rename from py_script/recipe_transform_combine.py rename to py_script/process_recipes.py index 334df84..7dad257 100644 --- a/py_script/recipe_transform_combine.py +++ b/py_script/process_recipes.py @@ -1,8 +1,22 @@ +""" +Used to process the raw data about crafting recipes pulled from the API. +Usage: +- python process_recipes.py [infile] [outfile] +OR +- python process_recipes.py [infile and outfile] +""" + +import json +import sys import os +import base64 -with open("../recipes_compress.json", "r") as infile: - recipe_data = json.loads(infile.read()) +infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] + + +with open(infile, "r") as in_file: + recipe_data = json.loads(in_file.read()) recipes = recipe_data["recipes"] if os.path.exists("recipe_map.json"): @@ -19,8 +33,6 @@ recipe_delete_keys = [ #lol ] -print("loaded all files.") - for recipe in recipes: for key in recipe_delete_keys: if key in recipe: @@ -34,13 +46,10 @@ for recipe in recipes: print(f'New Recipe: {recipe["name"]}') recipe["id"] = recipe_map[recipe["name"]] +#save recipe id map +with open("recipe_map.json", "w") as recipe_mapfile: + json.dump(recipe_map, recipe_mapfile, indent = 2) -with open("../recipes_clean.json", "w") as outfile: - json.dump(recipe_data, outfile, indent = 2) -with open("../recipes_compress.json", "w") as outfile: - json.dump(recipe_data, outfile) -with open("../recipe_map.json", "w") as recipe_mapfile: - json.dump(recipe_map,recipe_mapfile,indent = 2) - - -print('All ing jsons updated.') \ No newline at end of file +#save recipe data +with open(outfile, "w+") as out_file: + json.dump(recipe_data, out_file) \ No newline at end of file diff --git a/recipe_map.json b/py_script/recipe_map.json similarity index 100% rename from recipe_map.json rename to py_script/recipe_map.json diff --git a/py_script/recipes.py b/py_script/recipes.py deleted file mode 100644 index 91d9e63..0000000 --- a/py_script/recipes.py +++ /dev/null @@ -1,33 +0,0 @@ -import requests -import json -import time -''' -response = requests.get("https://api.wynncraft.com/v2/recipe/list") -with open("mats.json", "w") as outfile: - outfile.write(json.dumps(response.json()))''' - -recipes = ["Boots-3-5", "Boots-5-7", "Bow-1-3", "Boots-7-9", "Bow-3-5", "Bow-5-7", "Bow-7-9", "Bracelet-3-5", "Bracelet-5-7", "Bracelet-1-3", "Chestplate-1-3", "Chestplate-3-5", "Chestplate-7-9", "Chestplate-5-7", "Dagger-1-3", "Dagger-3-5", "Dagger-5-7", "Bracelet-7-9", "Dagger-7-9", "Food-3-5", "Food-1-3", "Food-5-7", "Food-7-9", "Helmet-1-3", "Helmet-3-5", "Helmet-7-9", "Helmet-5-7", "Necklace-3-5", "Necklace-1-3", "Necklace-7-9", "Necklace-5-7", "Pants-1-3", "Pants-3-5", "Pants-5-7", "Pants-7-9", "Potion-1-3", "Potion-3-5", "Potion-5-7", "Relik-1-3", "Potion-7-9", "Relik-5-7", "Relik-3-5", "Relik-7-9", "Boots-1-3", "Ring-3-5", "Ring-5-7", "Ring-7-9", "Scroll-1-3", "Scroll-3-5", "Scroll-5-7", "Scroll-7-9", "Spear-1-3", "Spear-3-5", "Spear-5-7", "Spear-7-9", "Wand-1-3", "Wand-3-5", "Wand-5-7", "Wand-7-9", "Boots-10-13", "Boots-13-15", "Boots-15-17", "Boots-17-19", "Bow-10-13", "Bow-13-15", "Bracelet-10-13", "Bracelet-13-15", "Bracelet-15-17", "Bow-17-19", "Bracelet-17-19", "Chestplate-10-13", "Chestplate-13-15", "Chestplate-15-17", "Bow-15-17", "Dagger-10-13", "Dagger-13-15", "Chestplate-17-19", "Dagger-17-19", "Food-13-15", "Food-15-17", "Food-10-13", "Food-17-19", "Helmet-10-13", "Helmet-13-15", "Helmet-15-17", "Helmet-17-19", "Necklace-10-13", "Dagger-15-17", "Necklace-15-17", "Necklace-17-19", "Pants-10-13", "Pants-13-15", "Pants-15-17", "Pants-17-19", "Potion-10-13", "Potion-13-15", "Potion-15-17", "Relik-10-13", "Relik-13-15", "Relik-15-17", "Relik-17-19", "Ring-10-13", "Ring-13-15", "Ring-15-17", "Ring-17-19", "Scroll-10-13", "Potion-17-19", "Scroll-13-15", "Scroll-15-17", "Spear-10-13", "Scroll-17-19", "Spear-13-15", "Spear-15-17", "Spear-17-19", "Wand-10-13", "Wand-13-15", "Wand-15-17", "Wand-17-19", "Boots-20-23", "Boots-23-25", "Boots-25-27", "Bow-23-25", "Bow-20-23", "Boots-27-29", "Bow-25-27", "Bow-27-29", "Bracelet-20-23", "Bracelet-23-25", "Bracelet-25-27", "Bracelet-27-29", "Chestplate-23-25", "Chestplate-25-27", "Chestplate-20-23", "Chestplate-27-29", "Dagger-20-23", "Dagger-23-25", "Dagger-25-27", "Dagger-27-29", "Food-20-23", "Food-25-27", "Food-27-29", "Helmet-20-23", "Food-23-25", "Helmet-23-25", "Helmet-27-29", "Helmet-25-27", "Necklace-20-23", "Necklace-23-25", "Necklace-27-29", "Pants-20-23", "Necklace-25-27", "Pants-23-25", "Pants-25-27", "Pants-27-29", "Potion-20-23", "Potion-23-25", "Potion-27-29", "Potion-25-27", "Relik-20-23", "Relik-23-25", "Relik-25-27", "Relik-27-29", "Ring-20-23", "Ring-23-25", "Ring-25-27", "Ring-27-29", "Scroll-20-23", "Scroll-23-25", "Scroll-25-27", "Scroll-27-29", "Spear-20-23", "Spear-23-25", "Spear-25-27", "Spear-27-29", "Wand-20-23", "Wand-23-25", "Wand-25-27", "Wand-27-29", "Boots-30-33", "Boots-33-35", "Boots-35-37", "Boots-37-39", "Bow-30-33", "Bow-33-35", "Bow-35-37", "Necklace-13-15", "Bow-37-39", "Bracelet-30-33", "Bracelet-33-35", "Bracelet-35-37", "Bracelet-37-39", "Chestplate-30-33", "Chestplate-35-37", "Chestplate-33-35", "Dagger-30-33", "Chestplate-37-39", "Dagger-35-37", "Dagger-37-39", "Food-30-33", "Dagger-33-35", "Food-33-35", "Food-35-37", "Food-37-39", "Helmet-30-33", "Helmet-33-35", "Helmet-35-37", "Helmet-37-39", "Necklace-30-33", "Necklace-33-35", "Necklace-35-37", "Necklace-37-39", "Pants-33-35", "Pants-30-33", "Pants-35-37", "Pants-37-39", "Potion-30-33", "Potion-33-35", "Potion-35-37", "Potion-37-39", "Relik-30-33", "Relik-33-35", "Relik-35-37", "Ring-30-33", "Relik-37-39", "Ring-33-35", "Ring-35-37", "Ring-37-39", "Scroll-30-33", "Scroll-33-35", "Scroll-35-37", "Scroll-37-39", "Spear-30-33", "Spear-33-35", "Spear-35-37", "Spear-37-39", "Wand-30-33", "Wand-33-35", "Wand-35-37", "Boots-40-43", "Wand-37-39", "Boots-45-47", "Boots-47-49", "Boots-43-45", "Bow-40-43", "Bow-47-49", "Bow-43-45", "Bracelet-40-43", "Bow-45-47", "Bracelet-45-47", "Bracelet-47-49", "Chestplate-43-45", "Bracelet-43-45", "Chestplate-40-43", "Chestplate-45-47", "Dagger-40-43", "Chestplate-47-49", "Dagger-43-45", "Dagger-47-49", "Dagger-45-47", "Food-40-43", "Food-43-45", "Food-45-47", "Food-47-49", "Helmet-40-43", "Helmet-43-45", "Helmet-45-47", "Necklace-40-43", "Necklace-43-45", "Necklace-45-47", "Helmet-47-49", "Necklace-47-49", "Pants-40-43", "Pants-43-45", "Pants-45-47", "Pants-47-49", "Potion-43-45", "Potion-45-47", "Relik-40-43", "Potion-47-49", "Potion-40-43", "Relik-45-47", "Ring-40-43", "Relik-43-45", "Ring-45-47", "Relik-47-49", "Ring-43-45", "Ring-47-49", "Scroll-40-43", "Scroll-43-45", "Scroll-45-47", "Scroll-47-49", "Spear-43-45", "Spear-40-43", "Spear-45-47", "Spear-47-49", "Wand-40-43", "Wand-43-45", "Wand-45-47", "Wand-47-49", "Boots-50-53", "Boots-53-55", "Boots-55-57", "Boots-57-59", "Bow-50-53", "Bow-55-57", "Bow-53-55", "Bow-57-59", "Bracelet-50-53", "Bracelet-53-55", "Bracelet-55-57", "Bracelet-57-59", "Chestplate-50-53", "Chestplate-53-55", "Chestplate-55-57", "Chestplate-57-59", "Dagger-50-53", "Dagger-53-55", "Dagger-55-57", "Food-50-53", "Food-53-55", "Food-55-57", "Dagger-57-59", "Food-57-59", "Helmet-50-53", "Helmet-53-55", "Helmet-55-57", "Helmet-57-59", "Necklace-50-53", "Necklace-57-59", "Necklace-53-55", "Necklace-55-57", "Pants-53-55", "Pants-55-57", "Pants-57-59", "Potion-53-55", "Potion-50-53", "Potion-55-57", "Relik-50-53", "Pants-50-53", "Relik-53-55", "Potion-57-59", "Relik-55-57", "Relik-57-59", "Ring-50-53", "Ring-53-55", "Ring-55-57", "Ring-57-59", "Scroll-50-53", "Scroll-53-55", "Scroll-57-59", "Scroll-55-57", "Spear-50-53", "Spear-53-55", "Spear-55-57", "Wand-50-53", "Spear-57-59", "Wand-55-57", "Wand-53-55", "Wand-57-59", "Boots-60-63", "Boots-65-67", "Boots-63-65", "Boots-67-69", "Bow-60-63", "Bow-63-65", "Bow-67-69", "Bow-65-67", "Bracelet-63-65", "Bracelet-60-63", "Bracelet-65-67", "Bracelet-67-69", "Chestplate-63-65", "Chestplate-60-63", "Chestplate-65-67", "Chestplate-67-69", "Dagger-60-63", "Dagger-63-65", "Dagger-65-67", "Dagger-67-69", "Food-60-63", "Food-63-65", "Food-67-69", "Helmet-60-63", "Helmet-63-65", "Food-65-67", "Helmet-65-67", "Helmet-67-69", "Necklace-60-63", "Necklace-63-65", "Necklace-65-67", "Necklace-67-69", "Pants-63-65", "Pants-60-63", "Pants-65-67", "Pants-67-69", "Potion-60-63", "Potion-63-65", "Potion-67-69", "Relik-63-65", "Relik-65-67", "Potion-65-67", "Relik-67-69", "Relik-60-63", "Ring-60-63", "Ring-65-67", "Ring-63-65", "Ring-67-69", "Scroll-60-63", "Scroll-63-65", "Scroll-67-69", "Scroll-65-67", "Spear-63-65", "Spear-60-63", "Spear-67-69", "Wand-60-63", "Wand-63-65", "Spear-65-67", "Wand-65-67", "Wand-67-69", "Boots-70-73", "Boots-73-75", "Boots-77-79", "Boots-75-77", "Bow-75-77", "Bow-73-75", "Bracelet-70-73", "Bow-77-79", "Bracelet-73-75", "Bow-70-73", "Bracelet-75-77", "Chestplate-70-73", "Bracelet-77-79", "Chestplate-73-75", "Chestplate-75-77", "Chestplate-77-79", "Dagger-70-73", "Dagger-73-75", "Dagger-75-77", "Food-70-73", "Dagger-77-79", "Food-73-75", "Food-75-77", "Food-77-79", "Helmet-70-73", "Helmet-73-75", "Helmet-75-77", "Helmet-77-79", "Necklace-70-73", "Necklace-73-75", "Necklace-75-77", "Necklace-77-79", "Pants-73-75", "Pants-75-77", "Pants-77-79", "Pants-70-73", "Potion-70-73", "Potion-73-75", "Potion-75-77", "Potion-77-79", "Relik-70-73", "Relik-73-75", "Relik-75-77", "Relik-77-79", "Ring-70-73", "Ring-73-75", "Ring-75-77", "Ring-77-79", "Scroll-70-73", "Scroll-73-75", "Scroll-75-77", "Scroll-77-79", "Spear-70-73", "Spear-73-75", "Spear-75-77", "Spear-77-79", "Wand-70-73", "Wand-73-75", "Wand-75-77", "Wand-77-79", "Boots-80-83", "Boots-83-85", "Boots-85-87", "Boots-87-89", "Bow-83-85", "Bow-85-87", "Bow-80-83", "Bracelet-83-85", "Bracelet-80-83", "Bracelet-85-87", "Bow-87-89", "Bracelet-87-89", "Chestplate-80-83", "Chestplate-83-85", "Chestplate-85-87", "Chestplate-87-89", "Dagger-83-85", "Dagger-80-83", "Dagger-85-87", "Dagger-87-89", "Food-83-85", "Food-80-83", "Food-85-87", "Food-87-89", "Helmet-80-83", "Helmet-83-85", "Helmet-85-87", "Helmet-87-89", "Necklace-80-83", "Necklace-83-85", "Necklace-85-87", "Necklace-87-89", "Pants-80-83", "Pants-83-85", "Pants-85-87", "Pants-87-89", "Potion-80-83", "Potion-83-85", "Potion-85-87", "Potion-87-89", "Relik-80-83", "Relik-83-85", "Relik-85-87", "Ring-83-85", "Relik-87-89", "Ring-85-87", "Ring-80-83", "Ring-87-89", "Scroll-80-83", "Scroll-85-87", "Scroll-83-85", "Spear-80-83", "Scroll-87-89", "Spear-85-87", "Wand-80-83", "Spear-87-89", "Wand-83-85", "Wand-85-87", "Wand-87-89", "Spear-83-85", "Boots-93-95", "Boots-95-97", "Boots-90-93", "Boots-97-99", "Bow-90-93", "Bow-93-95", "Bow-95-97", "Bow-97-99", "Bracelet-90-93", "Bracelet-93-95", "Bracelet-97-99", "Chestplate-90-93", "Chestplate-93-95", "Bracelet-95-97", "Chestplate-95-97", "Ring-1-3", "Chestplate-97-99", "Dagger-93-95", "Dagger-90-93", "Dagger-95-97", "Dagger-97-99", "Food-90-93", "Food-93-95", "Food-95-97", "Food-97-99", "Helmet-90-93", "Helmet-93-95", "Helmet-95-97", "Helmet-97-99", "Necklace-93-95", "Necklace-95-97", "Necklace-97-99", "Pants-90-93", "Necklace-90-93", "Pants-93-95", "Pants-97-99", "Potion-90-93", "Potion-93-95", "Potion-95-97", "Relik-90-93", "Potion-97-99", "Relik-93-95", "Relik-95-97", "Ring-90-93", "Ring-95-97", "Ring-93-95", "Scroll-90-93", "Scroll-93-95", "Ring-97-99", "Scroll-95-97", "Spear-90-93", "Spear-93-95", "Spear-95-97", "Spear-97-99", "Relik-97-99", "Pants-95-97", "Wand-90-93", "Scroll-97-99", "Wand-93-95", "Wand-95-97", "Wand-97-99", "Boots-100-103", "Bow-100-103", "Bracelet-100-103", "Chestplate-100-103", "Dagger-100-103", "Necklace-100-103", "Potion-100-103", "Relik-100-103", "Ring-100-103", "Scroll-100-103", "Spear-100-103", "Pants-100-103", "Boots-103-105", "Bow-103-105", "Bracelet-103-105", "Chestplate-103-105", "Dagger-103-105", "Food-103-105", "Helmet-103-105", "Food-100-103", "Pants-103-105", "Potion-103-105", "Relik-103-105", "Wand-100-103", "Ring-103-105", "Necklace-103-105", "Scroll-103-105", "Spear-103-105", "Wand-103-105", "Helmet-100-103"] -#recipes = ["Boots-3-5", "Boots-5-7", "Bow-1-3", "Boots-7-9", "Bow-3-5", "Bow-5-7", "Bow-7-9", "Bracelet-3-5", "Bracelet-5-7", "Bracelet-1-3", "Chestplate-1-3", "Chestplate-3-5", "Chestplate-7-9", "Chestplate-5-7", "Dagger-1-3", "Dagger-3-5", "Dagger-5-7", "Bracelet-7-9", "Dagger-7-9", "Food-3-5", "Food-1-3", "Food-5-7", "Food-7-9", "Helmet-1-3", "Helmet-3-5", "Helmet-7-9", "Helmet-5-7", "Necklace-3-5", "Necklace-1-3", "Necklace-7-9", "Necklace-5-7", "Pants-1-3", "Pants-3-5", "Pants-5-7", "Pants-7-9", "Potion-1-3", "Potion-3-5", "Potion-5-7", "Relik-1-3", "Potion-7-9", "Relik-5-7", "Relik-3-5", "Relik-7-9", "Boots-1-3", "Ring-3-5", "Ring-5-7", "Ring-7-9", "Scroll-1-3", "Scroll-3-5", "Scroll-5-7", "Scroll-7-9", "Spear-1-3", "Spear-3-5", "Spear-5-7", "Spear-7-9", "Wand-1-3", "Wand-3-5", "Wand-5-7", "Wand-7-9", "Boots-10-13", "Boots-13-15", "Boots-15-17", "Boots-17-19", "Bow-10-13", "Bow-13-15", "Bracelet-10-13", "Bracelet-13-15", "Bracelet-15-17", "Bow-17-19", "Bracelet-17-19", "Chestplate-10-13", "Chestplate-13-15", "Chestplate-15-17", "Bow-15-17", "Dagger-10-13", "Dagger-13-15", "Chestplate-17-19", "Dagger-17-19", "Food-13-15", "Food-15-17", "Food-10-13", "Food-17-19", "Helmet-10-13", "Helmet-13-15", "Helmet-15-17", "Helmet-17-19", "Necklace-10-13", "Dagger-15-17", "Necklace-15-17", "Necklace-17-19", "Pants-10-13", "Pants-13-15", "Pants-15-17", "Pants-17-19", "Potion-10-13", "Potion-13-15", "Potion-15-17", "Relik-10-13", "Relik-13-15", "Relik-15-17", "Relik-17-19", "Ring-10-13", "Ring-13-15", "Ring-15-17", "Ring-17-19", "Scroll-10-13", "Potion-17-19", "Scroll-13-15", "Scroll-15-17", "Spear-10-13", "Scroll-17-19", "Spear-13-15", "Spear-15-17", "Spear-17-19", "Wand-10-13", "Wand-13-15", "Wand-15-17", "Wand-17-19", "Boots-20-23", "Boots-23-25", "Boots-25-27", "Bow-23-25", "Bow-20-23", "Boots-27-29", "Bow-25-27", "Bow-27-29", "Bracelet-20-23", "Bracelet-23-25", "Bracelet-25-27", "Bracelet-27-29", "Chestplate-23-25", "Chestplate-25-27", "Chestplate-20-23", "Chestplate-27-29", "Dagger-20-23", "Dagger-23-25", "Dagger-25-27", "Dagger-27-29", "Food-20-23", "Food-25-27", "Food-27-29", "Helmet-20-23", "Food-23-25", "Helmet-23-25", "Helmet-27-29", "Helmet-25-27", "Necklace-20-23", "Necklace-23-25", "Necklace-27-29", "Pants-20-23", "Necklace-25-27", "Pants-23-25", "Pants-25-27", "Pants-27-29", "Potion-20-23", "Potion-23-25", "Potion-27-29", "Potion-25-27", "Relik-20-23", "Relik-23-25", "Relik-25-27", "Relik-27-29", "Ring-20-23", "Ring-23-25", "Ring-25-27", "Ring-27-29", "Scroll-20-23", "Scroll-23-25", "Scroll-25-27", "Scroll-27-29", "Spear-20-23", "Spear-23-25", "Spear-25-27", "Spear-27-29", "Wand-20-23", "Wand-23-25", "Wand-25-27", "Wand-27-29", "Boots-30-33", "Boots-33-35", "Dagger-33-35", "Food-33-35", "Food-35-37", "Food-37-39", "Helmet-30-33", "Helmet-33-35", "Helmet-35-37", "Helmet-37-39", "Necklace-30-33", "Necklace-33-35", "Necklace-35-37", "Necklace-37-39", "Pants-33-35", "Pants-30-33", "Pants-35-37", "Pants-37-39", "Potion-30-33", "Potion-33-35", "Potion-35-37", "Potion-37-39", "Relik-30-33", "Relik-33-35", "Relik-35-37", "Ring-30-33", "Relik-37-39", "Ring-33-35", "Ring-35-37", "Ring-37-39", "Scroll-30-33", "Scroll-33-35", "Scroll-35-37", "Scroll-37-39", "Spear-30-33", "Spear-33-35", "Spear-35-37", "Spear-37-39", "Wand-30-33", "Wand-33-35", "Wand-35-37", "Boots-40-43", "Wand-37-39", "Boots-45-47", "Boots-47-49", "Boots-43-45", "Bow-40-43", "Bow-47-49", "Bow-43-45", "Bracelet-40-43", "Bow-45-47", "Bracelet-45-47", "Bracelet-47-49", "Chestplate-43-45", "Bracelet-43-45", "Chestplate-40-43", "Chestplate-45-47", "Dagger-40-43", "Chestplate-47-49", "Dagger-43-45", "Dagger-47-49", "Dagger-45-47", "Food-40-43", "Food-43-45", "Food-45-47", "Food-47-49", "Helmet-40-43", "Helmet-43-45", "Helmet-45-47", "Necklace-40-43", "Necklace-43-45", "Necklace-45-47", "Helmet-47-49", "Necklace-47-49", "Pants-40-43", "Pants-43-45", "Pants-45-47", "Pants-47-49", "Potion-43-45", "Potion-45-47", "Relik-40-43", "Potion-47-49", "Potion-40-43", "Relik-45-47", "Ring-40-43", "Relik-43-45", "Ring-45-47", "Relik-47-49", "Ring-43-45", "Ring-47-49", "Scroll-40-43", "Scroll-43-45", "Scroll-45-47", "Scroll-47-49", "Spear-43-45", "Spear-40-43", "Spear-45-47", "Spear-47-49", "Wand-40-43", "Wand-43-45", "Wand-45-47", "Wand-47-49", "Boots-50-53", "Boots-53-55", "Boots-55-57", "Boots-57-59", "Bow-50-53", "Bow-55-57", "Bow-53-55", "Bow-57-59", "Bracelet-50-53", "Bracelet-53-55", "Bracelet-55-57", "Bracelet-57-59", "Chestplate-50-53", "Chestplate-53-55", "Chestplate-55-57", "Chestplate-57-59", "Dagger-50-53", "Dagger-53-55", "Dagger-55-57", "Food-50-53", "Food-53-55", "Food-55-57", "Dagger-57-59", "Food-57-59", "Helmet-50-53", "Helmet-53-55", "Helmet-55-57", "Helmet-57-59", "Necklace-50-53", "Necklace-57-59", "Necklace-53-55", "Necklace-55-57", "Pants-53-55", "Pants-55-57", "Pants-57-59", "Potion-53-55", "Potion-50-53", "Potion-55-57", "Relik-50-53", "Pants-50-53", "Relik-53-55", "Potion-57-59", "Relik-55-57", "Relik-57-59", "Ring-50-53", "Ring-53-55", "Ring-55-57", "Ring-57-59", "Scroll-50-53", "Scroll-53-55", "Scroll-57-59", "Scroll-55-57", "Spear-50-53", "Spear-53-55", "Spear-55-57", "Wand-50-53", "Spear-57-59", "Wand-55-57", "Wand-53-55", "Wand-57-59", "Boots-60-63", "Boots-65-67", "Boots-63-65", "Boots-67-69", "Bow-60-63", "Bow-63-65", "Bow-67-69", "Bow-65-67", "Bracelet-63-65", "Bracelet-60-63", "Bracelet-65-67", "Bracelet-67-69", "Chestplate-63-65", "Chestplate-60-63", "Chestplate-65-67", "Chestplate-67-69", "Dagger-60-63", "Dagger-63-65", "Dagger-65-67", "Dagger-67-69", "Spear-60-63", "Spear-67-69", "Wand-60-63", "Wand-63-65", "Spear-65-67", "Wand-65-67", "Wand-67-69", "Boots-70-73", "Boots-73-75", "Boots-77-79", "Boots-75-77", "Bow-75-77", "Bow-73-75", "Bracelet-70-73", "Bow-77-79", "Bracelet-73-75", "Bow-70-73", "Bracelet-75-77", "Chestplate-70-73", "Bracelet-77-79", "Chestplate-73-75", "Chestplate-75-77", "Chestplate-77-79", "Dagger-70-73", "Dagger-73-75", "Dagger-75-77", "Food-70-73", "Dagger-77-79", "Food-73-75", "Food-75-77", "Food-77-79", "Helmet-70-73", "Helmet-73-75", "Helmet-75-77", "Helmet-77-79", "Necklace-70-73", "Necklace-73-75", "Necklace-75-77", "Necklace-77-79", "Pants-73-75", "Pants-75-77", "Pants-77-79", "Pants-70-73", "Potion-70-73", "Potion-73-75", "Potion-75-77", "Potion-77-79", "Relik-70-73", "Relik-73-75", "Relik-75-77", "Relik-77-79", "Ring-70-73", "Ring-73-75", "Ring-75-77", "Ring-77-79", "Scroll-70-73", "Scroll-73-75", "Scroll-75-77", "Scroll-77-79", "Spear-70-73", "Spear-73-75", "Spear-75-77", "Spear-77-79", "Wand-70-73", "Wand-73-75", "Wand-75-77", "Wand-77-79", "Boots-80-83", "Boots-83-85", "Boots-85-87", "Boots-87-89", "Bow-83-85", "Bow-85-87", "Bow-80-83", "Bracelet-83-85", "Bracelet-80-83", "Bracelet-85-87", "Bow-87-89", "Bracelet-87-89", "Chestplate-80-83", "Chestplate-83-85", "Chestplate-85-87", "Chestplate-87-89", "Dagger-83-85", "Dagger-80-83", "Dagger-85-87", "Dagger-87-89", "Food-83-85", "Food-80-83", "Food-85-87", "Food-87-89", "Helmet-80-83", "Helmet-83-85", "Helmet-85-87", "Helmet-87-89", "Necklace-80-83", "Necklace-83-85", "Necklace-85-87", "Necklace-87-89", "Pants-80-83", "Pants-83-85", "Pants-85-87", "Pants-87-89", "Potion-80-83", "Potion-83-85", "Potion-85-87", "Potion-87-89", "Relik-80-83", "Relik-83-85", "Relik-85-87", "Ring-83-85", "Relik-87-89", "Ring-85-87", "Ring-80-83", "Ring-87-89", "Scroll-80-83", "Scroll-85-87", "Scroll-83-85", "Spear-80-83", "Scroll-87-89", "Spear-85-87", "Wand-80-83", "Spear-87-89", "Wand-83-85", "Wand-85-87", "Wand-87-89", "Spear-83-85", "Boots-93-95", "Boots-95-97", "Boots-90-93", "Boots-97-99", "Bow-90-93", "Bow-93-95", "Bow-95-97", "Bow-97-99", "Bracelet-90-93", "Bracelet-93-95", "Bracelet-97-99", "Chestplate-90-93", "Chestplate-93-95", "Bracelet-95-97", "Chestplate-95-97", "Ring-1-3", "Chestplate-97-99", "Dagger-93-95", "Dagger-90-93", "Dagger-95-97", "Dagger-97-99", "Food-90-93", "Food-93-95", "Food-95-97", "Food-97-99", "Helmet-90-93", "Helmet-93-95", "Helmet-95-97", "Helmet-97-99", "Necklace-93-95", "Necklace-95-97", "Necklace-97-99", "Pants-90-93", "Necklace-90-93", "Pants-93-95", "Pants-97-99", "Potion-90-93", "Potion-93-95", "Potion-95-97", "Relik-90-93", "Potion-97-99", "Relik-93-95", "Relik-95-97", "Ring-90-93", "Ring-95-97", "Ring-93-95", "Scroll-90-93", "Scroll-93-95", "Ring-97-99", "Scroll-95-97", "Spear-90-93", "Spear-93-95", "Spear-95-97", "Spear-97-99", "Relik-97-99", "Pants-95-97", "Wand-90-93", "Scroll-97-99", "Wand-93-95", "Wand-95-97", "Wand-97-99", "Boots-100-103", "Bow-100-103", "Bracelet-100-103", "Chestplate-100-103", "Dagger-100-103", "Necklace-100-103", "Potion-100-103", "Relik-100-103", "Ring-100-103", "Scroll-100-103", "Spear-100-103", "Pants-100-103", "Boots-103-105", "Bow-103-105", "Bracelet-103-105", "Chestplate-103-105", "Dagger-103-105", "Food-103-105", "Helmet-103-105", "Food-100-103", "Pants-103-105", "Potion-103-105", "Relik-103-105", "Wand-100-103", "Ring-103-105", "Necklace-103-105", "Scroll-103-105", "Spear-103-105", "Wand-103-105", "Helmet-100-103"] - -arr = [] -fail = [] -data = [] - -'''for i in range(330,len(recipes)): - response = requests.get("https://api.wynncraft.com/v2/recipe/get/" + recipes[i]) - if("message" in response.json()): - print("failed to get " + recipes[i]) - fail.append(recipes[i]) - else: - arr.append(response.json()) - time.sleep(0.2) - -with open("temp.json", "w") as outfile: - json.dump(arr,outfile)''' - - -with open("mats_clean.json", "w") as outfile: - json.dump(arr,outfile,indent = 2) - -with open("mats_compress.json", "w") as outfile: - json.dump(arr,outfile) \ No newline at end of file diff --git a/py_script/script.py b/py_script/script.py deleted file mode 100644 index e90655d..0000000 --- a/py_script/script.py +++ /dev/null @@ -1,9 +0,0 @@ -import requests -import json -response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&search=atlas").json() -atlas = response['items'][0] - -with open('test.json',"w") as outfile: - json.dump(atlas, outfile, indent = 2) - -print(atlas) diff --git a/py_script/sets/_Adventurer%27s.json b/py_script/sets/_Adventurer%27s.json deleted file mode 100644 index f50d86c..0000000 --- a/py_script/sets/_Adventurer%27s.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "items": [ - "Adventurer's Cap", - "Adventurer's Boots", - "Adventurer's Pants", - "Adventurer's Tunic" - ], - "bonuses": [ - {}, - { - "sdPct": 4, - "mdPct": 4, - "xpb": 10, - "lb": 5, - "spd": 2, - "hpBonus": 15, - "spRegen": 5 - }, - { - "sdPct": 12, - "mdPct": 12, - "xpb": 20, - "lb": 10, - "spd": 5, - "hpBonus": 40, - "spRegen": 15 - }, - { - "mr": 2, - "sdPct": 25, - "mdPct": 25, - "xpb": 50, - "lb": 30, - "spd": 15, - "hpBonus": 175, - "spRegen": 50 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Air+Relic.json b/py_script/sets/_Air+Relic.json deleted file mode 100644 index 77e7e65..0000000 --- a/py_script/sets/_Air+Relic.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "items": [ - "Air Relic Helmet", - "Air Relic Boots", - "Air Relic Leggings", - "Air Relic Chestplate" - ], - "bonuses": [ - {}, - { - "xpb": 5, - "lb": 10, - "spd": 10, - "hpBonus": 60 - }, - { - "xpb": 10, - "lb": 25, - "spd": 20, - "hpBonus": 190 - }, - { - "xpb": 25, - "lb": 50, - "agi": 20, - "spd": 60, - "hpBonus": 400 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Bandit%27s.json b/py_script/sets/_Bandit%27s.json deleted file mode 100644 index 3632ce1..0000000 --- a/py_script/sets/_Bandit%27s.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "items": [ - "Bandit's Locket", - "Bandit's Bangle", - "Bandit's Knuckle", - "Bandit's Ring" - ], - "bonuses": [ - {}, - { - "xpb": 3, - "lb": 4, - "eSteal": 1 - }, - { - "xpb": 7, - "lb": 9, - "eSteal": 3 - }, - { - "xpb": 12, - "lb": 15, - "eSteal": 6 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Beachside.json b/py_script/sets/_Beachside.json deleted file mode 100644 index a54b31e..0000000 --- a/py_script/sets/_Beachside.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Beachside Headwrap", - "Beachside Conch" - ], - "bonuses": [ - {}, - { - "lb": 20, - "wDamPct": 35, - "wDefPct": 25 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Bear.json b/py_script/sets/_Bear.json deleted file mode 100644 index ee929ed..0000000 --- a/py_script/sets/_Bear.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "items": [ - "Bear Mask", - "Bear Head", - "Bear Body" - ], - "bonuses": [ - {}, - { - "mdPct": 14, - "hpBonus": 30, - "mdRaw": 20 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Black+Catalyst.json b/py_script/sets/_Black+Catalyst.json deleted file mode 100644 index 2495417..0000000 --- a/py_script/sets/_Black+Catalyst.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "items": [ - "Black Catalyst" - ], - "bonuses": [ - { - "xpb": -5 - }, - { - "mr": 1, - "sdPct": 10, - "xpb": 30, - "expd": 10, - "hpBonus": 325, - "spRegen": 10, - "sdRaw": 90 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Black.json b/py_script/sets/_Black.json deleted file mode 100644 index 0229629..0000000 --- a/py_script/sets/_Black.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "items": [ - "Black Cap", - "Black Boots", - "Black Pants", - "Black Tunic" - ], - "bonuses": [ - {}, - { - "ms": 1, - "dex": 2, - "sdRaw": 15, - "mdRaw": 5 - }, - { - "ms": 1, - "dex": 6, - "sdRaw": 35, - "mdRaw": 10 - }, - { - "ms": 3, - "dex": 20, - "sdRaw": 65, - "mdRaw": 70 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Blue+Team.json b/py_script/sets/_Blue+Team.json deleted file mode 100644 index 8afc872..0000000 --- a/py_script/sets/_Blue+Team.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Blue Team Boots", - "Blue Team Leggings", - "Blue Team Chestplate", - "Blue Team Helmet" - ], - "bonuses": [ - {}, - {}, - {}, - {} - ] -} \ No newline at end of file diff --git a/py_script/sets/_Bony.json b/py_script/sets/_Bony.json deleted file mode 100644 index faa887b..0000000 --- a/py_script/sets/_Bony.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Bony Circlet", - "Bony Bow" - ], - "bonuses": [ - {}, - { - "agi": 8, - "mdRaw": 45, - "aDamPct": 15 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Builder%27s.json b/py_script/sets/_Builder%27s.json deleted file mode 100644 index c5ca2cc..0000000 --- a/py_script/sets/_Builder%27s.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "items": [ - "Builder's Helmet", - "Builder's Boots", - "Builder's Trousers", - "Builder's Breastplate" - ], - "bonuses": [ - {}, - { - "xpb": 5 - }, - { - "xpb": 10 - }, - { - "xpb": 15 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Champion.json b/py_script/sets/_Champion.json deleted file mode 100644 index ab4161f..0000000 --- a/py_script/sets/_Champion.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "items": [ - "Champion Helmet", - "Champion Boots", - "Champion Leggings", - "Champion Chestplate" - ], - "bonuses": [ - {}, - {}, - {}, - { - "mr": 5, - "sdPct": 75, - "mdPct": 75, - "ms": 5, - "ls": 400, - "hprRaw": 600 - } - ] -} diff --git a/py_script/sets/_Clock.json b/py_script/sets/_Clock.json deleted file mode 100644 index 1d1f5a4..0000000 --- a/py_script/sets/_Clock.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "items": [ - "Clock Helm", - "Clock Amulet", - "Watch Bracelet", - "Clockwork Ring", - "Time Ring", - "Clock Boots", - "Clock Leggings", - "Clock Mail" - ], - "bonuses": [ - {}, - { - "fDamPct": 15, - "wDamPct": 6, - "aDamPct": 5, - "tDamPct": 18, - "eDamPct": 8 - }, - { - "fDamPct": 14, - "wDamPct": 12, - "aDamPct": 13 - }, - { - "fDamPct": 13, - "wDamPct": 18, - "aDamPct": 20, - "tDamPct": 18, - "eDamPct": 14 - }, - { - "fDamPct": 12, - "wDamPct": 24, - "aDamPct": 28 - }, - { - "fDamPct": 11, - "wDamPct": 24, - "aDamPct": 24, - "tDamPct": 18, - "eDamPct": 22 - }, - { - "fDamPct": 10, - "wDamPct": 24, - "aDamPct": 19 - }, - { - "fDamPct": 9, - "wDamPct": 24, - "aDamPct": 14, - "tDamPct": 18, - "eDamPct": 34 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Corrupted+Nii.json b/py_script/sets/_Corrupted+Nii.json deleted file mode 100644 index 359d0cd..0000000 --- a/py_script/sets/_Corrupted+Nii.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "items": [ - "Corrupted Nii Mukluk", - "Corrupted Nii Plate", - "Corrupted Nii Shako" - ], - "bonuses": [ - {}, - { - "int": 3, - "def": 3, - "hprRaw": 60 - }, - { - "mr": 4, - "int": 15, - "def": 15, - "hpBonus": 1500, - "hprRaw": 270, - "fDefPct": 60, - "wDefPct": 60 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Corrupted+Uth.json b/py_script/sets/_Corrupted+Uth.json deleted file mode 100644 index 9430139..0000000 --- a/py_script/sets/_Corrupted+Uth.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "items": [ - "Corrupted Uth Sandals", - "Corrupted Uth Belt", - "Corrupted Uth Plume" - ], - "bonuses": [ - {}, - { - "ls": 125, - "agi": 3, - "def": 3 - }, - { - "ls": 375, - "ref": 70, - "agi": 15, - "def": 15, - "thorns": 70, - "fDefPct": 75, - "aDefPct": 75 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Cosmic.json b/py_script/sets/_Cosmic.json deleted file mode 100644 index 3e6e0c7..0000000 --- a/py_script/sets/_Cosmic.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "items": [ - "Cosmic Visor", - "Cosmic Walkers", - "Cosmic Ward", - "Cosmic Vest" - ], - "bonuses": [ - {}, - { - "xpb": 15, - "lb": 15, - "ref": 5, - "spRegen": 15, - "fDefPct": 10, - "wDefPct": 10, - "aDefPct": 10, - "tDefPct": 10, - "eDefPct": 10 - }, - { - "xpb": 35, - "lb": 35, - "ref": 15, - "spRegen": 35, - "fDefPct": 20, - "wDefPct": 20, - "aDefPct": 20, - "tDefPct": 20, - "eDefPct": 20 - }, - { - "xpb": 50, - "lb": 50, - "ref": 30, - "spRegen": 50, - "fDefPct": 30, - "wDefPct": 30, - "aDefPct": 30, - "tDefPct": 30, - "eDefPct": 30 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Earth+Relic.json b/py_script/sets/_Earth+Relic.json deleted file mode 100644 index 1a29526..0000000 --- a/py_script/sets/_Earth+Relic.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "items": [ - "Earth Relic Helmet", - "Earth Relic Boots", - "Earth Relic Leggings", - "Earth Relic Chestplate" - ], - "bonuses": [ - {}, - { - "mdPct": 10, - "xpb": 5, - "lb": 10, - "hpBonus": 65 - }, - { - "mdPct": 20, - "xpb": 10, - "lb": 25, - "hpBonus": 200 - }, - { - "mdPct": 45, - "xpb": 25, - "lb": 50, - "str": 20, - "hpBonus": 425 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Elf.json b/py_script/sets/_Elf.json deleted file mode 100644 index 2d82b67..0000000 --- a/py_script/sets/_Elf.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "items": [ - "Elf Cap", - "Elf Shoes", - "Elf Pants", - "Elf Robe" - ], - "bonuses": [ - {}, - { - "hprPct": 10, - "lb": 8, - "agi": 5, - "def": 5, - "spd": 6 - }, - { - "hprPct": 20, - "lb": 16, - "agi": 7, - "def": 7, - "spd": 14 - }, - { - "hprPct": 45, - "lb": 32, - "agi": 10, - "def": 10, - "spd": 20, - "hprRaw": 45 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Fire+Relic.json b/py_script/sets/_Fire+Relic.json deleted file mode 100644 index bd38ca6..0000000 --- a/py_script/sets/_Fire+Relic.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "items": [ - "Fire Relic Helmet", - "Fire Relic Boots", - "Fire Relic Leggings", - "Fire Relic Chestplate" - ], - "bonuses": [ - {}, - { - "xpb": 5, - "lb": 10, - "hpBonus": 90, - "hprRaw": 12 - }, - { - "xpb": 10, - "lb": 25, - "hpBonus": 270, - "hprRaw": 40 - }, - { - "xpb": 25, - "lb": 50, - "def": 20, - "hpBonus": 570, - "hprRaw": 100 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Flashfire.json b/py_script/sets/_Flashfire.json deleted file mode 100644 index 1b5732d..0000000 --- a/py_script/sets/_Flashfire.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "items": [ - "Flashfire Gauntlet", - "Flashfire Knuckle" - ], - "bonuses": [ - {}, - { - "spd": 8, - "atkTier": 1, - "wDamPct": -15, - "wDefPct": -15 - }, - { - "spd": 16, - "atkTier": 1, - "fDamPct": 12, - "wDamPct": -15, - "wDefPct": -15 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_GM%27s.json b/py_script/sets/_GM%27s.json deleted file mode 100644 index dd60ee3..0000000 --- a/py_script/sets/_GM%27s.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "items": [ - "GM's Helmet", - "GM's Boots", - "GM's Trousers", - "GM's Breastplate" - ], - "bonuses": [ - {}, - { - "xpb": 5 - }, - { - "xpb": 10 - }, - { - "xpb": 15 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Ghostly.json b/py_script/sets/_Ghostly.json deleted file mode 100644 index 7f59dfb..0000000 --- a/py_script/sets/_Ghostly.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "items": [ - "Ghostly Cap", - "Ghostly Boots", - "Ghostly Pants", - "Ghostly Tunic" - ], - "bonuses": [ - {}, - { - "mr": -1, - "ms": 2, - "sdRaw": 35, - "wDamPct": 5, - "tDamPct": 5, - "eDamPct": -34 - }, - { - "mr": -2, - "ms": 4, - "sdRaw": 100, - "wDamPct": 10, - "tDamPct": 10, - "eDamPct": -67 - }, - { - "mr": -3, - "ms": 6, - "sdRaw": 195, - "wDamPct": 25, - "tDamPct": 25, - "eDamPct": -100 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Goblin.json b/py_script/sets/_Goblin.json deleted file mode 100644 index 7217dbd..0000000 --- a/py_script/sets/_Goblin.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "items": [ - "Goblin Hood", - "Goblin Runners", - "Goblin Cloak" - ], - "bonuses": [ - { - "sdPct": -6, - "mdPct": -6, - "sdRaw": 15, - "mdRaw": 10 - }, - { - "sdPct": -12, - "mdPct": -12, - "ls": 22, - "sdRaw": 55, - "mdRaw": 45 - }, - { - "sdPct": -23, - "mdPct": -23, - "ls": 51, - "ms": 2, - "sdRaw": 130, - "mdRaw": 105 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Hallowynn+2016.json b/py_script/sets/_Hallowynn+2016.json deleted file mode 100644 index bc95522..0000000 --- a/py_script/sets/_Hallowynn+2016.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Treat", - "Trick" - ], - "bonuses": [ - {}, - { - "xpb": 15, - "spRegen": 10, - "eSteal": 5 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Horse.json b/py_script/sets/_Horse.json deleted file mode 100644 index 6cf44a5..0000000 --- a/py_script/sets/_Horse.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "items": [ - "Horse Mask", - "Horse Hoof" - ], - "bonuses": [ - {}, - { - "mdPct": 11, - "xpb": 10, - "spd": 20, - "aDamPct": 15, - "eDamPct": 15 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Jester.json b/py_script/sets/_Jester.json deleted file mode 100644 index bdcb2e8..0000000 --- a/py_script/sets/_Jester.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "items": [ - "Jester Necklace", - "Jester Bracelet", - "Jester Ring" - ], - "bonuses": [ - { - "xpb": -25, - "lb": -25 - }, - { - "xpb": -50, - "lb": -50, - "spd": -10, - "hpBonus": 300, - "sdRaw": -110, - "mdRaw": 60 - }, - { - "xpb": -75, - "lb": -75, - "spd": 5, - "hpBonus": -150, - "sdRaw": 100, - "mdRaw": -75 - }, - { - "xpb": -100, - "lb": -100, - "spd": 5, - "hpBonus": -150, - "sdRaw": 100, - "mdRaw": -75 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Kaerynn%27s.json b/py_script/sets/_Kaerynn%27s.json deleted file mode 100644 index d186b83..0000000 --- a/py_script/sets/_Kaerynn%27s.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "items": [ - "Kaerynn's Mind", - "Kaerynn's Body" - ], - "bonuses": [ - {}, - { - "mr": 2, - "xpb": 12, - "str": 4, - "hpBonus": 400, - "sdRaw": 100, - "mdRaw": 50 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Leaf.json b/py_script/sets/_Leaf.json deleted file mode 100644 index 8343c5c..0000000 --- a/py_script/sets/_Leaf.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "items": [ - "Leaf Cap", - "Leaf Boots", - "Leaf Pants", - "Leaf Tunic" - ], - "bonuses": [ - {}, - { - "hprPct": 5, - "thorns": 7, - "hpBonus": 10, - "hprRaw": 1 - }, - { - "hprPct": 12, - "thorns": 18, - "hpBonus": 20, - "hprRaw": 3 - }, - { - "hprPct": 25, - "thorns": 35, - "hpBonus": 60, - "hprRaw": 7 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Morph.json b/py_script/sets/_Morph.json deleted file mode 100644 index ba1dc87..0000000 --- a/py_script/sets/_Morph.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "items": [ - "Morph-Stardust", - "Morph-Ruby", - "Morph-Amethyst", - "Morph-Emerald", - "Morph-Topaz", - "Morph-Gold", - "Morph-Iron", - "Morph-Steel" - ], - "bonuses": [ - {}, - { - "xpb": 5, - "lb": 5 - }, - { - "mr": 1, - "xpb": 10, - "lb": 10, - "spRaw2": -1, - "hpBonus": 125 - }, - { - "mr": 1, - "xpb": 15, - "lb": 15, - "spRaw2": -1, - "hpBonus": 425 - }, - { - "mr": 2, - "xpb": 35, - "lb": 35, - "hpBonus": 1325, - "spRaw2": -1, - "spRaw4": -1 - }, - { - "mr": 2, - "xpb": 55, - "lb": 55, - "hpBonus": 2575, - "spRaw2": -1, - "spRaw4": -1 - }, - { - "mr": 3, - "xpb": 80, - "lb": 80, - "hpBonus": 4450, - "spRaw1": -1, - "spRaw2": -1, - "spRaw4": -1 - }, - { - "mr": 4, - "xpb": 100, - "lb": 100, - "str": 15, - "dex": 15, - "int": 15, - "agi": 15, - "def": 15, - "hpBonus": 6800, - "spRaw1": -1, - "spRaw2": -1, - "spRaw3": -1, - "spRaw4": -1 - } - ] -} diff --git a/py_script/sets/_Nether.json b/py_script/sets/_Nether.json deleted file mode 100644 index fa32664..0000000 --- a/py_script/sets/_Nether.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "items": [ - "Nether Cap", - "Nether Boots", - "Nether Pants", - "Nether Tunic" - ], - "bonuses": [ - {}, - { - "ls": 5, - "expd": 2, - "hprRaw": -1, - "fDamPct": 2, - "wDamPct": -10 - }, - { - "ls": 15, - "expd": 10, - "hprRaw": -2, - "fDamPct": 8, - "wDamPct": -25 - }, - { - "ls": 50, - "def": 15, - "expd": 60, - "hprRaw": -20, - "fDamPct": 42, - "wDamPct": -45 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Outlaw.json b/py_script/sets/_Outlaw.json deleted file mode 100644 index c6d49e0..0000000 --- a/py_script/sets/_Outlaw.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "items": [ - "Outlaw Cap", - "Outlaw Boots", - "Outlaw Pants", - "Outlaw Tunic" - ], - "bonuses": [ - {}, - { - "ls": 11, - "xpb": 5, - "agi": 4, - "eSteal": 2 - }, - { - "ls": 22, - "xpb": 10, - "agi": 8, - "eSteal": 4 - }, - { - "ls": 45, - "xpb": 25, - "agi": 28, - "eSteal": 8 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Pigman.json b/py_script/sets/_Pigman.json deleted file mode 100644 index 670b4b1..0000000 --- a/py_script/sets/_Pigman.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "items": [ - "Pigman Helmet", - "Pigman Battle Hammer" - ], - "bonuses": [ - {}, - { - "str": 20, - "eDamPct": 40 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Red+Team.json b/py_script/sets/_Red+Team.json deleted file mode 100644 index c2a3602..0000000 --- a/py_script/sets/_Red+Team.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Red Team Boots", - "Red Team Leggings", - "Red Team Chestplate", - "Red Team Helmet" - ], - "bonuses": [ - {}, - {}, - {}, - {} - ] -} \ No newline at end of file diff --git a/py_script/sets/_Relic.json b/py_script/sets/_Relic.json deleted file mode 100644 index 0c371cd..0000000 --- a/py_script/sets/_Relic.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "items": [ - "Relic Helmet", - "Relic Boots", - "Relic Leggings", - "Relic Chestplate" - ], - "bonuses": [ - {}, - { - "xpb": 10, - "lb": 10, - "hpBonus": 65, - "fDamPct": 5, - "wDamPct": 5, - "aDamPct": 5, - "tDamPct": 5, - "eDamPct": 5 - }, - { - "xpb": 25, - "lb": 25, - "hpBonus": 200, - "fDamPct": 12, - "wDamPct": 12, - "aDamPct": 12, - "tDamPct": 12, - "eDamPct": 12 - }, - { - "xpb": 50, - "lb": 50, - "str": 8, - "dex": 8, - "int": 8, - "agi": 8, - "def": 8, - "hpBonus": 425, - "fDamPct": 25, - "wDamPct": 25, - "aDamPct": 25, - "tDamPct": 25, - "eDamPct": 25 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Saint%27s.json b/py_script/sets/_Saint%27s.json deleted file mode 100644 index 99f5173..0000000 --- a/py_script/sets/_Saint%27s.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "items": [ - "Saint's Shawl", - "Saint's Sandals", - "Saint's Leggings", - "Saint's Tunic" - ], - "bonuses": [ - {}, - { - "mr": 1, - "sdPct": -5, - "mdPct": -10, - "def": 5, - "spRegen": 5, - "wDamPct": 10, - "aDamPct": 10 - }, - { - "mr": 3, - "sdPct": -10, - "mdPct": -20, - "def": 10, - "spRegen": 10, - "wDamPct": 20, - "aDamPct": 20 - }, - { - "mr": 5, - "sdPct": -15, - "mdPct": -35, - "def": 30, - "spRegen": 100, - "wDamPct": 35, - "aDamPct": 35 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Silverfish.json b/py_script/sets/_Silverfish.json deleted file mode 100644 index fa7d63a..0000000 --- a/py_script/sets/_Silverfish.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "items": [ - "Silverfish Helm", - "Silverfish Boots" - ], - "bonuses": [ - { - "spd": 5 - }, - { - "agi": 10, - "thorns": 20, - "spd": 20, - "poison": 290 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Skien%27s.json b/py_script/sets/_Skien%27s.json deleted file mode 100644 index 3eed0fb..0000000 --- a/py_script/sets/_Skien%27s.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "items": [ - "Skien Boots", - "Skien Leggings", - "Skien's Fatigues" - ], - "bonuses": [ - {}, - { - "sdPct": -10, - "mdPct": 12, - "sdRaw": -40, - "mdRaw": 30 - }, - { - "sdPct": -35, - "mdPct": 30, - "dex": 15, - "spd": 8, - "sdRaw": -90, - "mdRaw": 125 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Slime.json b/py_script/sets/_Slime.json deleted file mode 100644 index 342bf61..0000000 --- a/py_script/sets/_Slime.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "items": [ - "Slime Boots", - "Slime Plate" - ], - "bonuses": [ - {}, - { - "hprPct": 35, - "thorns": 15, - "spd": -6, - "poison": 300, - "hpBonus": 600, - "jh": 1 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Snail.json b/py_script/sets/_Snail.json deleted file mode 100644 index 260ba13..0000000 --- a/py_script/sets/_Snail.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "items": [ - "Snail Helm", - "Snail Boots", - "Snail Leggings", - "Snail Mail" - ], - "bonuses": [ - {}, - { - "str": 7, - "agi": -5, - "thorns": 10, - "spd": -5, - "poison": 880, - "hpBonus": 1100, - "hprRaw": 125 - }, - { - "str": 14, - "agi": -10, - "thorns": 20, - "spd": -10, - "poison": 2650, - "hpBonus": 2675, - "hprRaw": 275 - }, - { - "str": 21, - "agi": -15, - "thorns": 40, - "spd": -15, - "poison": 5500, - "hpBonus": 5500, - "hprRaw": 575 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Snow.json b/py_script/sets/_Snow.json deleted file mode 100644 index 6272591..0000000 --- a/py_script/sets/_Snow.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "items": [ - "Snow Helmet", - "Snow Boots", - "Snow Pants", - "Snow Tunic" - ], - "bonuses": [ - {}, - { - "hprPct": -10, - "mr": 1, - "sdPct": 4, - "ref": 10, - "thorns": 8 - }, - { - "hprPct": -20, - "mr": 2, - "sdPct": 12, - "ref": 30, - "thorns": 24 - }, - { - "hprPct": -35, - "mr": 4, - "sdPct": 28, - "ref": 70, - "thorns": 55 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Spider.json b/py_script/sets/_Spider.json deleted file mode 100644 index 9d0062d..0000000 --- a/py_script/sets/_Spider.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "items": [ - "Spinneret", - "Abdomen", - "Cephalothorax" - ], - "bonuses": [ - {}, - { - "xpb": 10, - "dex": 2, - "agi": 2, - "spd": 7, - "poison": 35 - }, - { - "xpb": 25, - "dex": 6, - "agi": 6, - "spd": 19, - "poison": 130 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Spore.json b/py_script/sets/_Spore.json deleted file mode 100644 index 1f32c8c..0000000 --- a/py_script/sets/_Spore.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Spore Cap", - "Spore Shortsword" - ], - "bonuses": [ - {}, - { - "ls": 20, - "expd": 20, - "poison": 70 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Thanos+Legionnaire.json b/py_script/sets/_Thanos+Legionnaire.json deleted file mode 100644 index 6ee1a85..0000000 --- a/py_script/sets/_Thanos+Legionnaire.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "items": [ - "Thanos Legionnaire Helm", - "Thanos Legionnaire Greaves", - "Thanos Legionnaire Leggings", - "Thanos Legionnaire Plate" - ], - "bonuses": [ - {}, - { - "str": 1, - "dex": -1, - "int": -1, - "agi": 1, - "def": 1, - "spd": 2, - "hprRaw": 60, - "mdRaw": 60 - }, - { - "str": 4, - "dex": -4, - "int": -4, - "agi": 4, - "def": 4, - "spd": 8, - "hprRaw": 180, - "mdRaw": 180 - }, - { - "str": 15, - "dex": -15, - "int": -15, - "agi": 15, - "def": 15, - "spd": 20, - "atkTier": 1, - "hprRaw": 480, - "mdRaw": 480 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Thunder+Relic.json b/py_script/sets/_Thunder+Relic.json deleted file mode 100644 index 8ae5909..0000000 --- a/py_script/sets/_Thunder+Relic.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "items": [ - "Thunder Relic Helmet", - "Thunder Relic Boots", - "Thunder Relic Leggings", - "Thunder Relic Chestplate" - ], - "bonuses": [ - {}, - { - "xpb": 5, - "lb": 10, - "hpBonus": 55, - "mdRaw": 12 - }, - { - "xpb": 10, - "lb": 25, - "hpBonus": 180, - "mdRaw": 32 - }, - { - "xpb": 25, - "lb": 50, - "dex": 20, - "hpBonus": 380, - "mdRaw": 105 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Tribal.json b/py_script/sets/_Tribal.json deleted file mode 100644 index 8532af9..0000000 --- a/py_script/sets/_Tribal.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "items": [ - "Tribal Cap", - "Tribal Boots", - "Tribal Pants", - "Tribal Tunic" - ], - "bonuses": [ - {}, - { - "str": 2, - "spd": 5 - }, - { - "str": 5, - "agi": 2, - "spd": 10 - }, - { - "sdPct": -15, - "str": 10, - "agi": 5, - "spd": 15, - "atkTier": 1 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Ultramarine.json b/py_script/sets/_Ultramarine.json deleted file mode 100644 index 52e625f..0000000 --- a/py_script/sets/_Ultramarine.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "items": [ - "Ultramarine Crown", - "Ultramarine Boots", - "Ultramarine Belt", - "Ultramarine Cape" - ], - "bonuses": [ - {}, - { - "mr": 2, - "mdPct": -24, - "int": 5, - "wDamPct": 10, - "tDamPct": -8, - "wDefPct": 16 - }, - { - "mr": 5, - "mdPct": -54, - "int": 15, - "wDamPct": 20, - "tDamPct": -18, - "wDefPct": 36 - }, - { - "mr": 8, - "mdPct": -90, - "int": 25, - "wDamPct": 40, - "tDamPct": -30, - "wDefPct": 56 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Veekhat%27s.json b/py_script/sets/_Veekhat%27s.json deleted file mode 100644 index 64277ab..0000000 --- a/py_script/sets/_Veekhat%27s.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "items": [ - "Veekhat's Horns", - "Veekhat's Udders" - ], - "bonuses": [ - {}, - { - "mdPct": 30, - "ms": 2, - "spd": 25, - "spPct2": -40 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Vexing.json b/py_script/sets/_Vexing.json deleted file mode 100644 index d51746d..0000000 --- a/py_script/sets/_Vexing.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "items": [ - "Mask of the Dark Vexations", - "Staff of the Dark Vexations" - ], - "bonuses": [ - {}, - { - "mr": 2, - "sdPct": 15, - "mdPct": -15, - "sdRaw": 30, - "spPct2": -50 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Villager.json b/py_script/sets/_Villager.json deleted file mode 100644 index c6dd53d..0000000 --- a/py_script/sets/_Villager.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "items": [ - "Villager Pants", - "Villager Mail" - ], - "bonuses": [ - {}, - { - "xpb": 20, - "lb": 60, - "eSteal": 8 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Visceral.json b/py_script/sets/_Visceral.json deleted file mode 100644 index bd5683f..0000000 --- a/py_script/sets/_Visceral.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "items": [ - "Visceral Skullcap", - "Visceral Toe", - "Visceral Legs", - "Visceral Chest" - ], - "bonuses": [ - {}, - { - "hprPct": 30, - "mdPct": 10, - "ls": 45, - "hpBonus": -1000, - "hprRaw": 35, - "mdRaw": 40 - }, - { - "hprPct": 100, - "mdPct": 25, - "ls": 90, - "hpBonus": -2500, - "hprRaw": 75, - "mdRaw": 80 - }, - { - "hprPct": 350, - "mdPct": 50, - "ls": 180, - "hpBonus": -4000, - "hprRaw": 145, - "mdRaw": 165 - } - ] -} \ No newline at end of file diff --git a/py_script/sets/_Water+Relic.json b/py_script/sets/_Water+Relic.json deleted file mode 100644 index f900450..0000000 --- a/py_script/sets/_Water+Relic.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "items": [ - "Water Relic Helmet", - "Water Relic Boots", - "Water Relic Leggings", - "Water Relic Chestplate" - ], - "bonuses": [ - {}, - { - "mr": 1, - "xpb": 5, - "lb": 10, - "hpBonus": 55 - }, - { - "mr": 2, - "xpb": 10, - "lb": 25, - "hpBonus": 170 - }, - { - "mr": 4, - "xpb": 25, - "lb": 50, - "int": 20, - "hpBonus": 360 - } - ] -} \ No newline at end of file diff --git a/py_script/skillpoint_test.py b/py_script/skillpoint_test.py index 9cdc68d..6c395b2 100644 --- a/py_script/skillpoint_test.py +++ b/py_script/skillpoint_test.py @@ -1,3 +1,8 @@ +""" +An old script used for testing skillpoint assignment algorithms. Not used commonly. +""" + + import json import math import copy @@ -7,7 +12,7 @@ with open("clean.json") as infile: def clean_item(item): if not "displayName" in item: - item["displayName"] = item["name"]; + item["displayName"] = item["name"] return item items = data["items"] diff --git a/py_script/terrs.py b/py_script/terrs.py deleted file mode 100644 index 942c320..0000000 --- a/py_script/terrs.py +++ /dev/null @@ -1,63 +0,0 @@ -import requests -import json -import time - -#used for requesting the api -'''response = requests.get("https://api.wynncraft.com/public_api.php?action=territoryList") -with open("terrs.json", "w") as outfile: - outfile.write(json.dumps(response.json()))''' - -#used for cleaning the data -'''with open("terrs.json", "r") as infile: - data = json.load(infile) - -data = data["territories"] -delkeys = ["territory","acquired","attacker"] - -for t in data: - for key in delkeys: - del data[t][key] - data[t]["neighbors"] = [] - - -with open("terrs_compress.json", "w") as outfile: - json.dump(data,outfile) -with open("terrs_clean.json", "w") as outfile: - json.dump(data,outfile,indent = 2)''' - -#used for pushing data to compress (edit in clean, move to compress) -'''with open("terrs.json", "r") as infile: - data = json.load(infile)["territories"]''' - -'''with open("terrs_clean.json", "r") as infile: - newdata = json.load(infile)''' - -'''for t in newdata: - del newdata[t]["attacker"] - del newdata[t]["acquired"]''' - - -'''response = requests.get("https://gist.githubusercontent.com/kristofbolyai/87ae828ecc740424c0f4b3749b2287ed/raw/0735f2e8bb2d2177ba0e7e96ade421621070a236/territories.json").json() -for t in data: - data[t]["neighbors"] = response[t]["Routes"] - data[t]["resources"] = response[t]["Resources"] - data[t]["storage"] = response[t]["Storage"] - data[t]["emeralds"] = response[t]["Emeralds"] - data[t]["doubleemeralds"] = response[t]["DoubleEmerald"] - data[t]["doubleresource"] = response[t]["DoubleResource"]''' - -'''with open("terrs_clean.json", "w") as outfile: - json.dump(newdata,outfile,indent=2) - -with open("terrs_compress.json", "w") as outfile: - json.dump(newdata,outfile)''' - -response = requests.get('https://api.wynncraft.com/public_api.php?action=mapLocations').json() -del response["request"] - -with open("maploc.json", "w") as outfile: - json.dump(response, outfile) -with open("maploc_clean.json", "w") as outfile: - json.dump(response, outfile, indent = 2) -with open("maploc_compress.json", "w") as outfile: - json.dump(response, outfile) \ No newline at end of file diff --git a/py_script/transform_combine.py b/py_script/transform_combine.py deleted file mode 100644 index 4662416..0000000 --- a/py_script/transform_combine.py +++ /dev/null @@ -1,179 +0,0 @@ -""" - -NOTE!!!!!!! - -DEMON TIDE 1.20 IS HARD CODED! - -AMBIVALENCE IS REMOVED! - -""" - -import json - -with open("dump.json", "r") as infile: - data = json.loads(infile.read()) - -items = data["items"] -if "request" in data: - del data["request"] - -import os -sets = dict() -item_set_map = dict() -for filename in os.listdir('sets'): - if "json" not in filename: - continue - set_name = filename[1:].split(".")[0].replace("+", " ").replace("%27", "'") - with open("sets/"+filename) as set_info: - set_obj = json.load(set_info) - for item in set_obj["items"]: - item_set_map[item] = set_name - sets[set_name] = set_obj - -data["sets"] = sets - -translate_mappings = { - #"name": "name", - #"displayName": "displayName", - #"tier": "tier", - #"set": "set", - "sockets": "slots", - #"type": "type", - #"armorType": "armorType", (deleted) - #"armorColor": "color", (deleted) - #"addedLore": "lore", (deleted) - #"material": "material", (deleted) - "dropType": "drop", - #"quest": "quest", - "restrictions": "restrict", - "damage": "nDam", - "fireDamage": "fDam", - "waterDamage": "wDam", - "airDamage": "aDam", - "thunderDamage": "tDam", - "earthDamage": "eDam", - "attackSpeed": "atkSpd", - "health": "hp", - "fireDefense": "fDef", - "waterDefense": "wDef", - "airDefense": "aDef", - "thunderDefense": "tDef", - "earthDefense": "eDef", - "level": "lvl", - "classRequirement": "classReq", - "strength": "strReq", - "dexterity": "dexReq", - "intelligence": "intReq", - "agility": "agiReq", - "defense": "defReq", - "healthRegen": "hprPct", - "manaRegen": "mr", - "spellDamage": "sdPct", - "damageBonus": "mdPct", - "lifeSteal": "ls", - "manaSteal": "ms", - "xpBonus": "xpb", - "lootBonus": "lb", - "reflection": "ref", - "strengthPoints": "str", - "dexterityPoints": "dex", - "intelligencePoints": "int", - "agilityPoints": "agi", - "defensePoints": "def", - #"thorns": "thorns", - "exploding": "expd", - "speed": "spd", - "attackSpeedBonus": "atkTier", - #"poison": "poison", - "healthBonus": "hpBonus", - "soulPoints": "spRegen", - "emeraldStealing": "eSteal", - "healthRegenRaw": "hprRaw", - "spellDamageRaw": "sdRaw", - "damageBonusRaw": "mdRaw", - "bonusFireDamage": "fDamPct", - "bonusWaterDamage": "wDamPct", - "bonusAirDamage": "aDamPct", - "bonusThunderDamage": "tDamPct", - "bonusEarthDamage": "eDamPct", - "bonusFireDefense": "fDefPct", - "bonusWaterDefense": "wDefPct", - "bonusAirDefense": "aDefPct", - "bonusThunderDefense": "tDefPct", - "bonusEarthDefense": "eDefPct", - "accessoryType": "type", - "identified": "fixID", - #"skin": "skin", - #"category": "category", - - "spellCostPct1": "spPct1", - "spellCostRaw1": "spRaw1", - "spellCostPct2": "spPct2", - "spellCostRaw2": "spRaw2", - "spellCostPct3": "spPct3", - "spellCostRaw3": "spRaw3", - "spellCostPct4": "spPct4", - "spellCostRaw4": "spRaw4", - - "rainbowSpellDamageRaw": "rainbowRaw", - #"sprint": "sprint", - "sprintRegen": "sprintReg", - "jumpHeight": "jh", - "lootQuality": "lq", - - "gatherXpBonus": "gXp", - "gatherSpeed": "gSpd", -} - -delete_keys = [ - "addedLore", - #"skin", - "armorType", - "armorColor", - "material" -] - -import os -if os.path.exists("id_map.json"): - with open("id_map.json","r") as id_mapfile: - id_map = json.load(id_mapfile) -else: - id_map = {item["name"]: i for i, item in enumerate(items)} - - -texture_names = [] - -import base64 -for item in items: - for key in delete_keys: - if key in item: - del item[key] - - for k in list(item.keys()): - if (item[k] == 0 or item[k] is None): - del item[k] - - for k, v in translate_mappings.items(): - if k in item: - item[v] = item[k] - del item[k] - - if not (item["name"] in id_map): - id_map[item["name"]] = len(id_map) - print(f'New item: {item["name"]}') - item["id"] = id_map[item["name"]] - - item["type"] = item["type"].lower() - if item["name"] in item_set_map: - item["set"] = item_set_map[item["name"]] - -#with open("1_20_ci.json", "r") as ci_file: -# ci_items = json.load(ci_file) -# items.extend(ci_items) - -with open("id_map.json","w") as id_mapfile: - json.dump(id_map, id_mapfile, indent=2) -with open("clean.json", "w") as outfile: - json.dump(data, outfile, indent=2) -with open("compress.json", "w") as outfile: - json.dump(data, outfile) diff --git a/py_script/transform_merge.py b/py_script/transform_merge.py deleted file mode 100644 index 49a962d..0000000 --- a/py_script/transform_merge.py +++ /dev/null @@ -1,214 +0,0 @@ -""" - -NOTE!!!!!!! - -DEMON TIDE 1.20 IS HARD CODED! - -AMBIVALENCE IS REMOVED! - -""" - -import json -import os - -with open("dump.json", "r") as infile: - data = json.load(infile) - -with open("updated.json", "r") as oldfile: - old_data = json.load(oldfile) - -items = data["items"] -old_items = old_data["items"] -old_tomes = old_data["tomes"] -if "request" in data: - del data["request"] - -#this script does not change sets or tomes. use the dedicated set and tome update scripts to update. -data["sets"] = old_data["sets"] -data["tomes"] = old_data["tomes"] - -item_set_map = dict() -for set_name, set_data in data["sets"].items(): - for item_name in set_data["items"]: - item_set_map[item_name] = set_name - -must_mappings = [ - "strength", - "dexterity", - "intelligence", - "agility", - "defense", - "strengthPoints", - "dexterityPoints", - "intelligencePoints", - "agilityPoints", - "defensePoints", -] - -translate_mappings = { - #"name": "name", - #"displayName": "displayName", - #"tier": "tier", - #"set": "set", - "sockets": "slots", - #"type": "type", - #"armorType": "armorType", (deleted) - "armorColor": "color", #(deleted) - "addedLore": "lore", #(deleted) - #"material": "material", (deleted) - "dropType": "drop", - #"quest": "quest", - "restrictions": "restrict", - "damage": "nDam", - "fireDamage": "fDam", - "waterDamage": "wDam", - "airDamage": "aDam", - "thunderDamage": "tDam", - "earthDamage": "eDam", - "attackSpeed": "atkSpd", - "health": "hp", - "fireDefense": "fDef", - "waterDefense": "wDef", - "airDefense": "aDef", - "thunderDefense": "tDef", - "earthDefense": "eDef", - "level": "lvl", - "classRequirement": "classReq", - "strength": "strReq", - "dexterity": "dexReq", - "intelligence": "intReq", - "agility": "agiReq", - "defense": "defReq", - "healthRegen": "hprPct", - "manaRegen": "mr", - "spellDamage": "sdPct", - "damageBonus": "mdPct", - "lifeSteal": "ls", - "manaSteal": "ms", - "xpBonus": "xpb", - "lootBonus": "lb", - "reflection": "ref", - "strengthPoints": "str", - "dexterityPoints": "dex", - "intelligencePoints": "int", - "agilityPoints": "agi", - "defensePoints": "def", - #"thorns": "thorns", - "exploding": "expd", - "speed": "spd", - "attackSpeedBonus": "atkTier", - #"poison": "poison", - "healthBonus": "hpBonus", - "soulPoints": "spRegen", - "emeraldStealing": "eSteal", - "healthRegenRaw": "hprRaw", - "spellDamageRaw": "sdRaw", - "damageBonusRaw": "mdRaw", - "bonusFireDamage": "fDamPct", - "bonusWaterDamage": "wDamPct", - "bonusAirDamage": "aDamPct", - "bonusThunderDamage": "tDamPct", - "bonusEarthDamage": "eDamPct", - "bonusFireDefense": "fDefPct", - "bonusWaterDefense": "wDefPct", - "bonusAirDefense": "aDefPct", - "bonusThunderDefense": "tDefPct", - "bonusEarthDefense": "eDefPct", - "accessoryType": "type", - "identified": "fixID", - #"skin": "skin", - #"category": "category", - - "spellCostPct1": "spPct1", - "spellCostRaw1": "spRaw1", - "spellCostPct2": "spPct2", - "spellCostRaw2": "spRaw2", - "spellCostPct3": "spPct3", - "spellCostRaw3": "spRaw3", - "spellCostPct4": "spPct4", - "spellCostRaw4": "spRaw4", - - "rainbowSpellDamageRaw": "rainbowRaw", - #"sprint": "sprint", - "sprintRegen": "sprintReg", - "jumpHeight": "jh", - "lootQuality": "lq", - - "gatherXpBonus": "gXp", - "gatherSpeed": "gSpd", -} - -delete_keys = [ - #"addedLore", - #"skin", - #"armorType", - #"armorColor", - #"material" -] - -id_map = {item["name"]: item["id"] for item in old_items} -used_ids = set([v for k, v in id_map.items()]) -max_id = 0 - -known_item_names = set() - -for item in items: - known_item_names.add(item["name"]) - -old_items_map = dict() -unchanged_items = [] -remap_items = [] -for item in old_items: - if "remapID" in item: - remap_items.append(item) - elif item["name"] not in known_item_names: - unchanged_items.append(item) - old_items_map[item["name"]] = item - -for item in items: - for key in delete_keys: - if key in item: - del item[key] - - for k in list(item.keys()): - if (item[k] == 0 or item[k] is None) and not k in must_mappings: - del item[k] - - for k, v in translate_mappings.items(): - if k in item: - item[v] = item[k] - del item[k] - - if not (item["name"] in id_map): - while max_id in used_ids: - max_id += 1 - used_ids.add(max_id) - id_map[item["name"]] = max_id - print(f'New item: {item["name"]} (id: {max_id})') - item["id"] = id_map[item["name"]] - - item["type"] = item["type"].lower() - if "displayName" in item: - item_name = item["displayName"] - else: - item_name = item["name"] - if item_name in item_set_map: - item["set"] = item_set_map[item_name] - if item["name"] in old_items_map: - old_item = old_items_map[item["name"]] - if "hideSet" in old_item: - item["hideSet"] = old_item["hideSet"] - -items.extend(unchanged_items) -items.extend(remap_items) -with open("id_map.json","w") as id_mapfile: - print("{", file=id_mapfile) - outputs = [] - for v, k in sorted((v, k) for k, v in id_map.items()): - outputs.append(f' "{k}": {v}') - print(',\n'.join(outputs), file=id_mapfile) - print("}", file=id_mapfile) -with open("clean.json", "w") as outfile: - json.dump(data, outfile, indent=2) -with open("compress.json", "w") as outfile: - json.dump(data, outfile) diff --git a/py_script/update_sets_in_items.py b/py_script/update_sets_in_items.py deleted file mode 100644 index fc4dd7d..0000000 --- a/py_script/update_sets_in_items.py +++ /dev/null @@ -1,25 +0,0 @@ -import os - -'''takes the data in updated.json and the jsons in the sets folder to update the sets in the db.''' - -with open("updated.json", "r") as oldfile: - data = json.load(oldfile) - -#This probably does not work. I have not checked :) -sets = dict() -for filename in os.listdir('sets'): - if "json" not in filename: - continue - set_name = filename[1:].split(".")[0].replace("+", " ").replace("%27", "'") - with open("sets/"+filename) as set_info: - set_obj = json.load(set_info) - for item in set_obj["items"]: - item_set_map[item] = set_name - sets[set_name] = set_obj - -data["sets"] = sets - -with open("clean.json", "w") as outfile: - json.dump(data, outfile, indent=2) -with open("compress.json", "w") as outfile: - json.dump(data, outfile) \ No newline at end of file diff --git a/py_script/update_tomes_in_items.py b/py_script/update_tomes_in_items.py deleted file mode 100644 index bc177bb..0000000 --- a/py_script/update_tomes_in_items.py +++ /dev/null @@ -1,42 +0,0 @@ -import os -import json - -'''takes updated data in tomes.json and updates the tome map''' - -#read in tomes json file -with open("../tomes.json", "r") as tomesfile: - tome_data = json.load(tomesfile) - -tomes = dict() -tome_mapping = dict() - - -max_id = 0 -for tome in tome_data: - if "id" in tome: - if tome["id"] > max_id: - max_id = tome["id"] - tome_mapping[tome["name"]] = tome["id"] -i = max_id + 1 - -for tome in tome_data: - if "id" not in tome: - tome["id"] = i - tome_mapping[tome["name"]] = i - i += 1 - - tomes[tome["name"]] = tome - - -''' -with open("clean.json", "w") as outfile: - json.dump(data, outfile, indent=2) -with open("compress.json", "w") as outfile: - json.dump(data, outfile) -''' -with open("tome_map.json", "w") as outfile: - json.dump(tome_mapping, outfile, indent = 2) -with open("../tomes2.json", "w") as outfile: - json.dump(tome_data, outfile, indent = 2) - - diff --git a/py_script/validate.py b/py_script/validate.py index 734b27d..d064ea2 100644 --- a/py_script/validate.py +++ b/py_script/validate.py @@ -1,3 +1,12 @@ +""" +Used to validate item file - searches for duplicate items. Does not change the file. + +TODO: Eventually integrate this into the process scripts, including ings and recipes + +Usage: +python validate.py [input file] +""" + import json import sys From 0166b7814d90f64d9c6ca2bfb1afd3f5e3261ac8 Mon Sep 17 00:00:00 2001 From: ferricles Date: Wed, 15 Jun 2022 12:53:51 -0700 Subject: [PATCH 009/155] crafted item version 4 build decode bug fix, crafted page slight bug fixes --- crafter/index.html | 2 +- js/crafter.js | 2 +- js/sq2builder.js | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crafter/index.html b/crafter/index.html index 645d2b6..76f6cf3 100644 --- a/crafter/index.html +++ b/crafter/index.html @@ -211,7 +211,7 @@
-
diff --git a/js/crafter.js b/js/crafter.js index dbbbfd6..66b9087 100644 --- a/js/crafter.js +++ b/js/crafter.js @@ -264,7 +264,7 @@ function populateFields() { */ function copyRecipeHash() { if (player_craft) { - copyTextToClipboard("CR-"+location.hash); + copyTextToClipboard("CR-"+location.hash.slice(1)); document.getElementById("copy-hash-button").textContent = "Copied!"; } } diff --git a/js/sq2builder.js b/js/sq2builder.js index b758e3b..2dc8e91 100644 --- a/js/sq2builder.js +++ b/js/sq2builder.js @@ -142,6 +142,7 @@ function parsePowdering(powder_info) { * Populate fields based on url, and calculate build. */ function decodeBuild(url_tag) { + console.log("decoding build"); if (url_tag) { //default values let equipment = [null, null, null, null, null, null, null, null, null]; @@ -168,11 +169,12 @@ function decodeBuild(url_tag) { let info_str = info[1]; let start_idx = 0; for (let i = 0; i < 9; ++i ) { - if (info_str.charAt(start_idx) === "-") { - equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18); - start_idx += 18; - } - else { + console.log(info_str.slice(start_idx,start_idx+3)); + if (info_str.slice(start_idx,start_idx+3) === "CR-") { + console.log(info_str.slice(start_idx, start_idx+20)); + equipment[i] = info_str.slice(start_idx, start_idx+20); + start_idx += 20; + } else { let equipment_str = info_str.slice(start_idx, start_idx+3); equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); start_idx += 3; From db7981024f04f2c7a1c938288f40b67e57b19167 Mon Sep 17 00:00:00 2001 From: ferricles Date: Wed, 15 Jun 2022 15:25:47 -0700 Subject: [PATCH 010/155] removed printouts --- js/craft.js | 1 - js/sq2builder.js | 3 --- 2 files changed, 4 deletions(-) diff --git a/js/craft.js b/js/craft.js index 81d632c..1d3ae41 100644 --- a/js/craft.js +++ b/js/craft.js @@ -190,7 +190,6 @@ class Craft{ let amounts = this.recipe.get("materials").map(x=> x.get("amount")); //Mat Multipliers - should work! matmult = (tierToMult[tiers[0]]*amounts[0] + tierToMult[tiers[1]]*amounts[1]) / (amounts[0]+amounts[1]); - console.log(matmult); let low = this.recipe.get("healthOrDamage")[0]; let high = this.recipe.get("healthOrDamage")[1]; diff --git a/js/sq2builder.js b/js/sq2builder.js index 2dc8e91..22f5f18 100644 --- a/js/sq2builder.js +++ b/js/sq2builder.js @@ -142,7 +142,6 @@ function parsePowdering(powder_info) { * Populate fields based on url, and calculate build. */ function decodeBuild(url_tag) { - console.log("decoding build"); if (url_tag) { //default values let equipment = [null, null, null, null, null, null, null, null, null]; @@ -169,9 +168,7 @@ function decodeBuild(url_tag) { let info_str = info[1]; let start_idx = 0; for (let i = 0; i < 9; ++i ) { - console.log(info_str.slice(start_idx,start_idx+3)); if (info_str.slice(start_idx,start_idx+3) === "CR-") { - console.log(info_str.slice(start_idx, start_idx+20)); equipment[i] = info_str.slice(start_idx, start_idx+20); start_idx += 20; } else { From 5216686040e6747c78bb775d2add578d5a0e3567 Mon Sep 17 00:00:00 2001 From: ferricles Date: Wed, 15 Jun 2022 17:07:54 -0700 Subject: [PATCH 011/155] ability trees for different classes + selection based on weapon --- builder/index.html | 8 +-- js/atree_constants.js | 110 +++++++++++++++++++++++++++++++++++++++++- js/build_utils.js | 1 + js/sq2bs.js | 8 ++- 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/builder/index.html b/builder/index.html index 919c603..3539c4b 100644 --- a/builder/index.html +++ b/builder/index.html @@ -272,7 +272,7 @@
- +
@@ -612,12 +612,12 @@
-
+
-
+
-
+
Active:
diff --git a/js/atree_constants.js b/js/atree_constants.js index 6161f9e..94dea24 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -1,3 +1,111 @@ +const atrees = +{ + "Assassin": [ + { + "title": "Spin Attack", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 0, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 1, + "col": 4 + }, + { + "title": "Dagger Proficiency I", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 2, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 2, + "col": 3 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 2, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 3, + "col": 4 + }, + { + "title": "Double Spin", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 4, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 5, + "col": 4 + }, + { + "title": "Dash", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 6, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 6, + "col": 3 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 6, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 5, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 6, + "col": 6 + }, + ], + "Warrior": [], + "Mage": [], + "Archer": [], + "Shaman": [] +} + const atree_example = [ { "title": "skill", @@ -10,7 +118,7 @@ const atree_example = [ { "image": "../media/atree/connect_angle.png", "connector": true, - "rotate": "270", + "rotate": 270, "row": 4, "col": 3, }, diff --git a/js/build_utils.js b/js/build_utils.js index 16bacb5..7bbcafd 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -56,6 +56,7 @@ const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ]; //0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27 const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"]; +const wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "Shaman"]]) const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tomeTypes).map(x => x.substring(0,1).toUpperCase() + x.substring(1)); //weaponTypes.push("sword"); diff --git a/js/sq2bs.js b/js/sq2bs.js index 8b97a88..092df9a 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -73,7 +73,7 @@ document.addEventListener('DOMContentLoaded', function() { }; }); - construct_AT(document.getElementById("atree-ui"), atree_example); + construct_AT(document.getElementById("atree-ui"), atrees["Assassin"]); //dagger is default atree document.getElementById("atree-dropdown").style.display = "none"; }); @@ -232,9 +232,13 @@ function update_field(field) { }; } - // set weapon img + // set weapon img and set ability tree if (category == 'weapon') { document.querySelector("#weapon-img").setAttribute('src', '../media/items/new/generic-'+type+'.png'); + construct_AT(document.getElementById("atree-ui"), atrees[wep_to_class[type.toLowerCase()]]); //dagger is default atree + document.getElementById("atree-dropdown").style.display = "none"; + + //TODO: reset chosen abilities (once ability implementation is here) } } /* tabulars | man i hate this code but too lazy to fix /shrug */ From 4f43a81313f9b77ab4293556b545979af3106547 Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 16 Jun 2022 07:18:29 +0700 Subject: [PATCH 012/155] change columns to 9, adjust sizes, fix selecteds --- builder/index.html | 2 +- css/sq2bs.css | 9 +++------ js/sq2bs.js | 18 ++++++++---------- media/atree/connect-3.png | Bin 0 -> 7142 bytes 4 files changed, 12 insertions(+), 17 deletions(-) create mode 100644 media/atree/connect-3.png diff --git a/builder/index.html b/builder/index.html index 919c603..c307134 100644 --- a/builder/index.html +++ b/builder/index.html @@ -617,7 +617,7 @@
-
+
Active:
diff --git a/css/sq2bs.css b/css/sq2bs.css index 6790cb9..27fb6dd 100644 --- a/css/sq2bs.css +++ b/css/sq2bs.css @@ -475,12 +475,9 @@ a:hover { } /* atree hover */ -.atree-node { - opacity: 75%; -} - -.atree-node:hover { - opacity: 100%; +.atree-selected { + border: 5px solid blue; + border-radius: 50%; } .hide-scroll { diff --git a/js/sq2bs.js b/js/sq2bs.js index 8b97a88..cf57510 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -544,10 +544,10 @@ function construct_AT(elem, tree) { let row = document.createElement('div'); row.classList.add("row"); row.id = "atree-row-" + j; - row.style.height = elem.getBoundingClientRect().width / 5 + "px"; + row.style.height = elem.getBoundingClientRect().width / 9 + "px"; - for (let k = 0; k < 5; k++) { + for (let k = 0; k < 9; k++) { col = document.createElement('div'); col.classList.add('col', 'px-0'); row.appendChild(col); @@ -571,7 +571,7 @@ function construct_AT(elem, tree) { if (e.target !== this) {return;} let tooltip = this.children[0]; tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px"; - tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .05 / 2) + "px"; + tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px"; tooltip.style.display = "block"; }); @@ -581,13 +581,11 @@ function construct_AT(elem, tree) { tooltip.style.display = "none"; }); - - - node_elem.classList.add("atree-node"); + node_elem.classList.add("fake-button"); let active_tooltip = document.createElement('div'); - active_tooltip.classList.add("rounded-bottom", "dark-7", "border"); - active_tooltip.style.width = elem.getBoundingClientRect().width * .95 + "px"; + active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto"); + active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; active_tooltip.style.display = "none"; // tooltip text formatting @@ -618,11 +616,11 @@ function construct_AT(elem, tree) { let tooltip = document.getElementById("atree-ab-" + node.title.replaceAll(" ", "")); if (tooltip.style.display == "block") { tooltip.style.display = "none"; - this.classList.add("atree-node"); + this.classList.remove("atree-selected"); } else { tooltip.style.display = "block"; - this.classList.remove("atree-node"); + this.classList.add("atree-selected"); } }); }; diff --git a/media/atree/connect-3.png b/media/atree/connect-3.png new file mode 100644 index 0000000000000000000000000000000000000000..736036614e8594d8f6ea71c1300c387407a62b4f GIT binary patch literal 7142 zcmeHMc{J2*`yXqvL?MI97?N$w3IQI;$v z(I$FQM2m>Dh-OevDV5)M)YEd__x+vIbAIpduVy)Of3M|pUDxNn?)%*LnRItIM;S>K zNeBcYOlW&PaiRf`{wE+TTSO*Got3( z^{#krfg3bEJ3mqodc17paQNJbQ;dt{f5mNk|74EW+Ok!=ZYgb0Eq}+G=D~L{9pSAb zyXoS(PdGh%wZ6J@WfszG7X{ZN@lsgvPt)(`jBKlPuDUL40ewPm^g+w9tE z^uWC$E6Vnd#n68F_M6c5%*H)@sI1uGIBtfFlJc(xSPxHO{O2g};%{ zZO@dd*JmobJo^>LaK~z@G7>|#k#!sB!?JJL-k%La+Os>q0F~3XxQjqC|!g|;7~TQ10!r>s8^KX1#RHlvS+Mv zU42uTu%N>p6NrZ@@E*Cc`qx9PDYaDXm`>J6V-C(ZoVtcxz&P zC`Wvc1t;IbF%RY;XQe5%K1Y}KVX^K_W9qZc+HPDCH&mrqYxT_={&k@(hojp*Onedw z{cMfUKkBl#x^l$eH6XzlNs=~#EZ;I?eNaYN4hN-dp4Jhw{S);tG~U-eK9^jWZhARB z=z!(k4^q(vlW8iA((%b&dD_mHx`#^HOTBqzQ;?Z^dZ{kV7+TEskM*t z`-`o5R5;=}t3qbt(>Lxr*gmE1tG(EFoVZtbTp43RbMrssTwhmNsAyJ~uKh4r_3h?7 z~ekizb$qz-O>qX?JVkk)mVI^Ns-MklP&6Fv4uvo>~arQ(w68+ZH^KOs3~C;kej9+Y@h3 z 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 TO{u6v2>5cgce6XaHi-K_$P=Ah literal 0 HcmV?d00001 From 9036de536ba25469e9b0518ef62b02e06cf480be Mon Sep 17 00:00:00 2001 From: ferricles Date: Wed, 15 Jun 2022 22:03:20 -0700 Subject: [PATCH 013/155] modified atree graphics, added full assassin ability tree --- css/sq2bs.css | 6 +- js/atree_constants.js | 1148 ++++++++++++++++++++++++++++++++- js/sq2bs.js | 2 + media/atree/connect-3.png | Bin 7142 -> 0 bytes media/atree/connect_angle.png | Bin 1066 -> 1081 bytes media/atree/connect_line.png | Bin 1184 -> 1270 bytes media/atree/connect_t.png | Bin 718 -> 694 bytes media/atree/node-blocked.png | Bin 0 -> 2416 bytes media/atree/node-selected.png | Bin 0 -> 2418 bytes media/atree/node.png | Bin 5645 -> 2806 bytes 10 files changed, 1150 insertions(+), 6 deletions(-) delete mode 100644 media/atree/connect-3.png create mode 100644 media/atree/node-blocked.png create mode 100644 media/atree/node-selected.png diff --git a/css/sq2bs.css b/css/sq2bs.css index 27fb6dd..0532fc0 100644 --- a/css/sq2bs.css +++ b/css/sq2bs.css @@ -474,11 +474,7 @@ a:hover { transform: rotate(270deg); } -/* atree hover */ -.atree-selected { - border: 5px solid blue; - border-radius: 50%; -} + .hide-scroll { -ms-overflow-style: none; /* Internet Explorer 10+ */ diff --git a/js/atree_constants.js b/js/atree_constants.js index 94dea24..8ed02d6 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -88,7 +88,7 @@ const atrees = "image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, - "row": 5, + "row": 6, "col": 5 }, { @@ -99,6 +99,1152 @@ const atrees = "row": 6, "col": 6 }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 7, + "col": 2 + }, + { + "title": "Smoke Bomb", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 8, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 7, + "col": 6 + }, + { + "title": "Multihit", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 8, + "col": 6 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 8, + "col": 3 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 8, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 8, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 8, + "col": 1 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 8, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 9, + "col": 0 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 10, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 9, + "col": 2 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 10, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 9, + "col": 6 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 10, + "col": 6 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 8, + "col": 7 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 8, + "col": 8 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 9, + "col": 8 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 10, + "col": 8 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 10, + "col": 1 + }, + { + "title": "Backstab", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 11, + "col": 1 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 9, + "col": 4 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 90, + "row": 10, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 10, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 11, + "col": 4 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 10, + "col": 7 + }, + { + "title": "Fatality", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 11, + "col": 7 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 11, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 12, + "col": 0 + }, + { + "title": "Violent Vortex", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 13, + "col": 0 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 11, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 12, + "col": 2 + }, + { + "title": "Vanish", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 13, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 12, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 13, + "col": 3 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 13, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 13, + "col": 6 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 14, + "col": 2 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 15, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 14, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 15, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 12, + "col": 7 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 13, + "col": 7 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 14, + "col": 7 + }, + { + "title": "Lacerate", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 15, + "col": 7 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 15, + "col": 1 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 16, + "col": 1 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 15, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 16, + "col": 5 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 15, + "col": 8 + }, + { + "title": "Wall of Smoke", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 16, + "col": 8 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 16, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 17, + "col": 0 + }, + { + "title": "Silent Killer", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 18, + "col": 0 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 16, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 17, + "col": 2 + }, + { + "title": "Shadow Travel", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 18, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 17, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 18, + "col": 5 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 17, + "col": 8 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 18, + "col": 8 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 18, + "col": 4 + }, + { + "title": "Exploding Clones", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 19, + "col": 4 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 18, + "col": 3 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 19, + "col": 0 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 20, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 19, + "col": 3 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 20, + "col": 3 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 18, + "col": 6 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 18, + "col": 7 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 19, + "col": 7 + }, + { + "title": "Weightless", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 20, + "col": 7 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 20, + "col": 1 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 20, + "col": 2 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 21, + "col": 1 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 20, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 21, + "col": 4 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 20, + "col": 6 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 21, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 21, + "col": 6 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 20, + "col": 8 + }, + { + "title": "Dancing Blade", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 21, + "col": 8 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 21, + "col": 0 + }, + { + "title": "Spin Attack Damage", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 22, + "col": 0 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 21, + "col": 3 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 22, + "col": 3 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 22, + "col": 1 + }, + { + "title": "Marked", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 23, + "col": 1 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 22, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 23, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 23, + "col": 5 + }, + { + "title": "Shurikens", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 23, + "col": 6 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 23, + "col": 7 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 22, + "col": 8 + }, + { + "title": "Far Reach", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 23, + "col": 8 + }, + { + "title": "Stronger Multihit", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 24, + "col": 5 + }, + { + "title": "Psithurism", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 24, + "col": 7 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 24, + "col": 1 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 25, + "col": 1 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 25, + "col": 3 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 24, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 25, + "col": 4 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 25, + "col": 5 + }, + { + "title": "Choke Bomb", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 25, + "col": 6 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 25, + "col": 7 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 25, + "col": 8 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 26, + "col": 5 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 25, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 26, + "col": 0 + }, + { + "title": "Death Magnet", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 27, + "col": 0 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 25, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 26, + "col": 2 + }, + { + "title": "Cheaper Multihit", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 27, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 26, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 27, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 26, + "col": 7 + }, + { + "title": "Parry", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 27, + "col": 7 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 27, + "col": 1 + }, + { + "title": "Fatal Spin", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 28, + "col": 1 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 27, + "col": 3 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 28, + "col": 3 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 27, + "col": 6 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 28, + "col": 6 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 27, + "col": 8 + }, + { + "title": "Wall Jump", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 28, + "col": 8 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 28, + "col": 0 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 29, + "col": 0 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 29, + "col": 1 + }, + { + "title": "Harvester", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 30, + "col": 1 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 28, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 29, + "col": 4 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 30, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 28, + "col": 7 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 29, + "col": 7 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 30, + "col": 7 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 30, + "col": 2 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 31, + "col": 2 + }, + { + "image": "../media/atree/connect_t.png", + "connector": true, + "rotate": 180, + "row": 30, + "col": 5 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 30, + "col": 6 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 31, + "col": 5 + }, + { + "title": "Ricochet", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 31, + "col": 8 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 31, + "col": 1 + }, + { + "title": "Satsujin", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 32, + "col": 1 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 31, + "col": 4 + }, + { + "title": "Forbidden Art", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 32, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 31, + "col": 7 + }, + { + "title": "Jasmine Bloom", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 32, + "col": 7 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 180, + "row": 32, + "col": 0 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 33, + "col": 0 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 32, + "col": 2 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 33, + "col": 2 + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 32, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 33, + "col": 5 + }, + { + "title": "Text", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 33, + "col": 8 + }, ], "Warrior": [], "Mage": [], diff --git a/js/sq2bs.js b/js/sq2bs.js index 67bbef3..a1de216 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -621,10 +621,12 @@ function construct_AT(elem, tree) { if (tooltip.style.display == "block") { tooltip.style.display = "none"; this.classList.remove("atree-selected"); + this.style.backgroundImage = 'url("../media/atree/node.png")'; } else { tooltip.style.display = "block"; this.classList.add("atree-selected"); + this.style.backgroundImage = 'url("../media/atree/node-selected.png")'; } }); }; diff --git a/media/atree/connect-3.png b/media/atree/connect-3.png deleted file mode 100644 index 736036614e8594d8f6ea71c1300c387407a62b4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7142 zcmeHMc{J2*`yXqvL?MI97?N$w3IQI;$v z(I$FQM2m>Dh-OevDV5)M)YEd__x+vIbAIpduVy)Of3M|pUDxNn?)%*LnRItIM;S>K zNeBcYOlW&PaiRf`{wE+TTSO*Got3( z^{#krfg3bEJ3mqodc17paQNJbQ;dt{f5mNk|74EW+Ok!=ZYgb0Eq}+G=D~L{9pSAb zyXoS(PdGh%wZ6J@WfszG7X{ZN@lsgvPt)(`jBKlPuDUL40ewPm^g+w9tE z^uWC$E6Vnd#n68F_M6c5%*H)@sI1uGIBtfFlJc(xSPxHO{O2g};%{ zZO@dd*JmobJo^>LaK~z@G7>|#k#!sB!?JJL-k%La+Os>q0F~3XxQjqC|!g|;7~TQ10!r>s8^KX1#RHlvS+Mv zU42uTu%N>p6NrZ@@E*Cc`qx9PDYaDXm`>J6V-C(ZoVtcxz&P zC`Wvc1t;IbF%RY;XQe5%K1Y}KVX^K_W9qZc+HPDCH&mrqYxT_={&k@(hojp*Onedw z{cMfUKkBl#x^l$eH6XzlNs=~#EZ;I?eNaYN4hN-dp4Jhw{S);tG~U-eK9^jWZhARB z=z!(k4^q(vlW8iA((%b&dD_mHx`#^HOTBqzQ;?Z^dZ{kV7+TEskM*t z`-`o5R5;=}t3qbt(>Lxr*gmE1tG(EFoVZtbTp43RbMrssTwhmNsAyJ~uKh4r_3h?7 z~ekizb$qz-O>qX?JVkk)mVI^Ns-MklP&6Fv4uvo>~arQ(w68+ZH^KOs3~C;kej9+Y@h3 z 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 TO{u6v2>5cgce6XaHi-K_$P=Ah diff --git a/media/atree/connect_angle.png b/media/atree/connect_angle.png index 3ea2c1650b9085635c3fd31d4868c18582e7df5f..46c5e81aa19b176dafaba4aa04be9b0359cc4982 100644 GIT binary patch delta 562 zcmZ3*v6ExNX2yD^0#6smkcv5PZ*1gsG8AAvX#DH%_8cyTMvn~%=}zwYQFHgS#c!0C z`*!%UVfNbP=kMP;n4Y(so#8nLLklP6sGf2oC^Dg*8lE801+m7P{reft6)-x?V5kRT z5W2B-yS>ATB9IWx(T!O%YpYo5v**ek$mY^x;1GtO$s3uICm&#D6K^PgcP5cbcWP+v QJO&`}boFyt=akR{0C4?~VE_OC delta 550 zcmdnVv5I5EX2yC3wj^(N7l!{JxM1({$qWn(oCO|{#S9F3${@^GvDCf{D9B#o>Fdh= zjE#p~P~SvC;5AT4vcxr_#5q4VH#M(>!MP|ku_QG`p**uBL&4qCHz2%`Pn>~)iOJK& zF{I+w+iM4T8x#aw5B4wQSiRX|^g zE}peR2rGbfdS zL1SX=L|c!;4l+mMgSRS)vRijpq-4z1>FBcZ675b{xJ5Lo)L`cq?pY=(ntFXr*#{5y zA6?bly?Gtqx+aAmjE`PCS@J?j^`E}xmX41PcF4bfXZ!9RqtoI;A)B*h0!&YP98FF# z+pfhHcd=RF&kRSGq-FCw^wS<#?tAy}@$1+r>6P<;7k|;NoXlu9v7+x~o9iA&o4uJY z=X~#UUZbLU?N9FJ%3Rg6rL&Yfk~uk56rJ37Q$rFIy;i$*b&9+_Zu94GpS`Ev z%$QVzBAo?XqLUu8|8Ks3cUJ33PIFBO=3VhQzm=I-${N$|Zrk6VyStL<$NWa^wQtv( zHQe;S^g36!hJ`1GS^Rpg?>%O{@`i=#u|_KHpDv4Vu|D|Qvrm(q=N?mSc3%G4+BtR% zlCQFUvRPQLxOGq2`|jhrd*|-`v%UQ4J^RYoqP;J+FVkaSVAl0?aSW-L^Y+R@!3GBb z7RSCn#dS;!%@4b08@dqXV?!=X_$8UiCQ1oT|4 WvN7~ca0~>NXAGXMelF{r5}E+Vf})lH literal 1184 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G$5xX)7d$|)7e>}peR2rGbfdS zL1SX=L|c!;4l+mMgSRS)vRijpq-4z1>FBcZ675b{xJ5Lo)L`cq?pY=(ntFXr*#{5y zA6?bly?Gtqx+aAmjE`PCS@J?j^`E}xmX41PcF4bfXZ!9RqtoI;A)B*h0!&YP98FF# z+pfhHcd=RF&kRSGq-FCw^wS<#?tAy}@$1+r>6P<;7k|;NoXlu9v7+x~o9iA&o4uJY z=X~#UUZbLU?N9FJ%3Rg6rL&Yfk~uk56rJ37Q$rFIy;i$*b&9+_Zu94GpS`Ev z%$QVzBAo?XqLUu8|8Ks3cUJ33PIFBO=3VhQzm=I-${N$|Zrk6VyStL<$NWa^wQtv( zHQe;S^g36!hJ`1GS^Rpg?>%O{@`i=#u|_KHpDv4Vu|D|Qvrm(q=N?mSc3%G4+BtR% zlCQFUvRPQLxOGq2`|jhrd*|-`v%UQ4J^RYoqP;J+FVh1?3tN)6y9>jA5L~c#`DCC7 zXMsm#F$061G6*wPEVVBK3bL1Y`ns||W8+~LwAg-nst!;{vcxr_#5q4VH#M(>!MP|k zu_QG`p**uBL&4qCHz2%`Pn>~)slwC6F{I+w+iMqj83cG797UaHoGVzinbrBn#T~O3 zWIlBj*>r<5oiDvQdMlFv!vp8DTtc&7&y92|=eK8Y0Hqxk1_4qT?mj0a*K_z@Z2X*f@bh^^>bP0l+XkK2ay&| diff --git a/media/atree/connect_t.png b/media/atree/connect_t.png index 8aa4f12c0fc15a07c8cc5e033756bc830c956e71..14d3c16b69250a233cd1de4ca7f2a0b44f98b040 100644 GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=B*Ko-U3d z6?5L+Fyv!0Y`KQ`W_}XlrNx5dCd*WBNtI?6u1u`@cU}owr<^ z;hZ1?hwvyOJp^{2KV!F-rT+BoUqyOIx}&S}bP0l+XkKd~%Vu diff --git a/media/atree/node-blocked.png b/media/atree/node-blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..42892b60f352bc10fd6c924985cdbaf9472d12ba GIT binary patch literal 2416 zcmZ`*dpy(YAAi2Hu`$f0QYoB%E~BhYC0cEB8AWWltU?+^Ds_s~(%6PbDRmNEu=)wd zWvE}mDV7FzSkvc0DjyfThIeaUr~*^eV*JC@xpVw0!4ABv zW&2KElCOV!=`4GOP`0Mj6MC{`r_}aB&iGVGn}IZ)=FrRYT0c98q(?sIF$AmGS;K>+ z2)V>?`fLTVjD$2z`gggCF3&(ze@WNh`0)6QmX0(%|L_*mCvgj`%7%?zS!Av1x#lAq z3v~K|fG5XUI-1#cUxZ5%j+DwEU)5f~444p5MiJF>?hq}qcyODQlK$f_Ay5byq1_iy zN@1W`f4oz2)PyT{UwT?CXfj3(tl&sW%MDF3)$%LEmFNUCI$yZo;dl|KP)i;?{BMff z-P*byk#57yiyQ0z7qY_nT33V#_pL=iz+qkIuUK(M(%SRiR3Ub&q)x8uNZv5`@ga9r2q-6GhhMLUho1PUJ>m}wVYq%Yz~DqTU&k|P&~8;Y zy~GB_YldQM`WL|-b5l;DblZiM(H%}I3eK!|*t9wbv~tY)?qb9W!=uDoJ41k3|Hlm? z2E2-9&LEk}bdvdtvNhnjJhqIpz#ZxPfM>p60M`A4pqTd;&qqRLnsD4%o0)nO?u12a zq#n4hbQ*F1=$J5&G`I;nyf@7k;BFweEcMOlMF3ehN7Q`QgkwYYI6)MqE{J%E#{=}( zs-HB?@^{}JMR3KY09gzyD=UEIS`^^H;ID5TpnhGk4;Pn?0f>O#)&6At$=Cr=EIVdq z*)b|FW{eF$!!<1ZPt<>cKG!1@U)!>R+n_+^A2}Z6{RT&d)BN9Op?RJGK46h2ZUD>k zyDm2T9T_H_@Lhnn;e9&{6Y#j!2RVmK{_0=ni&uq!0xj{Kx(6|T_Y`pIlonMaMN1qQ z>21K?{>$q8P`yC?0Bq6Wx_*99`Ny`~>M@!i=tpgeY&z;467a?xK>(Nxw=VCuna_Tw zH!x(O-5Q|>cq{BdFNp5FIzcWPLsaW?o8V8yOxF`TfuB#FRMJKhVGXg^?ui}$wZ2N)COK&yw;?tX`owHf1_-3p=DN6Nu+jqKL zZ*PGbzk-dv5jTwmKX1r?A-hhwjTi{G-lfT|Y{W({<2vPaMod_se@%prXAvI+MJ%dc z#JzoerAnl@%9K=f=&>>otTVVe1idwXkNrl-4q`E`rpxPqq`F~A_iMt?8B6rJzNvxl zDkqUl2QXv*IJy@>Nht{4@FigrUjXtZpO0{QZS!^_j!UjdX{FB=i@$>CwtUn8L9Yp- zRjKj61j4_aCP`hm3&l)dCKai!b+J|ghr{Q{)&woLpxP2?iLRIdE}uEDnKS2_F97T^ zmpQWfTvRrY*5cIJ%M!GiM2e{(m0YwLRh{s?H@Ca?RMva;!_E!?#k)=`bJSN+e&GIe z!&Ek~sxecS?PGaj*48kZ_o*&+*+FSNv5MQy+@;oHdaxnK`;z(dOk*Z`E3B4gaKIBI zsSSI^_usFxz7RR$PlGqmq;+d2;$%_1yrgHu^_3m;Sh8Oca9AOZV)$5EQz?m!Y2>0- z!((vgy@F=GzmAS`Au_u4vZ&_`weGQV`%-I4d?74;+2CZlfu}RNcYj_YgzF(uj9a*5cyxOWbZpMHqm>0!q>6$&ug4OW>0>#OFmLrF^v}_SQ>{Ajern^owRX(c84MdAV z(aI$=6Wbj_U2-i}Q}L+?9_>YANa{sa);F}9UiefuxK;({=_QDMCAicEV^Jwj2T6?? zdh-=tVVtLhmdCI&4&xo^LP`7?_m`r+i;ILKW9J##gQ4E#!UVnu3kc` literal 0 HcmV?d00001 diff --git a/media/atree/node-selected.png b/media/atree/node-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..f67fef90e09f0bb730dde7b5736b9d7587c9beb0 GIT binary patch literal 2418 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 Y>09jeIpAbuf8TfS@80izznOF9oZXyZ z;~gAAT)*AZEg-4%w-?le*WO037{7vYD6g`tclgOwDV2x6(le+KlS(YL-!VUKo)qqopbc;5`<;BLuyH1wYL>G5J0xIf`gg;B$fEE*e z8JEfpgadei|F*L{v(Q{KJfeF>QY15WlWSEI=1y}y-0yTiQ=jj{A4t?yJcMcHUh?28 zD~&h?BKLxosh@F0d6C=nGJCsameIq9{b$yh%)hQ%c&Fcr_x|z=k+k5Hm*XgrNGtTT zP0RO8IBhadz-|IP{{%-U_jDfIkr6Kn_ z_M0#y`JFBoZMtom!$z2=GI*|CV1of;k}1*)7aW0B5HB5HWw#r=RA`m8 zEjWr~WOD1zX%#s<@o8!cv`1U`mRyEVsjdWCfM0@b92@GTJX86&3(q*s+qvNODkmt_ zS9wkY;K4hs466?|ykWEQz_*5J`{8dBRSSth2z$)#W~}K8r;$Yuj|Nwr&9~vkg)Wx; zR*l95kNtc2@VoWcG$h#!o;91}5_%b$Gr%M?kHtV39?~E6VL#@9REEb+rz9=6O8O%P z$@sKeU;E-hVAfKO$g~*bs>Ll;pEkohMZNFzk>xHp&IH|vtSi566rz(HY8Lh^cl&=U zgO2-PfXT)6;GyE{ws0e!!c6nl;RJ+=Gf^K1Z2`J{O#cpuC;SDLY~yyZUPg}KL7FuJ zPD%OE;W#?0RxmxFGV5-<6uA=MuLAR{r57Nc*!6;vMv7fO_)kFCiIKFyt0&q3@&~#_ z5YIfUU0+@g9^eV_MZnw<#^nBF{lO3#0Q_wZf1u|l1m7KnnPW93{WmJ|m)ieg|An|V zym79&`KNeb_=l5S4d?&)br|wCX}GP%rhPU1s^RZWp%*cGS3kz!hm0`o%v*Jlqs#FD zvK(utKSOQAlLE(U^>!tH9>VsrUfokm<|j_etY+^YxOLYG@p}pYXqXRaVk%L5_=WTrL_N`!i?}L6&IIBy&ZVa@U9`Rb5|; zy36+2Y{XgNl7jMczzizX~6C&gC!0+A{_Ik%KAkBph(qRRTcK$=d79#C@&5q ztpT<%Su_g=4fnw2nu!{1VGuZfGGYs|;8s=-I5#K@G*wnq0hnmkRTZpC)?`uX2KAY= z>Md|%GID{z`?2|W-ZRdf{=RxTqeUb4jSpHIacl1WYynu?WTLtep5#q*YzpcMSDqcP z!etL7pe37mpIQia1(OlRxz!JI^;v|im|XTJ5#B^`b66+e?~s;L6<+4Y#1e=!ig!cb zRNs}?49VPnu)?*8525}ss4yyMhq;9p9Z7A^rd4;7k77r!ly~v{w6r!?;o05i1w(^g zEp3~7KeeDAtHRXJfAUS#9lW0(e(dlSOzYnw`y$Px@7W^t(@8gMtfQM^Q2)26@bFi} zdyo40F9(lhx4EM5Ab8|SKo}aO!t`ifpZA*i!fZ-DHoNuiAZ2T%Z7ddZ%`o&`w2b z?{!YD9V0qerMnSTt_PSLH$9TOJdcRrqK+5R8D}asT5^AFNTIb7j_kyj=`I}!&1F)g z(ZyFI1_OeuWq`_Gh4_c;3? z4m8fpv{m|%hPpZHdal|070^%F=x&pdS~oWkFKTFQDhwddLJ3R89~Il}Ln(!3c3#$* zP#W`bdr0Qlq`Y;^%iF9o-LYC3T&j~Sc;!H84h$y286B^ee>xWHwuK=bRpv)YA8n`l za_)(bo%DDj7&~itPxjlXAkC?EP0o-juQ#DzeOdWy6FScJrU~qoW`QL|Sro<&1;C!| MUcR0U?yU6x0PlfHm;e9( delta 5259 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 From a1227f6e7510054b6c18ae8e88f9b64c666b401a Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 16 Jun 2022 16:14:16 +0700 Subject: [PATCH 014/155] add archer tree --- js/atree_constants.js | 177 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 8ed02d6..38ca0a2 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -1,5 +1,181 @@ const atrees = { + "Archer": [ + {"title": "Arrow Bomb", "desc": "Throw a long-ranged arrow that explodes and deal high damage in a large area (self-damage for 30% of DPS) Mana cost: 50 Total damage: 180% (of DPS) - Neutral: 160% - Fire: 20% Range: 26 blocks AoE: 2.5 blocks (circular)", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 1, "col": 4, "rotate": 0}, + {"title": "Bow Proficiency", "desc": "Improve Main Attack damage and range w/ bow Main attack damage: +5% Main attack range: +6 blocks AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 2, "col": 5, "rotate": 90}, + {"title": "Cheaper Arrow Bomb", "desc": "Reduce Mana cost of Arrow Bomb Mana cost: -10 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 3, "col": 4, "rotate": 0}, + {"title": "Heart Shatter", "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage Total damage: +100% (of DPS) - Neutral: 100% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 5, "col": 4, "rotate": 0}, + {"title": "Escape", "desc": "Throw yourself backward to avoid danger (hold shift to cancel) Mana cost: 25 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 3, "rotate": 90}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 5, "rotate": 90}, + {"title": "Double Shots Ability\nBoltslinger Archetype", "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies) Blocks: - Power Shots AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, + {"title": "Power Shots Ability\nSharpshooter Archetype", "desc": "Main Attack arrows have increased speed and knockback Blocks: - Double Shots AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 6, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 2, "rotate": 0}, + {"title": "Arrow Storm", "desc": "Shoots 2 streams of 8 arrows, dealing damage to close mobs and pushing them back Mana cost: 40 Total damage: 40% (of DPS per arrow) - Neutral: 30% - Thunder: 10% Range: 16 blocks AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, + {"title": "Cheaper Escape", "desc": "Reduce mana cost of Escape Mana cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, + {"title": "Arrow Shield", "desc": "Create a shield around you that deal damage and knockback mobs when triggered (2 charges) Mana cost: 30 Total damage: 100% (of DPS) - Neutral: 90% - Air: 10% Duration: 60s AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 3, "rotate": 90}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 5, "rotate": 90}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 8, "col": 1, "rotate": 180}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 0, "rotate": 180}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 7, "rotate": 90}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 8, "rotate": 270}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 9, "col": 0, "rotate": 0}, + {"title": "Windy Feet Ability\nBoltslinger Archetype", "desc": "When casting Escape, give speed to yourself and nearby allies Effect: +20% Walk Speed Duration: 120s AoE: 8 blocks (circular) AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 9, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 2, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 6, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 8, "rotate": 0}, + {"title": "Air Mastery Ability\nBoltslinger Archetype", "desc": "Increases base damage from all Air attacks Air Damage: +3-4 Air Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, + {"title": "Thunder Mastery Ability\nBoltslinger Archetype", "desc": "Increases base damage from all Thunder attacks Thunder Damage: +1-8 Thunder Damage: +10% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_c.png", "connector": true, "row": 10, "col": 4, "rotate": 0}, + {"title": "Fire Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Fire attacks Fire Damage: +3-5 Fire Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, + {"title": "Earth Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Earth attacks Earth Damage: +2-4 Earth Damage: +20% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 0, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 2, "rotate": 0}, + {"title": "Water Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Water attacks Water Damage: +2-4 Water Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 6, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 8, "rotate": 0}, + {"title": "Arrow Rain", "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies Total Damage: 200% (of DPS per arrow) - Neutral: 120% - Air: 80% AP: 2 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 12, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 1, "rotate": 90}, + {"title": "Nimble String", "desc": "Arrow Storm throws out +8 arrows per stream and shoot twice as fast Total Damage: -15% (of DPS per arrow) - Neutral: -15% Blocks: - Phantom Ray AP: 2 Req: Arrow Storm", "image": "../media/atree/node.png", "connector": false, "row": 12, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 6, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 8, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 0, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 2, "rotate": 0}, + {"title": "Phantom Ray", "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second Mana cost: -10 Total Damage: 30% (of DPS per hit) - Neutral: 25% - Water: 5% Range: 12 blocks Blocks: - Windstorm - Nimble String - Arrow Hurricane AP: 2 Req: Arrow Storm Min Sharpshooter: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 5, "rotate": 90}, + {"title": "Fire Creep\nSharpshooter Archetype", "desc": "Arrow Bomb will leak a trail of fire for 6s, damaging enemies that walk into it every 0.4s Total Damage: 50% (of DPS) - Neutral: 30% - Fire: 20% Duration: 6s AoE: 2 blocks (linear) AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 7, "rotate": 90}, + {"title": "Bryophyte Roots\nTrapper Archetype", "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s Total Damage: 60% (of DPS) - Neutral: 40% - Earth: 20% Effect: 40% Slowness to Enemies Duration: 5s AoE: 2 blocks (circular) AP: 2 Req: Arrow Storm Min Trapper: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 8}, + {"title": "Triple Shot\nBoltslinger Archetype", "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow AP: 1 Req: Double Shots", "image": "../media/atree/node.png", "connector": false, "row": 14, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 14, "col": 1, "rotate": 180}, + {"title": "Frenzy\nBoltslinger Archetype", "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 14, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 8, "rotate": 0}, + {"title": "Guardian Angels Ability", "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies (Arrow Shield will no longer push nearby enemies) Total Damage: 60% (of DPS per arrow) - Neutral: 40% - Air: 20% Range: 4 Blocks Duration: 60s AP: 2 Req: Arrow Shield Min Boltslinger: 0/3", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 3, "rotate": 180}, + {"title": "Focus Ability\nSharpshooter Archetype", "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once Damage Bonus: +35% (per focus) AP: 2 Min Sharpshooter: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 5, "rotate": 270}, + {"title": "Basaltic Trap Ability\nTrapper Archetype", "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies (Max 2 Traps) Total Damage: 200% (of DPS) - Neutral: 140% - Earth: 30% - Fire: 30% AoE: 7 Blocks (Circular) AP: 2 Min Trapper: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 7, "rotate": 180}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 0, "rotate": 180}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 16, "col": 2, "rotate": 90}, + {"title": "Cheaper Arrow Storm", "desc": "Reduces the Mana cost of Arrow Storm. Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 3}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 4, "rotate": 180}, + {"title": "Grappling Hook Ability\nTrapper Archetype", "desc": "When casting Escape, you throw a hook that pulls you when hitting a block. If you hit a mob, pull them towards you instead. (Escape will not throw you backward anymore) Range: 20 blocks Blocks: - Escape Artist AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 6, "rotate": 180}, + {"title": "More Shields Ability", "desc": "Give +2 charges to Arrow Shield AP: 1 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 7}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 8, "rotate": 270}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 0, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 1, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 4, "rotate": 0}, + {"title": "Implosion Ability\nTrapper Archetype", "desc": "Arrow Bomb will pull enemies towards you. If a Trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage Total Damage: +40% (of DPS) - Neutral: +40% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 17, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 7, "rotate": 0}, + {"title": "Patient Hunter Ability\nTrapper Archetype", "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%) AP: 2 Req: Basaltic Trap", "image": "../media/atree/node.png", "connector": false, "row": 17, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 0, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 1}, + {"title": "More Focus Ability\nSharpshooter Archetype", "desc": "Add +2 max Focus Damage Bonus: -5% (per focus) AP: 1 Req: Focus", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 7, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 0, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 7, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 1, "rotate": 180}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 20, "col": 3, "rotate": 90}, + {"title": "Twain's Arc Ability\nSharpshooter Archetype", "desc": "If you have 2+ Focus, holding shift will summon Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage) Total Damage: 200% (of DPS) - Neutral: 200% Range: 64 blocks AP: 2 Min Sharpshooter: 0/4 Req: Focus", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 5, "rotate": 180}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 6, "rotate": 270}, + {"title": "Bouncing Bomb Ability", "desc": "Arrow Bomb will bounce once when hitting a block or mob AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 8, "rotate": 270}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 0, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 4, "rotate": 0}, + {"title": "Scorched Earth Ability\nSharpshooter Archetype", "desc": "Fire Creep becomes much stronger Total Damage: +15% (of DPS) - Neutral: +10% - Fire: +5% Duration: 2s AoE: +0.4 Blocks (linear) AP: 1 Req: Fire Creep", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 5}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 6, "rotate": 0}, + {"title": "More Traps Ability\nTrapper Archetype", "desc": "Increase the maximum amount of active Traps you can have by +2 AP: 1 Req: Basaltic Trap", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 0, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 6, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 8, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 1, "rotate": 180}, + {"title": "Homing Shots Ability", "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 3, "rotate": 90}, + {"title": "Shocking Bomb Ability\nSharpshooter Archetype", "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder AP: 2 Min Sharpshooter: 0/5 Req: Arrow Bomb", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 5, "rotate": 180}, + {"title": "Better Arrow Shield Ability", "desc": "Arrow Shield will gain additonal AoE, knockback, and damage Total Damage: +40% (of DPS) - Neutral: +40% AoE: +1 Blocks (Circular) AP: 1 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 7, "rotate": 90}, + {"title": "Mana Trap Ability\nTrapper Archetype", "desc": "Your Traps will give you 4 Mana per second when you stay close to them Mana Cost: +10 Range: 12 Blocks AP: 2 Min Trapper: 0/5", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 0, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 2, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 5, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 8, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 1, "rotate": 90}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 25, "col": 4, "rotate": 180}, + {"title": "Initiator Ability\nSharpshooter Archetype", "desc": "If you do not damage an enemy for 5s for more, your next successful hit will deal +50% damage and add +1 Focus AP: 2 Req: Focus Min Sharpshooter: 0/5", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 5}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 6, "rotate": 90}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 25, "col": 7, "rotate": 180}, + {"title": "Cheaper Arrow Storm Ability", "desc": "Reduce the Mana cost of Arrow Storm Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 0, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 2, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 4, "rotate": 0}, + {"title": "Call of the Hound Ability\nTrapper Archetype", "desc": "Arrow Shield summons a Hound that will attack and drag aggressive mobs towards your traps Total Damage: 40% (of DPS) - Neutral: 40% AP: 2 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 7}, + {"title": "Arrow Hurricane Ability\nBoltslinger Archetype", "desc": "Arrow Storm will shoot +2 stream of arrows Blocks: - Phantom Ray AP: 2 Min Boltslinger: 0/8", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 27, "col": 1, "rotate": 180}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 3, "rotate": 90}, + {"title": "Cheaper Arrow Shield Ability", "desc": "Reduce the Mana cost of Arrow Shield Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 27, "col": 5, "rotate": 270}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 7, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 4, "rotate": 0}, + {"title": "Decimator Ability\nSharpshooter Archetype", "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%) AP: 2 Req: Phantom Ray", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 5}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 6, "rotate": 90}, + {"title": "Cheaper Escape Ability", "desc": "Reduce the Mana cost of Escape Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 7}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 28, "col": 8, "rotate": 270}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 1, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 7, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 0, "rotate": 180}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 2, "rotate": 270}, + {"title": "Crepuscular Ray Ability\nSharpshooter Archetype", "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus While in that state, you will lose 1 Focus per second Total Damage: 15% (of DPS per arrrow) - Neutral: 10% - Water: 5% AP: 2 Req: Arrow Storm Min Sharpshooter: 0/8", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 6, "rotate": 180}, + {"title": "Grape Bomb Ability", "desc": "Arrow Bomb will throw 3 additional smaller bombs when exploding Total Damage: 40% (of DPS) - Neutral: 30% - Fire: 10% AoE: 2 Blocks (Circular) AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 8, "rotate": 270}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 2, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 4, "rotate": 0}, + {"title": "Tangled Traps Ability\nTrapper Archetype", "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s Total Damage: 40% (of DPS) - Neutral: 20% - Air: 20% AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 6}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 7, "rotate": 0}, + {"title": "Stringer Patient Hunter Ability\nTrapper Archetype", "desc": "Add +80% Max Damage to Patient Hunter AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 2, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 4, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 7, "rotate": 0}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 1, "rotate": 180}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 3, "rotate": 90}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 4}, + {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 5, "rotate": 270}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 7, "rotate": 0}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 1}, + {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 5}, + {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 34, "col": 6, "rotate": 90}, + {"title": "Minefield Ability\nTrapper Archetype", "desc": "Allow you to place +6 Traps, but with reduced damage and range Total Damage: -80% (of DPS) - Neutral: -80% AoE: -2 Blocks (Circular) AP: 2 Req: Basaltic Trap Min Trapper: 0/10", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 7}, + ], "Assassin": [ { "title": "Spin Attack", @@ -1248,7 +1424,6 @@ const atrees = ], "Warrior": [], "Mage": [], - "Archer": [], "Shaman": [] } From a1179911abb2ef56268932a034c6b6d45dd3e00c Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 16 Jun 2022 16:14:56 +0700 Subject: [PATCH 015/155] parse newlines to line breaks --- js/sq2bs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/sq2bs.js b/js/sq2bs.js index a1de216..4c25ad4 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -596,7 +596,7 @@ function construct_AT(elem, tree) { let active_tooltip_title = document.createElement('b'); active_tooltip_title.classList.add("scaled-font"); - active_tooltip_title.textContent = node.title; + active_tooltip_title.innerHTML = node.title.replaceAll("\n", "
"); let active_tooltip_text = document.createElement('p'); active_tooltip_text.classList.add("scaled-font-sm"); From 441300ec100ed22d6d732a9cd7411c1e589ba6fe Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 16 Jun 2022 16:15:08 +0700 Subject: [PATCH 016/155] add cross --- media/atree/connect_c.png | Bin 0 -> 6254 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 media/atree/connect_c.png diff --git a/media/atree/connect_c.png b/media/atree/connect_c.png new file mode 100644 index 0000000000000000000000000000000000000000..c274306f9ef6e69b70ab50b9f34a5f0b67d333c7 GIT binary patch literal 6254 zcmeHLX;f3!7QUej0wPfnq>6;#fMDhXk}yLUA|xPCWiH9hO^A?zBtU?o5wVIq2ZZ`m zKq<(eqD+D)h$t#5q6I4`76h#mBA|7wsP6`FTJQaMu4Vt3tR&~2^X+f%{hfWzJvSNt zY%c@7@AUuxFkpH!0ssI3Hxa-TZTKf#R>lVa?fuC?!HNJ*B1$Tg@P)AuN|7XmP>@mx z4=Q`wgG1^bT1_46wocaFux!ct$9a9OxD!6t9=9p%*N%9#F8VoY$zE&bhTmS^8)2>< zxSekBYU}*Fvkjb_UdQUwxpjUaiMFeHaprv^cDEG@Oz8S9k>UJdNyOP#p8pz1i>#k9 zU$oj^V$M5JyL?#l`oXyUS6KZp80EaDpL|EzPjpV zzADl%xHZInuKn5ud4mS~#P)F4MYk?txoFTWWJ}je>{OLQ%Y&D^Xz~v~yi;_B#YQA6 zE^j~b+9**+=Fn4m`}Dx@8vCxQZkrU^AAj=a4n5B_?=7gfNo%+Wk;<){GzRPE#1o)r z_zJFWMCg?L9@(P?$gHkk;&dC2RUPW`J#&>>qZN_un22>qDzA%MyRmOtwXRuAZhVY? zc=dJL(y^rD%AP-~AF`WyVTV%$h7n3kE~}_^SJa(LJ64*W3Vw6zN~TjPhi#z6x71nw zTU)5soRwDc{`eN7Ng2s;vY61j8eH*nk@|-L3*6uc(wTP(p!|P+L@eF)NQ6a zrfjI!6j$4z+Fk?in34VN3+nsbrx5m{EY{D6cuF`uJMj0t!P|Ux2Y3^T=Pxcrvl}%t zwlU`WoVT@~-@X-OhsZ7!N40C5*OHj{S{3&U?yp$0vV7mt_F8hi-(Q%+*8;M)hdrok z*gVz&`b8uUL!&Ai1Lsp4EDcPPa&%YZZZ6rwIlHsuqC?xs9`?hQXNZ`_8oo&&onThZ zAs1&(=~B$dAtCwaNwfWyBIfp33p}r-9JshxurQ3_QRz>#66F6vz~9%ofq!+%?E0o< z(6EstXUstIJnXLbzSmoidiCwD>WZJUGP4S!H!J8iGF9ocH+b=}>yC$?j=5sA30eHM zdBkM53se1tY;zw!@z3IImANgJt;}+{e+@pC?P7MOzC#ewS?Mmul;Z>GB`&MHDT+r| zHGl10Xjz0H;Od&?a}4|+2y%1fQ^TAm!!Y!*8Yy_t@n zqw0ami(Xk$gXU^;UZiD#{*M+eH$xL{cIF}*6#yNeJ^&7XC+$Jp*IZ)Pb*1)|V6N&A zih2i7?0$K$_z2j)?z`V+0i|A^8~PU5ZY@oGEN7kSe&2w93p+sBWIS z)-gjnJvX##Ws7IWa)$=vWlc$8+;;g!v!tC1Ow$x5$w-f*W1Fo4YFD`%iHvufc$idP z+F0mZzk1Q&t?Z}d`5~o+hghAJyEQAE=&bwVS0;n~yQ-`_ON^*epEdro3X#*#ZM-@_ zK54a_n`^A|0@Y+>gf)Iv^)|dVbWYpB+y?#FWL&M6-4Nf}`gHh{*s|kbUZ|GEA)k@o zecD=DwXTE8cKat%=xQA zYB{%Um50&3&=VFLZS^l_7U~BJJUYz(xSUZKqi3O~B^H=Jt=K-g%x6#2sIzf;V2{Sk zh78o2f+s6{v!+Y?%50GJ#w@~Oba~#XO~NU+svmvdnQ~!c(SXIAq1qCT?i;b{EkoH# zsXbG5r%>h3FDEic0c~~IfS-L3N85D309sDxR9g3*nMzKe-i>GZ0q15lw%;%!?ZOQk z6%}^}#06g5!J_!~1=$;|#rjZs=l@$~x4(lDB^WJ?X};9&(XD!uzQSu^*WP-?P|@Cj zSUW455_dNb=gmtI!XLO#YeMt(WyeQGxjZ#qzdY8WcRBAj^UNa$lM$ZzgF!F!{`R}u zRbs2Gu4v&iAQASqmE+^xETHKkY3Zys zJ}?b?=@`9UFRkBjLs52iVP=l<$AIYRx7W74rjF#dq`pA79lsb<18VqF+Pu#eau+sO zYe#mhTV);P%PpDbhbb92+B7_H`*lRRAnE37`7>);Ho_@Ox%t7*9*wRJHNppZFEZTA ztrnlYclx$A#e3{r{PjC_m$cSv?KUNLo=>`ktO||>k8TR-@XsjCXxX@bS<}|Ikb%ZC zA(#~*HGXeTMX|$}arrA}6P8$P)?}P3D@-RYVMKR(6SDUHRknWhxyOsnEyzBg>A6`e zsK8A&*1vi*rH;R9WhYC|bIm1_8*gVLG#Xrr_&090*7Z^mW0#wM>3Xtauz6_FgWB!J zP8xsLt;u}2ec=e=-}cS@*;vzUmaq1u|1?kOTqL=m@!b)Z%e(B4+@-P2Rpj=Sue7OD zO;7!t=(v z4$ev`2aJIfC@v%riXG7-4JXhjA2O=!jYFfxA&MABbTG>wqr`~imTHO# z4hAF#WkRV!C=sL7oE)wsUg3yF!{ey;@rk4?)(3jAd{PBi51f)C#SyT0oJfTG)IzTC zNPtNu1Nxs9@*wyyh6{k?l6V;ic_ct$h2^IdJn)0PG+q`vo(>PhL9vhshRR{DgwI@h zF2}2g4{9i_VaM@#^rH z3`exuKb7EjqhyzcjP{a{HIH_1L z9;RAwx~o6a5lzJ6Klb>?auj^n0A2$^F;9{x|2PyR6hVOsj#^KGErmjY^CsHSs6;zE z>POIWNG6ACQO!!gV@VUt>WQJl>A=!*)RhVoj9b9j(A{MaMoC~o3vViCj&$A?g;@xrBZzzHXEP6EUmcLdu_41v)cu>gWs z_+&}F?-%}$=^}8sGzymhPd16h!;ozu5JRPK$QT}%%i$3T1P+%-`dPR;?!&UeYOs`|I))@ z9Pr5|0NYLU!J7-bSK&TvR+DziC( zq`((}zg5>)lS}X8;|?T-Pe6(A!_3iO;5_^wq|Np5VgT>d?}Fo{*>I1p)H_5D0Qxi4 z9|W)~Z#LYiqhPW;bna>+wKa57{8C_?Mi!Id8l>!bbgfFyjeulym?gb?*9KZNdg{;N zSl-Lo=QacDL(D6b=s()kzTj2;P)|Y%RRamQsQ>HL1in_|>oI+Z=&MkwM#4#ft<77M zVGG}<9E=jB{;tyfYN;cE{_Q*Wfz-f;dxP8Rkuw1lpn~TGSQ6j<&~ob=_E`C|I^T(| rDeuSrpN0nT|De7I^UqT@|CppPdI&jIb0BaFT(1DrgUvYP7M1oNXi3{| literal 0 HcmV?d00001 From 112dd1b61931044553c110b2d2a2e5f4aa381b27 Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 16 Jun 2022 16:15:25 +0700 Subject: [PATCH 017/155] add script to convert atree csv to json --- py_script/atree_csv_to_json.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 py_script/atree_csv_to_json.py diff --git a/py_script/atree_csv_to_json.py b/py_script/atree_csv_to_json.py new file mode 100644 index 0000000..4c63d06 --- /dev/null +++ b/py_script/atree_csv_to_json.py @@ -0,0 +1,24 @@ +import csv +import json +import re + +with open('atree.csv', newline='') as csvfile: + res = "" + reader = csv.DictReader(csvfile) + for row in reader: + if not row["connector"]: + row["connector"] = False + else: + row["connector"] = True + row["row"] = int(row["row"]) + row["col"] = int(row["col"]) + if row["rotate"].isdigit(): + row["rotate"] = int(row["rotate"]) + else: + row.pop("rotate") + row["desc"] = re.sub("\n", " ", row["desc"]) + + resjson = json.dumps(row) + res += str(resjson) + ",\n" + + print(res) From e2eaf489849522f83bd78555561e504885e79ecc Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 16 Jun 2022 16:53:59 +0700 Subject: [PATCH 018/155] remove empty title and desc --- js/atree_constants.js | 212 +++++++++++++++++++++--------------------- 1 file changed, 108 insertions(+), 104 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 38ca0a2..2dea7bc 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -2,178 +2,182 @@ const atrees = { "Archer": [ {"title": "Arrow Bomb", "desc": "Throw a long-ranged arrow that explodes and deal high damage in a large area (self-damage for 30% of DPS) Mana cost: 50 Total damage: 180% (of DPS) - Neutral: 160% - Fire: 20% Range: 26 blocks AoE: 2.5 blocks (circular)", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 1, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 1, "col": 4, "rotate": 0}, {"title": "Bow Proficiency", "desc": "Improve Main Attack damage and range w/ bow Main attack damage: +5% Main attack range: +6 blocks AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 2, "col": 5, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 2, "col": 5, "rotate": 90}, {"title": "Cheaper Arrow Bomb", "desc": "Reduce Mana cost of Arrow Bomb Mana cost: -10 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 3, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 3, "col": 4, "rotate": 0}, {"title": "Heart Shatter", "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage Total damage: +100% (of DPS) - Neutral: 100% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 5, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 5, "col": 4, "rotate": 0}, {"title": "Escape", "desc": "Throw yourself backward to avoid danger (hold shift to cancel) Mana cost: 25 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 3, "rotate": 90}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 5, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 3, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 5, "rotate": 90}, {"title": "Double Shots Ability\nBoltslinger Archetype", "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies) Blocks: - Power Shots AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, {"title": "Power Shots Ability\nSharpshooter Archetype", "desc": "Main Attack arrows have increased speed and knockback Blocks: - Double Shots AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 6, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 6, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 2, "rotate": 0}, {"title": "Arrow Storm", "desc": "Shoots 2 streams of 8 arrows, dealing damage to close mobs and pushing them back Mana cost: 40 Total damage: 40% (of DPS per arrow) - Neutral: 30% - Thunder: 10% Range: 16 blocks AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, {"title": "Cheaper Escape", "desc": "Reduce mana cost of Escape Mana cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, {"title": "Arrow Shield", "desc": "Create a shield around you that deal damage and knockback mobs when triggered (2 charges) Mana cost: 30 Total damage: 100% (of DPS) - Neutral: 90% - Air: 10% Duration: 60s AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 3, "rotate": 90}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 5, "rotate": 90}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 8, "col": 1, "rotate": 180}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 0, "rotate": 180}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 7, "rotate": 90}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 8, "rotate": 270}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 9, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 3, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 5, "rotate": 90}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 8, "col": 1, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 0, "rotate": 180}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 7, "rotate": 90}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 8, "rotate": 270}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 0, "rotate": 0}, {"title": "Windy Feet Ability\nBoltslinger Archetype", "desc": "When casting Escape, give speed to yourself and nearby allies Effect: +20% Walk Speed Duration: 120s AoE: 8 blocks (circular) AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 9, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 2, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 6, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 8, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 6, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 8, "rotate": 0}, {"title": "Air Mastery Ability\nBoltslinger Archetype", "desc": "Increases base damage from all Air attacks Air Damage: +3-4 Air Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, {"title": "Thunder Mastery Ability\nBoltslinger Archetype", "desc": "Increases base damage from all Thunder attacks Thunder Damage: +1-8 Thunder Damage: +10% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_c.png", "connector": true, "row": 10, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 10, "col": 3, "rotate": 90}, + + {"image": "../media/atree/connect_c.png", "connector": true, "row": 10, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 10, "col": 5, "rotate": 90}, + {"title": "Fire Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Fire attacks Fire Damage: +3-5 Fire Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, {"title": "Earth Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Earth attacks Earth Damage: +2-4 Earth Damage: +20% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 0, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 2, "rotate": 0}, {"title": "Water Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Water attacks Water Damage: +2-4 Water Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 6, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 8, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 6, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 8, "rotate": 0}, {"title": "Arrow Rain", "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies Total Damage: 200% (of DPS per arrow) - Neutral: 120% - Air: 80% AP: 2 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 12, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 1, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 1, "rotate": 90}, {"title": "Nimble String", "desc": "Arrow Storm throws out +8 arrows per stream and shoot twice as fast Total Damage: -15% (of DPS per arrow) - Neutral: -15% Blocks: - Phantom Ray AP: 2 Req: Arrow Storm", "image": "../media/atree/node.png", "connector": false, "row": 12, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 6, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 8, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 0, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 6, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 8, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 2, "rotate": 0}, {"title": "Phantom Ray", "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second Mana cost: -10 Total Damage: 30% (of DPS per hit) - Neutral: 25% - Water: 5% Range: 12 blocks Blocks: - Windstorm - Nimble String - Arrow Hurricane AP: 2 Req: Arrow Storm Min Sharpshooter: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 5, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 5, "rotate": 90}, {"title": "Fire Creep\nSharpshooter Archetype", "desc": "Arrow Bomb will leak a trail of fire for 6s, damaging enemies that walk into it every 0.4s Total Damage: 50% (of DPS) - Neutral: 30% - Fire: 20% Duration: 6s AoE: 2 blocks (linear) AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 7, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 7, "rotate": 90}, {"title": "Bryophyte Roots\nTrapper Archetype", "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s Total Damage: 60% (of DPS) - Neutral: 40% - Earth: 20% Effect: 40% Slowness to Enemies Duration: 5s AoE: 2 blocks (circular) AP: 2 Req: Arrow Storm Min Trapper: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 8}, {"title": "Triple Shot\nBoltslinger Archetype", "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow AP: 1 Req: Double Shots", "image": "../media/atree/node.png", "connector": false, "row": 14, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 14, "col": 1, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 14, "col": 1, "rotate": 180}, {"title": "Frenzy\nBoltslinger Archetype", "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 14, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 8, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 8, "rotate": 0}, {"title": "Guardian Angels Ability", "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies (Arrow Shield will no longer push nearby enemies) Total Damage: 60% (of DPS per arrow) - Neutral: 40% - Air: 20% Range: 4 Blocks Duration: 60s AP: 2 Req: Arrow Shield Min Boltslinger: 0/3", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 3, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 3, "rotate": 180}, {"title": "Focus Ability\nSharpshooter Archetype", "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once Damage Bonus: +35% (per focus) AP: 2 Min Sharpshooter: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 5, "rotate": 270}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 5, "rotate": 270}, {"title": "Basaltic Trap Ability\nTrapper Archetype", "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies (Max 2 Traps) Total Damage: 200% (of DPS) - Neutral: 140% - Earth: 30% - Fire: 30% AoE: 7 Blocks (Circular) AP: 2 Min Trapper: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 7, "rotate": 180}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 0, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 7, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 0, "rotate": 180}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 16, "col": 2, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 16, "col": 2, "rotate": 90}, {"title": "Cheaper Arrow Storm", "desc": "Reduces the Mana cost of Arrow Storm. Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 3}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 4, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 4, "rotate": 180}, {"title": "Grappling Hook Ability\nTrapper Archetype", "desc": "When casting Escape, you throw a hook that pulls you when hitting a block. If you hit a mob, pull them towards you instead. (Escape will not throw you backward anymore) Range: 20 blocks Blocks: - Escape Artist AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 6, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 6, "rotate": 180}, {"title": "More Shields Ability", "desc": "Give +2 charges to Arrow Shield AP: 1 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 7}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 8, "rotate": 270}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 0, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 1, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 8, "rotate": 270}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 1, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 4, "rotate": 0}, {"title": "Implosion Ability\nTrapper Archetype", "desc": "Arrow Bomb will pull enemies towards you. If a Trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage Total Damage: +40% (of DPS) - Neutral: +40% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 17, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 7, "rotate": 0}, {"title": "Patient Hunter Ability\nTrapper Archetype", "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%) AP: 2 Req: Basaltic Trap", "image": "../media/atree/node.png", "connector": false, "row": 17, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 0, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 1}, {"title": "More Focus Ability\nSharpshooter Archetype", "desc": "Add +2 max Focus Damage Bonus: -5% (per focus) AP: 1 Req: Focus", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 7, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 0, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 7, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 1, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 1, "rotate": 180}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 20, "col": 3, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 20, "col": 3, "rotate": 90}, {"title": "Twain's Arc Ability\nSharpshooter Archetype", "desc": "If you have 2+ Focus, holding shift will summon Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage) Total Damage: 200% (of DPS) - Neutral: 200% Range: 64 blocks AP: 2 Min Sharpshooter: 0/4 Req: Focus", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 5, "rotate": 180}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 6, "rotate": 270}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 5, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 6, "rotate": 270}, {"title": "Bouncing Bomb Ability", "desc": "Arrow Bomb will bounce once when hitting a block or mob AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 8, "rotate": 270}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 8, "rotate": 270}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 0, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 4, "rotate": 0}, {"title": "Scorched Earth Ability\nSharpshooter Archetype", "desc": "Fire Creep becomes much stronger Total Damage: +15% (of DPS) - Neutral: +10% - Fire: +5% Duration: 2s AoE: +0.4 Blocks (linear) AP: 1 Req: Fire Creep", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 5}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 6, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 6, "rotate": 0}, {"title": "More Traps Ability\nTrapper Archetype", "desc": "Increase the maximum amount of active Traps you can have by +2 AP: 1 Req: Basaltic Trap", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 0, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 6, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 8, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 6, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 8, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 1, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 1, "rotate": 180}, {"title": "Homing Shots Ability", "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 3, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 3, "rotate": 90}, {"title": "Shocking Bomb Ability\nSharpshooter Archetype", "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder AP: 2 Min Sharpshooter: 0/5 Req: Arrow Bomb", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 5, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 5, "rotate": 180}, {"title": "Better Arrow Shield Ability", "desc": "Arrow Shield will gain additonal AoE, knockback, and damage Total Damage: +40% (of DPS) - Neutral: +40% AoE: +1 Blocks (Circular) AP: 1 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 7, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 7, "rotate": 90}, {"title": "Mana Trap Ability\nTrapper Archetype", "desc": "Your Traps will give you 4 Mana per second when you stay close to them Mana Cost: +10 Range: 12 Blocks AP: 2 Min Trapper: 0/5", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 0, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 2, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 5, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 8, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 5, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 8, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 1, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 1, "rotate": 90}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 25, "col": 4, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 25, "col": 4, "rotate": 180}, {"title": "Initiator Ability\nSharpshooter Archetype", "desc": "If you do not damage an enemy for 5s for more, your next successful hit will deal +50% damage and add +1 Focus AP: 2 Req: Focus Min Sharpshooter: 0/5", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 5}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 6, "rotate": 90}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 25, "col": 7, "rotate": 180}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 6, "rotate": 90}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 25, "col": 7, "rotate": 180}, {"title": "Cheaper Arrow Storm Ability", "desc": "Reduce the Mana cost of Arrow Storm Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 0, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 2, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 0, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 4, "rotate": 0}, {"title": "Call of the Hound Ability\nTrapper Archetype", "desc": "Arrow Shield summons a Hound that will attack and drag aggressive mobs towards your traps Total Damage: 40% (of DPS) - Neutral: 40% AP: 2 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 7}, {"title": "Arrow Hurricane Ability\nBoltslinger Archetype", "desc": "Arrow Storm will shoot +2 stream of arrows Blocks: - Phantom Ray AP: 2 Min Boltslinger: 0/8", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_t.png", "connector": true, "row": 27, "col": 1, "rotate": 180}, + {"image": "../media/atree/connect_t.png", "connector": true, "row": 27, "col": 1, "rotate": 180}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 3, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 3, "rotate": 90}, {"title": "Cheaper Arrow Shield Ability", "desc": "Reduce the Mana cost of Arrow Shield Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 27, "col": 5, "rotate": 270}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 27, "col": 5, "rotate": 270}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 7, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 4, "rotate": 0}, {"title": "Decimator Ability\nSharpshooter Archetype", "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%) AP: 2 Req: Phantom Ray", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 5}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 6, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 6, "rotate": 90}, {"title": "Cheaper Escape Ability", "desc": "Reduce the Mana cost of Escape Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 7}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 28, "col": 8, "rotate": 270}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 1, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 28, "col": 8, "rotate": 270}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 1, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 7, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 0, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 0, "rotate": 180}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 2, "rotate": 270}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 2, "rotate": 270}, {"title": "Crepuscular Ray Ability\nSharpshooter Archetype", "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus While in that state, you will lose 1 Focus per second Total Damage: 15% (of DPS per arrrow) - Neutral: 10% - Water: 5% AP: 2 Req: Arrow Storm Min Sharpshooter: 0/8", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 6, "rotate": 180}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 6, "rotate": 180}, {"title": "Grape Bomb Ability", "desc": "Arrow Bomb will throw 3 additional smaller bombs when exploding Total Damage: 40% (of DPS) - Neutral: 30% - Fire: 10% AoE: 2 Blocks (Circular) AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 8, "rotate": 270}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 8, "rotate": 270}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 2, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 4, "rotate": 0}, {"title": "Tangled Traps Ability\nTrapper Archetype", "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s Total Damage: 40% (of DPS) - Neutral: 20% - Air: 20% AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 6}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 7, "rotate": 0}, {"title": "Stringer Patient Hunter Ability\nTrapper Archetype", "desc": "Add +80% Max Damage to Patient Hunter AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 2, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 4, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 7, "rotate": 0}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 1, "rotate": 180}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 2, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 4, "rotate": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 1, "rotate": 180}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 3, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 3, "rotate": 90}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 4}, - {"title": "", "desc": "", "image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 5, "rotate": 270}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 7, "rotate": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 5, "rotate": 270}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 7, "rotate": 0}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 1}, {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 5}, - {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 34, "col": 6, "rotate": 90}, + {"image": "../media/atree/connect_line.png", "connector": true, "row": 34, "col": 6, "rotate": 90}, {"title": "Minefield Ability\nTrapper Archetype", "desc": "Allow you to place +6 Traps, but with reduced damage and range Total Damage: -80% (of DPS) - Neutral: -80% AoE: -2 Blocks (Circular) AP: 2 Req: Basaltic Trap Min Trapper: 0/10", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 7}, ], "Assassin": [ From 1c3ef6d545369bd4b26a8f5fc2337aaaffab811a Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 16 Jun 2022 04:38:09 -0700 Subject: [PATCH 019/155] Add warrior abilities (71) --- js/atree_constants.js | 72 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/js/atree_constants.js b/js/atree_constants.js index 38ca0a2..dda9834 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -176,6 +176,78 @@ const atrees = {"title": "", "desc": "", "image": "../media/atree/connect_line.png", "connector": true, "row": 34, "col": 6, "rotate": 90}, {"title": "Minefield Ability\nTrapper Archetype", "desc": "Allow you to place +6 Traps, but with reduced damage and range Total Damage: -80% (of DPS) - Neutral: -80% AoE: -2 Blocks (Circular) AP: 2 Req: Basaltic Trap Min Trapper: 0/10", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 7}, ], + "Warrior": [ + {"row": 0, "col": 4, "name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area"}, + {"row": 2, "col": 2, "name": "Cheaper Bash", "desc": "Reduce the Mana cost of Bash"}, + {"row": 2, "col": 4, "name": "Spear Proficiency 1", "desc": "Improve your Main Attack's damage and range w/ spear"}, + {"row": 4, "col": 4, "name": "Double Bash", "desc": "Bash will hit a second time at a farther range"}, + {"row": 6, "col": 2, "name": "Heavy Impact", "desc": "After using Charge, violently crash down into the ground and deal damage. (Does not work if Flying Kick is unlocked)"}, + {"row": 6, "col": 4, "name": "Charge", "desc": "Charge forward at high speed (hold shift to cancel)"}, + {"row": 6, "col": 6, "name": "Tougher Skin", "desc": "Harden your skin and become permanently +5% more resistant. For every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)"}, + {"row": 7, "col": 0, "name": "Vehement", "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%). Damage Bonus: +5 (Raw)"}, + {"row": 8, "col": 2, "name": "Uppercut", "desc": "Rocket enemies in the air and deal massive damage"}, + {"row": 8, "col": 4, "name": "Cheaper Charge", "desc": "Reduce the Mana cost of Charge"}, + {"row": 8, "col": 6, "name": "War Scream", "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies"}, + {"row": 10, "col": 0, "name": "Earth Mastery", "desc": "Increases base damage from all Earth attacks"}, + {"row": 10, "col": 2, "name": "Thunder Mastery", "desc": "Increases base damage from all Thunder attacks"}, + {"row": 10, "col": 6, "name": "Air Mastery", "desc": "Increases base damage from all Air attacks"}, + {"row": 10, "col": 8, "name": "Fire Mastery", "desc": "Increases base damage from all Fire attacks"}, + {"row": 11, "col": 4, "name": "Water Mastery", "desc": "Increases base damage from all Water attacks"}, + {"row": 12, "col": 0, "name": "Quadruple Bash", "desc": "Bash will hit 4 times at an even larger range"}, + {"row": 12, "col": 2, "name": "Fireworks", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage"}, + {"row": 12, "col": 6, "name": "Flyby Jab", "desc": "Damage enemies in your way when using Charge"}, + {"row": 12, "col": 8, "name": "Flaming Uppercut", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds"}, + {"row": 13, "col": 4, "name": "Half-Moon Swipe", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water"}, + {"row": 13, "col": 7, "name": "Iron Lungs", "desc": "War scream deals more damage"}, + {"row": 15, "col": 2, "name": "Generalist", "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana"}, + {"row": 15, "col": 4, "name": "Counter", "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back"}, + {"row": 15, "col": 7, "name": "Mantle of the Bovemists", "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)"}, + {"row": 16, "col": 1, "name": "Bak'al's Grasp", "desc": "After casting war Scream, become Corrupted (15s Cooldown). You cannot heal while in that state. While Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)."}, + {"row": 17, "col": 0, "name": "Spear Proficiency 2", "desc": "Improve your Main Attack's damage and range w/ spear"}, + {"row": 17, "col": 3, "name": "Cheaper Uppercut", "desc": "Reduce the Mana Cost of Uppercut"}, + {"row": 17, "col": 5, "name": "Aerodynamics", "desc": "During Charge, you can steer and change direction"}, + {"row": 17, "col": 7, "name": "Provoke", "desc": "Mobs damaged by War Scream will target only you for at least 5s. Reduce the Mana cost of War Scream."}, + {"row": 18, "col": 2, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 18, "col": 6, "name": "Air Shout", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times."}, + {"row": 20, "col": 0, "name": "Enraged Blow", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)"}, + {"row": 20, "col": 3, "name": "Flying Kick", "desc": "While using Charge, mobs hit will halt your momentum and get knocked back"}, + {"row": 20, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 20, "col": 8, "name": "Manachism", "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 mana (1s Cooldown)"}, + {"row": 22, "col": 0, "name": "Boiling Blood", "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds"}, + {"row": 22, "col": 2, "name": "Ragnarokkr", "desc": "War Scream becomes deafening, increasing its range and giving damage bonus to players"}, + {"row": 22, "col": 4, "name": "Ambidextrous", "desc": "Increase your change to attack with Counter by 30%"}, + {"row": 22, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 22, "col": 8, "name": "Stronger Bash", "desc": "Increase the damage of Bash"}, + {"row": 23, "col": 1, "name": "Massacre", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar"}, + {"row": 23, "col": 5, "name": "Collide", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage"}, + {"row": 23, "col": 7, "name": "Rejuvenating Skin", "desc": "Regain back 30% of the damage you take as healing over 30s"}, + {"row": 24, "col": 2, "name": "Comet", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage"}, + {"row": 26, "col": 0, "name": "Uncontainable Corruption", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1"}, + {"row": 26, "col": 2, "name": "Radiant Devotee", "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)"}, + {"row": 26, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 26, "col": 7, "name": "Mythril Skin", "desc": "Gain +5% Base Resistance and become immune to knockback"}, + {"row": 27, "col": 1, "name": "Armour Breaker", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage"}, + {"row": 27, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 27, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 27, "col": 8, "name": "Sparking Hope", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies"}, + {"row": 28, "col": 0, "name": "Massive Bash", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)"}, + {"row": 28, "col": 2, "name": "Tempest", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area"}, + {"row": 28, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 29, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 29, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 29, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 31, "col": 0, "name": "Cheaper War Scream", "desc": "Reduce the Mana cost of War Scream"}, + {"row": 31, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 32, "col": 1, "name": "Blood KO", "desc": "Gonna have to rewrite this one chief"}, + {"row": 32, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 32, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 32, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 34, "col": 1, "name": "Blood Pact", "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)"}, + {"row": 34, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 34, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 34, "col": 8, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 35, "col": 0, "name": "TEXT", "desc": "IDFK MMMM"} + ] "Assassin": [ { "title": "Spin Attack", From 0417a68e6591f014137b3fe6eea2245244c4c53c Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 16 Jun 2022 04:39:20 -0700 Subject: [PATCH 020/155] Remove duplicate warrior tag --- js/atree_constants.js | 145 +++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 73 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 195a192..5b90a40 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -180,78 +180,6 @@ const atrees = {"image": "../media/atree/connect_line.png", "connector": true, "row": 34, "col": 6, "rotate": 90}, {"title": "Minefield Ability\nTrapper Archetype", "desc": "Allow you to place +6 Traps, but with reduced damage and range Total Damage: -80% (of DPS) - Neutral: -80% AoE: -2 Blocks (Circular) AP: 2 Req: Basaltic Trap Min Trapper: 0/10", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 7}, ], - "Warrior": [ - {"row": 0, "col": 4, "name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area"}, - {"row": 2, "col": 2, "name": "Cheaper Bash", "desc": "Reduce the Mana cost of Bash"}, - {"row": 2, "col": 4, "name": "Spear Proficiency 1", "desc": "Improve your Main Attack's damage and range w/ spear"}, - {"row": 4, "col": 4, "name": "Double Bash", "desc": "Bash will hit a second time at a farther range"}, - {"row": 6, "col": 2, "name": "Heavy Impact", "desc": "After using Charge, violently crash down into the ground and deal damage. (Does not work if Flying Kick is unlocked)"}, - {"row": 6, "col": 4, "name": "Charge", "desc": "Charge forward at high speed (hold shift to cancel)"}, - {"row": 6, "col": 6, "name": "Tougher Skin", "desc": "Harden your skin and become permanently +5% more resistant. For every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)"}, - {"row": 7, "col": 0, "name": "Vehement", "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%). Damage Bonus: +5 (Raw)"}, - {"row": 8, "col": 2, "name": "Uppercut", "desc": "Rocket enemies in the air and deal massive damage"}, - {"row": 8, "col": 4, "name": "Cheaper Charge", "desc": "Reduce the Mana cost of Charge"}, - {"row": 8, "col": 6, "name": "War Scream", "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies"}, - {"row": 10, "col": 0, "name": "Earth Mastery", "desc": "Increases base damage from all Earth attacks"}, - {"row": 10, "col": 2, "name": "Thunder Mastery", "desc": "Increases base damage from all Thunder attacks"}, - {"row": 10, "col": 6, "name": "Air Mastery", "desc": "Increases base damage from all Air attacks"}, - {"row": 10, "col": 8, "name": "Fire Mastery", "desc": "Increases base damage from all Fire attacks"}, - {"row": 11, "col": 4, "name": "Water Mastery", "desc": "Increases base damage from all Water attacks"}, - {"row": 12, "col": 0, "name": "Quadruple Bash", "desc": "Bash will hit 4 times at an even larger range"}, - {"row": 12, "col": 2, "name": "Fireworks", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage"}, - {"row": 12, "col": 6, "name": "Flyby Jab", "desc": "Damage enemies in your way when using Charge"}, - {"row": 12, "col": 8, "name": "Flaming Uppercut", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds"}, - {"row": 13, "col": 4, "name": "Half-Moon Swipe", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water"}, - {"row": 13, "col": 7, "name": "Iron Lungs", "desc": "War scream deals more damage"}, - {"row": 15, "col": 2, "name": "Generalist", "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana"}, - {"row": 15, "col": 4, "name": "Counter", "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back"}, - {"row": 15, "col": 7, "name": "Mantle of the Bovemists", "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)"}, - {"row": 16, "col": 1, "name": "Bak'al's Grasp", "desc": "After casting war Scream, become Corrupted (15s Cooldown). You cannot heal while in that state. While Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)."}, - {"row": 17, "col": 0, "name": "Spear Proficiency 2", "desc": "Improve your Main Attack's damage and range w/ spear"}, - {"row": 17, "col": 3, "name": "Cheaper Uppercut", "desc": "Reduce the Mana Cost of Uppercut"}, - {"row": 17, "col": 5, "name": "Aerodynamics", "desc": "During Charge, you can steer and change direction"}, - {"row": 17, "col": 7, "name": "Provoke", "desc": "Mobs damaged by War Scream will target only you for at least 5s. Reduce the Mana cost of War Scream."}, - {"row": 18, "col": 2, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 18, "col": 6, "name": "Air Shout", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times."}, - {"row": 20, "col": 0, "name": "Enraged Blow", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)"}, - {"row": 20, "col": 3, "name": "Flying Kick", "desc": "While using Charge, mobs hit will halt your momentum and get knocked back"}, - {"row": 20, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 20, "col": 8, "name": "Manachism", "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 mana (1s Cooldown)"}, - {"row": 22, "col": 0, "name": "Boiling Blood", "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds"}, - {"row": 22, "col": 2, "name": "Ragnarokkr", "desc": "War Scream becomes deafening, increasing its range and giving damage bonus to players"}, - {"row": 22, "col": 4, "name": "Ambidextrous", "desc": "Increase your change to attack with Counter by 30%"}, - {"row": 22, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 22, "col": 8, "name": "Stronger Bash", "desc": "Increase the damage of Bash"}, - {"row": 23, "col": 1, "name": "Massacre", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar"}, - {"row": 23, "col": 5, "name": "Collide", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage"}, - {"row": 23, "col": 7, "name": "Rejuvenating Skin", "desc": "Regain back 30% of the damage you take as healing over 30s"}, - {"row": 24, "col": 2, "name": "Comet", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage"}, - {"row": 26, "col": 0, "name": "Uncontainable Corruption", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1"}, - {"row": 26, "col": 2, "name": "Radiant Devotee", "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)"}, - {"row": 26, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 26, "col": 7, "name": "Mythril Skin", "desc": "Gain +5% Base Resistance and become immune to knockback"}, - {"row": 27, "col": 1, "name": "Armour Breaker", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage"}, - {"row": 27, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 27, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 27, "col": 8, "name": "Sparking Hope", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies"}, - {"row": 28, "col": 0, "name": "Massive Bash", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)"}, - {"row": 28, "col": 2, "name": "Tempest", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area"}, - {"row": 28, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 29, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 29, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 29, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 31, "col": 0, "name": "Cheaper War Scream", "desc": "Reduce the Mana cost of War Scream"}, - {"row": 31, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 32, "col": 1, "name": "Blood KO", "desc": "Gonna have to rewrite this one chief"}, - {"row": 32, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 32, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 32, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 34, "col": 1, "name": "Blood Pact", "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)"}, - {"row": 34, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 34, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 34, "col": 8, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 35, "col": 0, "name": "TEXT", "desc": "IDFK MMMM"} - ] "Assassin": [ { "title": "Spin Attack", @@ -1498,7 +1426,78 @@ const atrees = "col": 8 }, ], - "Warrior": [], + "Warrior": [ + {"row": 0, "col": 4, "name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area"}, + {"row": 2, "col": 2, "name": "Cheaper Bash", "desc": "Reduce the Mana cost of Bash"}, + {"row": 2, "col": 4, "name": "Spear Proficiency 1", "desc": "Improve your Main Attack's damage and range w/ spear"}, + {"row": 4, "col": 4, "name": "Double Bash", "desc": "Bash will hit a second time at a farther range"}, + {"row": 6, "col": 2, "name": "Heavy Impact", "desc": "After using Charge, violently crash down into the ground and deal damage. (Does not work if Flying Kick is unlocked)"}, + {"row": 6, "col": 4, "name": "Charge", "desc": "Charge forward at high speed (hold shift to cancel)"}, + {"row": 6, "col": 6, "name": "Tougher Skin", "desc": "Harden your skin and become permanently +5% more resistant. For every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)"}, + {"row": 7, "col": 0, "name": "Vehement", "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%). Damage Bonus: +5 (Raw)"}, + {"row": 8, "col": 2, "name": "Uppercut", "desc": "Rocket enemies in the air and deal massive damage"}, + {"row": 8, "col": 4, "name": "Cheaper Charge", "desc": "Reduce the Mana cost of Charge"}, + {"row": 8, "col": 6, "name": "War Scream", "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies"}, + {"row": 10, "col": 0, "name": "Earth Mastery", "desc": "Increases base damage from all Earth attacks"}, + {"row": 10, "col": 2, "name": "Thunder Mastery", "desc": "Increases base damage from all Thunder attacks"}, + {"row": 10, "col": 6, "name": "Air Mastery", "desc": "Increases base damage from all Air attacks"}, + {"row": 10, "col": 8, "name": "Fire Mastery", "desc": "Increases base damage from all Fire attacks"}, + {"row": 11, "col": 4, "name": "Water Mastery", "desc": "Increases base damage from all Water attacks"}, + {"row": 12, "col": 0, "name": "Quadruple Bash", "desc": "Bash will hit 4 times at an even larger range"}, + {"row": 12, "col": 2, "name": "Fireworks", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage"}, + {"row": 12, "col": 6, "name": "Flyby Jab", "desc": "Damage enemies in your way when using Charge"}, + {"row": 12, "col": 8, "name": "Flaming Uppercut", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds"}, + {"row": 13, "col": 4, "name": "Half-Moon Swipe", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water"}, + {"row": 13, "col": 7, "name": "Iron Lungs", "desc": "War scream deals more damage"}, + {"row": 15, "col": 2, "name": "Generalist", "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana"}, + {"row": 15, "col": 4, "name": "Counter", "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back"}, + {"row": 15, "col": 7, "name": "Mantle of the Bovemists", "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)"}, + {"row": 16, "col": 1, "name": "Bak'al's Grasp", "desc": "After casting war Scream, become Corrupted (15s Cooldown). You cannot heal while in that state. While Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)."}, + {"row": 17, "col": 0, "name": "Spear Proficiency 2", "desc": "Improve your Main Attack's damage and range w/ spear"}, + {"row": 17, "col": 3, "name": "Cheaper Uppercut", "desc": "Reduce the Mana Cost of Uppercut"}, + {"row": 17, "col": 5, "name": "Aerodynamics", "desc": "During Charge, you can steer and change direction"}, + {"row": 17, "col": 7, "name": "Provoke", "desc": "Mobs damaged by War Scream will target only you for at least 5s. Reduce the Mana cost of War Scream."}, + {"row": 18, "col": 2, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 18, "col": 6, "name": "Air Shout", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times."}, + {"row": 20, "col": 0, "name": "Enraged Blow", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)"}, + {"row": 20, "col": 3, "name": "Flying Kick", "desc": "While using Charge, mobs hit will halt your momentum and get knocked back"}, + {"row": 20, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 20, "col": 8, "name": "Manachism", "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 mana (1s Cooldown)"}, + {"row": 22, "col": 0, "name": "Boiling Blood", "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds"}, + {"row": 22, "col": 2, "name": "Ragnarokkr", "desc": "War Scream becomes deafening, increasing its range and giving damage bonus to players"}, + {"row": 22, "col": 4, "name": "Ambidextrous", "desc": "Increase your change to attack with Counter by 30%"}, + {"row": 22, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 22, "col": 8, "name": "Stronger Bash", "desc": "Increase the damage of Bash"}, + {"row": 23, "col": 1, "name": "Massacre", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar"}, + {"row": 23, "col": 5, "name": "Collide", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage"}, + {"row": 23, "col": 7, "name": "Rejuvenating Skin", "desc": "Regain back 30% of the damage you take as healing over 30s"}, + {"row": 24, "col": 2, "name": "Comet", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage"}, + {"row": 26, "col": 0, "name": "Uncontainable Corruption", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1"}, + {"row": 26, "col": 2, "name": "Radiant Devotee", "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)"}, + {"row": 26, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 26, "col": 7, "name": "Mythril Skin", "desc": "Gain +5% Base Resistance and become immune to knockback"}, + {"row": 27, "col": 1, "name": "Armour Breaker", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage"}, + {"row": 27, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 27, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 27, "col": 8, "name": "Sparking Hope", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies"}, + {"row": 28, "col": 0, "name": "Massive Bash", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)"}, + {"row": 28, "col": 2, "name": "Tempest", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area"}, + {"row": 28, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 29, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 29, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 29, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 31, "col": 0, "name": "Cheaper War Scream", "desc": "Reduce the Mana cost of War Scream"}, + {"row": 31, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 32, "col": 1, "name": "Blood KO", "desc": "Gonna have to rewrite this one chief"}, + {"row": 32, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 32, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 32, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 34, "col": 1, "name": "Blood Pact", "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)"}, + {"row": 34, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 34, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 34, "col": 8, "name": "TEXT", "desc": "IDFK MMMM"}, + {"row": 35, "col": 0, "name": "TEXT", "desc": "IDFK MMMM"} + ], "Mage": [], "Shaman": [] } From ec13bdea1ed50e1974cd305268eba135f37a0e85 Mon Sep 17 00:00:00 2001 From: ferricles Date: Fri, 17 Jun 2022 20:46:52 -0700 Subject: [PATCH 021/155] small bug fixes for atree swapping - some unwanted behavior still exists --- js/atree_constants.js | 1411 +++++------------------------------------ js/sq2bs.js | 13 +- 2 files changed, 176 insertions(+), 1248 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 5b90a40..31c22eb 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -181,1250 +181,173 @@ const atrees = {"title": "Minefield Ability\nTrapper Archetype", "desc": "Allow you to place +6 Traps, but with reduced damage and range Total Damage: -80% (of DPS) - Neutral: -80% AoE: -2 Blocks (Circular) AP: 2 Req: Basaltic Trap Min Trapper: 0/10", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 7}, ], "Assassin": [ - { - "title": "Spin Attack", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 0, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 1, - "col": 4 - }, - { - "title": "Dagger Proficiency I", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 2, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 2, - "col": 3 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 2, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 3, - "col": 4 - }, - { - "title": "Double Spin", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 4, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 5, - "col": 4 - }, - { - "title": "Dash", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 6, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 6, - "col": 3 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 6, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 6, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 6, - "col": 6 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 7, - "col": 2 - }, - { - "title": "Smoke Bomb", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 8, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 7, - "col": 6 - }, - { - "title": "Multihit", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 8, - "col": 6 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 8, - "col": 3 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 8, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 8, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 8, - "col": 1 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 8, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 9, - "col": 0 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 10, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 9, - "col": 2 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 10, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 9, - "col": 6 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 10, - "col": 6 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 8, - "col": 7 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 8, - "col": 8 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 9, - "col": 8 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 10, - "col": 8 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 10, - "col": 1 - }, - { - "title": "Backstab", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 11, - "col": 1 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 9, - "col": 4 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 90, - "row": 10, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 10, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 11, - "col": 4 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 10, - "col": 7 - }, - { - "title": "Fatality", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 11, - "col": 7 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 11, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 12, - "col": 0 - }, - { - "title": "Violent Vortex", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 13, - "col": 0 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 11, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 12, - "col": 2 - }, - { - "title": "Vanish", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 13, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 12, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 13, - "col": 3 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 13, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 13, - "col": 6 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 14, - "col": 2 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 15, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 14, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 15, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 12, - "col": 7 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 13, - "col": 7 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 14, - "col": 7 - }, - { - "title": "Lacerate", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 15, - "col": 7 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 15, - "col": 1 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 16, - "col": 1 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 15, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 16, - "col": 5 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 15, - "col": 8 - }, - { - "title": "Wall of Smoke", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 16, - "col": 8 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 16, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 17, - "col": 0 - }, - { - "title": "Silent Killer", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 18, - "col": 0 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 16, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 17, - "col": 2 - }, - { - "title": "Shadow Travel", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 18, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 17, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 18, - "col": 5 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 17, - "col": 8 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 18, - "col": 8 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 18, - "col": 4 - }, - { - "title": "Exploding Clones", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 19, - "col": 4 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 18, - "col": 3 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 19, - "col": 0 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 20, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 19, - "col": 3 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 20, - "col": 3 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 18, - "col": 6 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 18, - "col": 7 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 19, - "col": 7 - }, - { - "title": "Weightless", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 20, - "col": 7 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 20, - "col": 1 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 20, - "col": 2 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 21, - "col": 1 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 20, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 21, - "col": 4 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 20, - "col": 6 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 21, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 21, - "col": 6 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 20, - "col": 8 - }, - { - "title": "Dancing Blade", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 21, - "col": 8 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 21, - "col": 0 - }, - { - "title": "Spin Attack Damage", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 22, - "col": 0 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 21, - "col": 3 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 22, - "col": 3 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 22, - "col": 1 - }, - { - "title": "Marked", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 23, - "col": 1 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 22, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 23, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 23, - "col": 5 - }, - { - "title": "Shurikens", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 23, - "col": 6 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 23, - "col": 7 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 22, - "col": 8 - }, - { - "title": "Far Reach", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 23, - "col": 8 - }, - { - "title": "Stronger Multihit", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 24, - "col": 5 - }, - { - "title": "Psithurism", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 24, - "col": 7 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 24, - "col": 1 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 25, - "col": 1 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 25, - "col": 3 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 24, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 25, - "col": 4 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 25, - "col": 5 - }, - { - "title": "Choke Bomb", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 25, - "col": 6 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 25, - "col": 7 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 25, - "col": 8 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 26, - "col": 5 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 25, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 26, - "col": 0 - }, - { - "title": "Death Magnet", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 27, - "col": 0 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 25, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 26, - "col": 2 - }, - { - "title": "Cheaper Multihit", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 27, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 26, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 27, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 26, - "col": 7 - }, - { - "title": "Parry", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 27, - "col": 7 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 27, - "col": 1 - }, - { - "title": "Fatal Spin", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 28, - "col": 1 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 27, - "col": 3 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 28, - "col": 3 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 27, - "col": 6 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 28, - "col": 6 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 27, - "col": 8 - }, - { - "title": "Wall Jump", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 28, - "col": 8 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 28, - "col": 0 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 29, - "col": 0 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 29, - "col": 1 - }, - { - "title": "Harvester", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 30, - "col": 1 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 28, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 29, - "col": 4 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 30, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 28, - "col": 7 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 29, - "col": 7 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 30, - "col": 7 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 30, - "col": 2 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 31, - "col": 2 - }, - { - "image": "../media/atree/connect_t.png", - "connector": true, - "rotate": 180, - "row": 30, - "col": 5 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 30, - "col": 6 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 31, - "col": 5 - }, - { - "title": "Ricochet", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 31, - "col": 8 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 31, - "col": 1 - }, - { - "title": "Satsujin", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 32, - "col": 1 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 31, - "col": 4 - }, - { - "title": "Forbidden Art", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 32, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 31, - "col": 7 - }, - { - "title": "Jasmine Bloom", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 32, - "col": 7 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 180, - "row": 32, - "col": 0 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 33, - "col": 0 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 32, - "col": 2 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 33, - "col": 2 - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 32, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 33, - "col": 5 - }, - { - "title": "Text", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 33, - "col": 8 - }, + {"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4}, + {"title": "Dagger Proficiency I", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 2, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 3, "col": 4}, + {"title": "Double Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 5, "col": 4}, + {"title": "Dash", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 2}, + {"title": "Smoke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 6}, + {"title": "Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 1}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 8, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 6}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 8, "col": 8}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 8}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 1}, + {"title": "Backstab", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 90, "row": 10, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 10, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 7}, + {"title": "Fatality", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 11, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 0}, + {"title": "Violent Vortex", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 11, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 2}, + {"title": "Vanish", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 13, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 13, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 7}, + {"title": "Lacerate", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 15, "col": 1}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 8}, + {"title": "Wall of Smoke", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 8}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 16, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 0}, + {"title": "Silent Killer", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 16, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 2}, + {"title": "Shadow Travel", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 5}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 8}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 8}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 4}, + {"title": "Exploding Clones", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 19, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 18, "col": 6}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 7}, + {"title": "Weightless", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 20, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 20, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 4}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 20, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 21, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 6}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 8}, + {"title": "Dancing Blade", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 21, "col": 0}, + {"title": "Spin Attack Damage", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 21, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 3}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 22, "col": 1}, + {"title": "Marked", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 5}, + {"title": "Shurikens", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 8}, + {"title": "Far Reach", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, + {"title": "Stronger Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 5}, + {"title": "Psithurism", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 1}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 1}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 5}, + {"title": "Choke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 6}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 7}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 5}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 25, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 0}, + {"title": "Death Magnet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 25, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 2}, + {"title": "Cheaper Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 7}, + {"title": "Parry", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 7}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 1}, + {"title": "Fatal Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 3}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 27, "col": 6}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 6}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 27, "col": 8}, + {"title": "Wall Jump", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 8}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 28, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 1}, + {"title": "Harvester", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 7}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7 }, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 30, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 2 }, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 30, "col": 5}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 30, "col": 6}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 5}, + {"title": "Ricochet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 1}, + {"title": "Satsujin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 4}, + {"title": "Forbidden Art", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 7}, + {"title": "Jasmine Bloom", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 32, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8}, ], "Warrior": [ {"row": 0, "col": 4, "name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area"}, diff --git a/js/sq2bs.js b/js/sq2bs.js index 4c25ad4..3d8161e 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -73,11 +73,9 @@ document.addEventListener('DOMContentLoaded', function() { }; }); - construct_AT(document.getElementById("atree-ui"), atrees["Assassin"]); //dagger is default atree document.getElementById("atree-dropdown").style.display = "none"; }); -// phanta scheduler let calcBuildTask = null; let updateStatTask = null; let doSearchTask = null; @@ -235,8 +233,13 @@ function update_field(field) { // set weapon img and set ability tree if (category == 'weapon') { document.querySelector("#weapon-img").setAttribute('src', '../media/items/new/generic-'+type+'.png'); - construct_AT(document.getElementById("atree-ui"), atrees[wep_to_class[type.toLowerCase()]]); //dagger is default atree - document.getElementById("atree-dropdown").style.display = "none"; + //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'); + } //TODO: reset chosen abilities (once ability implementation is here) } @@ -538,6 +541,8 @@ function init_autocomplete() { // atree parsing function construct_AT(elem, tree) { + console.log("constructing ability tree UI"); + elem.innerHTML = ""; //reset the atree in the DOM for (let i = 0; i < tree.length; i++) { let node = tree[i]; From 6277b50d12343252c7e55f29c8daeaeeb71af182 Mon Sep 17 00:00:00 2001 From: ferricles Date: Sat, 18 Jun 2022 21:00:32 -0700 Subject: [PATCH 022/155] fixed atree display bugs | functional atree toggle on/off + switching based on inferred class type --- builder/index.html | 3 +-- js/sq2bs.js | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/builder/index.html b/builder/index.html index 443bd8b..398110c 100644 --- a/builder/index.html +++ b/builder/index.html @@ -617,8 +617,7 @@
-
- Active: +
diff --git a/js/sq2bs.js b/js/sq2bs.js index 3d8161e..41612bd 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -233,6 +233,13 @@ function update_field(field) { // set weapon img and set ability tree if (category == 'weapon') { document.querySelector("#weapon-img").setAttribute('src', '../media/items/new/generic-'+type+'.png'); + + //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)]); @@ -542,7 +549,15 @@ function init_autocomplete() { // atree parsing function construct_AT(elem, tree) { console.log("constructing ability tree UI"); + document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later elem.innerHTML = ""; //reset the atree in the DOM + + // add in the "Active" title to atree + let active_row = document.createElement("div"); + active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center"); + active_row.textContent = "Active:"; + document.getElementById("atree-active").appendChild(active_row); + for (let i = 0; i < tree.length; i++) { let node = tree[i]; @@ -553,12 +568,16 @@ function construct_AT(elem, tree) { let row = document.createElement('div'); row.classList.add("row"); row.id = "atree-row-" + j; - row.style.height = elem.getBoundingClientRect().width / 9 + "px"; + //was causing atree rows to be 0 height + console.log(elem.scrollWidth / 9); + row.style.minHeight = elem.scrollWidth / 9 + "px"; + //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; for (let k = 0; k < 9; k++) { col = document.createElement('div'); col.classList.add('col', 'px-0'); + col.style.minHeight = elem.scrollWidth / 9 + "px"; row.appendChild(col); }; elem.appendChild(row); @@ -594,7 +613,8 @@ function construct_AT(elem, tree) { let active_tooltip = document.createElement('div'); active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto"); - active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; + //was causing active element boxes to be 0 width + // active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; active_tooltip.style.display = "none"; // tooltip text formatting From 554de7ea87da38470ec45352ee3438f9c9b0d35a Mon Sep 17 00:00:00 2001 From: ferricles Date: Sat, 18 Jun 2022 21:40:43 -0700 Subject: [PATCH 023/155] fixed asymmetric connector line images --- media/atree/connect_angle.png | Bin 1081 -> 1079 bytes media/atree/connect_c.png | Bin 6254 -> 1316 bytes media/atree/connect_line.png | Bin 1270 -> 1270 bytes media/atree/connect_t.png | Bin 694 -> 692 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/media/atree/connect_angle.png b/media/atree/connect_angle.png index 46c5e81aa19b176dafaba4aa04be9b0359cc4982..b1b03fd5193027c1a6854b70ffbf65cbde6be8c4 100644 GIT binary patch delta 115 zcmV-(0F3{+2)78Zv;jG8Nkl3cX`LRxdy}sNa~J@Cm+pA4 V3M)Je{Jj7G002ovPDHLkV1iVPFeLy0 delta 111 zcmV-#0FeK;2)PKbv;jGANkl}peR2rGbfdS zL1SX=L|c!;0V1vb!CRF?S*<%PQda0i35XU>(Gol8l&QVtRl=uB_8FHZEZJefwT_j& zUTig=-nm^?JGh!2@OLbpJb6*~l#2ME48h0Er+-_#pL@9WJEPOyGa;LknF35tdmLMo zq_$g&E$(7#omPTR(lR-J-DMAV?tAy}@$cA{^ve6ci@$1FPG+>5Skd>g&2^8X&F0LP zbG~;vuhGyv^8CnSHdBv3CrWQVH9yDN&N@%h=+eo`M*wAJtE& zPF~I8*2fyMz^UlZgZdBE+tTW?F8Ok~HJ%f{{ZE3CX}81TwA=TOZ@X*3_~-nAsn)WuMyFld#FtCZfd$$D2s{?$Js}E-!31lv$9G`tF!(0+Op#Q^_Pqd#Vibm zMiChyAeX(iwuibu0ISc}7Fl1JpKi^a!mAS!yk+x__pO Bg>3); literal 6254 zcmeHLX;f3!7QUej0wPfnq>6;#fMDhXk}yLUA|xPCWiH9hO^A?zBtU?o5wVIq2ZZ`m zKq<(eqD+D)h$t#5q6I4`76h#mBA|7wsP6`FTJQaMu4Vt3tR&~2^X+f%{hfWzJvSNt zY%c@7@AUuxFkpH!0ssI3Hxa-TZTKf#R>lVa?fuC?!HNJ*B1$Tg@P)AuN|7XmP>@mx z4=Q`wgG1^bT1_46wocaFux!ct$9a9OxD!6t9=9p%*N%9#F8VoY$zE&bhTmS^8)2>< zxSekBYU}*Fvkjb_UdQUwxpjUaiMFeHaprv^cDEG@Oz8S9k>UJdNyOP#p8pz1i>#k9 zU$oj^V$M5JyL?#l`oXyUS6KZp80EaDpL|EzPjpV zzADl%xHZInuKn5ud4mS~#P)F4MYk?txoFTWWJ}je>{OLQ%Y&D^Xz~v~yi;_B#YQA6 zE^j~b+9**+=Fn4m`}Dx@8vCxQZkrU^AAj=a4n5B_?=7gfNo%+Wk;<){GzRPE#1o)r z_zJFWMCg?L9@(P?$gHkk;&dC2RUPW`J#&>>qZN_un22>qDzA%MyRmOtwXRuAZhVY? zc=dJL(y^rD%AP-~AF`WyVTV%$h7n3kE~}_^SJa(LJ64*W3Vw6zN~TjPhi#z6x71nw zTU)5soRwDc{`eN7Ng2s;vY61j8eH*nk@|-L3*6uc(wTP(p!|P+L@eF)NQ6a zrfjI!6j$4z+Fk?in34VN3+nsbrx5m{EY{D6cuF`uJMj0t!P|Ux2Y3^T=Pxcrvl}%t zwlU`WoVT@~-@X-OhsZ7!N40C5*OHj{S{3&U?yp$0vV7mt_F8hi-(Q%+*8;M)hdrok z*gVz&`b8uUL!&Ai1Lsp4EDcPPa&%YZZZ6rwIlHsuqC?xs9`?hQXNZ`_8oo&&onThZ zAs1&(=~B$dAtCwaNwfWyBIfp33p}r-9JshxurQ3_QRz>#66F6vz~9%ofq!+%?E0o< z(6EstXUstIJnXLbzSmoidiCwD>WZJUGP4S!H!J8iGF9ocH+b=}>yC$?j=5sA30eHM zdBkM53se1tY;zw!@z3IImANgJt;}+{e+@pC?P7MOzC#ewS?Mmul;Z>GB`&MHDT+r| zHGl10Xjz0H;Od&?a}4|+2y%1fQ^TAm!!Y!*8Yy_t@n zqw0ami(Xk$gXU^;UZiD#{*M+eH$xL{cIF}*6#yNeJ^&7XC+$Jp*IZ)Pb*1)|V6N&A zih2i7?0$K$_z2j)?z`V+0i|A^8~PU5ZY@oGEN7kSe&2w93p+sBWIS z)-gjnJvX##Ws7IWa)$=vWlc$8+;;g!v!tC1Ow$x5$w-f*W1Fo4YFD`%iHvufc$idP z+F0mZzk1Q&t?Z}d`5~o+hghAJyEQAE=&bwVS0;n~yQ-`_ON^*epEdro3X#*#ZM-@_ zK54a_n`^A|0@Y+>gf)Iv^)|dVbWYpB+y?#FWL&M6-4Nf}`gHh{*s|kbUZ|GEA)k@o zecD=DwXTE8cKat%=xQA zYB{%Um50&3&=VFLZS^l_7U~BJJUYz(xSUZKqi3O~B^H=Jt=K-g%x6#2sIzf;V2{Sk zh78o2f+s6{v!+Y?%50GJ#w@~Oba~#XO~NU+svmvdnQ~!c(SXIAq1qCT?i;b{EkoH# zsXbG5r%>h3FDEic0c~~IfS-L3N85D309sDxR9g3*nMzKe-i>GZ0q15lw%;%!?ZOQk z6%}^}#06g5!J_!~1=$;|#rjZs=l@$~x4(lDB^WJ?X};9&(XD!uzQSu^*WP-?P|@Cj zSUW455_dNb=gmtI!XLO#YeMt(WyeQGxjZ#qzdY8WcRBAj^UNa$lM$ZzgF!F!{`R}u zRbs2Gu4v&iAQASqmE+^xETHKkY3Zys zJ}?b?=@`9UFRkBjLs52iVP=l<$AIYRx7W74rjF#dq`pA79lsb<18VqF+Pu#eau+sO zYe#mhTV);P%PpDbhbb92+B7_H`*lRRAnE37`7>);Ho_@Ox%t7*9*wRJHNppZFEZTA ztrnlYclx$A#e3{r{PjC_m$cSv?KUNLo=>`ktO||>k8TR-@XsjCXxX@bS<}|Ikb%ZC zA(#~*HGXeTMX|$}arrA}6P8$P)?}P3D@-RYVMKR(6SDUHRknWhxyOsnEyzBg>A6`e zsK8A&*1vi*rH;R9WhYC|bIm1_8*gVLG#Xrr_&090*7Z^mW0#wM>3Xtauz6_FgWB!J zP8xsLt;u}2ec=e=-}cS@*;vzUmaq1u|1?kOTqL=m@!b)Z%e(B4+@-P2Rpj=Sue7OD zO;7!t=(v z4$ev`2aJIfC@v%riXG7-4JXhjA2O=!jYFfxA&MABbTG>wqr`~imTHO# z4hAF#WkRV!C=sL7oE)wsUg3yF!{ey;@rk4?)(3jAd{PBi51f)C#SyT0oJfTG)IzTC zNPtNu1Nxs9@*wyyh6{k?l6V;ic_ct$h2^IdJn)0PG+q`vo(>PhL9vhshRR{DgwI@h zF2}2g4{9i_VaM@#^rH z3`exuKb7EjqhyzcjP{a{HIH_1L z9;RAwx~o6a5lzJ6Klb>?auj^n0A2$^F;9{x|2PyR6hVOsj#^KGErmjY^CsHSs6;zE z>POIWNG6ACQO!!gV@VUt>WQJl>A=!*)RhVoj9b9j(A{MaMoC~o3vViCj&$A?g;@xrBZzzHXEP6EUmcLdu_41v)cu>gWs z_+&}F?-%}$=^}8sGzymhPd16h!;ozu5JRPK$QT}%%i$3T1P+%-`dPR;?!&UeYOs`|I))@ z9Pr5|0NYLU!J7-bSK&TvR+DziC( zq`((}zg5>)lS}X8;|?T-Pe6(A!_3iO;5_^wq|Np5VgT>d?}Fo{*>I1p)H_5D0Qxi4 z9|W)~Z#LYiqhPW;bna>+wKa57{8C_?Mi!Id8l>!bbgfFyjeulym?gb?*9KZNdg{;N zSl-Lo=QacDL(D6b=s()kzTj2;P)|Y%RRamQsQ>HL1in_|>oI+Z=&MkwM#4#ft<77M zVGG}<9E=jB{;tyfYN;cE{_Q*Wfz-f;dxP8Rkuw1lpn~TGSQ6j<&~ob=_E`C|I^T(| rDeuSrpN0nT|De7I^UqT@|CppPdI&jIb0BaFT(1DrgUvYP7M1oNXi3{| diff --git a/media/atree/connect_line.png b/media/atree/connect_line.png index 02771e5dbeb70cba490f7f1011a6266174b75b61..41ee50731e02a10dae99c90a20decc01234064cf 100644 GIT binary patch delta 52 zcmeyy`HgeKEk<53gTe~ HDWM4fG*=SK diff --git a/media/atree/connect_t.png b/media/atree/connect_t.png index 14d3c16b69250a233cd1de4ca7f2a0b44f98b040..8ed976eb9f0d5d3c8d87891a0c058fa5598b8603 100644 GIT binary patch literal 692 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=B+po-U3d z6?5L+Fyv!0$Te-*MEOd-G7;z z;W-CG3+E^zJp^pi)8@xD?SB$`w}G4C5DN@lyLUUC*JUSzf)9;R&e=0|dszzFkSqr# d3HpEsi^UU5cvNTD9S{Lo=;`X`vd$@?2>@Frgo6M8 literal 694 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=B*Ko-U3d z6?5L+Fyv!0Y`KQ`W_}XlrNx5dCd*WBNtI?6u1u`@cU}owr<^ z;hZ1?hwvyOJp^{2KV!F-rT+BoUqyOIx}&S} Date: Sun, 19 Jun 2022 00:42:49 -0700 Subject: [PATCH 024/155] Compute graph cleanup, prepping for full build calc (currently broke) --- builder/index.html | 7 +- js/build.js | 262 ++------------------------------------ js/build_encode_decode.js | 201 +++++++++++++++++++++++++++++ js/builder.js | 211 ++---------------------------- js/builder_graph.js | 56 ++++++++ js/computation_graph.js | 81 +++++++++--- 6 files changed, 346 insertions(+), 472 deletions(-) create mode 100644 js/build_encode_decode.js diff --git a/builder/index.html b/builder/index.html index d7a802c..d9c3e8f 100644 --- a/builder/index.html +++ b/builder/index.html @@ -313,10 +313,10 @@
- +
- +
@@ -1399,8 +1399,9 @@ - + + diff --git a/js/build.js b/js/build.js index 84ff86a..447162d 100644 --- a/js/build.js +++ b/js/build.js @@ -100,238 +100,12 @@ class Build{ * @param {Number[]} powders : Powder application. List of lists of integers (powder IDs). * In order: boots, Chestplate, Leggings, Boots, Weapon. * @param {Object[]} inputerrors : List of instances of error-like classes. + * + * @param {Object[]} tomes: List of tomes. + * In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome. + * 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building) */ - constructor(level,equipment, powders, externalStats, inputerrors=[]){ - - let errors = inputerrors; - //this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem). - this.craftedItems = []; - this.customItems = []; - // NOTE: powders is just an array of arrays of powder IDs. Not powder objects. - this.powders = powders; - if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") { - const helmet = itemMap.get(equipment[0]); - this.powders[0] = this.powders[0].slice(0,helmet.slots); - this.helmet = expandItem(helmet, this.powders[0]); - } else { - try { - let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined); - if (helmet.statMap.get("type") !== "helmet") { - throw new Error("Not a helmet"); - } - this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots")); - helmet.statMap.set("powders",this.powders[0].slice()); - this.helmet = helmet.statMap; - applyArmorPowders(this.helmet, this.powders[0]); - if (this.helmet.get("custom")) { - this.customItems.push(helmet); - } else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(helmet); - } - - } catch (Error) { - const helmet = itemMap.get("No Helmet"); - this.powders[0] = this.powders[0].slice(0,helmet.slots); - this.helmet = expandItem(helmet, this.powders[0]); - errors.push(new ItemNotFound(equipment[0], "helmet", true)); - } - } - if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") { - const chestplate = itemMap.get(equipment[1]); - this.powders[1] = this.powders[1].slice(0,chestplate.slots); - this.chestplate = expandItem(chestplate, this.powders[1]); - } else { - try { - let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined); - if (chestplate.statMap.get("type") !== "chestplate") { - throw new Error("Not a chestplate"); - } - this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots")); - chestplate.statMap.set("powders",this.powders[1].slice()); - this.chestplate = chestplate.statMap; - applyArmorPowders(this.chestplate, this.powders[1]); - if (this.chestplate.get("custom")) { - this.customItems.push(chestplate); - } else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(chestplate); - } - } catch (Error) { - console.log(Error); - const chestplate = itemMap.get("No Chestplate"); - this.powders[1] = this.powders[1].slice(0,chestplate.slots); - this.chestplate = expandItem(chestplate, this.powders[1]); - errors.push(new ItemNotFound(equipment[1], "chestplate", true)); - } - } - if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") { - const leggings = itemMap.get(equipment[2]); - this.powders[2] = this.powders[2].slice(0,leggings.slots); - this.leggings = expandItem(leggings, this.powders[2]); - } else { - try { - let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined); - if (leggings.statMap.get("type") !== "leggings") { - throw new Error("Not a leggings"); - } - this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots")); - leggings.statMap.set("powders",this.powders[2].slice()); - this.leggings = leggings.statMap; - applyArmorPowders(this.leggings, this.powders[2]); - if (this.leggings.get("custom")) { - this.customItems.push(leggings); - } else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(leggings); - } - } catch (Error) { - const leggings = itemMap.get("No Leggings"); - this.powders[2] = this.powders[2].slice(0,leggings.slots); - this.leggings = expandItem(leggings, this.powders[2]); - errors.push(new ItemNotFound(equipment[2], "leggings", true)); - } - } - if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") { - const boots = itemMap.get(equipment[3]); - this.powders[3] = this.powders[3].slice(0,boots.slots); - this.boots = expandItem(boots, this.powders[3]); - } else { - try { - let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined); - if (boots.statMap.get("type") !== "boots") { - throw new Error("Not a boots"); - } - this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots")); - boots.statMap.set("powders",this.powders[3].slice()); - this.boots = boots.statMap; - applyArmorPowders(this.boots, this.powders[3]); - if (this.boots.get("custom")) { - this.customItems.push(boots); - } else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(boots); - } - } catch (Error) { - const boots = itemMap.get("No Boots"); - this.powders[3] = this.powders[3].slice(0,boots.slots); - this.boots = expandItem(boots, this.powders[3]); - errors.push(new ItemNotFound(equipment[3], "boots", true)); - } - } - if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") { - const ring = itemMap.get(equipment[4]); - this.ring1 = expandItem(ring, []); - }else{ - try { - let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined); - if (ring.statMap.get("type") !== "ring") { - throw new Error("Not a ring"); - } - this.ring1 = ring.statMap; - if (this.ring1.get("custom")) { - this.customItems.push(ring); - } else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(ring); - } - } catch (Error) { - const ring = itemMap.get("No Ring 1"); - this.ring1 = expandItem(ring, []); - errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring")); - } - } - if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") { - const ring = itemMap.get(equipment[5]); - this.ring2 = expandItem(ring, []); - }else{ - try { - let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined); - if (ring.statMap.get("type") !== "ring") { - throw new Error("Not a ring"); - } - this.ring2 = ring.statMap; - if (this.ring2.get("custom")) { - this.customItems.push(ring); - } else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(ring); - } - } catch (Error) { - const ring = itemMap.get("No Ring 2"); - this.ring2 = expandItem(ring, []); - errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring")); - } - } - if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") { - const bracelet = itemMap.get(equipment[6]); - this.bracelet = expandItem(bracelet, []); - }else{ - try { - let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined); - if (bracelet.statMap.get("type") !== "bracelet") { - throw new Error("Not a bracelet"); - } - this.bracelet = bracelet.statMap; - if (this.bracelet.get("custom")) { - this.customItems.push(bracelet); - } else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(bracelet); - } - } catch (Error) { - const bracelet = itemMap.get("No Bracelet"); - this.bracelet = expandItem(bracelet, []); - errors.push(new ItemNotFound(equipment[6], "bracelet", true)); - } - } - if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") { - const necklace = itemMap.get(equipment[7]); - this.necklace = expandItem(necklace, []); - }else{ - try { - let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined); - if (necklace.statMap.get("type") !== "necklace") { - throw new Error("Not a necklace"); - } - this.necklace = necklace.statMap; - if (this.necklace.get("custom")) { - this.customItems.push(necklace); - } else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(necklace); - } - } catch (Error) { - const necklace = itemMap.get("No Necklace"); - this.necklace = expandItem(necklace, []); - errors.push(new ItemNotFound(equipment[7], "necklace", true)); - } - } - if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") { - const weapon = itemMap.get(equipment[8]); - this.powders[4] = this.powders[4].slice(0,weapon.slots); - this.weapon = expandItem(weapon, this.powders[4]); - if (equipment[8] !== "No Weapon") { - document.getElementsByClassName("powder-specials")[0].style.display = "grid"; - } else { - document.getElementsByClassName("powder-specials")[0].style.display = "none"; - } - }else{ - try { - let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined); - if (weapon.statMap.get("category") !== "weapon") { - throw new Error("Not a weapon"); - } - this.weapon = weapon.statMap; - if (this.weapon.get("custom")) { - this.customItems.push(weapon); - } else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(weapon); - } - this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots")); - this.weapon.set("powders",this.powders[4].slice()); - document.getElementsByClassName("powder-specials")[0].style.display = "grid"; - } catch (Error) { - const weapon = itemMap.get("No Weapon"); - this.powders[4] = this.powders[4].slice(0,weapon.slots); - this.weapon = expandItem(weapon, this.powders[4]); - document.getElementsByClassName("powder-specials")[0].style.display = "none"; - errors.push(new ItemNotFound(equipment[8], "weapon", true)); - } - } - //console.log(this.craftedItems) + constructor(level, items, tomes, weapon){ if (level < 1) { //Should these be constants? this.level = 1; @@ -348,10 +122,12 @@ class Build{ document.getElementById("level-choice").value = this.level; this.availableSkillpoints = levelToSkillPoints(this.level); - this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ]; - this.items = this.equipment.concat([this.weapon]); + this.equipment = items; + this.tomes = tomes; + this.weapon = weapon; + this.items = this.equipment.concat([this.weapon]).concat(this.tomes); // return [equip_order, best_skillpoints, final_skillpoints, best_total]; - let result = calculate_skillpoints(this.equipment, this.weapon); + let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon); console.log(result); this.equip_order = result[0]; // How many skillpoints the player had to assign (5 number) @@ -361,28 +137,14 @@ class Build{ // How many skillpoints assigned (1 number, sum of base_skillpoints) this.assigned_skillpoints = result[3]; this.activeSetCounts = result[4]; - - // For strength boosts like warscream, vanish, etc. - this.damageMultiplier = 1.0; - this.defenseMultiplier = 1.0; - - // For other external boosts ;-; - this.externalStats = externalStats; this.initBuildStats(); - - // Remove every error before adding specific ones - for (let i of document.getElementsByClassName("error")) { - i.textContent = ""; - } - this.errors = errors; - if (errors.length > 0) this.errored = true; } /*Returns build in string format */ toString(){ - return [this.equipment,this.weapon].flat(); + return [this.equipment,this.weapon,this.tomes].flat(); } /* Getters */ @@ -392,7 +154,7 @@ class Build{ } getBaseSpellCost(spellIdx, cost) { - cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); + // old intelligence: cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); cost += this.statMap.get("spRaw"+spellIdx); return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)); } diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js new file mode 100644 index 0000000..9b168b1 --- /dev/null +++ b/js/build_encode_decode.js @@ -0,0 +1,201 @@ + +/* + * Populate fields based on url, and calculate build. + */ +function decodeBuild(url_tag) { + if (url_tag) { + //default values + let equipment = [null, null, null, null, null, null, null, null, null]; + let tomes = [null, null, null, null, null, null, null]; + let powdering = ["", "", "", "", ""]; + let info = url_tag.split("_"); + let version = info[0]; + let save_skp = false; + let skillpoints = [0, 0, 0, 0, 0]; + let level = 106; + + let version_number = parseInt(version) + //equipment (items) + // TODO: use filters + if (version_number < 4) { + let equipments = info[1]; + for (let i = 0; i < 9; ++i ) { + let equipment_str = equipments.slice(i*3,i*3+3); + equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); + } + info[1] = equipments.slice(27); + } + else if (version_number == 4) { + let info_str = info[1]; + let start_idx = 0; + for (let i = 0; i < 9; ++i ) { + if (info_str.charAt(start_idx) === "-") { + equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18); + start_idx += 18; + } + else { + let equipment_str = info_str.slice(start_idx, start_idx+3); + equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); + start_idx += 3; + } + } + info[1] = info_str.slice(start_idx); + } + else if (version_number <= 6) { + let info_str = info[1]; + let start_idx = 0; + for (let i = 0; i < 9; ++i ) { + if (info_str.slice(start_idx,start_idx+3) === "CR-") { + equipment[i] = info_str.slice(start_idx, start_idx+20); + start_idx += 20; + } else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") { + let len = Base64.toInt(info_str.slice(start_idx,start_idx+3)); + equipment[i] = info_str.slice(start_idx+3,start_idx+3+len); + start_idx += (3+len); + } else { + let equipment_str = info_str.slice(start_idx, start_idx+3); + equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); + start_idx += 3; + } + } + info[1] = info_str.slice(start_idx); + } + //constant in all versions + for (let i in equipment) { + setValue(equipmentInputs[i], equipment[i]); + } + + //level, skill point assignments, and powdering + if (version_number == 1) { + let powder_info = info[1]; + let res = parsePowdering(powder_info); + powdering = res[0]; + } else if (version_number == 2) { + save_skp = true; + let skillpoint_info = info[1].slice(0, 10); + for (let i = 0; i < 5; ++i ) { + skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2)); + } + + let powder_info = info[1].slice(10); + let res = parsePowdering(powder_info); + powdering = res[0]; + } else if (version_number <= 6){ + level = Base64.toInt(info[1].slice(10,12)); + setValue("level-choice",level); + save_skp = true; + let skillpoint_info = info[1].slice(0, 10); + for (let i = 0; i < 5; ++i ) { + skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2)); + } + + let powder_info = info[1].slice(12); + + let res = parsePowdering(powder_info); + powdering = res[0]; + info[1] = res[1]; + } + // Tomes. + if (version == 6) { + //tome values do not appear in anything before v6. + for (let i = 0; i < 7; ++i) { + let tome_str = info[1].charAt(i); + for (let i in tomes) { + setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str))); + } + } + info[1] = info[1].slice(7); + } + + for (let i in powderInputs) { + setValue(powderInputs[i], powdering[i]); + } + } +} + +/* Stores the entire build in a string using B64 encoding and adds it to the URL. +*/ +function encodeBuild(build) { + + if (build) { + let build_string; + + //V6 encoding - Tomes + build_version = 4; + build_string = ""; + tome_string = ""; + + let crafted_idx = 0; + let custom_idx = 0; + for (const item of build.items) { + + if (item.get("custom")) { + let custom = "CI-"+encodeCustom(build.customItems[custom_idx],true); + build_string += Base64.fromIntN(custom.length, 3) + custom; + custom_idx += 1; + build_version = Math.max(build_version, 5); + } else if (item.get("crafted")) { + build_string += "CR-"+encodeCraft(build.craftedItems[crafted_idx]); + crafted_idx += 1; + } else if (item.get("category") === "tome") { + let tome_id = item.get("id"); + if (tome_id <= 60) { + // valid normal tome. ID 61-63 is for NONE tomes. + build_version = Math.max(build_version, 6); + } + tome_string += Base64.fromIntN(tome_id, 1); + } else { + build_string += Base64.fromIntN(item.get("id"), 3); + } + } + + for (const skp of skp_order) { + build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 + } + build_string += Base64.fromIntN(build.level, 2); + for (const _powderset of build.powders) { + let n_bits = Math.ceil(_powderset.length / 6); + build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. + // Slice copy. + let powderset = _powderset.slice(); + while (powderset.length != 0) { + let firstSix = powderset.slice(0,6).reverse(); + let powder_hash = 0; + for (const powder of firstSix) { + powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first. + } + build_string += Base64.fromIntN(powder_hash, 5); + powderset = powderset.slice(6); + } + } + build_string += tome_string; + + return build_version.toString() + "_" + build_string; + } +} + +function copyBuild(build) { + if (build) { + copyTextToClipboard(url_base+location.hash); + document.getElementById("copy-button").textContent = "Copied!"; + } +} + +function shareBuild(build) { + if (build) { + let text = url_base+location.hash+"\n"+ + "WynnBuilder build:\n"+ + "> "+build.helmet.get("displayName")+"\n"+ + "> "+build.chestplate.get("displayName")+"\n"+ + "> "+build.leggings.get("displayName")+"\n"+ + "> "+build.boots.get("displayName")+"\n"+ + "> "+build.ring1.get("displayName")+"\n"+ + "> "+build.ring2.get("displayName")+"\n"+ + "> "+build.bracelet.get("displayName")+"\n"+ + "> "+build.necklace.get("displayName")+"\n"+ + "> "+build.weapon.get("displayName")+" ["+build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]"; + copyTextToClipboard(text); + document.getElementById("share-button").textContent = "Copied!"; + } +} + diff --git a/js/builder.js b/js/builder.js index 1e51e31..249bf32 100644 --- a/js/builder.js +++ b/js/builder.js @@ -35,206 +35,6 @@ function parsePowdering(powder_info) { return [powdering, powder_info]; } -/* - * Populate fields based on url, and calculate build. - */ -function decodeBuild(url_tag) { - if (url_tag) { - //default values - let equipment = [null, null, null, null, null, null, null, null, null]; - let tomes = [null, null, null, null, null, null, null]; - let powdering = ["", "", "", "", ""]; - let info = url_tag.split("_"); - let version = info[0]; - let save_skp = false; - let skillpoints = [0, 0, 0, 0, 0]; - let level = 106; - - let version_number = parseInt(version) - //equipment (items) - // TODO: use filters - if (version_number < 4) { - let equipments = info[1]; - for (let i = 0; i < 9; ++i ) { - let equipment_str = equipments.slice(i*3,i*3+3); - equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); - } - info[1] = equipments.slice(27); - } - else if (version_number == 4) { - let info_str = info[1]; - let start_idx = 0; - for (let i = 0; i < 9; ++i ) { - if (info_str.charAt(start_idx) === "-") { - equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18); - start_idx += 18; - } - else { - let equipment_str = info_str.slice(start_idx, start_idx+3); - equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); - start_idx += 3; - } - } - info[1] = info_str.slice(start_idx); - } - else if (version_number <= 6) { - let info_str = info[1]; - let start_idx = 0; - for (let i = 0; i < 9; ++i ) { - if (info_str.slice(start_idx,start_idx+3) === "CR-") { - equipment[i] = info_str.slice(start_idx, start_idx+20); - start_idx += 20; - } else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") { - let len = Base64.toInt(info_str.slice(start_idx,start_idx+3)); - equipment[i] = info_str.slice(start_idx+3,start_idx+3+len); - start_idx += (3+len); - } else { - let equipment_str = info_str.slice(start_idx, start_idx+3); - equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); - start_idx += 3; - } - } - info[1] = info_str.slice(start_idx); - } - //constant in all versions - for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); - } - - //level, skill point assignments, and powdering - if (version_number == 1) { - let powder_info = info[1]; - let res = parsePowdering(powder_info); - powdering = res[0]; - } else if (version_number == 2) { - save_skp = true; - let skillpoint_info = info[1].slice(0, 10); - for (let i = 0; i < 5; ++i ) { - skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2)); - } - - let powder_info = info[1].slice(10); - let res = parsePowdering(powder_info); - powdering = res[0]; - } else if (version_number <= 6){ - level = Base64.toInt(info[1].slice(10,12)); - setValue("level-choice",level); - save_skp = true; - let skillpoint_info = info[1].slice(0, 10); - for (let i = 0; i < 5; ++i ) { - skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2)); - } - - let powder_info = info[1].slice(12); - - let res = parsePowdering(powder_info); - powdering = res[0]; - info[1] = res[1]; - } - // Tomes. - if (version == 6) { - //tome values do not appear in anything before v6. - for (let i = 0; i < 7; ++i) { - let tome_str = info[1].charAt(i); - for (let i in tomes) { - setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str))); - } - } - info[1] = info[1].slice(7); - } - - for (let i in powderInputs) { - setValue(powderInputs[i], powdering[i]); - } - } -} - -/* Stores the entire build in a string using B64 encoding and adds it to the URL. -*/ -function encodeBuild() { - - if (player_build) { - let build_string; - - //V6 encoding - Tomes - build_version = 4; - build_string = ""; - tome_string = ""; - - let crafted_idx = 0; - let custom_idx = 0; - for (const item of player_build.items) { - - if (item.get("custom")) { - let custom = "CI-"+encodeCustom(player_build.customItems[custom_idx],true); - build_string += Base64.fromIntN(custom.length, 3) + custom; - custom_idx += 1; - build_version = Math.max(build_version, 5); - } else if (item.get("crafted")) { - build_string += "CR-"+encodeCraft(player_build.craftedItems[crafted_idx]); - crafted_idx += 1; - } else if (item.get("category") === "tome") { - let tome_id = item.get("id"); - if (tome_id <= 60) { - // valid normal tome. ID 61-63 is for NONE tomes. - build_version = Math.max(build_version, 6); - } - tome_string += Base64.fromIntN(tome_id, 1); - } else { - build_string += Base64.fromIntN(item.get("id"), 3); - } - } - - for (const skp of skp_order) { - build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 - } - build_string += Base64.fromIntN(player_build.level, 2); - for (const _powderset of player_build.powders) { - let n_bits = Math.ceil(_powderset.length / 6); - build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. - // Slice copy. - let powderset = _powderset.slice(); - while (powderset.length != 0) { - let firstSix = powderset.slice(0,6).reverse(); - let powder_hash = 0; - for (const powder of firstSix) { - powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first. - } - build_string += Base64.fromIntN(powder_hash, 5); - powderset = powderset.slice(6); - } - } - build_string += tome_string; - - return build_version.toString() + "_" + build_string; - } -} - -function copyBuild() { - if (player_build) { - copyTextToClipboard(url_base+location.hash); - document.getElementById("copy-button").textContent = "Copied!"; - } -} - -function shareBuild() { - if (player_build) { - let text = url_base+location.hash+"\n"+ - "WynnBuilder build:\n"+ - "> "+player_build.helmet.get("displayName")+"\n"+ - "> "+player_build.chestplate.get("displayName")+"\n"+ - "> "+player_build.leggings.get("displayName")+"\n"+ - "> "+player_build.boots.get("displayName")+"\n"+ - "> "+player_build.ring1.get("displayName")+"\n"+ - "> "+player_build.ring2.get("displayName")+"\n"+ - "> "+player_build.bracelet.get("displayName")+"\n"+ - "> "+player_build.necklace.get("displayName")+"\n"+ - "> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]"; - copyTextToClipboard(text); - document.getElementById("share-button").textContent = "Copied!"; - } -} - function populateBuildList() { const buildList = document.getElementById("build-choice"); const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); @@ -250,7 +50,7 @@ function saveBuild() { if (player_build) { const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); const saveName = document.getElementById("build-name").value; - const encodedBuild = encodeBuild(); + const encodedBuild = encodeBuild(player_build); if ((!Object.keys(savedBuilds).includes(saveName) || document.getElementById("saved-error").textContent !== "") && encodedBuild !== "") { savedBuilds[saveName] = encodedBuild.replace("#", ""); @@ -330,6 +130,15 @@ function toggleButton(button_id) { } } +// toggle tab +function toggle_tab(tab) { + if (document.querySelector("#"+tab).style.display == "none") { + document.querySelector("#"+tab).style.display = ""; + } else { + document.querySelector("#"+tab).style.display = "none"; + } +} + // TODO: Learn and use await function init() { console.log("builder.js init"); diff --git a/js/builder_graph.js b/js/builder_graph.js index fd0de13..111cd92 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -1,5 +1,52 @@ +class BuildEncodeNode extends ComputeNode { + constructor() { + super("builder-encode"); + } + + compute_func(input_map) { + if (input_map.size !== 1) { throw "BuildEncodeNode accepts exactly one input (build)"; } + const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element + return encodeBuild(build); + } +} + +class URLUpdateNode extends ComputeNode { + constructor() { + super("builder-url-update"); + } + + 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; + } +} + +class BuildAssembleNode extends ComputeNode { + constructor() { + super("builder-make-build"); + } + + 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'), + input_map.get('necklace-input') + ]; + let weapon = input_map.get('weapon-input'); + let level = input_map.get('level-input'); + console.log('build node run'); + return new Build(level, equipments, [], weapon); + } +} + let item_nodes = []; document.addEventListener('DOMContentLoaded', function() { @@ -16,6 +63,14 @@ document.addEventListener('DOMContentLoaded', function() { } let weapon_image = document.getElementById("weapon-img"); new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); + let level_input = new InputNode('level-input', document.getElementById('level-choice')); + new PrintNode('lvl-debug').link_to(level_input); + + let build_node = new BuildAssembleNode(); + for (const input of item_nodes) { + build_node.link_to(input); + } + build_node.link_to(level_input); console.log("Set up graph"); }); @@ -98,6 +153,7 @@ function init_autocomplete() { if (event.detail.selection.value) { event.target.value = event.detail.selection.value; } + event.target.dispatchEvent(new Event('input')); }, }, } diff --git a/js/computation_graph.js b/js/computation_graph.js index 417c9bf..155d95a 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -6,14 +6,16 @@ class ComputeNode { * @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes). */ constructor(name) { - this.inputs = []; + this.inputs = []; // parent nodes this.children = []; this.value = 0; this.name = name; this.update_task = null; this.update_time = Date.now(); this.fail_cb = false; // Set to true to force updates even if parent failed. - this.calc_inputs = new Map(); + this.dirty = false; + this.inputs_dirty = new Map(); + this.inputs_dirty_count = 0; } /** @@ -24,7 +26,19 @@ class ComputeNode { return; } this.update_time = timestamp; - this.set_value(this.compute_func(this.calc_inputs)); + + if (this.inputs_dirty_count != 0) { + return; + } + let calc_inputs = new Map(); + for (const input of this.inputs) { + calc_inputs.set(input.name, input.value); + } + this.value = this.compute_func(calc_inputs); + this.dirty = false; + for (const child of this.children) { + child.mark_input_clean(this.name, this.value, timestamp); + } } /** @@ -40,22 +54,35 @@ class ComputeNode { } /** - * Set an input value. Propagates calculation if all inputs are present. + * Mark parent as not dirty. Propagates calculation if all inputs are present. */ - set_input(input_name, value, timestamp) { - if (value || this.fail_cb) { - this.calc_inputs.set(input_name, value) - if (this.calc_inputs.size === this.inputs.length) { - this.update(timestamp) + mark_input_clean(input_name, value, timestamp) { + if (value !== null || this.fail_cb) { + if (this.inputs_dirty.get(input_name)) { + this.inputs_dirty.set(input_name, false); + this.inputs_dirty_count -= 1; + } + if (this.inputs_dirty_count === 0) { + this.update(timestamp); } } } - /** - * Remove cached input values to this calculation. - */ - clear_cache() { - this.calc_inputs = new Map(); + mark_input_dirty(input_name) { + if (!this.inputs_dirty.get(input_name)) { + this.inputs_dirty.set(input_name, true); + this.inputs_dirty_count += 1; + } + } + + mark_dirty() { + if (!this.dirty) { + this.dirty = true; + for (const child of this.children) { + child.mark_input_dirty(this.name); + child.mark_dirty(); + } + } } /** @@ -74,6 +101,10 @@ class ComputeNode { link_to(parent_node) { this.inputs.push(parent_node) + this.inputs_dirty.set(parent_node.name, parent_node.dirty); + if (parent_node.dirty) { + this.inputs_dirty_count += 1; + } parent_node.children.push(this); } } @@ -87,6 +118,7 @@ function calcSchedule(node) { if (node.update_task !== null) { clearTimeout(node.update_task); } + node.mark_dirty(); node.update_task = setTimeout(function() { const timestamp = Date.now(); node.update(timestamp); @@ -107,10 +139,25 @@ class PrintNode extends ComputeNode { } } +/** + * Node for getting an input from an input field. + */ +class InputNode extends ComputeNode { + constructor(name, input_field) { + super(name); + this.input_field = input_field; + this.input_field.addEventListener("input", () => calcSchedule(this)); + } + + compute_func(input_map) { + return this.input_field.value; + } +} + /** * Node for getting an item's stats from an item input field. */ -class ItemInputNode extends ComputeNode { +class ItemInputNode extends InputNode { /** * Make an item stat pulling compute node. * @@ -119,9 +166,7 @@ class ItemInputNode extends ComputeNode { * @param none_item: Item object to use as the "none" for this field. */ constructor(name, item_input_field, none_item) { - super(name); - this.input_field = item_input_field; - this.input_field.addEventListener("input", () => calcSchedule(this)); + super(name, item_input_field); this.none_item = new Item(none_item); } From 8db34d68a75f6743308d19d928a756325c4426e8 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 09:49:04 -0700 Subject: [PATCH 025/155] Almost working spell damage calculation --- js/build.js | 45 +++++++-------- js/build_constants.js | 2 +- js/build_encode_decode.js | 26 ++++++++- js/builder.js | 38 +++++-------- js/builder_graph.js | 113 +++++++++++++++++++++++++++++++++++--- js/computation_graph.js | 29 +++++----- js/damage_calc.js | 19 +------ js/load.js | 21 +++++-- js/skillpoints.js | 9 +-- js/sq2display.js | 24 ++++---- 10 files changed, 212 insertions(+), 114 deletions(-) diff --git a/js/build.js b/js/build.js index 447162d..00300c1 100644 --- a/js/build.js +++ b/js/build.js @@ -127,7 +127,9 @@ class Build{ this.weapon = weapon; this.items = this.equipment.concat([this.weapon]).concat(this.tomes); // return [equip_order, best_skillpoints, final_skillpoints, best_total]; - let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon); + + // calc skillpoints requires statmaps only + let result = calculate_skillpoints(this.equipment.concat(this.tomes).map((x) => x.statMap), this.weapon.statMap); console.log(result); this.equip_order = result[0]; // How many skillpoints the player had to assign (5 number) @@ -165,8 +167,9 @@ class Build{ */ getMeleeStats(){ const stats = this.statMap; - if (this.weapon.get("tier") === "Crafted") { - stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]); + const weapon_stats = this.weapon.statMap; + if (weapon_stats.get("tier") === "Crafted") { + stats.set("damageBases", [weapon_stats.get("nDamBaseHigh"),weapon_stats.get("eDamBaseHigh"),weapon_stats.get("tDamBaseHigh"),weapon_stats.get("wDamBaseHigh"),weapon_stats.get("fDamBaseHigh"),weapon_stats.get("aDamBaseHigh")]); } let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier"); if(adjAtkSpd > 6){ @@ -176,13 +179,13 @@ class Build{ } let damage_mult = 1; - if (this.weapon.get("type") === "relik") { + 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") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats); + let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct"), 0, this.weapon.statMap, this.total_skillpoints, damage_mult * this.damageMultiplier); let dex = this.total_skillpoints[1]; @@ -217,8 +220,8 @@ class Build{ defenseStats.push(totalHp); //EHP let ehp = [totalHp, totalHp]; - let defMult = classDefenseMultipliers.get(this.weapon.get("type")); - ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); + let defMult = classDefenseMultipliers.get(this.weapon.statMap.get("type")); + ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); defenseStats.push(ehp); //HPR @@ -259,26 +262,27 @@ class Build{ let major_ids = new Set(); for (const item of this.items){ - for (let [id, value] of item.get("maxRolls")) { + const item_stats = item.statMap; + for (let [id, value] of item_stats.get("maxRolls")) { if (staticIDs.includes(id)) { continue; } statMap.set(id,(statMap.get(id) || 0)+value); } for (const staticID of staticIDs) { - if (item.get(staticID)) { - statMap.set(staticID, statMap.get(staticID) + item.get(staticID)); + if (item_stats.get(staticID)) { + statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID)); } } - if (item.get("majorIds")) { - for (const major_id of item.get("majorIds")) { + if (item_stats.get("majorIds")) { + for (const major_id of item_stats.get("majorIds")) { major_ids.add(major_id); } } } statMap.set("activeMajorIDs", major_ids); for (const [setName, count] of this.activeSetCounts) { - const bonus = sets[setName].bonuses[count-1]; + const bonus = sets.get(setName).bonuses[count-1]; for (const id in bonus) { if (skp_order.includes(id)) { // pass. Don't include skillpoints in ids @@ -291,16 +295,8 @@ class Build{ statMap.set("poisonPct", 100); // The stuff relevant for damage calculation!!! @ferricles - statMap.set("atkSpd", this.weapon.get("atkSpd")); + statMap.set("atkSpd", this.weapon.statMap.get("atkSpd")); - for (const x of skp_elements) { - this.externalStats.set(x + "DamPct", 0); - } - this.externalStats.set("mdPct", 0); - this.externalStats.set("sdPct", 0); - this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]); - this.externalStats.set("defBonus",[0, 0, 0, 0, 0]); - this.externalStats.set("poisonPct", 0); this.statMap = statMap; this.aggregateStats(); @@ -308,10 +304,11 @@ class Build{ aggregateStats() { let statMap = this.statMap; - statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]); + let weapon_stats = this.weapon.statMap; + statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]); statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]); statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]); statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]); - statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type"))); + statMap.set("defMult", classDefenseMultipliers.get(weapon_stats.get("type"))); } } diff --git a/js/build_constants.js b/js/build_constants.js index fbcbb3f..2983328 100644 --- a/js/build_constants.js +++ b/js/build_constants.js @@ -88,7 +88,7 @@ let equipmentInputs = equipment_fields.map(x => x + "-choice"); let buildFields = equipment_fields.map(x => x+"-tooltip").concat(tome_fields.map(x => x + "-tooltip")); let tomeInputs = tome_fields.map(x => x + "-choice"); -let powderInputs = [ +let powder_inputs = [ "helmet-powder", "chestplate-powder", "leggings-powder", diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 9b168b1..0929e4e 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -1,3 +1,25 @@ +function parsePowdering(powder_info) { + // TODO: Make this run in linear instead of quadratic time... ew + let powdering = []; + for (let i = 0; i < 5; ++i) { + let powders = ""; + let n_blocks = Base64.toInt(powder_info.charAt(0)); + // console.log(n_blocks + " blocks"); + powder_info = powder_info.slice(1); + for (let j = 0; j < n_blocks; ++j) { + let block = powder_info.slice(0,5); + console.log(block); + let six_powders = Base64.toInt(block); + for (let k = 0; k < 6 && six_powders != 0; ++k) { + powders += powderNames.get((six_powders & 0x1f) - 1); + six_powders >>>= 5; + } + powder_info = powder_info.slice(5); + } + powdering[i] = powders; + } + return [powdering, powder_info]; +} /* * Populate fields based on url, and calculate build. @@ -107,8 +129,8 @@ function decodeBuild(url_tag) { info[1] = info[1].slice(7); } - for (let i in powderInputs) { - setValue(powderInputs[i], powdering[i]); + for (let i in powder_inputs) { + setValue(powder_inputs[i], powdering[i]); } } } diff --git a/js/builder.js b/js/builder.js index 249bf32..fcdf850 100644 --- a/js/builder.js +++ b/js/builder.js @@ -12,29 +12,6 @@ function getTomeNameFromID(id) { return tomeIDMap.get(id); } -function parsePowdering(powder_info) { - // TODO: Make this run in linear instead of quadratic time... ew - let powdering = []; - for (let i = 0; i < 5; ++i) { - let powders = ""; - let n_blocks = Base64.toInt(powder_info.charAt(0)); - // console.log(n_blocks + " blocks"); - powder_info = powder_info.slice(1); - for (let j = 0; j < n_blocks; ++j) { - let block = powder_info.slice(0,5); - console.log(block); - let six_powders = Base64.toInt(block); - for (let k = 0; k < 6 && six_powders != 0; ++k) { - powders += powderNames.get((six_powders & 0x1f) - 1); - six_powders >>>= 5; - } - powder_info = powder_info.slice(5); - } - powdering[i] = powders; - } - return [powdering, powder_info]; -} - function populateBuildList() { const buildList = document.getElementById("build-choice"); const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); @@ -139,6 +116,21 @@ function toggle_tab(tab) { } } + +let tabs = ['overall-stats', 'offensive-stats', 'defensive-stats']; +function show_tab(tab) { + //console.log(itemFilters) + + //hide all tabs, then show the tab of the div clicked and highlight the correct button + for (const i in tabs) { + document.querySelector("#" + tabs[i]).style.display = "none"; + document.getElementById("tab-" + tabs[i].split("-")[0] + "-btn").classList.remove("selected-btn"); + } + document.querySelector("#" + tab).style.display = ""; + document.getElementById("tab-" + tab.split("-")[0] + "-btn").classList.add("selected-btn"); +} + + // TODO: Learn and use await function init() { console.log("builder.js init"); diff --git a/js/builder_graph.js b/js/builder_graph.js index 111cd92..c1d80d4 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -42,14 +42,71 @@ class BuildAssembleNode extends ComputeNode { ]; let weapon = input_map.get('weapon-input'); let level = input_map.get('level-input'); - console.log('build node run'); + + let all_none = weapon.statMap.has('NONE'); + for (const item of equipments) { + all_none = all_none && item.statMap.has('NONE'); + } + if (all_none) { + return null; + } return new Build(level, equipments, [], weapon); } } +class PowderInputNode extends InputNode { + + constructor(name, input_field) { + super(name, input_field); + } + + 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); + } + //console.log("POWDERING: " + powdering); + return powdering; + } +} + +class SpellDamageCalcNode extends ComputeNode { + constructor(spell_num) { + super("builder-spell"+spell_num+"-calc"); + this.spell_idx = spell_num; + } + + compute_func(input_map) { + // inputs: + let weapon = new Map(input_map.get('weapon-input').statMap); + let build = input_map.get('build'); + let weapon_powder = input_map.get('weapon-powder'); + weapon.set("powders", weapon_powder); + const i = this.spell_idx; + let spell = spell_table[weapon.get("type")][i]; + let parent_elem = document.getElementById("spell"+i+"-info"); + let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); + displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, i+1, weapon); + } +} + let item_nodes = []; +let powder_nodes = []; +let spell_nodes = []; document.addEventListener('DOMContentLoaded', function() { + // Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff). for (const [eq, none_item] of zip(equipment_fields, none_items)) { let input_field = document.getElementById(eq+"-choice"); let item_image = document.getElementById(eq+"-img"); @@ -57,28 +114,70 @@ document.addEventListener('DOMContentLoaded', function() { let item_input = new ItemInputNode(eq+'-input', input_field, none_item); item_nodes.push(item_input); new ItemInputDisplayNode(eq+'-display', input_field, item_image).link_to(item_input); - new PrintNode(eq+'-debug').link_to(item_input); + //new PrintNode(eq+'-debug').link_to(item_input); //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); - } + + // weapon image changer node. let weapon_image = document.getElementById("weapon-img"); new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); - let level_input = new InputNode('level-input', document.getElementById('level-choice')); - new PrintNode('lvl-debug').link_to(level_input); + // 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. let build_node = new BuildAssembleNode(); for (const input of item_nodes) { build_node.link_to(input); } build_node.link_to(level_input); + + + for (const input of powder_inputs) { + powder_nodes.push(new PowderInputNode(input, document.getElementById(input))); + } + + for (let i = 0; i < 4; ++i) { + let spell_node = new SpellDamageCalcNode(i); + spell_node.link_to(item_nodes[8], 'weapon-input'); + spell_node.link_to(build_node, 'build'); + spell_node.link_to(powder_nodes[4], 'weapon-powder'); + spell_nodes.push(spell_node); + } + console.log("Set up graph"); + let masonry = Macy({ + container: "#masonry-container", + columns: 1, + mobileFirst: true, + breakAt: { + 1200: 4, + }, + margin: { + x: 20, + y: 20, + } + + }); + + let search_masonry = Macy({ + container: "#search-results", + columns: 1, + mobileFirst: true, + breakAt: { + 1200: 4, + }, + margin: { + x: 20, + y: 20, + } + + }); }); // autocomplete initialize function init_autocomplete() { - console.log("autocomplete init"); - console.log(itemLists) let dropdowns = new Map(); for (const eq of equipment_keys) { if (tome_keys.includes(eq)) { diff --git a/js/computation_graph.js b/js/computation_graph.js index 155d95a..45ba267 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -7,13 +7,14 @@ class ComputeNode { */ constructor(name) { this.inputs = []; // parent nodes + this.input_translation = new Map(); this.children = []; - this.value = 0; + this.value = null; this.name = name; this.update_task = null; this.update_time = Date.now(); this.fail_cb = false; // Set to true to force updates even if parent failed. - this.dirty = false; + this.dirty = true; this.inputs_dirty = new Map(); this.inputs_dirty_count = 0; } @@ -32,7 +33,7 @@ class ComputeNode { } let calc_inputs = new Map(); for (const input of this.inputs) { - calc_inputs.set(input.name, input.value); + calc_inputs.set(this.input_translation.get(input.name), input.value); } this.value = this.compute_func(calc_inputs); this.dirty = false; @@ -41,18 +42,6 @@ class ComputeNode { } } - /** - * Set this node's value directly. Notifies children. - */ - set_value(value) { - let timestamp = Date.now(); - this.update_time = timestamp; - this.value = value; - for (const child of this.children) { - child.set_input(this.name, this.value, timestamp); - } - } - /** * Mark parent as not dirty. Propagates calculation if all inputs are present. */ @@ -99,8 +88,10 @@ class ComputeNode { throw "no compute func specified"; } - link_to(parent_node) { + link_to(parent_node, link_name) { this.inputs.push(parent_node) + link_name = (link_name !== undefined) ? link_name : parent_node.name; + this.input_translation.set(parent_node.name, link_name); this.inputs_dirty.set(parent_node.name, parent_node.dirty); if (parent_node.dirty) { this.inputs_dirty_count += 1; @@ -147,6 +138,7 @@ class InputNode extends ComputeNode { super(name); this.input_field = input_field; this.input_field.addEventListener("input", () => calcSchedule(this)); + calcSchedule(this); } compute_func(input_map) { @@ -168,6 +160,7 @@ class ItemInputNode extends InputNode { 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); } compute_func(input_map) { @@ -231,9 +224,13 @@ class ItemInputDisplayNode extends ComputeNode { return null; } + if (item.statMap.has('NONE')) { + return null; + } const tier = item.statMap.get('tier'); this.input_field.classList.add(tier); this.image.classList.add(tier + "-shadow"); + return null; } } diff --git a/js/damage_calc.js b/js/damage_calc.js index 564ab2e..6aa7c64 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -1,29 +1,12 @@ const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]); // Calculate spell damage given a spell elemental conversion table, and a spell multiplier. // If spell mult is 0, its melee damage and we don't multiply by attack speed. -// externalStats should be a map -function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier, externalStats) { +function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) { let buildStats = new Map(stats); let tooltipinfo = new Map(); //6x for damages, normal min normal max crit min crit max let damageformulas = [["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "]]; - if(externalStats) { //if nothing is passed in, then this hopefully won't trigger - for (const entry of externalStats) { - const key = entry[0]; - const value = entry[1]; - if (typeof value === "number") { - buildStats.set(key, buildStats.get(key) + value); - } else if (Array.isArray(value)) { - arr = []; - for (let j = 0; j < value.length; j++) { - arr[j] = buildStats.get(key)[j] + value[j]; - } - buildStats.set(key, arr); - } - } - } - let powders = weapon.get("powders").slice(); // Array of neutral + ewtfa damages. Each entry is a pair (min, max). diff --git a/js/load.js b/js/load.js index 4f6e4b2..521765e 100644 --- a/js/load.js +++ b/js/load.js @@ -6,7 +6,7 @@ let reload = false; let load_complete = false; let load_in_progress = false; let items; -let sets; +let sets = new Map(); let itemMap; let idMap; let redirectMap; @@ -20,7 +20,6 @@ async function load_local() { let sets_store = get_tx.objectStore('set_db'); let get_store = get_tx.objectStore('item_db'); let request = get_store.getAll(); - let request2 = sets_store.getAll(); request.onerror = function(event) { reject("Could not read local item db..."); } @@ -28,15 +27,27 @@ async function load_local() { console.log("Successfully read local item db."); } + // key-value iteration (hpp don't break this again) + // https://stackoverflow.com/questions/47931595/indexeddb-getting-all-data-with-keys + let request2 = sets_store.openCursor(); request2.onerror = function(event) { reject("Could not read local set db..."); } request2.onsuccess = function(event) { - console.log("Successfully read local set db."); - } + let cursor = event.target.result; + if (cursor) { + let key = cursor.primaryKey; + let value = cursor.value; + sets.set(key, value); + cursor.continue(); + } + else { + // no more results + console.log("Successfully read local set db."); + } + }; get_tx.oncomplete = function(event) { items = request.result; - sets = request2.result; init_maps(); load_complete = true; db.close(); diff --git a/js/skillpoints.js b/js/skillpoints.js index 12c8805..fcf24b6 100644 --- a/js/skillpoints.js +++ b/js/skillpoints.js @@ -31,14 +31,15 @@ function calculate_skillpoints(equipment, weapon) { let setCount = activeSetCounts.get(setName); let old_bonus = {}; if (setCount) { - old_bonus = sets[setName].bonuses[setCount-1]; + old_bonus = sets.get(setName).bonuses[setCount-1]; activeSetCounts.set(setName, setCount + 1); } else { setCount = 0; activeSetCounts.set(setName, 1); } - const new_bonus = sets[setName].bonuses[setCount]; + console.log(sets); + const new_bonus = sets.get(setName).bonuses[setCount]; //let skp_order = ["str","dex","int","def","agi"]; for (const i in skp_order) { const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0); @@ -74,8 +75,8 @@ function calculate_skillpoints(equipment, weapon) { if (setName) { // undefined/null means no set. const setCount = activeSetCounts.get(setName); if (setCount) { - const old_bonus = sets[setName].bonuses[setCount-1]; - const new_bonus = sets[setName].bonuses[setCount]; + const old_bonus = sets.get(setName).bonuses[setCount-1]; + const new_bonus = sets.get(setName).bonuses[setCount]; //let skp_order = ["str","dex","int","def","agi"]; for (const i in skp_order) { const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0); diff --git a/js/sq2display.js b/js/sq2display.js index b6b588b..195aea5 100644 --- a/js/sq2display.js +++ b/js/sq2display.js @@ -41,7 +41,7 @@ function displaysq2BuildStats(parent_id,build,command_group){ // id instruction else { let id = command; - if (stats.get(id) || build.externalStats.get(id)) { + if (stats.get(id)) { let style = null; // TODO: add pos and neg style @@ -54,10 +54,6 @@ function displaysq2BuildStats(parent_id,build,command_group){ // ignore let id_val = stats.get(id); - if (build.externalStats.has(id)) { - id_val += build.externalStats.get(id); - } - if (reversedIDs.includes(id)) { style === "positive" ? style = "negative" : style = "positive"; } @@ -650,7 +646,7 @@ function displaysq2PoisonDamage(overallparent_elem, build) { let overallpoisonDamage = document.createElement("p"); let overallpoisonDamageFirst = document.createElement("span"); let overallpoisonDamageSecond = document.createElement("span"); - let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct") + build.externalStats.get("poisonPct"))/100 /3); + let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct"))/100 /3); overallpoisonDamageFirst.textContent = "Poison Tick: "; overallpoisonDamageSecond.textContent = Math.max(poison_tick,0); overallpoisonDamageSecond.classList.add("Damage"); @@ -1126,8 +1122,8 @@ function displaysq2PowderSpecials(parent_elem, powderSpecials, build, overall=fa let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]); let part = spell["parts"][0]; let _results = calculateSpellDamage(stats, part.conversion, - stats.get("mdRaw"), stats.get("mdPct") + build.externalStats.get("mdPct"), - 0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)), build.externalStats);//part.multiplier[power] / 100 + stats.get("mdRaw"), stats.get("mdPct"), + 0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100 let critChance = skillPointsToPercentage(build.total_skillpoints[1]); let save_damages = []; @@ -1215,7 +1211,7 @@ function displaysq2PowderSpecials(parent_elem, powderSpecials, build, overall=fa } } -function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx) { +function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx, weapon) { parent_elem.textContent = ""; @@ -1264,7 +1260,7 @@ function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, sp parent_elem.append(title_elem); overallparent_elem.append(title_elemavg); - overallparent_elem.append(displaysq2NextCosts(spell, build)); + overallparent_elem.append(displaysq2NextCosts(spell, build, weapon)); let critChance = skillPointsToPercentage(build.total_skillpoints[1]); @@ -1300,8 +1296,8 @@ function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, sp if (part.type === "damage") { //console.log(build.expandedStats); let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct") + build.externalStats.get("sdPct"), - part.multiplier / 100, build.weapon, build.total_skillpoints, build.damageMultiplier, build.externalStats); + stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct"), + part.multiplier / 100, weapon, build.total_skillpoints, build.damageMultiplier); let totalDamNormal = _results[0]; let totalDamCrit = _results[1]; let results = _results[2]; @@ -1427,9 +1423,9 @@ function displaysq2EquipOrder(parent_elem, buildOrder){ } } -function displaysq2NextCosts(spell, build) { +function displaysq2NextCosts(spell, build, weapon) { let int = build.total_skillpoints[2]; - let spells = spell_table[build.weapon.get("type")]; + let spells = spell_table[weapon.get("type")]; let row = document.createElement("div"); row.classList.add("spellcost-tooltip"); From e5b653619f29f818ed2be314cbb11f7767c2227b Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 11:02:28 -0700 Subject: [PATCH 026/155] Working spell damage calculation pipeline broke like everything else --- builder/index.html | 1 - js/build_utils.js | 65 +++++++++++ js/builder_graph.js | 98 ++++++++++++++-- js/damage_calc.js | 29 +---- js/display.js | 278 +++++++++++--------------------------------- 5 files changed, 219 insertions(+), 252 deletions(-) diff --git a/builder/index.html b/builder/index.html index d9c3e8f..eaa3bc2 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1390,7 +1390,6 @@ - diff --git a/js/build_utils.js b/js/build_utils.js index 9381035..4da3309 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -196,3 +196,68 @@ class Item { this.statMap = expandItem(item_obj); } } + +/* Takes in an ingredient object and returns an equivalent Map(). +*/ +function expandIngredient(ing) { + let expandedIng = new Map(); + let mapIds = ['consumableIDs', 'itemIDs', 'posMods']; + for (const id of mapIds) { + let idMap = new Map(); + for (const key of Object.keys(ing[id])) { + idMap.set(key, ing[id][key]); + } + expandedIng.set(id, idMap); + } + let normIds = ['lvl','name', 'displayName','tier','skills','id']; + for (const id of normIds) { + expandedIng.set(id, ing[id]); + } + if (ing['isPowder']) { + expandedIng.set("isPowder",ing['isPowder']); + expandedIng.set("pid",ing['pid']); + } + //now the actually hard one + let idMap = new Map(); + idMap.set("minRolls", new Map()); + idMap.set("maxRolls", new Map()); + for (const field of ingFields) { + let val = (ing['ids'][field] || 0); + idMap.get("minRolls").set(field, val['minimum']); + idMap.get("maxRolls").set(field, val['maximum']); + } + expandedIng.set("ids",idMap); + return expandedIng; +} + +/* Takes in a recipe object and returns an equivalent Map(). +*/ +function expandRecipe(recipe) { + let expandedRecipe = new Map(); + let normIDs = ["name", "skill", "type","id"]; + for (const id of normIDs) { + expandedRecipe.set(id,recipe[id]); + } + let rangeIDs = ["durability","lvl", "healthOrDamage", "duration", "basicDuration"]; + for (const id of rangeIDs) { + if(recipe[id]){ + expandedRecipe.set(id, [recipe[id]['minimum'], recipe[id]['maximum']]); + } else { + expandedRecipe.set(id, [0,0]); + } + } + expandedRecipe.set("materials", [ new Map([ ["item", recipe['materials'][0]['item']], ["amount", recipe['materials'][0]['amount']] ]) , new Map([ ["item", recipe['materials'][1]['item']], ["amount",recipe['materials'][1]['amount'] ] ]) ]); + return expandedRecipe; +} + +/*An independent helper function that rounds a rolled ID to the nearest integer OR brings the roll away from 0. +* @param id +*/ +function idRound(id){ + rounded = Math.round(id); + if(rounded == 0){ + return 1; //this is a hack, will need changing along w/ rest of ID system if anything changes + }else{ + return rounded; + } +} diff --git a/js/builder_graph.js b/js/builder_graph.js index c1d80d4..d4a8eb5 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -81,6 +81,36 @@ class PowderInputNode extends InputNode { } } +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]; + } +} + class SpellDamageCalcNode extends ComputeNode { constructor(spell_num) { super("builder-spell"+spell_num+"-calc"); @@ -88,22 +118,59 @@ class SpellDamageCalcNode extends ComputeNode { } compute_func(input_map) { - // inputs: - let weapon = new Map(input_map.get('weapon-input').statMap); - let build = input_map.get('build'); - let weapon_powder = input_map.get('weapon-powder'); + const weapon = new Map(input_map.get('weapon-input').statMap); + const build = input_map.get('build'); + const weapon_powder = input_map.get('weapon-powder'); + const damage_mult = 1; // TODO: hook up + const spell_info = input_map.get('spell-info'); + const spell_parts = spell_info[1]; + weapon.set("powders", weapon_powder); + let spell_results = [] + let stats = build.statMap; + + 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"), + part.multiplier / 100, weapon, build.total_skillpoints, damage_mult); + spell_results.push(results); + } else if (part.type === "heal") { + // TODO: wynn2 formula + let heal_amount = (part.strength * build.getDefenseStats()[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2); + spell_results.push(heal_amount); + } else if (part.type === "total") { + // TODO: remove "total" type + spell_results.push(null); + } + } + return spell_results; + } +} + +class SpellDisplayNode extends ComputeNode { + constructor(spell_num) { + super("builder-spell"+spell_num+"-display"); + this.spell_idx = spell_num; + } + + compute_func(input_map) { + const build = input_map.get('build'); + 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]; + const i = this.spell_idx; - let spell = spell_table[weapon.get("type")][i]; let parent_elem = document.getElementById("spell"+i+"-info"); let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, i+1, weapon); + displaySpellDamage(parent_elem, overallparent_elem, build, spell, i+1, spell_parts, damages); } } let item_nodes = []; let powder_nodes = []; -let spell_nodes = []; +let spelldmg_nodes = []; document.addEventListener('DOMContentLoaded', function() { // Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff). @@ -138,11 +205,20 @@ document.addEventListener('DOMContentLoaded', function() { } for (let i = 0; i < 4; ++i) { - let spell_node = new SpellDamageCalcNode(i); - spell_node.link_to(item_nodes[8], 'weapon-input'); + let spell_node = new SpellSelectNode(i); spell_node.link_to(build_node, 'build'); - spell_node.link_to(powder_nodes[4], 'weapon-powder'); - spell_nodes.push(spell_node); + + let calc_node = new SpellDamageCalcNode(i); + calc_node.link_to(item_nodes[8], 'weapon-input'); + calc_node.link_to(build_node, 'build'); + calc_node.link_to(powder_nodes[4], 'weapon-powder'); + calc_node.link_to(spell_node, 'spell-info'); + spelldmg_nodes.push(calc_node); + + let display_node = new SpellDisplayNode(i); + display_node.link_to(build_node, 'build'); + display_node.link_to(spell_node, 'spell-info'); + display_node.link_to(calc_node, 'spell-damage'); } console.log("Set up graph"); diff --git a/js/damage_calc.js b/js/damage_calc.js index 6aa7c64..9149a13 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -3,9 +3,7 @@ const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["va // If spell mult is 0, its melee damage and we don't multiply by attack speed. function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) { let buildStats = new Map(stats); - let tooltipinfo = new Map(); //6x for damages, normal min normal max crit min crit max - let damageformulas = [["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "]]; let powders = weapon.get("powders").slice(); @@ -75,28 +73,22 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, } - - //console.log(tooltipinfo); damages[0] = neutralRemainingRaw; - tooltipinfo.set("damageBases", damages); let damageMult = damageMultiplier; let melee = false; // If we are doing melee calculations: - tooltipinfo.set("dmgMult", damageMult); if (spellMultiplier == 0) { spellMultiplier = 1; melee = true; } else { - tooltipinfo.set("dmgMult", `(${tooltipinfo.get("dmgMult")} * ${spellMultiplier} * ${baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]})`) damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]; } //console.log(damages); //console.log(damageMult); - tooltipinfo.set("rawModifier", `(${rawModifier} * ${spellMultiplier} * ${damageMultiplier})`); rawModifier *= spellMultiplier * damageMultiplier; let totalDamNorm = [0, 0]; let totalDamCrit = [0, 0]; @@ -109,33 +101,21 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, let baseDamCrit = rawModifier * (1 + strBoost); totalDamNorm = [baseDam, baseDam]; totalDamCrit = [baseDamCrit, baseDamCrit]; - for (let arr of damageformulas) { - arr = arr.map(x => x + " + " +tooltipinfo.get("rawModifier")); - } } let staticBoost = (pctModifier / 100.); - tooltipinfo.set("staticBoost", `${(pctModifier/ 100.).toFixed(2)}`); - tooltipinfo.set("skillBoost",["","","","","",""]); let skillBoost = [0]; for (let i in total_skillpoints) { skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get("damageBonus")[i] / 100.); - tooltipinfo.get("skillBoost")[parseInt(i,10)+1] = `(${skillPointsToPercentage(total_skillpoints[i]).toFixed(2)} + ${(buildStats.get("damageBonus")[i]/100.).toFixed(2)})` } - tooltipinfo.get("skillBoost")[0] = undefined; for (let i in damages) { let damageBoost = 1 + skillBoost[i] + staticBoost; - tooltipinfo.set("damageBoost", `(1 + ${(tooltipinfo.get("skillBoost")[i] ? tooltipinfo.get("skillBoost")[i] + " + " : "")} ${tooltipinfo.get("staticBoost")})`) damages_results.push([ Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max ]); - damageformulas[i][0] += `(max((${tooltipinfo.get("damageBases")[i][0]} * ${strBoost} * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))` - damageformulas[i][1] += `(max((${tooltipinfo.get("damageBases")[i][1]} * ${strBoost} * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))` - damageformulas[i][2] += `(max((${tooltipinfo.get("damageBases")[i][0]} * ${strBoost} * 2 * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))` - damageformulas[i][3] += `(max((${tooltipinfo.get("damageBases")[i][1]} * ${strBoost} * 2 * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))` totalDamNorm[0] += damages_results[i][0]; totalDamNorm[1] += damages_results[i][1]; totalDamCrit[0] += damages_results[i][2]; @@ -151,20 +131,13 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, damages_results[0][1] += strBoost*rawModifier; damages_results[0][2] += (strBoost + 1)*rawModifier; damages_results[0][3] += (strBoost + 1)*rawModifier; - for (let i = 0; i < 2; i++) { - damageformulas[0][i] += ` + (${strBoost} * ${tooltipinfo.get("rawModifier")})` - } - for (let i = 2; i < 4; i++) { - damageformulas[0][i] += ` + (2 * ${strBoost} * ${tooltipinfo.get("rawModifier")})` - } if (totalDamNorm[0] < 0) totalDamNorm[0] = 0; if (totalDamNorm[1] < 0) totalDamNorm[1] = 0; if (totalDamCrit[0] < 0) totalDamCrit[0] = 0; if (totalDamCrit[1] < 0) totalDamCrit[1] = 0; - tooltipinfo.set("damageformulas", damageformulas); - return [totalDamNorm, totalDamCrit, damages_results, tooltipinfo]; + return [totalDamNorm, totalDamCrit, damages_results]; } diff --git a/js/display.js b/js/display.js index 707efe3..c2ca48b 100644 --- a/js/display.js +++ b/js/display.js @@ -27,72 +27,6 @@ function applyArmorPowdersOnce(expandedItem, powders) { } } - -/* Takes in an ingredient object and returns an equivalent Map(). -*/ -function expandIngredient(ing) { - let expandedIng = new Map(); - let mapIds = ['consumableIDs', 'itemIDs', 'posMods']; - for (const id of mapIds) { - let idMap = new Map(); - for (const key of Object.keys(ing[id])) { - idMap.set(key, ing[id][key]); - } - expandedIng.set(id, idMap); - } - let normIds = ['lvl','name', 'displayName','tier','skills','id']; - for (const id of normIds) { - expandedIng.set(id, ing[id]); - } - if (ing['isPowder']) { - expandedIng.set("isPowder",ing['isPowder']); - expandedIng.set("pid",ing['pid']); - } - //now the actually hard one - let idMap = new Map(); - idMap.set("minRolls", new Map()); - idMap.set("maxRolls", new Map()); - for (const field of ingFields) { - let val = (ing['ids'][field] || 0); - idMap.get("minRolls").set(field, val['minimum']); - idMap.get("maxRolls").set(field, val['maximum']); - } - expandedIng.set("ids",idMap); - return expandedIng; -} - -/* Takes in a recipe object and returns an equivalent Map(). -*/ -function expandRecipe(recipe) { - let expandedRecipe = new Map(); - let normIDs = ["name", "skill", "type","id"]; - for (const id of normIDs) { - expandedRecipe.set(id,recipe[id]); - } - let rangeIDs = ["durability","lvl", "healthOrDamage", "duration", "basicDuration"]; - for (const id of rangeIDs) { - if(recipe[id]){ - expandedRecipe.set(id, [recipe[id]['minimum'], recipe[id]['maximum']]); - } else { - expandedRecipe.set(id, [0,0]); - } - } - expandedRecipe.set("materials", [ new Map([ ["item", recipe['materials'][0]['item']], ["amount", recipe['materials'][0]['amount']] ]) , new Map([ ["item", recipe['materials'][1]['item']], ["amount",recipe['materials'][1]['amount'] ] ]) ]); - return expandedRecipe; -} - -/*An independent helper function that rounds a rolled ID to the nearest integer OR brings the roll away from 0. -* @param id -*/ -function idRound(id){ - rounded = Math.round(id); - if(rounded == 0){ - return 1; //this is a hack, will need changing along w/ rest of ID system if anything changes - }else{ - return rounded; - } -} - function apply_elemental_format(p_elem, id, suffix) { suffix = (typeof suffix !== 'undefined') ? suffix : ""; // THIS IS SO JANK BUT IM TOO LAZY TO FIX IT TODO @@ -998,78 +932,54 @@ function displayExpandedIngredient(ingred, parent_id) { } } -function displayNextCosts(parent_id, build) { - let p_elem = document.getElementById(parent_id); +function displayNextCosts(spell, build, weapon) { let int = build.total_skillpoints[2]; - let spells = spell_table[build.weapon.get("type")]; + let spells = spell_table[weapon.get("type")]; - p_elem.textContent = ""; - - let title = document.createElement("p"); - title.classList.add("title"); - title.classList.add("Normal"); - title.textContent = "Next Spell Costs"; - - let int_title = document.createElement("p"); - int_title.classList.add("itemp"); - int_title.textContent = int + " Intelligence points."; - - p_elem.append(title); - p_elem.append(int_title); - - for (const spell of spells) { - let spellp = document.createElement("p"); - let spelltitle = document.createElement("p"); - spelltitle.classList.add("itemp"); - spelltitle.textContent = spell.title; - spellp.appendChild(spelltitle); - let row = document.createElement("p"); - row.classList.add("itemp"); - let init_cost = document.createElement("b"); - init_cost.textContent = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost); - init_cost.classList.add("Mana"); - let arrow = document.createElement("b"); - arrow.textContent = "\u279C"; - let next_cost = document.createElement("b"); - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1); - next_cost.classList.add("Mana"); - let int_needed = document.createElement("b"); - if (init_cost.textContent === "1") { - int_needed.textContent = ": n/a (+0)"; - }else { //do math - let target = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1; - let needed = int; - let noUpdate = false; - //forgive me... I couldn't inverse ceil, floor, and max. - while (build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) > target) { - if(needed > 150) { - noUpdate = true; - break; - } - needed++; - build.total_skillpoints[2] = needed; + let row = document.createElement("div"); + row.classList.add("spellcost-tooltip"); + let init_cost = document.createElement("b"); + init_cost.textContent = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost); + init_cost.classList.add("Mana"); + let arrow = document.createElement("b"); + arrow.textContent = "\u279C"; + let next_cost = document.createElement("b"); + next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1); + next_cost.classList.add("Mana"); + let int_needed = document.createElement("b"); + if (init_cost.textContent === "1") { + int_needed.textContent = ": n/a (+0)"; + }else { //do math + let target = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1; + let needed = int; + let noUpdate = false; + //forgive me... I couldn't inverse ceil, floor, and max. + while (build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) > target) { + if(needed > 150) { + noUpdate = true; + break; } - let missing = needed - int; - //in rare circumstances, the next spell cost can jump. - if (noUpdate) { - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)-1); - }else { - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)); - } - - - build.total_skillpoints[2] = int;//forgive me pt 2 - int_needed.textContent = ": " + (needed > 150 ? ">150" : needed) + " int (+" + (needed > 150 ? "n/a" : missing) + ")"; + needed++; + build.total_skillpoints[2] = needed; + } + let missing = needed - int; + //in rare circumstances, the next spell cost can jump. + if (noUpdate) { + next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)-1); + }else { + next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)); } - row.appendChild(init_cost); - row.appendChild(arrow); - row.appendChild(next_cost); - row.appendChild(int_needed); - spellp.appendChild(row); - - p_elem.append(spellp); + + build.total_skillpoints[2] = int;//forgive me pt 2 + int_needed.textContent = ": " + (needed > 150 ? ">150" : needed) + " int (+" + (needed > 150 ? "n/a" : missing) + ")"; } + + // row.appendChild(init_cost); + row.appendChild(arrow); + row.appendChild(next_cost); + row.appendChild(int_needed); + return row; } function displayRolledID(item, id, elemental_format) { @@ -1733,31 +1643,26 @@ function displayPowderSpecials(parent_elem, powderSpecials, build) { } } -function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx) { +function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx, spell_parts, damages) { + // TODO: remove spellIdx (just used to flag melee and cost) + // TODO: move cost calc out parent_elem.textContent = ""; - - let tooltip; let tooltiptext; const stats = build.statMap; let title_elem = document.createElement("p"); - title_elem.classList.add("smalltitle"); - title_elem.classList.add("Normal"); overallparent_elem.textContent = ""; - let title_elemavg = document.createElement("p"); - title_elemavg.classList.add('smalltitle'); - title_elemavg.classList.add('Normal'); + let title_elemavg = document.createElement("b"); if (spellIdx != 0) { - let first = document.createElement("b"); + let first = document.createElement("span"); first.textContent = spell.title + " ("; title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here. title_elemavg.appendChild(first); - let second = document.createElement("b"); + let second = document.createElement("span"); second.textContent = build.getSpellCost(spellIdx, spell.cost); second.classList.add("Mana"); - second.classList.add("tooltip"); let int_redux = skillPointsToPercentage(build.total_skillpoints[2]).toFixed(2); let spPct_redux = (build.statMap.get("spPct" + spellIdx)/100).toFixed(2); @@ -1765,17 +1670,14 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell spPct_redux >= 0 ? spPct_redux = "+ " + spPct_redux : spPct_redux = "- " + Math.abs(spPct_redux); spRaw_redux >= 0 ? spRaw_redux = "+ " + spRaw_redux : spRaw_redux = "- " + Math.abs(spRaw_redux); - tooltiptext = `= max(1, floor((ceil(${spell.cost} * (1 - ${int_redux})) ${spRaw_redux}) * (1 ${spPct_redux})))`; - tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spellcostcalc"]); - second.appendChild(tooltip); title_elem.appendChild(second.cloneNode(true)); title_elemavg.appendChild(second); - let third = document.createElement("b"); + let third = document.createElement("span"); third.textContent = ") [Base: " + build.getBaseSpellCost(spellIdx, spell.cost) + " ]"; title_elem.appendChild(third); - let third_summary = document.createElement("b"); + let third_summary = document.createElement("span"); third_summary.textContent = ")"; title_elemavg.appendChild(third_summary); } @@ -1787,48 +1689,33 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell parent_elem.append(title_elem); overallparent_elem.append(title_elemavg); + overallparent_elem.append(displayNextCosts(spell, build, build.weapon.statMap)); + + let critChance = skillPointsToPercentage(build.total_skillpoints[1]); let save_damages = []; let part_divavg = document.createElement("p"); - part_divavg.classList.add("lessbottom"); overallparent_elem.append(part_divavg); - 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; - } - } - } - //console.log(spell_parts); + for (let i = 0; i < spell_parts.length; ++i) { + const part = spell_parts[i]; + const damage = damages[i]; - for (const part of spell_parts) { - parent_elem.append(document.createElement("br")); let part_div = document.createElement("p"); parent_elem.append(part_div); let subtitle_elem = document.createElement("p"); subtitle_elem.textContent = part.subtitle; - subtitle_elem.classList.add("nomargin"); part_div.append(subtitle_elem); if (part.type === "damage") { //console.log(build.expandedStats); - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct") + build.externalStats.get("sdPct"), - part.multiplier / 100, build.weapon, build.total_skillpoints, build.damageMultiplier, build.externalStats); + let _results = damage; let totalDamNormal = _results[0]; let totalDamCrit = _results[1]; let results = _results[2]; - let tooltipinfo = _results[3]; for (let i = 0; i < 6; ++i) { for (let j in results[i]) { @@ -1841,30 +1728,25 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell let averageLabel = document.createElement("p"); averageLabel.textContent = "Average: "+averageDamage.toFixed(2); - tooltiptext = ` = ((1 - ${critChance}) * ${nonCritAverage.toFixed(2)}) + (${critChance} * ${critAverage.toFixed(2)})` - averageLabel.classList.add("damageSubtitle"); - tooltip = createTooltip(tooltip, "p", tooltiptext, averageLabel, ["spell-tooltip"]); + // averageLabel.classList.add("damageSubtitle"); part_div.append(averageLabel); if (part.summary == true) { let overallaverageLabel = document.createElement("p"); - let first = document.createElement("b"); - let second = document.createElement("b"); + let first = document.createElement("span"); + let second = document.createElement("span"); first.textContent = part.subtitle + " Average: "; second.textContent = averageDamage.toFixed(2); overallaverageLabel.appendChild(first); overallaverageLabel.appendChild(second); - tooltip = createTooltip(tooltip, "p", tooltiptext, overallaverageLabel, ["spell-tooltip", "summary-tooltip"]); second.classList.add("Damage"); - overallaverageLabel.classList.add("itemp"); part_divavg.append(overallaverageLabel); } function _damage_display(label_text, average, result_idx) { let label = document.createElement("p"); label.textContent = label_text+average.toFixed(2); - label.classList.add("damageSubtitle"); part_div.append(label); let arrmin = []; @@ -1872,84 +1754,56 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell for (let i = 0; i < 6; i++){ if (results[i][1] != 0){ let p = document.createElement("p"); - p.classList.add("damagep"); p.classList.add(damageClasses[i]); p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1]; - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(0,2).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, p, ["spell-tooltip"]); arrmin.push(results[i][result_idx]); arrmax.push(results[i][result_idx + 1]); part_div.append(p); } } - tooltiptext = ` = ((${arrmin.join(" + ")}) + (${arrmax.join(" + ")})) / 2`; - tooltip = createTooltip(tooltip, "p", tooltiptext, label, ["spell-tooltip"]); } _damage_display("Non-Crit Average: ", nonCritAverage, 0); _damage_display("Crit Average: ", critAverage, 2); save_damages.push(averageDamage); } else if (part.type === "heal") { - let heal_amount = (part.strength * build.getDefenseStats()[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2); - tooltiptext = ` = ${part.strength} * ${build.getDefenseStats()[0]} * max(0.5, min(1.75, 1 + 0.5 * ${stats.get("wDamPct")/100}))`; + let heal_amount = damage; let healLabel = document.createElement("p"); healLabel.textContent = heal_amount; - healLabel.classList.add("damagep"); - tooltip = createTooltip(tooltip, "p", tooltiptext, healLabel, ["spell-tooltip"]); + // healLabel.classList.add("damagep"); part_div.append(healLabel); if (part.summary == true) { let overallhealLabel = document.createElement("p"); - let first = document.createElement("b"); - let second = document.createElement("b"); + let first = document.createElement("span"); + let second = document.createElement("span"); first.textContent = part.subtitle + ": "; second.textContent = heal_amount; overallhealLabel.appendChild(first); second.classList.add("Set"); overallhealLabel.appendChild(second); - overallhealLabel.classList.add("itemp"); - tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spell-tooltip"]); part_divavg.append(overallhealLabel); - - let effectiveHealLabel = document.createElement("p"); - first = document.createElement("b"); - second = document.createElement("b"); - let defStats = build.getDefenseStats(); - tooltiptext = ` = ${heal_amount} * ${defStats[1][0].toFixed(2)} / ${defStats[0]}`; - first.textContent = "Effective Heal: "; - second.textContent = (defStats[1][0]*heal_amount/defStats[0]).toFixed(2); - effectiveHealLabel.appendChild(first); - second.classList.add("Set"); - effectiveHealLabel.appendChild(second); - effectiveHealLabel.classList.add("itemp"); - tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spell-tooltip"]); - part_divavg.append(effectiveHealLabel); } } else if (part.type === "total") { let total_damage = 0; - tooltiptext = ""; for (let i in part.factors) { total_damage += save_damages[i] * part.factors[i]; } let dmgarr = part.factors.slice(); dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")"); - tooltiptext = " = " + dmgarr.join(" + "); let averageLabel = document.createElement("p"); averageLabel.textContent = "Average: "+total_damage.toFixed(2); averageLabel.classList.add("damageSubtitle"); - tooltip = createTooltip(tooltip, "p", tooltiptext, averageLabel, ["spell-tooltip"]); part_div.append(averageLabel); let overallaverageLabel = document.createElement("p"); - overallaverageLabel.classList.add("damageSubtitle"); - let overallaverageLabelFirst = document.createElement("b"); - let overallaverageLabelSecond = document.createElement("b"); + let overallaverageLabelFirst = document.createElement("span"); + let overallaverageLabelSecond = document.createElement("span"); overallaverageLabelFirst.textContent = "Average: "; overallaverageLabelSecond.textContent = total_damage.toFixed(2); overallaverageLabelSecond.classList.add("Damage"); - tooltip = createTooltip(tooltip, "p", tooltiptext, overallaverageLabel, ["spell-tooltip", "summary-tooltip"]); overallaverageLabel.appendChild(overallaverageLabelFirst); From fd97eb45e05d09b6d34d2a5e509f7a4c674d6e6e Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 11:30:10 -0700 Subject: [PATCH 027/155] Copying code from sq2display.js to display.js --- js/display.js | 591 +++++++++++++++++++++++++------------------------- 1 file changed, 297 insertions(+), 294 deletions(-) diff --git a/js/display.js b/js/display.js index c2ca48b..02104a5 100644 --- a/js/display.js +++ b/js/display.js @@ -1,23 +1,10 @@ /** - * Apply armor powdering. - * Applies twice for crafted items because wynn. - * Also for jeweling for crafted items. - */ -function applyArmorPowders(expandedItem, powders) { - applyArmorPowdersOnce(expandedItem, powders); - // NOTE: armor powder only applies once! - //if (expandedItem.get("crafted")) { - // applyArmorPowdersOnce(expandedItem, powders); - //} -} - -/** - * Apply armor powders once only. + * 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 applyArmorPowdersOnce(expandedItem, powders) { +function applyArmorPowders(expandedItem, powders) { for(const id of powders){ let powder = powderStats[id]; let name = powderNames.get(id).charAt(0); @@ -33,12 +20,12 @@ function apply_elemental_format(p_elem, id, suffix) { let parts = idPrefixes[id].split(/ (.*)/); let element_prefix = parts[0]; let desc = parts[1]; - let i_elem = document.createElement('b'); + let i_elem = document.createElement('span'); i_elem.classList.add(element_prefix); i_elem.textContent = element_prefix; p_elem.appendChild(i_elem); - let i_elem2 = document.createElement('b'); + let i_elem2 = document.createElement('span'); i_elem2.textContent = " " + desc + suffix; p_elem.appendChild(i_elem2); } @@ -90,48 +77,20 @@ function displaySetBonuses(parent_id,build) { } -function displayBuildStats(parent_id,build){ +function displayBuildStats(parent_id,build,command_group){ // Commands to "script" the creation of nice formatting. // #commands create a new element. // !elemental is some janky hack for elemental damage. // normals just display a thing. - let display_commands = build_overall_display_commands; - - // Clear the parent div. - setHTML(parent_id, ""); + let display_commands = command_group; let parent_div = document.getElementById(parent_id); - let title = document.createElement("p"); - title.classList.add("itemcenter"); - title.classList.add("itemp"); - title.classList.add("title"); - title.classList.add("Normal"); - title.textContent = "Overall Build Stats"; - parent_div.append(title); - parent_div.append(document.createElement("br")); - - if (build.activeSetCounts.size > 0) { - let set_summary_elem = document.createElement('p'); - set_summary_elem.classList.add('itemp'); - set_summary_elem.classList.add('left'); - set_summary_elem.textContent = "Set Summary:"; - parent_div.append(set_summary_elem); - for (const [setName, count] of build.activeSetCounts) { - const active_set = sets[setName]; - if (active_set["hidden"]) { continue; } - - let set_elem = document.createElement('p'); - set_elem.classList.add('itemp'); - set_elem.classList.add('left'); - set_elem.textContent = " "+setName+" Set: "+count+"/"+sets[setName].items.length; - set_summary_elem.append(set_elem); - } + // Clear the parent div. + if (parent_div != null) { + setHTML(parent_id, ""); } - displayDefenseStats(parent_div, build, true); - let stats = build.statMap; - //console.log(build.statMap); let active_elem; let elemental_format = false; @@ -140,37 +99,35 @@ function displayBuildStats(parent_id,build){ let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"]; for (const command of display_commands) { + // style instructions + if (command.charAt(0) === "#") { - if (command === "#cdiv") { - active_elem = document.createElement('div'); - active_elem.classList.add('itemcenter'); + if (command === "#defense-stats") { + displayDefenseStats(parent_div, build, true); } - else if (command === "#ldiv") { - active_elem = document.createElement('div'); - active_elem.classList.add('itemleft'); - } - else if (command === "#table") { - active_elem = document.createElement('table'); - active_elem.classList.add('itemtable'); - } - parent_div.appendChild(active_elem); } - else if (command.charAt(0) === "!") { + if (command.charAt(0) === "!") { // TODO: This is sooo incredibly janky..... if (command === "!elemental") { elemental_format = !elemental_format; } } + + // id instruction else { let id = command; if (stats.get(id)) { let style = null; + + // TODO: add pos and neg style if (!staticIDs.includes(id)) { style = "positive"; if (stats.get(id) < 0) { style = "negative"; } } + + // ignore let id_val = stats.get(id); if (reversedIDs.includes(id)) { style === "positive" ? style = "negative" : style = "positive"; @@ -178,12 +135,14 @@ function displayBuildStats(parent_id,build){ if (id === "poison" && id_val > 0) { id_val = Math.ceil(id_val*build.statMap.get("poisonPct")/100); } - displayFixedID(active_elem, id, id_val, elemental_format, style); + displayFixedID(parent_div, id, id_val, elemental_format, style); if (id === "poison" && id_val > 0) { - let row = document.createElement('tr'); - let value_elem = document.createElement('td'); - value_elem.classList.add('right'); - value_elem.setAttribute("colspan", "2"); + let row = document.createElement('div'); + row.classList.add("row") + let value_elem = document.createElement('div'); + value_elem.classList.add('col'); + value_elem.classList.add('text-end'); + let prefix_elem = document.createElement('b'); prefix_elem.textContent = "\u279C With Strength: "; let number_elem = document.createElement('b'); @@ -192,23 +151,30 @@ function displayBuildStats(parent_id,build){ value_elem.append(prefix_elem); value_elem.append(number_elem); row.appendChild(value_elem); - - active_elem.appendChild(row); - } else if (id === "ls" && id_val != 0) { - let row = document.createElement("tr"); - let title = document.createElement("td"); - title.classList.add("left"); - title.textContent = "Effective Life Steal:" - let value = document.createElement("td"); - let defStats = build.getDefenseStats(); - value.textContent = Math.round(defStats[1][0]*id_val/defStats[0]) + "/3s"; - value.classList.add("right"); - value.classList.add(style); - row.appendChild(title); - row.appendChild(value); - active_elem.appendChild(row); + parent_div.appendChild(row); } - } else if (skp_order.includes(id)) { + else if (id === "ls" && id_val != 0) { + let row = document.createElement('div'); + row.classList.add("row") + let value_elem = document.createElement('div'); + value_elem.classList.add('col'); + value_elem.classList.add('text-end'); + + let prefix_elem = document.createElement('b'); + prefix_elem.textContent = "\u279C Effective LS: "; + + let defStats = build.getDefenseStats(); + let number_elem = document.createElement('b'); + number_elem.classList.add(style); + number_elem.textContent = Math.round(defStats[1][0]*id_val/defStats[0]) + "/3s"; + value_elem.append(prefix_elem); + value_elem.append(number_elem); + row.appendChild(value_elem); + parent_div.appendChild(row); + } + } + // sp thingy (WHY IS THIS HANDLED SEPARATELY TODO + else if (skp_order.includes(id)) { let total_assigned = build.total_skillpoints[skp_order.indexOf(id)]; let base_assigned = build.base_skillpoints[skp_order.indexOf(id)]; let diff = total_assigned - base_assigned; @@ -219,7 +185,7 @@ function displayBuildStats(parent_id,build){ style = "negative"; } if (diff != 0) { - displayFixedID(active_elem, id, diff, false, style); + displayFixedID(parent_div, id, diff, false, style); } } } @@ -276,38 +242,28 @@ function displayExpandedItem(item, parent_id){ } else if (item.get("category") === "armor") { } - let display_commands = item_display_commands; + let display_commands = sq2_item_display_commands; // Clear the parent div. setHTML(parent_id, ""); let parent_div = document.getElementById(parent_id); + parent_div.classList.add("border", "border-2", "border-dark"); - let active_elem; let fix_id = item.has("fixID") && item.get("fixID"); let elemental_format = false; for (let i = 0; i < display_commands.length; i++) { const command = display_commands[i]; - if (command.charAt(0) === "#") { - if (command === "#cdiv") { - active_elem = document.createElement('div'); - active_elem.classList.add('itemcenter'); - } - else if (command === "#ldiv") { - active_elem = document.createElement('div'); - active_elem.classList.add('itemleft'); - } - else if (command === "#table") { - active_elem = document.createElement('table'); - active_elem.classList.add('itemtable'); - } - active_elem.style.maxWidth = "100%"; - parent_div.appendChild(active_elem); - } - else if (command.charAt(0) === "!") { + if (command.charAt(0) === "!") { // TODO: This is sooo incredibly janky..... if (command === "!elemental") { elemental_format = !elemental_format; - } + } + else if (command === "!spacer") { + let spacer = document.createElement('div'); + spacer.classList.add("row", "my-2"); + parent_div.appendChild(spacer); + continue; + } } else { let id = command; @@ -319,12 +275,13 @@ function displayExpandedItem(item, parent_id){ } } if (id === "slots") { - let p_elem = document.createElement("p"); + let p_elem = document.createElement("div"); + p_elem.classList.add("col"); + // PROPER POWDER DISPLAYING let numerals = new Map([[1, "I"], [2, "II"], [3, "III"], [4, "IV"], [5, "V"], [6, "VI"]]); let powderPrefix = document.createElement("b"); - powderPrefix.classList.add("powderLeft"); powderPrefix.classList.add("left"); powderPrefix.textContent = "Powder Slots: " + item.get(id) + " ["; p_elem.appendChild(powderPrefix); @@ -337,21 +294,21 @@ function displayExpandedItem(item, parent_id){ } let powderSuffix = document.createElement("b"); - powderSuffix.classList.add("powderRight"); powderSuffix.classList.add("left"); powderSuffix.textContent = "]"; p_elem.appendChild(powderSuffix); - active_elem.appendChild(p_elem); + parent_div.appendChild(p_elem); } else if (id === "set") { if (item.get("hideSet")) { continue; } - let p_elem = document.createElement("p"); - p_elem.classList.add("itemp"); + let p_elem = document.createElement("div"); + p_elem.classList.add("col"); p_elem.textContent = "Set: " + item.get(id).toString(); - active_elem.appendChild(p_elem); + parent_div.appendChild(p_elem); } else if (id === "majorIds") { + //console.log(item.get(id)); for (let majorID of item.get(id)) { - let p_elem = document.createElement("p"); - p_elem.classList.add("itemp"); + let p_elem = document.createElement("div"); + p_elem.classList.add("col"); let title_elem = document.createElement("b"); let b_elem = document.createElement("b"); @@ -372,60 +329,94 @@ function displayExpandedItem(item, parent_id){ b_elem.textContent = name; p_elem.appendChild(b_elem); } - active_elem.appendChild(p_elem); + parent_div.appendChild(p_elem); } } else if (id === "lvl" && item.get("tier") === "Crafted") { - let p_elem = document.createElement("p"); - p_elem.classList.add("itemp"); + let p_elem = document.createElement("div"); + p_elem.classList.add("col"); p_elem.textContent = "Combat Level Min: " + item.get("lvlLow") + "-" + item.get(id); - active_elem.appendChild(p_elem); + parent_div.appendChild(p_elem); } else if (id === "displayName") { - let p_elem = document.createElement("a"); - p_elem.classList.add('itemp'); - p_elem.classList.add("smalltitle"); - p_elem.classList.add(item.has("tier") ? item.get("tier").replace(" ","") : "none"); + let row = document.createElement("div"); + + let a_elem = document.createElement("a"); + row.classList.add("row", "justify-content-center"); + a_elem.classList.add("col-auto", "text-center", "item-title", "p-0"); + a_elem.classList.add(item.has("tier") ? item.get("tier").replace(" ","") : "Normal"); + // a_elem.style.textGrow = 1; + row.appendChild(a_elem); + + /* + FUNCTIONALITY FOR THIS FEATURE HAS SINCE BEEN REMOVED (WITH SQ2). + IF WE WANT TO USE IT IN THE FUTURE, I'VE LEFT THE CODE TO ADD IT IN HERE + */ + + //allow the plus minus element to toggle upon click: âž•âž– + let plusminus = document.createElement("div"); + plusminus.id = parent_div.id.split("-")[0] + "-pm"; + plusminus.classList.add("col", "plus_minus", "text_end"); + plusminus.style.flexGrow = 0; + plusminus.textContent = "\u2795"; + row.appendChild(plusminus); + + if (item.get("custom")) { + a_elem.href = "../custom/#" + item.get("hash"); + a_elem.textContent = item.get("displayName"); + } else if (item.get("crafted")) { + a_elem.href = "../crafter/#" + item.get("hash"); + a_elem.textContent = item.get(id); + } else { + a_elem.href = "../item/#" + item.get("displayName"); + a_elem.textContent = item.get("displayName"); + } + parent_div.appendChild(row); + + let nolink_row = document.createElement("div"); + let p_elem = document.createElement("p"); + nolink_row.classList.add("row", "justify-content-center"); + nolink_row.style.display = "none"; + p_elem.classList.add("col-auto", "text-center", "item-title", "p-0"); + p_elem.classList.add(item.has("tier") ? item.get("tier").replace(" ","") : "Normal"); if (item.get("custom")) { - p_elem.href = url_base.replace(/\w+.html/, "") + "customizer.html#" + item.get("hash"); p_elem.textContent = item.get("displayName"); } else if (item.get("crafted")) { - p_elem.href = url_base.replace(/\w+.html/, "") + "crafter.html#" + item.get("hash"); p_elem.textContent = item.get(id); } else { - p_elem.href = url_base.replace(/\w+.html/, "") + "item.html#" + item.get("displayName"); p_elem.textContent = item.get("displayName"); } + + nolink_row.appendChild(p_elem); + parent_div.appendChild(nolink_row); - p_elem.target = "_blank"; - active_elem.appendChild(p_elem); let img = document.createElement("img"); if (item && item.has("type")) { img.src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + item.get("type") + ".png"; img.alt = item.get("type"); - img.style = " z=index: 1;max-width: 64px; max-height: 64px; position: relative; top: 50%; transform: translateY(-50%);"; - let bckgrd = document.createElement("p"); - bckgrd.style = "width: 96px; height: 96px; border-radius: 50%;background-image: radial-gradient(closest-side, " + colorMap.get(item.get("tier")) + " 20%," + "#121516 80%); margin-left: auto; margin-right: auto;" - bckgrd.classList.add("center"); - bckgrd.classList.add("itemp"); - active_elem.appendChild(bckgrd); + img.style = " z=index: 1; position: relative;"; + let container = document.createElement("div"); + + let bckgrd = document.createElement("div"); + bckgrd.classList.add("col", "px-0", "d-flex", "align-items-center", "justify-content-center");// , "no-collapse"); + bckgrd.style = "border-radius: 50%;background-image: radial-gradient(closest-side, " + colorMap.get(item.get("tier")) + " 20%," + "hsl(0, 0%, 16%) 80%); margin-left: auto; margin-right: auto;" + bckgrd.classList.add("scaled-bckgrd"); + parent_div.appendChild(container); + container.appendChild(bckgrd); bckgrd.appendChild(img); } } else { let p_elem; - if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && active_elem.nodeName === "DIV") ) { //skp warp - p_elem = displayFixedID(active_elem, id, item.get(id), elemental_format); + if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && parent_div.nodeName === "table") ) { //skp warp + p_elem = displayFixedID(parent_div, id, item.get(id), elemental_format); } else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") { - p_elem = displayFixedID(active_elem, id, item.get(id+"Low")+"-"+item.get(id), elemental_format); + p_elem = displayFixedID(parent_div, id, item.get(id+"Low")+"-"+item.get(id), elemental_format); } if (id === "lore") { p_elem.style = "font-style: italic"; - p_elem.classList.add("lore"); } else if (skp_order.includes(id)) { //id = str, dex, int, def, or agi - if ( item.get("tier") !== "Crafted" && active_elem.nodeName === "DIV") { - p_elem.textContent = ""; - p_elem.classList.add("itemp"); - row = document.createElement("p"); - row.classList.add("left"); + if ( item.get("tier") !== "Crafted") { + row = document.createElement("div"); + row.classList.add("col"); let title = document.createElement("b"); title.textContent = idPrefixes[id] + " "; @@ -438,10 +429,10 @@ function displayExpandedItem(item, parent_id){ boost.textContent = item.get(id); row.appendChild(title); row.appendChild(boost); - p_elem.appendChild(row); - } else if ( item.get("tier") === "Crafted" && active_elem.nodeName === "TABLE") { + parent_div.appendChild(row); + } else if ( item.get("tier") === "Crafted") { let row = displayRolledID(item, id, elemental_format); - active_elem.appendChild(row); + parent_div.appendChild(row); } } else if (id === "restrict") { p_elem.classList.add("restrict"); @@ -459,11 +450,17 @@ function displayExpandedItem(item, parent_id){ style === "positive" ? style = "negative" : style = "positive"; } if (fix_id) { - displayFixedID(active_elem, id, item.get("minRolls").get(id), elemental_format, style); + p_elem = document.createElement("div"); + p_elem.classList.add("col", "text-nowrap"); + if (id == "dex") { + console.log("dex activated at fix_id") + } + displayFixedID(p_elem, id, item.get("minRolls").get(id), elemental_format, style); + parent_div.appendChild(p_elem); } else { let row = displayRolledID(item, id, elemental_format); - active_elem.appendChild(row); + parent_div.appendChild(row); } }else{ // :/ @@ -473,8 +470,8 @@ function displayExpandedItem(item, parent_id){ //Show powder specials ;-; let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots", "ring", "bracelet", "necklace"]; if(nonConsumables.includes(item.get("type"))) { - let powder_special = document.createElement("p"); - powder_special.classList.add("left"); + let powder_special = document.createElement("div"); + powder_special.classList.add("col"); let powders = item.get("powders"); let element = ""; let power = 0; @@ -495,10 +492,9 @@ function displayExpandedItem(item, parent_id){ if (element !== "") {//powder special is "[e,t,w,f,a]+[0,1,2,3,4]" let powderSpecial = powderSpecialStats[ skp_elements.indexOf(element)]; let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]); - let specialTitle = document.createElement("p"); - let specialEffects = document.createElement("p"); - addClasses(specialTitle, ["left", "itemp", damageClasses[skp_elements.indexOf(element) + 1]]); - addClasses(specialEffects, ["left", "itemp", "nocolor"]); + let specialTitle = document.createElement("span"); + let specialEffects = document.createElement("span"); + addClasses(specialTitle, [damageClasses[skp_elements.indexOf(element) + 1]]); let effects; if (item.get("category") === "weapon") {//weapon effects = powderSpecial["weaponSpecialEffects"]; @@ -510,7 +506,7 @@ function displayExpandedItem(item, parent_id){ for (const [key,value] of effects.entries()) { if (key !== "Description") { let effect = document.createElement("p"); - effect.classList.add("itemp"); + effect.classList.add("m-0"); effect.textContent = key + ": " + value[power] + specialSuffixes.get(key); if(key === "Damage"){ effect.textContent += elementIcons[skp_elements.indexOf(element)]; @@ -530,8 +526,8 @@ function displayExpandedItem(item, parent_id){ } if(item.get("tier") && item.get("tier") === "Crafted") { - let dura_elem = document.createElement("p"); - dura_elem.classList.add("itemp"); + let dura_elem = document.createElement("div"); + dura_elem.classList.add("col"); let dura = []; let suffix = ""; if(nonConsumables.includes(item.get("type"))) { @@ -543,8 +539,7 @@ function displayExpandedItem(item, parent_id){ suffix = " sec." let charges = document.createElement("b"); charges.textContent = "Charges: " + item.get("charges"); - charges.classList.add("spaceleft"); - active_elem.appendChild(charges); + parent_div.appendChild(charges); } if (typeof(dura) === "string") { @@ -552,16 +547,21 @@ function displayExpandedItem(item, parent_id){ } else { dura_elem.textContent += dura[0]+"-"+dura[1] + suffix; } - active_elem.append(dura_elem); + parent_div.append(dura_elem); } //Show item tier if (item.get("tier") && item.get("tier") !== " ") { - let item_desc_elem = document.createElement("p"); - item_desc_elem.classList.add('itemp'); + let item_desc_elem = document.createElement("div"); + item_desc_elem.classList.add("col"); item_desc_elem.classList.add(item.get("tier")); - item_desc_elem.textContent = item.get("tier")+" "+item.get("type"); - active_elem.append(item_desc_elem); + if (tome_types.includes(item.get("type"))) { + tome_type_map = new Map([["weaponTome", "Weapon Tome"],["armorTome", "Armor Tome"],["guildTome", "Guild Tome"]]); + item_desc_elem.textContent = item.get("tier")+" "+tome_type_map.get(item.get("type")); + } else { + item_desc_elem.textContent = item.get("tier")+" "+item.get("type"); + } + parent_div.append(item_desc_elem); } //Show item hash if applicable @@ -572,7 +572,7 @@ function displayExpandedItem(item, parent_id){ item_desc_elem.style.wordWrap = "break-word"; item_desc_elem.style.wordBreak = "break-word"; item_desc_elem.textContent = item.get("hash"); - active_elem.append(item_desc_elem); + parent_div.append(item_desc_elem); } if (item.get("category") === "weapon") { @@ -1021,9 +1021,12 @@ function displayRolledID(item, id, elemental_format) { function displayFixedID(active, id, value, elemental_format, style) { if (style) { - let row = document.createElement('tr'); - let desc_elem = document.createElement('td'); - desc_elem.classList.add('left'); + let row = document.createElement('div'); + row.classList.add("row"); + let desc_elem = document.createElement('div'); + desc_elem.classList.add('col'); + desc_elem.classList.add('text-start'); + if (elemental_format) { apply_elemental_format(desc_elem, id); } @@ -1032,8 +1035,9 @@ function displayFixedID(active, id, value, elemental_format, style) { } row.appendChild(desc_elem); - let value_elem = document.createElement('td'); - value_elem.classList.add('right'); + let value_elem = document.createElement('div'); + value_elem.classList.add('col'); + value_elem.classList.add('text-end'); value_elem.classList.add(style); value_elem.textContent = value + idSuffixes[id]; row.appendChild(value_elem); @@ -1045,8 +1049,8 @@ function displayFixedID(active, id, value, elemental_format, style) { if (value === "0-0" || value === "0-0\u279c0-0") { return; } - let p_elem = document.createElement('p'); - p_elem.classList.add('itemp'); + let p_elem = document.createElement('div'); + p_elem.classList.add('col'); if (elemental_format) { apply_elemental_format(p_elem, id, value); } @@ -1057,6 +1061,28 @@ function displayFixedID(active, id, value, elemental_format, style) { return p_elem; } } + +function displayPoisonDamage(overallparent_elem, build) { + overallparent_elem.textContent = ""; + + //Title + let title_elemavg = document.createElement("b"); + title_elemavg.textContent = "Poison Stats"; + overallparent_elem.append(title_elemavg); + + let overallpoisonDamage = document.createElement("p"); + let overallpoisonDamageFirst = document.createElement("span"); + let overallpoisonDamageSecond = document.createElement("span"); + let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct"))/100 /3); + overallpoisonDamageFirst.textContent = "Poison Tick: "; + overallpoisonDamageSecond.textContent = Math.max(poison_tick,0); + overallpoisonDamageSecond.classList.add("Damage"); + + overallpoisonDamage.appendChild(overallpoisonDamageFirst); + overallpoisonDamage.appendChild(overallpoisonDamageSecond); + overallparent_elem.append(overallpoisonDamage); +} + function displayEquipOrder(parent_elem,buildOrder){ parent_elem.textContent = ""; const order = buildOrder.slice(); @@ -1076,29 +1102,6 @@ function displayEquipOrder(parent_elem,buildOrder){ } } -function displayPoisonDamage(overallparent_elem, build) { - overallparent_elem.textContent = ""; - - //Title - let title_elemavg = document.createElement("p"); - title_elemavg.classList.add("smalltitle"); - title_elemavg.classList.add("Normal"); - title_elemavg.textContent = "Poison Stats"; - overallparent_elem.append(title_elemavg); - - let overallpoisonDamage = document.createElement("p"); - overallpoisonDamage.classList.add("lessbottom"); - let overallpoisonDamageFirst = document.createElement("b"); - let overallpoisonDamageSecond = document.createElement("b"); - let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct") + build.externalStats.get("poisonPct"))/100 /3); - overallpoisonDamageFirst.textContent = "Poison Tick: "; - overallpoisonDamageSecond.textContent = Math.max(poison_tick,0); - overallpoisonDamageSecond.classList.add("Damage"); - - overallpoisonDamage.appendChild(overallpoisonDamageFirst); - overallpoisonDamage.appendChild(overallpoisonDamageSecond); - overallparent_elem.append(overallpoisonDamage); -} function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ console.log("Melee Stats"); @@ -1314,40 +1317,8 @@ function displayDefenseStats(parent_elem, build, insertSummary){ } const stats = defenseStats.slice(); - if (!insertSummary) { - let title_elem = document.createElement("p"); - title_elem.textContent = "Defense Stats"; - title_elem.classList.add("title"); - title_elem.classList.add("Normal"); - title_elem.classList.add("itemp"); - parent_elem.append(title_elem); - - let base_stat_elem = document.createElement("p"); - base_stat_elem.id = "base-stat"; - parent_elem.append(base_stat_elem); - - let mock_item = new Map(); - - mock_item.set("fixID", true); - let mock_minRolls = new Map(); - mock_item.set("minRolls", mock_minRolls); - const stats = ["hp", "hpBonus", "hprRaw", "hprPct", "fDef", "wDef", "aDef", "tDef", "eDef", - "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct"]; - for (const stat of stats) { - if (rolledIDs.includes(stat)) { - mock_minRolls.set(stat, build.statMap.get(stat)); - } - else { - mock_item.set(stat, build.statMap.get(stat)); - } - } - mock_item.set("powders", []); - displayExpandedItem(mock_item, base_stat_elem.id); - } - - parent_elem.append(document.createElement("br")); - let statsTable = document.createElement("table"); - statsTable.classList.add("itemtable"); + // parent_elem.append(document.createElement("br")); + let statsTable = document.createElement("div"); //[total hp, ehp, total hpr, ehpr, [def%, agi%], [edef,tdef,wdef,fdef,adef]] for(const i in stats){ @@ -1361,18 +1332,26 @@ function displayDefenseStats(parent_elem, build, insertSummary){ } //total HP - let hpRow = document.createElement("tr"); - let hp = document.createElement("td"); + let hpRow = document.createElement("div"); + hpRow.classList.add('row'); + let hp = document.createElement("div"); + hp.classList.add('col'); hp.classList.add("Health"); - hp.classList.add("left"); + hp.classList.add("text-start"); hp.textContent = "Total HP:"; - let boost = document.createElement("td"); + let boost = document.createElement("div"); + boost.classList.add('col'); boost.textContent = stats[0]; - boost.classList.add("right"); + boost.classList.add("text-end"); hpRow.appendChild(hp); hpRow.append(boost); - statsTable.appendChild(hpRow); + + if (insertSummary) { + parent_elem.appendChild(hpRow); + } else { + statsTable.appendChild(hpRow); + } let tooltip; let tooltiptext; @@ -1380,100 +1359,113 @@ function displayDefenseStats(parent_elem, build, insertSummary){ if (!defMult) {defMult = 1} //EHP - let ehpRow = document.createElement("tr"); - let ehp = document.createElement("td"); - ehp.classList.add("left"); + let ehpRow = document.createElement("div"); + ehpRow.classList.add("row"); + let ehp = document.createElement("div"); + ehp.classList.add("col"); + ehp.classList.add("text-start"); ehp.textContent = "Effective HP:"; - boost = document.createElement("td"); + boost = document.createElement("div"); boost.textContent = stats[1][0]; - boost.classList.add("right"); + boost.classList.add("col"); + boost.classList.add("text-end"); tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); + // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); ehpRow.appendChild(ehp); ehpRow.append(boost); - statsTable.append(ehpRow); - ehpRow = document.createElement("tr"); - ehp = document.createElement("td"); - ehp.classList.add("left"); + if (insertSummary) { + parent_elem.appendChild(ehpRow) + } else { + statsTable.append(ehpRow); + } + + ehpRow = document.createElement("div"); + ehpRow.classList.add("row"); + ehp = document.createElement("div"); + ehp.classList.add("col"); + ehp.classList.add("text-start"); ehp.textContent = "Effective HP (no agi):"; - boost = document.createElement("td"); + boost = document.createElement("div"); boost.textContent = stats[1][1]; - boost.classList.add("right"); + boost.classList.add("col"); + boost.classList.add("text-end"); tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); + // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); ehpRow.appendChild(ehp); ehpRow.append(boost); statsTable.append(ehpRow); //total HPR - let hprRow = document.createElement("tr"); - let hpr = document.createElement("td"); + let hprRow = document.createElement("div"); + hprRow.classList.add("row") + let hpr = document.createElement("div"); hpr.classList.add("Health"); - hpr.classList.add("left"); + hpr.classList.add("col"); + hpr.classList.add("text-start"); hpr.textContent = "HP Regen (Total):"; - boost = document.createElement("td"); + boost = document.createElement("div"); boost.textContent = stats[2]; - boost.classList.add("right"); + boost.classList.add("col"); + boost.classList.add("text-end"); hprRow.appendChild(hpr); hprRow.appendChild(boost); - statsTable.appendChild(hprRow); + + if (insertSummary) { + parent_elem.appendChild(hprRow); + } else { + statsTable.appendChild(hprRow); + } + //EHPR - let ehprRow = document.createElement("tr"); - let ehpr = document.createElement("td"); - ehpr.classList.add("left"); + let ehprRow = document.createElement("div"); + ehprRow.classList.add("row") + let ehpr = document.createElement("div"); + ehpr.classList.add("col"); + ehpr.classList.add("text-start"); ehpr.textContent = "Effective HP Regen:"; - boost = document.createElement("td"); + boost = document.createElement("div"); boost.textContent = stats[3][0]; - boost.classList.add("right"); + boost.classList.add("col"); + boost.classList.add("text-end"); tooltiptext = `= ${stats[2]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); + // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); ehprRow.appendChild(ehpr); ehprRow.append(boost); statsTable.append(ehprRow); - /* - ehprRow = document.createElement("tr"); - ehpr = document.createElement("td"); - ehpr.classList.add("left"); - ehpr.textContent = "Effective HP Regen (no agi):"; - - boost = document.createElement("td"); - boost.textContent = stats[3][1]; - boost.classList.add("right"); - - ehprRow.appendChild(ehpr); - ehprRow.append(boost); - statsTable.append(ehprRow); */ //eledefs let eledefs = stats[5]; for (let i = 0; i < eledefs.length; i++){ - let eledefElemRow = document.createElement("tr"); + let eledefElemRow = document.createElement("div"); + eledefElemRow.classList.add("row") - let eledef = document.createElement("td"); - eledef.classList.add("left") - let eledefTitle = document.createElement("b"); + let eledef = document.createElement("div"); + eledef.classList.add("col"); + eledef.classList.add("text-start"); + let eledefTitle = document.createElement("span"); eledefTitle.textContent = damageClasses[i+1]; eledefTitle.classList.add(damageClasses[i+1]); - let defense = document.createElement("b"); + let defense = document.createElement("span"); defense.textContent = " Def (Total): "; eledef.appendChild(eledefTitle); eledef.appendChild(defense); eledefElemRow.appendChild(eledef); - let boost = document.createElement("td"); + let boost = document.createElement("div"); boost.textContent = eledefs[i]; boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative"); - boost.classList.add("right"); + boost.classList.add("col"); + boost.classList.add("text-end"); let defRaw = build.statMap.get("defRaw")[i]; let defPct = build.statMap.get("defBonus")[i]/100; @@ -1484,39 +1476,51 @@ function displayDefenseStats(parent_elem, build, insertSummary){ defPct >= 0 ? defPct = "+ " + defPct: defPct = "- " + defPct; tooltiptext = `= ${defRaw} * (1 ${defPct})` } - tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); + // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); eledefElemRow.appendChild(boost); - - statsTable.appendChild(eledefElemRow); + + if (insertSummary) { + parent_elem.appendChild(eledefElemRow); + } else { + statsTable.appendChild(eledefElemRow); + } } if (!insertSummary) { //skp - let defRow = document.createElement("tr"); - let defElem = document.createElement("td"); - defElem.classList.add("left"); + let defRow = document.createElement("div"); + defRow.classList.add("row"); + let defElem = document.createElement("div"); + defElem.classList.add("col"); + defElem.classList.add("text-start"); defElem.textContent = "Damage Absorbed %:"; - boost = document.createElement("td"); - boost.classList.add("right"); + boost = document.createElement("div"); + boost.classList.add("col"); + boost.classList.add("text-end"); boost.textContent = stats[4][0] + "%"; defRow.appendChild(defElem); defRow.appendChild(boost); statsTable.append(defRow); - let agiRow = document.createElement("tr"); - let agiElem = document.createElement("td"); - agiElem.classList.add("left"); + let agiRow = document.createElement("div"); + agiRow.classList.add("row"); + let agiElem = document.createElement("div"); + agiElem.classList.add("col"); + agiElem.classList.add("text-start"); agiElem.textContent = "Dodge Chance %:"; - boost = document.createElement("td"); - boost.classList.add("right"); + boost = document.createElement("div"); + boost.classList.add("col"); + boost.classList.add("text-end"); boost.textContent = stats[4][1] + "%"; agiRow.appendChild(agiElem); agiRow.appendChild(boost); statsTable.append(agiRow); } - parent_elem.append(statsTable); + if (!insertSummary) { + parent_elem.append(statsTable); + } } function displayPowderSpecials(parent_elem, powderSpecials, build) { @@ -1691,7 +1695,6 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell overallparent_elem.append(displayNextCosts(spell, build, build.weapon.statMap)); - let critChance = skillPointsToPercentage(build.total_skillpoints[1]); let save_damages = []; From 81dfe767a3f11758002a607663a0b99c1cedfa32 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 13:44:02 -0700 Subject: [PATCH 028/155] Item display, build display (partially) --- builder/index.html | 6 +- js/build_constants.js | 4 +- js/build_encode_decode.js | 46 ++-- js/builder.js | 252 ++++++++++++++++++- js/builder_graph.js | 505 +++++++++++++++++++------------------- js/computation_graph.js | 114 +-------- js/display.js | 347 +++++++++++++------------- js/utils.js | 3 +- 8 files changed, 704 insertions(+), 573 deletions(-) diff --git a/builder/index.html b/builder/index.html index d0ab71a..73e20b1 100644 --- a/builder/index.html +++ b/builder/index.html @@ -313,7 +313,7 @@
- +
@@ -1291,7 +1291,7 @@
-
+
diff --git a/js/build_constants.js b/js/build_constants.js index 2983328..820beb7 100644 --- a/js/build_constants.js +++ b/js/build_constants.js @@ -85,7 +85,7 @@ let tome_names = [ "Guild Tome", ] let equipmentInputs = equipment_fields.map(x => x + "-choice"); -let buildFields = equipment_fields.map(x => x+"-tooltip").concat(tome_fields.map(x => x + "-tooltip")); +let build_fields = equipment_fields.map(x => x+"-tooltip"); let tomeInputs = tome_fields.map(x => x + "-choice"); let powder_inputs = [ @@ -100,7 +100,7 @@ let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear']; let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots']; let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace']; let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon']; -let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'].concat(tome_keys); +let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon']; let spell_disp = ['spell0-info', 'spell1-info', 'spell2-info', 'spell3-info']; let other_disp = ['build-order', 'set-info', 'int-info']; diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 0929e4e..12b45b1 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -137,7 +137,7 @@ function decodeBuild(url_tag) { /* Stores the entire build in a string using B64 encoding and adds it to the URL. */ -function encodeBuild(build) { +function encodeBuild(build, powders) { if (build) { let build_string; @@ -147,19 +147,15 @@ function encodeBuild(build) { build_string = ""; tome_string = ""; - let crafted_idx = 0; - let custom_idx = 0; for (const item of build.items) { - if (item.get("custom")) { - let custom = "CI-"+encodeCustom(build.customItems[custom_idx],true); + if (item.statMap.get("custom")) { + let custom = "CI-"+encodeCustom(item, true); build_string += Base64.fromIntN(custom.length, 3) + custom; - custom_idx += 1; build_version = Math.max(build_version, 5); - } else if (item.get("crafted")) { - build_string += "CR-"+encodeCraft(build.craftedItems[crafted_idx]); - crafted_idx += 1; - } else if (item.get("category") === "tome") { + } else if (item.statMap.get("crafted")) { + build_string += "CR-"+encodeCraft(item); + } else if (item.statMap.get("category") === "tome") { let tome_id = item.get("id"); if (tome_id <= 60) { // valid normal tome. ID 61-63 is for NONE tomes. @@ -167,7 +163,7 @@ function encodeBuild(build) { } tome_string += Base64.fromIntN(tome_id, 1); } else { - build_string += Base64.fromIntN(item.get("id"), 3); + build_string += Base64.fromIntN(item.statMap.get("id"), 3); } } @@ -175,7 +171,7 @@ function encodeBuild(build) { build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 } build_string += Base64.fromIntN(build.level, 2); - for (const _powderset of build.powders) { + for (const _powderset of powders) { let n_bits = Math.ceil(_powderset.length / 6); build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. // Slice copy. @@ -196,26 +192,24 @@ function encodeBuild(build) { } } -function copyBuild(build) { - if (build) { - copyTextToClipboard(url_base+location.hash); - document.getElementById("copy-button").textContent = "Copied!"; - } +function copyBuild() { + copyTextToClipboard(url_base+location.hash); + document.getElementById("copy-button").textContent = "Copied!"; } function shareBuild(build) { if (build) { let text = url_base+location.hash+"\n"+ "WynnBuilder build:\n"+ - "> "+build.helmet.get("displayName")+"\n"+ - "> "+build.chestplate.get("displayName")+"\n"+ - "> "+build.leggings.get("displayName")+"\n"+ - "> "+build.boots.get("displayName")+"\n"+ - "> "+build.ring1.get("displayName")+"\n"+ - "> "+build.ring2.get("displayName")+"\n"+ - "> "+build.bracelet.get("displayName")+"\n"+ - "> "+build.necklace.get("displayName")+"\n"+ - "> "+build.weapon.get("displayName")+" ["+build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]"; + "> "+build.helmet.statMap.get("displayName")+"\n"+ + "> "+build.chestplate.statMap.get("displayName")+"\n"+ + "> "+build.leggings.statMap.get("displayName")+"\n"+ + "> "+build.boots.statMap.get("displayName")+"\n"+ + "> "+build.ring1.statMap.get("displayName")+"\n"+ + "> "+build.ring2.statMap.get("displayName")+"\n"+ + "> "+build.bracelet.statMap.get("displayName")+"\n"+ + "> "+build.necklace.statMap.get("displayName")+"\n"+ + "> "+build.weapon.statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]"; copyTextToClipboard(text); document.getElementById("share-button").textContent = "Copied!"; } diff --git a/js/builder.js b/js/builder.js index 1ce6a3f..0251fef 100644 --- a/js/builder.js +++ b/js/builder.js @@ -1,3 +1,5 @@ +let build_powders; + function getItemNameFromID(id) { if (redirectMap.has(id)) { return getItemNameFromID(redirectMap.get(id)); @@ -142,15 +144,263 @@ function show_tab(tab) { document.getElementById("tab-" + tab.split("-")[0] + "-btn").classList.add("selected-btn"); } +// autocomplete initialize +function init_autocomplete() { + let dropdowns = new Map(); + for (const eq of equipment_keys) { + if (tome_keys.includes(eq)) { + continue; + } + // build dropdown + let item_arr = []; + if (eq == 'weapon') { + for (const weaponType of weapon_keys) { + for (const weapon of itemLists.get(weaponType)) { + let item_obj = itemMap.get(weapon); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { + continue; + } + item_arr.push(weapon); + } + } + } else { + for (const item of itemLists.get(eq.replace(/[0-9]/g, ''))) { + let item_obj = itemMap.get(item); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { + continue; + } + item_arr.push(item) + } + } + + // create dropdown + dropdowns.set(eq, new autoComplete({ + data: { + src: item_arr + }, + selector: "#"+ eq +"-choice", + wrapper: false, + resultsList: { + maxResults: 1000, + tabSelect: true, + noResults: true, + class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", + element: (list, data) => { + // dynamic result loc + let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); + list.style.top = position.bottom + window.scrollY +"px"; + list.style.left = position.x+"px"; + list.style.width = position.width+"px"; + list.style.maxHeight = position.height * 2 +"px"; + + if (!data.results.length) { + message = document.createElement('li'); + message.classList.add('scaled-font'); + message.textContent = "No results found!"; + list.prepend(message); + } + }, + }, + resultItem: { + class: "scaled-font search-item", + selected: "dark-5", + element: (item, data) => { + item.classList.add(itemMap.get(data.value).tier); + }, + }, + events: { + input: { + selection: (event) => { + if (event.detail.selection.value) { + event.target.value = event.detail.selection.value; + } + event.target.dispatchEvent(new Event('input')); + }, + }, + } + })); + } + + for (const eq of tome_keys) { + // build dropdown + let tome_arr = []; + for (const tome of tomeLists.get(eq.replace(/[0-9]/g, ''))) { + let tome_obj = tomeMap.get(tome); + if (tome_obj["restrict"] && tome_obj["restrict"] === "DEPRECATED") { + continue; + } + //this should suffice for tomes - jank + if (tome_obj["name"].includes('No ' + eq.charAt(0).toUpperCase())) { + continue; + } + let tome_name = tome; + tome_arr.push(tome_name); + } + + // create dropdown + dropdowns.set(eq, new autoComplete({ + data: { + src: tome_arr + }, + selector: "#"+ eq +"-choice", + wrapper: false, + resultsList: { + maxResults: 1000, + tabSelect: true, + noResults: true, + class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", + element: (list, data) => { + // dynamic result loc + let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); + list.style.top = position.bottom + window.scrollY +"px"; + list.style.left = position.x+"px"; + list.style.width = position.width+"px"; + list.style.maxHeight = position.height * 2 +"px"; + + if (!data.results.length) { + message = document.createElement('li'); + message.classList.add('scaled-font'); + message.textContent = "No results found!"; + list.prepend(message); + } + }, + }, + resultItem: { + class: "scaled-font search-item", + selected: "dark-5", + element: (tome, data) => { + tome.classList.add(tomeMap.get(data.value).tier); + }, + }, + events: { + input: { + selection: (event) => { + if (event.detail.selection.value) { + event.target.value = event.detail.selection.value; + } + }, + }, + } + })); + } + + let filter_loc = ["filter1", "filter2", "filter3", "filter4"]; + for (const i of filter_loc) { + dropdowns.set(i+"-choice", new autoComplete({ + data: { + src: sq2ItemFilters, + }, + selector: "#"+i+"-choice", + wrapper: false, + resultsList: { + tabSelect: true, + noResults: true, + class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", + element: (list, data) => { + // dynamic result loc + console.log(i); + list.style.zIndex = "100"; + let position = document.getElementById(i+"-dropdown").getBoundingClientRect(); + window_pos = document.getElementById("search-container").getBoundingClientRect(); + list.style.top = position.bottom - window_pos.top + 5 +"px"; + list.style.left = position.x - window_pos.x +"px"; + list.style.width = position.width+"px"; + + if (!data.results.length) { + message = document.createElement('li'); + message.classList.add('scaled-font'); + message.textContent = "No filters found!"; + list.prepend(message); + } + }, + }, + resultItem: { + class: "scaled-font search-item", + selected: "dark-5", + }, + events: { + input: { + selection: (event) => { + if (event.detail.selection.value) { + event.target.value = event.detail.selection.value; + } + }, + }, + } + })); + } +} + +function collapse_element(elmnt) { + elem_list = document.querySelector(elmnt).children; + if (elem_list) { + for (elem of elem_list) { + if (elem.classList.contains("no-collapse")) { continue; } + if (elem.style.display == "none") { + elem.style.display = ""; + } else { + elem.style.display = "none"; + } + } + } + // macy quirk + window.dispatchEvent(new Event('resize')); + // weird bug where display: none overrides?? + document.querySelector(elmnt).style.removeProperty('display'); +} // TODO: Learn and use await function init() { console.log("builder.js init"); init_autocomplete(); + + // Other "main" stuff + // Spell dropdowns + for (const i of spell_disp) { + document.querySelector("#"+i+"Avg").addEventListener("click", () => toggle_spell_tab(i)); + } + for (const eq of equipment_keys) { + document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip')); + } + + // Masonry setup + let masonry = Macy({ + container: "#masonry-container", + columns: 1, + mobileFirst: true, + breakAt: { + 1200: 4, + }, + margin: { + x: 20, + y: 20, + } + + }); + + let search_masonry = Macy({ + container: "#search-results", + columns: 1, + mobileFirst: true, + breakAt: { + 1200: 4, + }, + margin: { + x: 20, + y: 20, + } + + }); decodeBuild(url_tag); + builder_graph_init(); } -//load_init(init3); (async function() { let load_promises = [ load_init(), load_ing_init(), load_tome_init() ]; await Promise.all(load_promises); diff --git a/js/builder_graph.js b/js/builder_graph.js index 4fcd47b..083eb32 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -1,22 +1,180 @@ - - -class BuildEncodeNode extends ComputeNode { - constructor() { - super("builder-encode"); +/** + * Node for getting an item's stats from an item input field. + * + * Signature: ItemInputNode() => Item | null + */ +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); } compute_func(input_map) { - if (input_map.size !== 1) { throw "BuildEncodeNode accepts exactly one input (build)"; } - const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element - return encodeBuild(build); + // built on the assumption of no one will type in CI/CR letter by letter + + let item_text = this.input_field.value; + if (!item_text) { + return this.none_item; + } + + let item; + + 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)); + } + + if (item) { + 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'); + } + if (type_match) { return item; } + } + return null; } } -class URLUpdateNode extends ComputeNode { - constructor() { - super("builder-url-update"); +/** + * 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; } + 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 + + 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'); + + 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'); + } + this.level_field.textContent = item.statMap.get('lvl'); + this.image.classList.add(tier + "-shadow"); + return null; + } +} + +/** + * 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; + } + + 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'); + } +} + +/** + * Encode the build into a url-able string. + * + * Signature: BuildEncodeNode(build: Build, + helmet-powder: List[powder], + chestplate-powder: List[powder], + leggings-powder: List[powder], + boots-powder: List[powder], + weapon-powder: List[powder]) => str + */ +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') + ]; + // TODO: grr global state for copy button.. + player_build = build; + build_powders = powders; + return encodeBuild(build, powders); + } +} + +/** + * Update the window's URL. + * + * Signature: URLUpdateNode(build_str: str) => null + */ +class URLUpdateNode extends ComputeNode { + constructor() { super("builder-url-update"); } + 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 @@ -24,10 +182,25 @@ class URLUpdateNode extends ComputeNode { } } +/** + * Create a "build" object from a set of equipments. + * Returns a new Build object, or null if all items are NONE items. + * + * TODO: add tomes + * + * 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 + */ class BuildAssembleNode extends ComputeNode { - constructor() { - super("builder-make-build"); - } + constructor() { super("builder-make-build"); } compute_func(input_map) { let equipments = [ @@ -54,11 +227,15 @@ class BuildAssembleNode extends ComputeNode { } } +/** + * 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 + */ class PowderInputNode extends InputNode { - constructor(name, input_field) { - super(name, input_field); - } + constructor(name, input_field) { super(name, input_field); } compute_func(input_map) { // TODO: haha improve efficiency to O(n) dumb @@ -81,6 +258,13 @@ class PowderInputNode extends InputNode { } } +/** + * 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(build: Build) => [Spell, SpellParts] + */ class SpellSelectNode extends ComputeNode { constructor(spell_num) { super("builder-spell"+spell_num+"-select"); @@ -111,10 +295,18 @@ class SpellSelectNode extends ComputeNode { } } +/** + * Compute spell damage of spell parts. + * Currently kinda janky / TODO while we rework the internal rep. of spells. + * + * Signature: SpellDamageCalcNode(weapon-input: Item, + * build: Build, + * weapon-powder: List[powder], + * spell-info: [Spell, SpellParts]) => List[SpellDamage] + */ class SpellDamageCalcNode extends ComputeNode { constructor(spell_num) { super("builder-spell"+spell_num+"-calc"); - this.spell_idx = spell_num; } compute_func(input_map) { @@ -148,6 +340,15 @@ class SpellDamageCalcNode extends ComputeNode { } } + +/** + * Display spell damage from spell parts. + * Currently kinda janky / TODO while we rework the internal rep. of spells. + * + * Signature: SpellDisplayNode(build: Build, + * spell-info: [Spell, SpellParts], + * spell-damage: List[SpellDamage]) => null + */ class SpellDisplayNode extends ComputeNode { constructor(spell_num) { super("builder-spell"+spell_num+"-display"); @@ -168,26 +369,44 @@ class SpellDisplayNode extends ComputeNode { } } +/** + * Display build stats. + * + * Signature: BuildDisplayNode(build: Build) => null + */ +class BuildDisplayNode extends ComputeNode { + constructor(spell_num) { super("builder-stats-display"); } + + compute_func(input_map) { + if (input_map.size !== 1) { throw "BuildDisplayNode accepts exactly one input (build)"; } + const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element + displayBuildStats('overall-stats', build, build_all_display_commands); + displayBuildStats("offensive-stats", build, build_offensive_display_commands); + displaySetBonuses("set-info", build); + } +} + let item_nodes = []; let powder_nodes = []; let spelldmg_nodes = []; -document.addEventListener('DOMContentLoaded', function() { +function builder_graph_init() { // Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff). - for (const [eq, none_item] of zip(equipment_fields, none_items)) { + for (const [eq, display_elem, none_item] of zip3(equipment_fields, build_fields, none_items)) { 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+'-display', input_field, item_image).link_to(item_input); + new ItemInputDisplayNode(eq+'-input-display', eq, item_image).link_to(item_input); + new ItemDisplayNode(eq+'-item-display', display_elem).link_to(item_input); //new PrintNode(eq+'-debug').link_to(item_input); //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); } // weapon image changer node. let weapon_image = document.getElementById("weapon-img"); - new WeaponDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); + new WeaponInputDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); // Level input node. let level_input = new InputNode('level-input', document.getElementById('level-choice')); @@ -198,10 +417,19 @@ document.addEventListener('DOMContentLoaded', function() { build_node.link_to(input); } build_node.link_to(level_input); + new BuildDisplayNode().link_to(build_node, 'build'); + + 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'); for (const input of powder_inputs) { - powder_nodes.push(new PowderInputNode(input, document.getElementById(input))); + let powder_node = new PowderInputNode(input, document.getElementById(input)); + powder_nodes.push(powder_node); + build_encode_node.link_to(powder_node, input); } for (let i = 0; i < 4; ++i) { @@ -222,234 +450,5 @@ document.addEventListener('DOMContentLoaded', function() { } console.log("Set up graph"); - - // Other "main" stuff - // TODO: consolidate and comment - - // Spell dropdowns - for (const i of spell_disp) { - document.querySelector("#"+i+"Avg").addEventListener("click", () => toggle_spell_tab(i)); - } - - // Masonry setup - let masonry = Macy({ - container: "#masonry-container", - columns: 1, - mobileFirst: true, - breakAt: { - 1200: 4, - }, - margin: { - x: 20, - y: 20, - } - - }); - - let search_masonry = Macy({ - container: "#search-results", - columns: 1, - mobileFirst: true, - breakAt: { - 1200: 4, - }, - margin: { - x: 20, - y: 20, - } - - }); -}); - -// autocomplete initialize -function init_autocomplete() { - let dropdowns = new Map(); - for (const eq of equipment_keys) { - if (tome_keys.includes(eq)) { - continue; - } - // build dropdown - let item_arr = []; - if (eq == 'weapon') { - for (const weaponType of weapon_keys) { - for (const weapon of itemLists.get(weaponType)) { - let item_obj = itemMap.get(weapon); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { - continue; - } - item_arr.push(weapon); - } - } - } else { - for (const item of itemLists.get(eq.replace(/[0-9]/g, ''))) { - let item_obj = itemMap.get(item); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { - continue; - } - item_arr.push(item) - } - } - - // create dropdown - dropdowns.set(eq, new autoComplete({ - data: { - src: item_arr - }, - selector: "#"+ eq +"-choice", - wrapper: false, - resultsList: { - maxResults: 1000, - tabSelect: true, - noResults: true, - class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", - element: (list, data) => { - // dynamic result loc - let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); - list.style.top = position.bottom + window.scrollY +"px"; - list.style.left = position.x+"px"; - list.style.width = position.width+"px"; - list.style.maxHeight = position.height * 2 +"px"; - - if (!data.results.length) { - message = document.createElement('li'); - message.classList.add('scaled-font'); - message.textContent = "No results found!"; - list.prepend(message); - } - }, - }, - resultItem: { - class: "scaled-font search-item", - selected: "dark-5", - element: (item, data) => { - item.classList.add(itemMap.get(data.value).tier); - }, - }, - events: { - input: { - selection: (event) => { - if (event.detail.selection.value) { - event.target.value = event.detail.selection.value; - } - event.target.dispatchEvent(new Event('input')); - }, - }, - } - })); - } - - for (const eq of tome_keys) { - // build dropdown - let tome_arr = []; - for (const tome of tomeLists.get(eq.replace(/[0-9]/g, ''))) { - let tome_obj = tomeMap.get(tome); - if (tome_obj["restrict"] && tome_obj["restrict"] === "DEPRECATED") { - continue; - } - //this should suffice for tomes - jank - if (tome_obj["name"].includes('No ' + eq.charAt(0).toUpperCase())) { - continue; - } - let tome_name = tome; - tome_arr.push(tome_name); - } - - // create dropdown - dropdowns.set(eq, new autoComplete({ - data: { - src: tome_arr - }, - selector: "#"+ eq +"-choice", - wrapper: false, - resultsList: { - maxResults: 1000, - tabSelect: true, - noResults: true, - class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", - element: (list, data) => { - // dynamic result loc - let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); - list.style.top = position.bottom + window.scrollY +"px"; - list.style.left = position.x+"px"; - list.style.width = position.width+"px"; - list.style.maxHeight = position.height * 2 +"px"; - - if (!data.results.length) { - message = document.createElement('li'); - message.classList.add('scaled-font'); - message.textContent = "No results found!"; - list.prepend(message); - } - }, - }, - resultItem: { - class: "scaled-font search-item", - selected: "dark-5", - element: (tome, data) => { - tome.classList.add(tomeMap.get(data.value).tier); - }, - }, - events: { - input: { - selection: (event) => { - if (event.detail.selection.value) { - event.target.value = event.detail.selection.value; - } - }, - }, - } - })); - } - - let filter_loc = ["filter1", "filter2", "filter3", "filter4"]; - for (const i of filter_loc) { - dropdowns.set(i+"-choice", new autoComplete({ - data: { - src: sq2ItemFilters, - }, - selector: "#"+i+"-choice", - wrapper: false, - resultsList: { - tabSelect: true, - noResults: true, - class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", - element: (list, data) => { - // dynamic result loc - console.log(i); - list.style.zIndex = "100"; - let position = document.getElementById(i+"-dropdown").getBoundingClientRect(); - window_pos = document.getElementById("search-container").getBoundingClientRect(); - list.style.top = position.bottom - window_pos.top + 5 +"px"; - list.style.left = position.x - window_pos.x +"px"; - list.style.width = position.width+"px"; - - if (!data.results.length) { - message = document.createElement('li'); - message.classList.add('scaled-font'); - message.textContent = "No filters found!"; - list.prepend(message); - } - }, - }, - resultItem: { - class: "scaled-font search-item", - selected: "dark-5", - }, - events: { - input: { - selection: (event) => { - if (event.detail.selection.value) { - event.target.value = event.detail.selection.value; - } - }, - }, - } - })); - } } + diff --git a/js/computation_graph.js b/js/computation_graph.js index fc5ad46..11113bd 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -22,10 +22,10 @@ class ComputeNode { * Request update of this compute node. Pushes updates to children. */ update() { - if (!this.dirty) { + if (this.inputs_dirty_count != 0) { return; } - if (this.inputs_dirty_count != 0) { + if (!this.dirty) { return; } let calc_inputs = new Map(); @@ -128,6 +128,9 @@ class PrintNode extends ComputeNode { /** * Node for getting an input from an input field. + * Fires updates whenever the input field is updated. + * + * Signature: InputNode() => str */ class InputNode extends ComputeNode { constructor(name, input_field) { @@ -141,110 +144,3 @@ class InputNode extends ComputeNode { return this.input_field.value; } } - -/** - * Node for getting an item's stats from an item input field. - */ -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); - } - - compute_func(input_map) { - // built on the assumption of no one will type in CI/CR letter by letter - - let item_text = this.input_field.value; - if (!item_text) { - return this.none_item; - } - - let item; - - 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)); - } - - if (item) { - 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'); - } - if (type_match) { return item; } - } - return null; - } -} - -/** - * Node for updating item input fields from parsed items. - */ -class ItemInputDisplayNode extends ComputeNode { - - constructor(name, item_input_field, item_image) { - super(name); - this.input_field = item_input_field; - this.image = item_image; - this.fail_cb = true; - } - - 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 - - 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'); - - 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); - this.image.classList.add(tier + "-shadow"); - return null; - } -} - -/** - * Change the weapon to match correct type. - */ -class WeaponDisplayNode extends ComputeNode { - - constructor(name, image_field) { - super(name); - this.image = image_field; - } - - 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'); - } -} diff --git a/js/display.js b/js/display.js index 0ef1c02..76f85cf 100644 --- a/js/display.js +++ b/js/display.js @@ -46,7 +46,7 @@ function displaySetBonuses(parent_id,build) { } for (const [setName, count] of build.activeSetCounts) { - const active_set = sets[setName]; + const active_set = sets.get(setName); if (active_set["hidden"]) { continue; } let set_elem = document.createElement('p'); @@ -353,12 +353,12 @@ function displayExpandedItem(item, parent_id){ */ //allow the plus minus element to toggle upon click: âž•âž– - let plusminus = document.createElement("div"); - plusminus.id = parent_div.id.split("-")[0] + "-pm"; - plusminus.classList.add("col", "plus_minus", "text_end"); - plusminus.style.flexGrow = 0; - plusminus.textContent = "\u2795"; - row.appendChild(plusminus); + //let plusminus = document.createElement("div"); + //plusminus.id = parent_div.id.split("-")[0] + "-pm"; + //plusminus.classList.add("col", "plus_minus", "text_end"); + //plusminus.style.flexGrow = 0; + //plusminus.textContent = "\u2795"; + //row.appendChild(plusminus); if (item.get("custom")) { a_elem.href = "../custom/#" + item.get("hash"); @@ -595,12 +595,17 @@ function displayExpandedItem(item, parent_id){ } } -/* Displays stats about a recipe that are NOT displayed in the craft stats. -* Includes: mat name and amounts -* ingred names in an "array" with ingred effectiveness +/* +* Displays stats about a recipe that are NOT displayed in the craft stats. +* Includes: mat name and amounts, ingred names in an "array" with ingred effectiveness */ function displayRecipeStats(craft, parent_id) { let elem = document.getElementById(parent_id); + if (!elem.classList.contains("col")) { + elem.classList.add("col"); + } + + //local vars elem.textContent = ""; recipe = craft["recipe"]; mat_tiers = craft["mat_tiers"]; @@ -610,30 +615,33 @@ function displayRecipeStats(craft, parent_id) { } let effectiveness = craft["statMap"].get("ingredEffectiveness"); - let ldiv = document.createElement("div"); - ldiv.classList.add("itemleft"); - let title = document.createElement("p"); - title.classList.add("smalltitle"); + let title = document.createElement("div"); + title.classList.add("row", "box-title", "fw-bold", "justify-content-center"); title.textContent = "Recipe Stats"; - ldiv.appendChild(title); - let mats = document.createElement("p"); - mats.classList.add("itemp"); + elem.appendChild(title); + + let mats = document.createElement("div"); + mats.classList.add("row"); mats.textContent = "Crafting Materials: "; + elem.appendChild(mats); + for (let i = 0; i < 2; i++) { let tier = mat_tiers[i]; - let row = document.createElement("p"); - row.classList.add("left"); - let b = document.createElement("b"); + let row = document.createElement("div"); + row.classList.add("row", "px-0", "mx-0"); + let b = document.createElement("div"); let mat = recipe.get("materials")[i]; b.textContent = "- " + mat.get("amount") + "x " + mat.get("item").split(" ").slice(1).join(" "); - b.classList.add("space"); - let starsB = document.createElement("b"); - starsB.classList.add("T1-bracket"); - starsB.textContent = "["; + b.classList.add("col"); row.appendChild(b); + + let starsB = document.createElement("div"); + starsB.classList.add("T1-bracket", "col-auto", "px-0"); + starsB.textContent = "["; row.appendChild(starsB); for(let j = 0; j < 3; j ++) { - let star = document.createElement("b"); + let star = document.createElement("div"); + star.classList.add("col-auto", "px-0"); star.textContent = "\u272B"; if(j < tier) { star.classList.add("T1"); @@ -642,51 +650,57 @@ function displayRecipeStats(craft, parent_id) { } row.append(star); } - let starsE = document.createElement("b"); - starsE.classList.add("T1-bracket"); + let starsE = document.createElement("div"); + starsE.classList.add("T1-bracket", "col-auto", "px-0"); starsE.textContent = "]"; row.appendChild(starsE); - mats.appendChild(row); - } - ldiv.appendChild(mats); - let ingredTable = document.createElement("table"); - ingredTable.classList.add("itemtable"); - ingredTable.classList.add("ingredTable"); + elem.appendChild(row); + } + + let ingredTable = document.createElement("div"); + ingredTable.classList.add("row"); + for (let i = 0; i < 3; i++) { - let row = document.createElement("tr"); + let row = document.createElement("div"); + row.classList.add("row", "g-1", "justify-content-center"); + + for (let j = 0; j < 2; j++) { + if (j == 1) { + let spacer = document.createElement("div"); + spacer.classList.add("col-1"); + row.appendChild(spacer); + } let ingredName = ingreds[2 * i + j]; - let cell = document.createElement("td"); - cell.style.minWidth = "50%"; - cell.classList.add("center"); - cell.classList.add("box"); - cell.classList.add("tooltip"); - let b = document.createElement("b"); - b.textContent = ingredName; - b.classList.add("space"); - let eff = document.createElement("b"); + let col = document.createElement("div"); + col.classList.add("col-5", "rounded", "dark-6", "border", "border-3", "dark-shadow"); + + let temp_row = document.createElement("div"); + temp_row.classList.add("row"); + col.appendChild(temp_row); + + let ingred_div = document.createElement("div"); + ingred_div.classList.add("col"); + ingred_div.textContent = ingredName; + temp_row.appendChild(ingred_div); + + let eff_div = document.createElement("div"); + eff_div.classList.add("col-auto"); let e = effectiveness[2 * i + j]; if (e > 0) { - eff.classList.add("positive"); + eff_div.classList.add("positive"); } else if (e < 0) { - eff.classList.add("negative"); + eff_div.classList.add("negative"); } - eff.textContent = "[" + e + "%]"; - cell.appendChild(b); - cell.appendChild(eff); - row.appendChild(cell); + eff_div.textContent = "[" + e + "%]"; - let tooltip = document.createElement("div"); - tooltip.classList.add("tooltiptext"); - tooltip.classList.add("ing-tooltip"); - tooltip.classList.add("center"); - tooltip.id = "tooltip-" + (2*i + j); - cell.appendChild(tooltip); + temp_row.appendChild(eff_div); + + row.appendChild(col); } ingredTable.appendChild(row); } - elem.appendChild(ldiv); elem.appendChild(ingredTable); } @@ -696,23 +710,14 @@ function displayCraftStats(craft, parent_id) { displayExpandedItem(mock_item,parent_id); } -//Displays an ingredient in item format. However, an ingredient is too far from a normal item to display as one. +/* +* Displays an ingredient in item format. +* However, an ingredient is too far from a normal item to display as one. +*/ function displayExpandedIngredient(ingred, parent_id) { let parent_elem = document.getElementById(parent_id); parent_elem.textContent = ""; - let display_order = [ - "#cdiv", - "displayName", //tier will be displayed w/ name - "#table", - "ids", - "#ldiv", - "posMods", - "itemIDs", - "consumableIDs", - "#ldiv", - "lvl", - "skills", - ] + let item_order = [ "dura", "strReq", @@ -787,83 +792,85 @@ function displayExpandedIngredient(ingred, parent_id) { let active_elem; let elemental_format = false; let style; - for (const command of display_order) { - if (command.charAt(0) === "#") { - if (command === "#cdiv") { - active_elem = document.createElement('div'); - active_elem.classList.add('itemcenter'); + for (const command of sq2_ing_display_order) { + if (command.charAt(0) === "!") { + // TODO: This is sooo incredibly janky..... + if (command === "!elemental") { + elemental_format = !elemental_format; } - else if (command === "#ldiv") { - active_elem = document.createElement('div'); - active_elem.classList.add('itemleft'); + else if (command === "!spacer") { + let spacer = document.createElement('div'); + spacer.classList.add("row", "my-2"); + parent_elem.appendChild(spacer); + continue; } - else if (command === "#table") { - active_elem = document.createElement('table'); - active_elem.classList.add('itemtable'); - } - parent_elem.appendChild(active_elem); - }else { - let p_elem = document.createElement("p"); - p_elem.classList.add("left"); + } else { + let div = document.createElement("div"); + div.classList.add("row"); if (command === "displayName") { - p_elem.classList.add("title"); - p_elem.classList.remove("left"); - let title_elem = document.createElement("b"); + div.classList.add("box-title"); + let title_elem = document.createElement("div"); + title_elem.classList.add("col-auto", "justify-content-center", "pr-1"); title_elem.textContent = ingred.get("displayName"); - p_elem.appendChild(title_elem); - - let space = document.createElement("b"); - space.classList.add("space"); - p_elem.appendChild(space); + div.appendChild(title_elem); let tier = ingred.get("tier"); //tier in [0,3] let begin = document.createElement("b"); - begin.classList.add("T"+tier+"-bracket"); + begin.classList.add("T"+tier+"-bracket", "col-auto", "px-0"); begin.textContent = "["; - p_elem.appendChild(begin); + div.appendChild(begin); for (let i = 0; i < 3; i++) { let tier_elem = document.createElement("b"); - if(i < tier) {tier_elem.classList.add("T"+tier)} - else {tier_elem.classList.add("T0")} + if (i < tier) { + tier_elem.classList.add("T"+tier); + } else { + tier_elem.classList.add("T0"); + } + tier_elem.classList.add("px-0", "col-auto"); tier_elem.textContent = "\u272B"; - p_elem.appendChild(tier_elem); + div.appendChild(tier_elem); } let end = document.createElement("b"); - end.classList.add("T"+tier+"-bracket"); + end.classList.add("T"+tier+"-bracket", "px-0", "col-auto"); end.textContent = "]"; - p_elem.appendChild(end); + div.appendChild(end); }else if (command === "lvl") { - p_elem.textContent = "Crafting Lvl Min: " + ingred.get("lvl"); + div.textContent = "Crafting Lvl Min: " + ingred.get("lvl"); }else if (command === "posMods") { for (const [key,value] of ingred.get("posMods")) { - let p = document.createElement("p"); - p.classList.add("nomarginp"); + let posModRow = document.createElement("div"); + posModRow.classList.add("row"); if (value != 0) { - let title = document.createElement("b"); - title.textContent = posModPrefixes[key]; - let val = document.createElement("b"); + let posMod = document.createElement("div"); + posMod.classList.add("col-auto"); + posMod.textContent = posModPrefixes[key]; + posModRow.appendChild(posMod); + + let val = document.createElement("div"); + val.classList.add("col-auto", "px-0"); val.textContent = value + posModSuffixes[key]; if(value > 0) { val.classList.add("positive"); } else { val.classList.add("negative"); } - p.appendChild(title); - p.appendChild(val); - p_elem.appendChild(p); + posModRow.appendChild(val); + div.appendChild(posModRow); } } } else if (command === "itemIDs") { //dura, reqs for (const [key,value] of ingred.get("itemIDs")) { - let p = document.createElement("p"); - p.classList.add("nomarginp"); + let idRow = document.createElement("div"); + idRow.classList.add("row"); if (value != 0) { - let title = document.createElement("b"); + let title = document.createElement("div"); + title.classList.add("col-auto"); title.textContent = itemIDPrefixes[key]; - p.appendChild(title); + idRow.appendChild(title); } - let desc = document.createElement("b"); + let desc = document.createElement("div"); + desc.classList.add("col-auto"); if(value > 0) { if(key !== "dura") { desc.classList.add("negative"); @@ -880,20 +887,22 @@ function displayExpandedIngredient(ingred, parent_id) { desc.textContent = value; } if(value != 0){ - p.appendChild(desc); + idRow.appendChild(desc); } - p_elem.append(p); + div.appendChild(idRow); } } else if (command === "consumableIDs") { //dura, charges for (const [key,value] of ingred.get("consumableIDs")) { - let p = document.createElement("p"); - p.classList.add("nomarginp"); + let idRow = document.createElement("div"); + idRow.classList.add("row"); if (value != 0) { - let title = document.createElement("b"); + let title = document.createElement("div"); + title.classList.add("col-auto"); title.textContent = consumableIDPrefixes[key]; - p.appendChild(title); + idRow.appendChild(title); } - let desc = document.createElement("b"); + let desc = document.createElement("div"); + desc.classList.add("col-auto"); if(value > 0) { desc.classList.add("positive"); desc.textContent = "+"+value; @@ -902,32 +911,41 @@ function displayExpandedIngredient(ingred, parent_id) { desc.textContent = value; } if(value != 0){ - p.appendChild(desc); - let suffix = document.createElement("b"); + idRow.appendChild(desc); + let suffix = document.createElement("div"); + suffix.classList.add("col-auto"); suffix.textContent = consumableIDSuffixes[key]; - p.appendChild(suffix); + idRow.appendChild(suffix); } - p_elem.append(p); + div.appendChild(idRow); } }else if (command === "skills") { - p_elem.textContent = "Used in:"; + let row = document.createElement("div"); + row.classList.add("row"); + let title = document.createElement("div"); + title.classList.add("row"); + title.textContent = "Used in:"; + row.appendChild(title); for(const skill of ingred.get("skills")) { - let p = document.createElement("p"); - p.textContent = skill.charAt(0) + skill.substring(1).toLowerCase(); - p.classList.add("left"); - p_elem.append(p); + let skill_div = document.createElement("div"); + skill_div.classList.add("row"); + skill_div.textContent = skill.charAt(0) + skill.substring(1).toLowerCase(); + row.appendChild(skill_div); } + div.appendChild(row); } else if (command === "ids") { //warp for (let [key,value] of ingred.get("ids").get("maxRolls")) { if (value !== undefined && value != 0) { - let row = displayRolledID(ingred.get("ids"), key, false, "auto"); - active_elem.appendChild(row); + let row = displayRolledID(ingred.get("ids"), key, elemental_format); + row.classList.remove("col"); + row.classList.remove("col-12"); + div.appendChild(row); } } } else {//this shouldn't be happening } - active_elem.appendChild(p_elem); + parent_elem.appendChild(div); } } } @@ -1083,29 +1101,21 @@ function displayPoisonDamage(overallparent_elem, build) { overallparent_elem.append(overallpoisonDamage); } -function displayEquipOrder(parent_elem,buildOrder){ +function displayEquipOrder(parent_elem, buildOrder){ parent_elem.textContent = ""; const order = buildOrder.slice(); - let title_elem = document.createElement("p"); + let title_elem = document.createElement("b"); title_elem.textContent = "Equip order "; - title_elem.classList.add("title"); - title_elem.classList.add("Normal"); - title_elem.classList.add("itemp"); + title_elem.classList.add("Normal", "text-center"); parent_elem.append(title_elem); - parent_elem.append(document.createElement("br")); for (const item of order) { - let p_elem = document.createElement("p"); - p_elem.classList.add("itemp"); - p_elem.classList.add("left"); + let p_elem = document.createElement("b"); p_elem.textContent = item.get("displayName"); parent_elem.append(p_elem); } } - -function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ - console.log("Melee Stats"); - console.log(meleeStats); +function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { let tooltipinfo = meleeStats[13]; let attackSpeeds = ["Super Slow", "Very Slow", "Slow", "Normal", "Fast", "Very Fast", "Super Fast"]; //let damagePrefixes = ["Neutral Damage: ","Earth Damage: ","Thunder Damage: ","Water Damage: ","Fire Damage: ","Air Damage: "]; @@ -1123,7 +1133,7 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ stats[i][j] = stats[i][j].toFixed(2); } } - for (let i = 8; i < 11; ++i){ + for (let i = 8; i < 11; ++i) { stats[i] = stats[i].toFixed(2); } //tooltipelem, tooltiptext @@ -1132,24 +1142,18 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ //title let title_elem = document.createElement("p"); title_elem.classList.add("title"); - title_elem.classList.add("Normal"); - title_elem.classList.add("itemp"); title_elem.textContent = "Melee Stats"; parent_elem.append(title_elem); parent_elem.append(document.createElement("br")); //overall title - let title_elemavg = document.createElement("p"); - title_elemavg.classList.add("smalltitle"); - title_elemavg.classList.add("Normal"); + let title_elemavg = document.createElement("b"); title_elemavg.textContent = "Melee Stats"; overallparent_elem.append(title_elemavg); //average DPS let averageDamage = document.createElement("p"); averageDamage.classList.add("left"); - averageDamage.classList.add("itemp"); - averageDamage.classList.add("tooltip"); averageDamage.textContent = "Average DPS: " + stats[10]; tooltiptext = `= ((${stats[8]} * ${(stats[6][2]).toFixed(2)}) + (${stats[9]} * ${(stats[7][2]).toFixed(2)}))` tooltip = createTooltip(tooltip, "p", tooltiptext, averageDamage, ["melee-tooltip"]); @@ -1158,14 +1162,12 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ //overall average DPS let overallaverageDamage = document.createElement("p"); - overallaverageDamage.classList.add("itemp"); - let overallaverageDamageFirst = document.createElement("b"); + let overallaverageDamageFirst = document.createElement("span"); overallaverageDamageFirst.textContent = "Average DPS: " - let overallaverageDamageSecond = document.createElement("b"); + let overallaverageDamageSecond = document.createElement("span"); overallaverageDamageSecond.classList.add("Damage"); overallaverageDamageSecond.textContent = stats[10]; - tooltip = createTooltip(tooltip, "p", tooltiptext, overallaverageDamage, ["melee-tooltip", "summary-tooltip"]); overallaverageDamage.appendChild(overallaverageDamageFirst); overallaverageDamage.appendChild(overallaverageDamageSecond); @@ -1175,18 +1177,15 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ //attack speed let atkSpd = document.createElement("p"); atkSpd.classList.add("left"); - atkSpd.classList.add("itemp"); atkSpd.textContent = "Attack Speed: " + attackSpeeds[stats[11]]; parent_elem.append(atkSpd); parent_elem.append(document.createElement("br")); //overall attack speed let overallatkSpd = document.createElement("p"); - overallatkSpd.classList.add("center"); - overallatkSpd.classList.add("itemp"); - let overallatkSpdFirst = document.createElement("b"); + let overallatkSpdFirst = document.createElement("span"); overallatkSpdFirst.textContent = "Attack Speed: "; - let overallatkSpdSecond = document.createElement("b"); + let overallatkSpdSecond = document.createElement("span"); overallatkSpdSecond.classList.add("Damage"); overallatkSpdSecond.textContent = attackSpeeds[stats[11]]; overallatkSpd.appendChild(overallatkSpdFirst); @@ -1196,11 +1195,10 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ //Non-Crit: n->elem, total dmg, DPS let nonCritStats = document.createElement("p"); nonCritStats.classList.add("left"); - nonCritStats.classList.add("itemp"); nonCritStats.textContent = "Non-Crit Stats: "; nonCritStats.append(document.createElement("br")); - for (let i = 0; i < 6; i++){ - if(stats[i][1] != 0){ + for (let i = 0; i < 6; i++) { + if (stats[i][1] != 0) { let dmg = document.createElement("p"); dmg.textContent = stats[i][0] + " \u2013 " + stats[i][1]; dmg.classList.add(damageClasses[i]); @@ -1213,7 +1211,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ let normalDamage = document.createElement("p"); normalDamage.textContent = "Total: " + stats[6][0] + " \u2013 " + stats[6][1]; - normalDamage.classList.add("itemp"); let tooltiparr = ["Min: = ", "Max: = "] let arr = []; let arr2 = []; for (let i = 0; i < 6; i++) { @@ -1228,7 +1225,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ let normalDPS = document.createElement("p"); normalDPS.textContent = "Normal DPS: " + stats[8]; - normalDPS.classList.add("itemp"); normalDPS.classList.add("tooltip"); tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; tooltip = createTooltip(tooltip, "p", tooltiptext, normalDPS, ["melee-tooltip"]); @@ -1236,14 +1232,13 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ //overall average DPS let singleHitDamage = document.createElement("p"); - singleHitDamage.classList.add("itemp"); - let singleHitDamageFirst = document.createElement("b"); + let singleHitDamageFirst = document.createElement("span"); singleHitDamageFirst.textContent = "Single Hit Average: "; - let singleHitDamageSecond = document.createElement("b"); + let singleHitDamageSecond = document.createElement("span"); singleHitDamageSecond.classList.add("Damage"); singleHitDamageSecond.textContent = stats[12].toFixed(2); tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${stats[6][2].toFixed(2)} + ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${stats[7][2].toFixed(2)}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, singleHitDamage, ["melee-tooltip", "summary-tooltip"]); + // tooltip = createTooltip(tooltip, "p", tooltiptext, singleHitDamage, ["melee-tooltip", "summary-tooltip"]); singleHitDamage.appendChild(singleHitDamageFirst); singleHitDamage.appendChild(singleHitDamageSecond); @@ -1251,7 +1246,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ let normalChance = document.createElement("p"); normalChance.textContent = "Non-Crit Chance: " + (stats[6][2]*100).toFixed(2) + "%"; - normalChance.classList.add("itemp"); normalChance.append(document.createElement("br")); normalChance.append(document.createElement("br")); nonCritStats.append(normalChance); @@ -1262,7 +1256,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ //Crit: n->elem, total dmg, DPS let critStats = document.createElement("p"); critStats.classList.add("left"); - critStats.classList.add("itemp"); critStats.textContent = "Crit Stats: "; critStats.append(document.createElement("br")); for (let i = 0; i < 6; i++){ @@ -1278,7 +1271,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ } let critDamage = document.createElement("p"); critDamage.textContent = "Total: " + stats[7][0] + " \u2013 " + stats[7][1]; - critDamage.classList.add("itemp"); tooltiparr = ["Min: = ", "Max: = "] arr = []; arr2 = []; for (let i = 0; i < 6; i++) { @@ -1294,14 +1286,12 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats){ let critDPS = document.createElement("p"); critDPS.textContent = "Crit DPS: " + stats[9]; - critDPS.classList.add("itemp"); tooltiptext = ` = ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; tooltip = createTooltip(tooltip, "p", tooltiptext, critDPS, ["melee-tooltip"]); critStats.append(critDPS); let critChance = document.createElement("p"); critChance.textContent = "Crit Chance: " + (stats[7][2]*100).toFixed(2) + "%"; - critChance.classList.add("itemp"); critChance.append(document.createElement("br")); critChance.append(document.createElement("br")); critStats.append(critChance); @@ -2249,6 +2239,7 @@ function stringPDF(id,val,base,amp) { document.getElementById(id + "-pdf").appendChild(b2); document.getElementById(id + "-pdf").appendChild(b3); } + function stringCDF(id,val,base,amp) { let p; let min; let max; let minr; let maxr; let minround; let maxround; if (base > 0) { diff --git a/js/utils.js b/js/utils.js index 164543d..5691ac9 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1,7 +1,8 @@ let getUrl = window.location; const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1]; -const zip = (a, b) => a.map((k, i) => [k, b[i]]); +const zip2 = (a, b) => a.map((k, i) => [k, b[i]]); +const zip3 = (a, b, c) => a.map((k, i) => [k, b[i], c[i]]); function clamp(num, low, high){ return Math.min(Math.max(num, low), high); From 7980980f9a806fbc89f3cabfb3663af77a792e6d Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 13:59:16 -0700 Subject: [PATCH 029/155] Build display working, still have to do edit ids / skillpoints --- builder/index.html | 1 - js/build.js | 10 +++--- js/build_constants.js | 1 + js/build_utils.js | 6 ++-- js/builder_graph.js | 6 ++++ js/display.js | 67 +++++++---------------------------------- js/display_constants.js | 63 ++++++++++++++++++++++++++++++-------- js/load_tome.js | 2 +- 8 files changed, 78 insertions(+), 78 deletions(-) diff --git a/builder/index.html b/builder/index.html index 73e20b1..283d301 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1391,7 +1391,6 @@ - diff --git a/js/build.js b/js/build.js index 00300c1..f451ff8 100644 --- a/js/build.js +++ b/js/build.js @@ -221,19 +221,19 @@ class Build{ //EHP let ehp = [totalHp, totalHp]; let defMult = classDefenseMultipliers.get(this.weapon.statMap.get("type")); - ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); - ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); + ehp[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); + ehp[1] /= (1-def_pct)*(2-defMult); defenseStats.push(ehp); //HPR let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.); defenseStats.push(totalHpr); //EHPR let ehpr = [totalHpr, totalHpr]; - ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); - ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); + ehpr[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); + ehpr[1] /= (1-def_pct)*(2-defMult); defenseStats.push(ehpr); //skp stats - defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*100, agi_pct*100]); + 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 diff --git a/js/build_constants.js b/js/build_constants.js index 820beb7..eeaae5d 100644 --- a/js/build_constants.js +++ b/js/build_constants.js @@ -101,6 +101,7 @@ let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots']; let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace']; let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon']; let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon']; +let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1']; let spell_disp = ['spell0-info', 'spell1-info', 'spell2-info', 'spell3-info']; let other_disp = ['build-order', 'set-info', 'int-info']; diff --git a/js/build_utils.js b/js/build_utils.js index 4da3309..bbb2d89 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -51,16 +51,16 @@ const armorTypes = [ "helmet", "chestplate", "leggings", "boots" ]; const accessoryTypes = [ "ring", "bracelet", "necklace" ]; const weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ]; const consumableTypes = [ "potion", "scroll", "food"]; -const tomeTypes = ["armorTome", "weaponTome", "guildTome"]; //"dungeonTome", "gatheringTome", "slayingTome" +const tome_types = ['weaponTome', 'armorTome', 'guildTome']; const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"]; const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ]; //0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27 const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"]; const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula -const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tomeTypes).map(x => x.substring(0,1).toUpperCase() + x.substring(1)); +const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1)); //weaponTypes.push("sword"); //console.log(types) -let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tomeTypes); +let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types); let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ]; let skpReqs = skp_order.map(x => x + "Req"); diff --git a/js/builder_graph.js b/js/builder_graph.js index 083eb32..24c1d2d 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -383,6 +383,12 @@ class BuildDisplayNode extends ComputeNode { displayBuildStats('overall-stats', build, build_all_display_commands); displayBuildStats("offensive-stats", build, build_offensive_display_commands); displaySetBonuses("set-info", build); + let meleeStats = build.getMeleeStats(); + displayMeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); + + displayDefenseStats(document.getElementById("defensive-stats"), build); + + displayPoisonDamage(document.getElementById("build-poison-stats"), build); } } diff --git a/js/display.js b/js/display.js index 76f85cf..3f02bd5 100644 --- a/js/display.js +++ b/js/display.js @@ -1116,7 +1116,6 @@ function displayEquipOrder(parent_elem, buildOrder){ } function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { - let tooltipinfo = meleeStats[13]; let attackSpeeds = ["Super Slow", "Very Slow", "Slow", "Normal", "Fast", "Very Fast", "Super Fast"]; //let damagePrefixes = ["Neutral Damage: ","Earth Damage: ","Thunder Damage: ","Water Damage: ","Fire Damage: ","Air Damage: "]; parent_elem.textContent = ""; @@ -1136,8 +1135,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { for (let i = 8; i < 11; ++i) { stats[i] = stats[i].toFixed(2); } - //tooltipelem, tooltiptext - let tooltip; let tooltiptext; //title let title_elem = document.createElement("p"); @@ -1155,9 +1152,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { let averageDamage = document.createElement("p"); averageDamage.classList.add("left"); averageDamage.textContent = "Average DPS: " + stats[10]; - tooltiptext = `= ((${stats[8]} * ${(stats[6][2]).toFixed(2)}) + (${stats[9]} * ${(stats[7][2]).toFixed(2)}))` - tooltip = createTooltip(tooltip, "p", tooltiptext, averageDamage, ["melee-tooltip"]); - averageDamage.appendChild(tooltip); parent_elem.append(averageDamage); //overall average DPS @@ -1203,31 +1197,16 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { dmg.textContent = stats[i][0] + " \u2013 " + stats[i][1]; dmg.classList.add(damageClasses[i]); dmg.classList.add("itemp"); - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(0,2).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]); nonCritStats.append(dmg); } } let normalDamage = document.createElement("p"); normalDamage.textContent = "Total: " + stats[6][0] + " \u2013 " + stats[6][1]; - let tooltiparr = ["Min: = ", "Max: = "] - let arr = []; let arr2 = []; - for (let i = 0; i < 6; i++) { - if (stats[i][0] != 0) { - arr.push(stats[i][0]); - arr2.push(stats[i][1]); - } - } - tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + "); - tooltip = createTooltip(tooltip, "p", tooltiptext, normalDamage, ["melee-tooltip"]); nonCritStats.append(normalDamage); let normalDPS = document.createElement("p"); normalDPS.textContent = "Normal DPS: " + stats[8]; - normalDPS.classList.add("tooltip"); - tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, normalDPS, ["melee-tooltip"]); nonCritStats.append(normalDPS); //overall average DPS @@ -1237,9 +1216,6 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { let singleHitDamageSecond = document.createElement("span"); singleHitDamageSecond.classList.add("Damage"); singleHitDamageSecond.textContent = stats[12].toFixed(2); - tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${stats[6][2].toFixed(2)} + ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${stats[7][2].toFixed(2)}`; - // tooltip = createTooltip(tooltip, "p", tooltiptext, singleHitDamage, ["melee-tooltip", "summary-tooltip"]); - singleHitDamage.appendChild(singleHitDamageFirst); singleHitDamage.appendChild(singleHitDamageSecond); overallparent_elem.append(singleHitDamage); @@ -1264,30 +1240,15 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { dmg.textContent = stats[i][2] + " \u2013 " + stats[i][3]; dmg.classList.add(damageClasses[i]); dmg.classList.add("itemp"); - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(2,4).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]); critStats.append(dmg); } } let critDamage = document.createElement("p"); critDamage.textContent = "Total: " + stats[7][0] + " \u2013 " + stats[7][1]; - tooltiparr = ["Min: = ", "Max: = "] - arr = []; arr2 = []; - for (let i = 0; i < 6; i++) { - if (stats[i][0] != 0) { - arr.push(stats[i][2]); - arr2.push(stats[i][3]); - } - } - tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + "); - tooltip = createTooltip(tooltip, "p", tooltiptext, critDamage, ["melee-tooltip"]); - critStats.append(critDamage); let critDPS = document.createElement("p"); critDPS.textContent = "Crit DPS: " + stats[9]; - tooltiptext = ` = ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, critDPS, ["melee-tooltip"]); critStats.append(critDPS); let critChance = document.createElement("p"); @@ -1343,8 +1304,6 @@ function displayDefenseStats(parent_elem, build, insertSummary){ statsTable.appendChild(hpRow); } - let tooltip; let tooltiptext; - let defMult = build.statMap.get("defMult"); if (!defMult) {defMult = 1} @@ -1360,9 +1319,6 @@ function displayDefenseStats(parent_elem, build, insertSummary){ boost.textContent = stats[1][0]; boost.classList.add("col"); boost.classList.add("text-end"); - tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - ehpRow.appendChild(ehp); ehpRow.append(boost); @@ -1383,12 +1339,13 @@ function displayDefenseStats(parent_elem, build, insertSummary){ boost.textContent = stats[1][1]; boost.classList.add("col"); boost.classList.add("text-end"); - tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - ehpRow.appendChild(ehp); ehpRow.append(boost); - statsTable.append(ehpRow); + if (insertSummary) { + parent_elem.appendChild(ehpRow) + } else { + statsTable.append(ehpRow); + } //total HPR let hprRow = document.createElement("div"); @@ -1424,12 +1381,14 @@ function displayDefenseStats(parent_elem, build, insertSummary){ boost.textContent = stats[3][0]; boost.classList.add("col"); boost.classList.add("text-end"); - tooltiptext = `= ${stats[2]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - ehprRow.appendChild(ehpr); ehprRow.append(boost); - statsTable.append(ehprRow); + + if (insertSummary) { + parent_elem.appendChild(ehprRow); + } else { + statsTable.appendChild(ehprRow); + } //eledefs let eledefs = stats[5]; @@ -1461,13 +1420,9 @@ function displayDefenseStats(parent_elem, build, insertSummary){ let defPct = build.statMap.get("defBonus")[i]/100; if (defRaw < 0) { defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct; - tooltiptext = `= min(0, ${defRaw} * (1 ${defPct}))` } else { defPct >= 0 ? defPct = "+ " + defPct: defPct = "- " + defPct; - tooltiptext = `= ${defRaw} * (1 ${defPct})` } - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - eledefElemRow.appendChild(boost); if (insertSummary) { diff --git a/js/display_constants.js b/js/display_constants.js index 93f77e6..4901e88 100644 --- a/js/display_constants.js +++ b/js/display_constants.js @@ -209,8 +209,8 @@ let posModSuffixes = { /* * Display commands */ -let build_overall_display_commands = [ - "#table", +let build_all_display_commands = [ + "#defense-stats", "str", "dex", "int", "def", "agi", "mr", "ms", "hprRaw", "hprPct", @@ -235,26 +235,51 @@ let build_overall_display_commands = [ "gXp", "gSpd", ]; -let item_display_commands = [ - "#cdiv", +let build_offensive_display_commands = [ + "str", "dex", "int", "def", "agi", + "mr", "ms", + "sdRaw", "sdPct", + "mdRaw", "mdPct", + "ref", "thorns", + "ls", + "poison", + "expd", + "spd", + "atkTier", + "rainbowRaw", + "!elemental", + "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", + "!elemental", + "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", +]; + +let build_basic_display_commands = [ + '#defense-stats', + // defense stats [hp, ehp, hpr, ] + // "sPot", // base * atkspd + spell raws + // melee potential + // "mPot", // melee% * (base * atkspd) + melee raws + "mr", "ms", + "ls", + "poison", + "spd", + "atkTier", +] + +let sq2_item_display_commands = [ "displayName", - //"type", //REPLACE THIS WITH SKIN - "#ldiv", "atkSpd", - "#ldiv", "!elemental", "hp", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", + "!spacer", "fDef", "wDef", "aDef", "tDef", "eDef", "!elemental", - "#ldiv", "classReq", "lvl", "strReq", "dexReq", "intReq", "defReq","agiReq", - "#ldiv", + "!spacer", "str", "dex", "int", "def", "agi", - "#table", - "str", "dex", "int", "def", "agi", //jank lmao "hpBonus", "hprRaw", "hprPct", "sdRaw", "sdPct", @@ -278,11 +303,25 @@ let item_display_commands = [ "spRegen", "eSteal", "gXp", "gSpd", - "#ldiv", "majorIds", + "!spacer", "slots", + "!spacer", "set", "lore", "quest", "restrict" ]; + +let sq2_ing_display_order = [ + "displayName", //tier will be displayed w/ name + "!spacer", + "ids", + "!spacer", + "posMods", + "itemIDs", + "consumableIDs", + "!spacer", + "lvl", + "skills", +] diff --git a/js/load_tome.js b/js/load_tome.js index 2186a60..62bb135 100644 --- a/js/load_tome.js +++ b/js/load_tome.js @@ -128,7 +128,7 @@ function init_tome_maps() { tomeIDMap = new Map(); tomeRedirectMap = new Map(); - for (const it of tomeTypes) { + for (const it of tome_types) { tomeLists.set(it, []); } From 4a54635e2d9b319dec82e05cad11d8c832469b4f Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 19:07:59 -0700 Subject: [PATCH 030/155] More cleanup --- js/builder_graph.js | 13 +++++++++ js/computation_graph.js | 2 +- js/display.js | 63 ++--------------------------------------- 3 files changed, 16 insertions(+), 62 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index 24c1d2d..663e14a 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -389,9 +389,17 @@ class BuildDisplayNode extends ComputeNode { displayDefenseStats(document.getElementById("defensive-stats"), build); displayPoisonDamage(document.getElementById("build-poison-stats"), build); + displayEquipOrder(document.getElementById("build-order"), build.equip_order); } } +/** + * Set the editble id fields. + */ +class EditableIDSetterNode extends ComputeNode { + +} + let item_nodes = []; let powder_nodes = []; let spelldmg_nodes = []; @@ -455,6 +463,11 @@ function builder_graph_init() { display_node.link_to(calc_node, 'spell-damage'); } + for (const input_node of item_nodes.concat(powder_nodes)) { + input_node.update(); + } + level_input.update(); + console.log("Set up graph"); } diff --git a/js/computation_graph.js b/js/computation_graph.js index 11113bd..68a1717 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -137,7 +137,7 @@ class InputNode extends ComputeNode { super(name); this.input_field = input_field; this.input_field.addEventListener("input", () => calcSchedule(this)); - calcSchedule(this); + //calcSchedule(this); Manually fire first update for better control } compute_func(input_map) { diff --git a/js/display.js b/js/display.js index 3f02bd5..c9ea548 100644 --- a/js/display.js +++ b/js/display.js @@ -35,15 +35,9 @@ function displaySetBonuses(parent_id,build) { let parent_div = document.getElementById(parent_id); let set_summary_elem = document.createElement('p'); - set_summary_elem.classList.add('itemcenter'); - set_summary_elem.textContent = "Set Bonuses:"; + set_summary_elem.classList.add('text-center'); + set_summary_elem.textContent = "Set Bonuses"; parent_div.append(set_summary_elem); - - if (build.activeSetCounts.size) { - parent_div.parentElement.style.display = "block"; - } else { - parent_div.parentElement.style.display = "none"; - } for (const [setName, count] of build.activeSetCounts) { const active_set = sets.get(setName); @@ -76,7 +70,6 @@ function displaySetBonuses(parent_id,build) { } } - function displayBuildStats(parent_id,build,command_group){ // Commands to "script" the creation of nice formatting. // #commands create a new element. @@ -1916,58 +1909,6 @@ function displayAdditionalInfo(elemID, item) { return; } -/** Displays all set bonuses (0/n, 1/n, ... n/n) for a given set - * - * @param {String} parent_id - id of the parent element - * @param {String} setName - the name of the set - */ - function displayAllSetBonuses(parent_id,setName) { - let parent_elem = document.getElementById(parent_id); - parent_elem.style.display = ""; - let set = sets[setName]; - let title_elem = document.createElement("p"); - title_elem.textContent = setName + " Set Bonuses"; - title_elem.classList.add("Set"); - title_elem.classList.add("title"); - parent_elem.appendChild(title_elem); - let grid_elem = document.createElement("div"); - grid_elem.style.display = "flex"; - grid_elem.style.flexDirection = "rows"; - grid_elem.style.flexWrap = "wrap"; - grid_elem.style.gap = "5px"; - parent_elem.appendChild(grid_elem); - - for (let i = 0; i < set.items.length; i++) { - - let set_elem = document.createElement('p'); - set_elem.classList.add("container"); - set_elem.style = "grid-item-"+(i+1); - set_elem.style.maxWidth = "max(180px, 15%)"; - set_elem.id = "set-"+setName+"-"+i; - grid_elem.appendChild(set_elem); - const bonus = set.bonuses[i]; - let mock_item = new Map(); - mock_item.set("fixID", true); - mock_item.set("displayName", setName+" Set: " + (i+1) + "/"+sets[setName].items.length); - set_elem.textContent = mock_item.get("displayName"); - let mock_minRolls = new Map(); - let mock_maxRolls = new Map(); - mock_item.set("minRolls", mock_minRolls); - mock_item.set("maxRolls", mock_maxRolls); - for (const id in bonus) { - if (rolledIDs.includes(id)) { - mock_minRolls.set(id, bonus[id]); - mock_maxRolls.set(id, bonus[id]); - } - else { - mock_item.set(id, bonus[id]); - } - } - mock_item.set("powders", []); - displayExpandedItem(mock_item, set_elem.id); - } - -} /** Displays the individual probabilities of each possible value of each rollable ID for this item. * From 4d7197fc53554482c58ba1fbc1c9af79ce212c34 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 19 Jun 2022 20:20:22 -0700 Subject: [PATCH 031/155] Add instructions --- js/builder_graph.js | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index 663e14a..fb2fc45 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -405,6 +405,8 @@ let powder_nodes = []; let spelldmg_nodes = []; function builder_graph_init() { + // Phase 1/2: Set up item input, propagate updates, etc. + // Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff). for (const [eq, display_elem, none_item] of zip3(equipment_fields, build_fields, none_items)) { let input_field = document.getElementById(eq+"-choice"); @@ -446,9 +448,28 @@ function builder_graph_init() { build_encode_node.link_to(powder_node, input); } + for (const input_node of item_nodes.concat(powder_nodes)) { + input_node.update(); + } + level_input.update(); + + // Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage + + // 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) + // let stat_agg_node = + for (const field of editable_elems) { + // Create nodes that listens to each editable id input, the node name should match the "id" + + // stat_agg_node.link_to( ... ) + } + + // Also do something similar for skill points + for (let i = 0; i < 4; ++i) { let spell_node = new SpellSelectNode(i); spell_node.link_to(build_node, 'build'); + // link and rewrite spell_node to the stat agg node + // spell_node.link_to(stat_agg_node, 'stats') let calc_node = new SpellDamageCalcNode(i); calc_node.link_to(item_nodes[8], 'weapon-input'); @@ -462,11 +483,17 @@ function builder_graph_init() { display_node.link_to(spell_node, 'spell-info'); display_node.link_to(calc_node, 'spell-damage'); } + + // Create a node that binds the build to the edit ids text boxes + // (make it export to the textboxes) + // This node is a bit tricky since it has to make a "weak link" (directly mark dirty and call update() for each of the stat nodes). + // IMPORTANT: mark all children dirty, then update each child, in that order (2 loops). else performance issues will be bad + // let id_exporter_node = ... + // id_exporter_node.link_to(build_node, 'build') - for (const input_node of item_nodes.concat(powder_nodes)) { - input_node.update(); - } - level_input.update(); + // 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 console.log("Set up graph"); } From 18d21e21ae5ddab8a2b0d89a84995d5d099f481c Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 20 Jun 2022 06:12:22 -0700 Subject: [PATCH 032/155] Ready for dev branch merge --- js/build.js | 41 +---- js/build_encode_decode.js | 11 +- js/builder_graph.js | 347 ++++++++++++++++++++++++++++++++++---- js/computation_graph.js | 1 + js/custom.js | 1 + js/display.js | 24 ++- js/skillpoints.js | 1 - 7 files changed, 332 insertions(+), 94 deletions(-) diff --git a/js/build.js b/js/build.js index f451ff8..a655290 100644 --- a/js/build.js +++ b/js/build.js @@ -156,7 +156,8 @@ class Build{ } getBaseSpellCost(spellIdx, cost) { - // old intelligence: cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); + // old intelligence: + cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); cost += this.statMap.get("spRaw"+spellIdx); return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)); } @@ -206,44 +207,6 @@ class Build{ return damages_results.concat([totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS,adjAtkSpd, singleHitTotal]).concat(results[3]); } - /* - Get all defensive stats for this build. - */ - getDefenseStats(){ - const stats = this.statMap; - let defenseStats = []; - let def_pct = skillPointsToPercentage(this.total_skillpoints[3]); - let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]); - //total hp - let totalHp = stats.get("hp") + stats.get("hpBonus"); - if (totalHp < 5) totalHp = 5; - defenseStats.push(totalHp); - //EHP - let ehp = [totalHp, totalHp]; - let defMult = classDefenseMultipliers.get(this.weapon.statMap.get("type")); - ehp[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); - ehp[1] /= (1-def_pct)*(2-defMult); - defenseStats.push(ehp); - //HPR - let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.); - defenseStats.push(totalHpr); - //EHPR - let ehpr = [totalHpr, totalHpr]; - ehpr[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); - ehpr[1] /= (1-def_pct)*(2-defMult); - 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; - } /* Get all stats for this build. Stores in this.statMap. @pre The build itself should be valid. No checking of validity of pieces is done here. diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 12b45b1..834afdd 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -132,12 +132,16 @@ function decodeBuild(url_tag) { for (let i in powder_inputs) { setValue(powder_inputs[i], powdering[i]); } + for (let i in skillpoints) { + console.log(skillpoints[i]); + setValue(skp_order[i] + "-skp", skillpoints[i]); + } } } /* Stores the entire build in a string using B64 encoding and adds it to the URL. */ -function encodeBuild(build, powders) { +function encodeBuild(build, powders, skillpoints) { if (build) { let build_string; @@ -148,7 +152,6 @@ function encodeBuild(build, powders) { tome_string = ""; for (const item of build.items) { - if (item.statMap.get("custom")) { let custom = "CI-"+encodeCustom(item, true); build_string += Base64.fromIntN(custom.length, 3) + custom; @@ -167,8 +170,8 @@ function encodeBuild(build, powders) { } } - for (const skp of skp_order) { - build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 + for (const skp of skillpoints) { + build_string += Base64.fromIntN(skp, 2); // Maximum skillpoints: 2048 } build_string += Base64.fromIntN(build.level, 2); for (const _powderset of powders) { diff --git a/js/builder_graph.js b/js/builder_graph.js index fb2fc45..d1b7292 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -160,10 +160,17 @@ class BuildEncodeNode extends ComputeNode { input_map.get('boots-powder'), input_map.get('weapon-powder') ]; + const skillpoints = [ + input_map.get('str'), + input_map.get('dex'), + input_map.get('int'), + input_map.get('def'), + input_map.get('agi') + ]; // TODO: grr global state for copy button.. player_build = build; build_powders = powders; - return encodeBuild(build, powders); + return encodeBuild(build, powders, skillpoints); } } @@ -295,12 +302,50 @@ class SpellSelectNode extends ComputeNode { } } +/* + * 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]; + let defMult = stats.get("classDef"); + ehp[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); + ehp[1] /= (1-def_pct)*(2-defMult); + defenseStats.push(ehp); + //HPR + let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.); + defenseStats.push(totalHpr); + //EHPR + let ehpr = [totalHpr, totalHpr]; + ehpr[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); + ehpr[1] /= (1-def_pct)*(2-defMult); + 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; +} + /** * Compute spell damage of spell parts. * Currently kinda janky / TODO while we rework the internal rep. of spells. * * Signature: SpellDamageCalcNode(weapon-input: Item, - * build: Build, + * stats: StatMap, * weapon-powder: List[powder], * spell-info: [Spell, SpellParts]) => List[SpellDamage] */ @@ -311,25 +356,31 @@ class SpellDamageCalcNode extends ComputeNode { compute_func(input_map) { const weapon = new Map(input_map.get('weapon-input').statMap); - const build = input_map.get('build'); const weapon_powder = input_map.get('weapon-powder'); const damage_mult = 1; // TODO: hook up const spell_info = input_map.get('spell-info'); const spell_parts = spell_info[1]; + const stats = input_map.get('stats'); + const skillpoints = [ + stats.get('str'), + stats.get('dex'), + stats.get('int'), + stats.get('def'), + stats.get('agi') + ]; weapon.set("powders", weapon_powder); let spell_results = [] - let stats = build.statMap; 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"), - part.multiplier / 100, weapon, build.total_skillpoints, damage_mult); + part.multiplier / 100, weapon, skillpoints, damage_mult); spell_results.push(results); } else if (part.type === "heal") { // TODO: wynn2 formula - let heal_amount = (part.strength * build.getDefenseStats()[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2); + 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); spell_results.push(heal_amount); } else if (part.type === "total") { // TODO: remove "total" type @@ -345,7 +396,7 @@ class SpellDamageCalcNode extends ComputeNode { * Display spell damage from spell parts. * Currently kinda janky / TODO while we rework the internal rep. of spells. * - * Signature: SpellDisplayNode(build: Build, + * Signature: SpellDisplayNode(stats: StatMap, * spell-info: [Spell, SpellParts], * spell-damage: List[SpellDamage]) => null */ @@ -375,29 +426,231 @@ class SpellDisplayNode extends ComputeNode { * Signature: BuildDisplayNode(build: Build) => null */ class BuildDisplayNode extends ComputeNode { - constructor(spell_num) { super("builder-stats-display"); } + constructor() { super("builder-stats-display"); } compute_func(input_map) { - if (input_map.size !== 1) { throw "BuildDisplayNode accepts exactly one input (build)"; } - const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element - displayBuildStats('overall-stats', build, build_all_display_commands); - displayBuildStats("offensive-stats", build, build_offensive_display_commands); + 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); displaySetBonuses("set-info", build); let meleeStats = build.getMeleeStats(); displayMeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); - displayDefenseStats(document.getElementById("defensive-stats"), build); + displayDefenseStats(document.getElementById("defensive-stats"), stats); displayPoisonDamage(document.getElementById("build-poison-stats"), build); displayEquipOrder(document.getElementById("build-order"), build.equip_order); } } +/** + * 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"); + lvlWarning.classList.add("itemp"); + lvlWarning.classList.add("warning"); + lvlWarning.textContent = "WARNING: A level " + build.level + " player cannot use some piece(s) of this build." + } + let baditem = document.createElement("p"); + baditem.classList.add("nocolor"); + baditem.classList.add("itemp"); + baditem.textContent = item.get("displayName") + " requires level " + item_lvl + " to use."; + lvlWarning.appendChild(baditem); + } + } + if(lvlWarning){ + summarybox.append(lvlWarning); + } + for (const [setName, count] of build.activeSetCounts) { + const bonus = sets.get(setName).bonuses[count-1]; + // console.log(setName); + if (bonus["illegal"]) { + let setWarning = document.createElement("p"); + setWarning.classList.add("itemp"); + setWarning.classList.add("warning"); + setWarning.textContent = "WARNING: illegal item combination: " + setName + summarybox.append(setWarning); + } + } + } +} + +/** + * Aggregate stats from the build and from inputs. + * + * Signature: AggregateStatsNode(build: Build, *args) => StatMap + */ +class AggregateStatsNode extends ComputeNode { + constructor() { super("builder-aggregate-stats"); } + + compute_func(input_map) { + const build = input_map.get('build'); + const weapon = input_map.get('weapon'); + const output_stats = new Map(build.statMap); + for (const [k, v] of input_map.entries()) { + if (k === 'build') { + continue; + } + output_stats.set(k, v); + } + output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type"))); + return output_stats; + } +} + /** * Set the editble id fields. + * + * Signature: EditableIDSetterNode(build: Build) => null */ class EditableIDSetterNode extends ComputeNode { + constructor() { super("builder-id-setter"); } + 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) { + document.getElementById(id).value = build.statMap.get(id); + } + } +} + +/** + * 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"); + this.notify_nodes = notify_nodes; + } + + compute_func(input_map) { + console.log("mmm"); + 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; + } } let item_nodes = []; @@ -433,7 +686,6 @@ function builder_graph_init() { build_node.link_to(input); } build_node.link_to(level_input); - new BuildDisplayNode().link_to(build_node, 'build'); let build_encode_node = new BuildEncodeNode(); build_encode_node.link_to(build_node, 'build'); @@ -448,48 +700,69 @@ function builder_graph_init() { build_encode_node.link_to(powder_node, input); } + // Edit IDs setter declared up here to set ids so they will be populated by default. + let edit_id_output = new EditableIDSetterNode(); + edit_id_output.link_to(build_node); + + // Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage + + let build_disp_node = new BuildDisplayNode() + build_disp_node.link_to(build_node, 'build'); + let build_warnings_node = new DisplayBuildWarningsNode(); + build_warnings_node.link_to(build_node, 'build'); + + // 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) + let stat_agg_node = new AggregateStatsNode(); + stat_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); + let edit_input_nodes = []; + for (const field of editable_item_fields) { + // Create nodes that listens to each editable id input, the node name should match the "id" + const elem = document.getElementById(field); + const node = new SumNumberInputNode('builder-'+field+'-input', elem); + + stat_agg_node.link_to(node, field); + edit_input_nodes.push(node); + } + for (const skp of skp_order) { + const elem = document.getElementById(skp+'-skp'); + const node = new SumNumberInputNode('builder-'+skp+'-input', elem); + + stat_agg_node.link_to(node, skp); + build_encode_node.link_to(node, skp); + build_warnings_node.link_to(node, skp); + edit_input_nodes.push(node); + } + build_disp_node.link_to(stat_agg_node, 'stats'); + for (const input_node of item_nodes.concat(powder_nodes)) { input_node.update(); } level_input.update(); - // Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage - - // 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) - // let stat_agg_node = - for (const field of editable_elems) { - // Create nodes that listens to each editable id input, the node name should match the "id" - - // stat_agg_node.link_to( ... ) - } - // Also do something similar for skill points for (let i = 0; i < 4; ++i) { let spell_node = new SpellSelectNode(i); spell_node.link_to(build_node, 'build'); - // link and rewrite spell_node to the stat agg node - // spell_node.link_to(stat_agg_node, 'stats') + // TODO: link and rewrite spell_node to the stat agg node + spell_node.link_to(stat_agg_node, 'stats') let calc_node = new SpellDamageCalcNode(i); - calc_node.link_to(item_nodes[8], 'weapon-input'); - calc_node.link_to(build_node, 'build'); - calc_node.link_to(powder_nodes[4], 'weapon-powder'); - calc_node.link_to(spell_node, 'spell-info'); + calc_node.link_to(item_nodes[8], 'weapon-input').link_to(stat_agg_node, 'stats') + .link_to(powder_nodes[4], 'weapon-powder').link_to(spell_node, 'spell-info'); spelldmg_nodes.push(calc_node); let display_node = new SpellDisplayNode(i); - display_node.link_to(build_node, 'build'); + display_node.link_to(build_node, 'build'); // TODO: same here.. display_node.link_to(spell_node, 'spell-info'); display_node.link_to(calc_node, 'spell-damage'); } + for (const node of edit_input_nodes) { + node.update(); + } - // Create a node that binds the build to the edit ids text boxes - // (make it export to the textboxes) - // This node is a bit tricky since it has to make a "weak link" (directly mark dirty and call update() for each of the stat nodes). - // IMPORTANT: mark all children dirty, then update each child, in that order (2 loops). else performance issues will be bad - // let id_exporter_node = ... - // id_exporter_node.link_to(build_node, 'build') + let skp_output = new SkillPointSetterNode(edit_input_nodes); + skp_output.link_to(build_node); // 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 diff --git a/js/computation_graph.js b/js/computation_graph.js index 68a1717..f99a966 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -94,6 +94,7 @@ class ComputeNode { this.inputs_dirty_count += 1; } parent_node.children.push(this); + return this; } } diff --git a/js/custom.js b/js/custom.js index 50ead5b..8c712af 100644 --- a/js/custom.js +++ b/js/custom.js @@ -175,6 +175,7 @@ function getCustomFromHash(hash) { } } statMap.set("hash", "CI-" + name); + statMap.set("custom", true); return new Custom(statMap); } } catch (error) { diff --git a/js/display.js b/js/display.js index c9ea548..30175c8 100644 --- a/js/display.js +++ b/js/display.js @@ -50,7 +50,7 @@ function displaySetBonuses(parent_id,build) { const bonus = active_set.bonuses[count-1]; let mock_item = new Map(); mock_item.set("fixID", true); - mock_item.set("displayName", setName+" Set: "+count+"/"+sets[setName].items.length); + mock_item.set("displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length); let mock_minRolls = new Map(); let mock_maxRolls = new Map(); mock_item.set("minRolls", mock_minRolls); @@ -70,7 +70,7 @@ function displaySetBonuses(parent_id,build) { } } -function displayBuildStats(parent_id,build,command_group){ +function displayBuildStats(parent_id,build,command_group,stats){ // Commands to "script" the creation of nice formatting. // #commands create a new element. // !elemental is some janky hack for elemental damage. @@ -82,8 +82,6 @@ function displayBuildStats(parent_id,build,command_group){ if (parent_div != null) { setHTML(parent_id, ""); } - - let stats = build.statMap; let active_elem; let elemental_format = false; @@ -96,7 +94,7 @@ function displayBuildStats(parent_id,build,command_group){ if (command.charAt(0) === "#") { if (command === "#defense-stats") { - displayDefenseStats(parent_div, build, true); + displayDefenseStats(parent_div, stats, true); } } if (command.charAt(0) === "!") { @@ -126,7 +124,7 @@ function displayBuildStats(parent_id,build,command_group){ style === "positive" ? style = "negative" : style = "positive"; } if (id === "poison" && id_val > 0) { - id_val = Math.ceil(id_val*build.statMap.get("poisonPct")/100); + id_val = Math.ceil(id_val*stats.get("poisonPct")/100); } displayFixedID(parent_div, id, id_val, elemental_format, style); if (id === "poison" && id_val > 0) { @@ -140,7 +138,7 @@ function displayBuildStats(parent_id,build,command_group){ prefix_elem.textContent = "\u279C With Strength: "; let number_elem = document.createElement('b'); number_elem.classList.add(style); - number_elem.textContent = (id_val * (1+skillPointsToPercentage(build.total_skillpoints[0])) ).toFixed(0) + idSuffixes[id]; + number_elem.textContent = (id_val * (1+skillPointsToPercentage(stats.get('str'))) ).toFixed(0) + idSuffixes[id]; value_elem.append(prefix_elem); value_elem.append(number_elem); row.appendChild(value_elem); @@ -156,7 +154,7 @@ function displayBuildStats(parent_id,build,command_group){ let prefix_elem = document.createElement('b'); prefix_elem.textContent = "\u279C Effective LS: "; - let defStats = build.getDefenseStats(); + let defStats = getDefenseStats(stats); let number_elem = document.createElement('b'); number_elem.classList.add(style); number_elem.textContent = Math.round(defStats[1][0]*id_val/defStats[0]) + "/3s"; @@ -1253,8 +1251,8 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) { parent_elem.append(critStats); } -function displayDefenseStats(parent_elem, build, insertSummary){ - let defenseStats = build.getDefenseStats(); +function displayDefenseStats(parent_elem, statMap, insertSummary){ + let defenseStats = getDefenseStats(statMap); insertSummary = (typeof insertSummary !== 'undefined') ? insertSummary : false; if (!insertSummary) { parent_elem.textContent = ""; @@ -1297,7 +1295,7 @@ function displayDefenseStats(parent_elem, build, insertSummary){ statsTable.appendChild(hpRow); } - let defMult = build.statMap.get("defMult"); + let defMult = statMap.get("defMult"); if (!defMult) {defMult = 1} //EHP @@ -1409,8 +1407,8 @@ function displayDefenseStats(parent_elem, build, insertSummary){ boost.classList.add("col"); boost.classList.add("text-end"); - let defRaw = build.statMap.get("defRaw")[i]; - let defPct = build.statMap.get("defBonus")[i]/100; + let defRaw = statMap.get("defRaw")[i]; + let defPct = statMap.get("defBonus")[i]/100; if (defRaw < 0) { defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct; } else { diff --git a/js/skillpoints.js b/js/skillpoints.js index fcf24b6..5748a00 100644 --- a/js/skillpoints.js +++ b/js/skillpoints.js @@ -38,7 +38,6 @@ function calculate_skillpoints(equipment, weapon) { setCount = 0; activeSetCounts.set(setName, 1); } - console.log(sets); const new_bonus = sets.get(setName).bonuses[setCount]; //let skp_order = ["str","dex","int","def","agi"]; for (const i in skp_order) { From f7a1f4fc7e3be9276428e55ea07f4a7dcb369236 Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 20 Jun 2022 07:37:14 -0700 Subject: [PATCH 033/155] Fix melee damage, fix edit ids spell calc --- builder/index.html | 54 ++++++++++++++++++------------------- js/build.js | 58 ---------------------------------------- js/builder_graph.js | 65 +++++++++++++++++++++++++++++++++++++++------ js/damage_calc.js | 2 +- js/display.js | 55 ++++++++++++++++++++------------------ js/optimize.js | 2 +- 6 files changed, 115 insertions(+), 121 deletions(-) diff --git a/builder/index.html b/builder/index.html index 283d301..2084f43 100644 --- a/builder/index.html +++ b/builder/index.html @@ -619,7 +619,7 @@ Spell Damage %:
- +
Original Value: 0 @@ -630,7 +630,7 @@ Spell Damage Raw:
- +
Original Value: 0 @@ -641,7 +641,7 @@ Melee Damage %:
- +
Original Value: 0 @@ -652,7 +652,7 @@ Melee Damage Raw:
- +
Original Value: 0 @@ -665,7 +665,7 @@ Poison:
- +
Original Value: 0 @@ -676,7 +676,7 @@ Damage %:
- +
Original Value: 0 @@ -687,7 +687,7 @@ Damage %:
- +
Original Value: 0 @@ -698,7 +698,7 @@ Damage %:
- +
Original Value: 0 @@ -711,7 +711,7 @@ Damage %:
- +
Original Value: 0 @@ -722,7 +722,7 @@ Damage %:
- +
Original Value: 0 @@ -733,7 +733,7 @@ + Tier:
- +
Original Value: 0 @@ -752,7 +752,7 @@ Defense %:
- +
Original Value: 0 @@ -763,7 +763,7 @@ Defense %:
- +
Original Value: 0 @@ -774,7 +774,7 @@ Defense %:
- +
Original Value: 0 @@ -785,7 +785,7 @@ Defense %:
- +
Original Value: 0 @@ -798,7 +798,7 @@ Defense %:
- +
Original Value: 0 @@ -809,7 +809,7 @@ Health Regen Raw:
- +
Original Value: 0 @@ -820,7 +820,7 @@ Health Regen %:
- +
Original Value: 0 @@ -831,7 +831,7 @@ Health Bonus:
- +
Original Value: 0 @@ -847,7 +847,7 @@ 1st Spell Cost %:
- +
Original Value: 0 @@ -858,7 +858,7 @@ 2nd Spell Cost %:
- +
Original Value: 0 @@ -869,7 +869,7 @@ 3rd Spell Cost %:
- +
Original Value: 0 @@ -880,7 +880,7 @@ 4th Spell Cost %:
- +
Original Value: 0 @@ -893,7 +893,7 @@ 1st Spell Cost Raw:
- +
Original Value: 0 @@ -904,7 +904,7 @@ 2nd Spell Cost Raw:
- +
Original Value: 0 @@ -915,7 +915,7 @@ 3rd Spell Cost Raw:
- +
Original Value: 0 @@ -926,7 +926,7 @@ 4th Spell Cost Raw:
- +
Original Value: 0 diff --git a/js/build.js b/js/build.js index a655290..78cf2ac 100644 --- a/js/build.js +++ b/js/build.js @@ -149,64 +149,6 @@ class Build{ return [this.equipment,this.weapon,this.tomes].flat(); } - /* Getters */ - - getSpellCost(spellIdx, cost) { - return Math.max(1, this.getBaseSpellCost(spellIdx, cost)); - } - - getBaseSpellCost(spellIdx, cost) { - // old intelligence: - cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); - cost += this.statMap.get("spRaw"+spellIdx); - return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)); - } - - - /* Get melee stats for build. - Returns an array in the order: - */ - getMeleeStats(){ - const stats = this.statMap; - const weapon_stats = this.weapon.statMap; - if (weapon_stats.get("tier") === "Crafted") { - stats.set("damageBases", [weapon_stats.get("nDamBaseHigh"),weapon_stats.get("eDamBaseHigh"),weapon_stats.get("tDamBaseHigh"),weapon_stats.get("wDamBaseHigh"),weapon_stats.get("fDamBaseHigh"),weapon_stats.get("aDamBaseHigh")]); - } - let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier"); - if(adjAtkSpd > 6){ - adjAtkSpd = 6; - }else if(adjAtkSpd < 0){ - adjAtkSpd = 0; - } - - let damage_mult = 1; - 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, this.weapon.statMap, this.total_skillpoints, damage_mult * this.damageMultiplier); - - let dex = this.total_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]); - } - /* Get all stats for this build. Stores in this.statMap. @pre The build itself should be valid. No checking of validity of pieces is done here. diff --git a/js/builder_graph.js b/js/builder_graph.js index d1b7292..29ec4d6 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -260,7 +260,6 @@ class PowderInputNode extends InputNode { } input = input.slice(2); } - //console.log("POWDERING: " + powdering); return powdering; } } @@ -407,7 +406,7 @@ class SpellDisplayNode extends ComputeNode { } compute_func(input_map) { - const build = input_map.get('build'); + const stats = input_map.get('stats'); const spell_info = input_map.get('spell-info'); const damages = input_map.get('spell-damage'); const spell = spell_info[0]; @@ -416,10 +415,60 @@ class SpellDisplayNode extends ComputeNode { const i = this.spell_idx; let parent_elem = document.getElementById("spell"+i+"-info"); let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaySpellDamage(parent_elem, overallparent_elem, build, spell, i+1, spell_parts, damages); + displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, spell_parts, damages); } } +/* Get melee stats for build. + 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; + } + + let damage_mult = 1; + 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]); +} + /** * Display build stats. * @@ -434,7 +483,9 @@ class BuildDisplayNode extends ComputeNode { displayBuildStats('overall-stats', build, build_all_display_commands, stats); displayBuildStats("offensive-stats", build, build_offensive_display_commands, stats); displaySetBonuses("set-info", build); - let meleeStats = build.getMeleeStats(); + let meleeStats = getMeleeStats(stats, build.weapon); + // TODO: move weapon out? + console.log(meleeStats); displayMeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); displayDefenseStats(document.getElementById("defensive-stats"), stats); @@ -541,7 +592,6 @@ class DisplayBuildWarningsNode extends ComputeNode { } for (const [setName, count] of build.activeSetCounts) { const bonus = sets.get(setName).bonuses[count-1]; - // console.log(setName); if (bonus["illegal"]) { let setWarning = document.createElement("p"); setWarning.classList.add("itemp"); @@ -606,7 +656,6 @@ class SkillPointSetterNode extends ComputeNode { } compute_func(input_map) { - console.log("mmm"); 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()) { @@ -656,6 +705,7 @@ class SumNumberInputNode extends InputNode { let item_nodes = []; let powder_nodes = []; let spelldmg_nodes = []; +let edit_input_nodes = []; function builder_graph_init() { // Phase 1/2: Set up item input, propagate updates, etc. @@ -714,7 +764,6 @@ function builder_graph_init() { // 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) let stat_agg_node = new AggregateStatsNode(); stat_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); - let edit_input_nodes = []; for (const field of editable_item_fields) { // Create nodes that listens to each editable id input, the node name should match the "id" const elem = document.getElementById(field); @@ -753,7 +802,7 @@ function builder_graph_init() { spelldmg_nodes.push(calc_node); let display_node = new SpellDisplayNode(i); - display_node.link_to(build_node, 'build'); // TODO: same here.. + display_node.link_to(stat_agg_node, 'stats'); // TODO: same here.. display_node.link_to(spell_node, 'spell-info'); display_node.link_to(calc_node, 'spell-damage'); } diff --git a/js/damage_calc.js b/js/damage_calc.js index 9149a13..eef3825 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -105,7 +105,7 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, let staticBoost = (pctModifier / 100.); let skillBoost = [0]; for (let i in total_skillpoints) { - skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get("damageBonus")[i] / 100.); + skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get(skp_elements[i]+"DamPct") / 100.); } for (let i in damages) { diff --git a/js/display.js b/js/display.js index 30175c8..c87f689 100644 --- a/js/display.js +++ b/js/display.js @@ -941,46 +941,45 @@ function displayExpandedIngredient(ingred, parent_id) { } } -function displayNextCosts(spell, build, weapon) { - let int = build.total_skillpoints[2]; - let spells = spell_table[weapon.get("type")]; +function displayNextCosts(_stats, spell, spellIdx) { + let stats = new Map(_stats); + let intel = stats.get('int'); let row = document.createElement("div"); row.classList.add("spellcost-tooltip"); let init_cost = document.createElement("b"); - init_cost.textContent = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost); + init_cost.textContent = getSpellCost(stats, spellIdx, spell.cost); init_cost.classList.add("Mana"); let arrow = document.createElement("b"); arrow.textContent = "\u279C"; let next_cost = document.createElement("b"); - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1); + next_cost.textContent = (init_cost.textContent === "1" ? 1 : getSpellCost(stats, spellIdx, spell.cost) - 1); next_cost.classList.add("Mana"); let int_needed = document.createElement("b"); if (init_cost.textContent === "1") { int_needed.textContent = ": n/a (+0)"; }else { //do math - let target = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1; - let needed = int; + let target = getSpellCost(stats, spellIdx, spell.cost) - 1; + let needed = intel; let noUpdate = false; //forgive me... I couldn't inverse ceil, floor, and max. - while (build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) > target) { + while (getSpellCost(stats, spellIdx, spell.cost) > target) { if(needed > 150) { noUpdate = true; break; } needed++; - build.total_skillpoints[2] = needed; + stats.set('int', stats.get('int') + 1); } - let missing = needed - int; + let missing = needed - intel; //in rare circumstances, the next spell cost can jump. if (noUpdate) { - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)-1); + next_cost.textContent = (init_cost.textContent === "1" ? 1 : getSpellCost(stats, spellIdx, spell.cost)-1); }else { - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)); + next_cost.textContent = (init_cost.textContent === "1" ? 1 : getSpellCost(stats, spellIdx, spell.cost)); } - build.total_skillpoints[2] = int;//forgive me pt 2 int_needed.textContent = ": " + (needed > 150 ? ">150" : needed) + " int (+" + (needed > 150 ? "n/a" : missing) + ")"; } @@ -1583,12 +1582,23 @@ function displayPowderSpecials(parent_elem, powderSpecials, build) { } } -function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx, spell_parts, damages) { +function getSpellCost(stats, spellIdx, cost) { + return Math.max(1, getBaseSpellCost(stats, spellIdx, cost)); +} + +function getBaseSpellCost(stats, spellIdx, cost) { + // old intelligence: + cost = Math.ceil(cost * (1 - skillPointsToPercentage(stats.get('int')))); + cost += stats.get("spRaw"+spellIdx); + return Math.floor(cost * (1 + stats.get("spPct"+spellIdx) / 100)); +} + + +function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_parts, damages) { // TODO: remove spellIdx (just used to flag melee and cost) // TODO: move cost calc out parent_elem.textContent = ""; - const stats = build.statMap; let title_elem = document.createElement("p"); overallparent_elem.textContent = ""; @@ -1601,21 +1611,15 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell title_elemavg.appendChild(first); let second = document.createElement("span"); - second.textContent = build.getSpellCost(spellIdx, spell.cost); + second.textContent = getSpellCost(stats, spellIdx, spell.cost); second.classList.add("Mana"); - let int_redux = skillPointsToPercentage(build.total_skillpoints[2]).toFixed(2); - let spPct_redux = (build.statMap.get("spPct" + spellIdx)/100).toFixed(2); - let spRaw_redux = (build.statMap.get("spRaw" + spellIdx)).toFixed(2); - spPct_redux >= 0 ? spPct_redux = "+ " + spPct_redux : spPct_redux = "- " + Math.abs(spPct_redux); - spRaw_redux >= 0 ? spRaw_redux = "+ " + spRaw_redux : spRaw_redux = "- " + Math.abs(spRaw_redux); - title_elem.appendChild(second.cloneNode(true)); title_elemavg.appendChild(second); let third = document.createElement("span"); - third.textContent = ") [Base: " + build.getBaseSpellCost(spellIdx, spell.cost) + " ]"; + third.textContent = ") [Base: " + getBaseSpellCost(stats, spellIdx, spell.cost) + " ]"; title_elem.appendChild(third); let third_summary = document.createElement("span"); third_summary.textContent = ")"; @@ -1629,9 +1633,9 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell parent_elem.append(title_elem); overallparent_elem.append(title_elemavg); - overallparent_elem.append(displayNextCosts(spell, build, build.weapon.statMap)); + overallparent_elem.append(displayNextCosts(stats, spell, spellIdx)); - let critChance = skillPointsToPercentage(build.total_skillpoints[1]); + let critChance = skillPointsToPercentage(stats.get('dex')); let save_damages = []; @@ -1650,7 +1654,6 @@ function displaySpellDamage(parent_elem, overallparent_elem, build, spell, spell part_div.append(subtitle_elem); if (part.type === "damage") { - //console.log(build.expandedStats); let _results = damage; let totalDamNormal = _results[0]; let totalDamCrit = _results[1]; diff --git a/js/optimize.js b/js/optimize.js index 6185edd..fdce392 100644 --- a/js/optimize.js +++ b/js/optimize.js @@ -40,7 +40,7 @@ function optimizeStrDex() { for (const part of spell_parts) { if (part.type === "damage") { let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw"), stats.get("sdPct") + player_build.externalStats.get("sdPct"), + stats.get("sdRaw"), stats.get("sdPct"), part.multiplier / 100, player_build.weapon, total_skillpoints, player_build.damageMultiplier, player_build.externalStats); let totalDamNormal = _results[0]; From ebcdf9ae271e91924983dfce558dd12c7efd9d50 Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 20 Jun 2022 10:51:17 -0700 Subject: [PATCH 034/155] Powder specials and potion boosts --- builder/index.html | 72 +++++------ js/build_constants.js | 2 +- js/build_encode_decode.js | 6 +- js/builder.js | 47 +++++++- js/builder_graph.js | 244 +++++++++++++++++++++++++++++++------- js/damage_calc.js | 3 - js/display.js | 210 +++++++++++++++++--------------- js/load_tome.js | 1 - 8 files changed, 391 insertions(+), 194 deletions(-) diff --git a/builder/index.html b/builder/index.html index 2084f43..bc5c7d3 100644 --- a/builder/index.html +++ b/builder/index.html @@ -307,7 +307,7 @@
- +
@@ -944,27 +944,27 @@
-
-
-
-
-
@@ -1001,27 +1001,27 @@
-
-
-
-
-
@@ -1031,7 +1031,7 @@ Rage (Passive)
- +
@@ -1045,27 +1045,27 @@
-
-
-
-
-
@@ -1075,7 +1075,7 @@ Kill Streak (Passive)
- +
@@ -1089,27 +1089,27 @@
-
-
-
-
-
@@ -1119,7 +1119,7 @@ Concentration (Passive)
- +
@@ -1133,27 +1133,27 @@
-
-
-
-
-
@@ -1163,7 +1163,7 @@ Endurance (Passive)
- +
@@ -1177,27 +1177,27 @@
-
-
-
-
-
@@ -1207,7 +1207,7 @@ Dodge (Passive)
- +
diff --git a/js/build_constants.js b/js/build_constants.js index eeaae5d..edd88ac 100644 --- a/js/build_constants.js +++ b/js/build_constants.js @@ -84,7 +84,7 @@ let tome_names = [ "Armor Tome", "Guild Tome", ] -let equipmentInputs = equipment_fields.map(x => x + "-choice"); +let equipment_inputs = equipment_fields.map(x => x + "-choice"); let build_fields = equipment_fields.map(x => x+"-tooltip"); let tomeInputs = tome_fields.map(x => x + "-choice"); diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 834afdd..2601830 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -8,7 +8,6 @@ function parsePowdering(powder_info) { powder_info = powder_info.slice(1); for (let j = 0; j < n_blocks; ++j) { let block = powder_info.slice(0,5); - console.log(block); let six_powders = Base64.toInt(block); for (let k = 0; k < 6 && six_powders != 0; ++k) { powders += powderNames.get((six_powders & 0x1f) - 1); @@ -84,7 +83,7 @@ function decodeBuild(url_tag) { } //constant in all versions for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); + setValue(equipment_inputs[i], equipment[i]); } //level, skill point assignments, and powdering @@ -133,7 +132,6 @@ function decodeBuild(url_tag) { setValue(powder_inputs[i], powdering[i]); } for (let i in skillpoints) { - console.log(skillpoints[i]); setValue(skp_order[i] + "-skp", skillpoints[i]); } } @@ -147,7 +145,7 @@ function encodeBuild(build, powders, skillpoints) { let build_string; //V6 encoding - Tomes - build_version = 4; + build_version = 5; build_string = ""; tome_string = ""; diff --git a/js/builder.js b/js/builder.js index 0251fef..ec4f93f 100644 --- a/js/builder.js +++ b/js/builder.js @@ -69,20 +69,48 @@ function loadBuild() { } function resetFields(){ - for (let i in powderInputs) { - setValue(powderInputs[i], ""); + for (const i of powder_inputs) { + setValue(i, ""); } - for (let i in equipmentInputs) { - setValue(equipmentInputs[i], ""); + for (const i of equipment_inputs) { + setValue(i, ""); } setValue("str-skp", "0"); setValue("dex-skp", "0"); setValue("int-skp", "0"); setValue("def-skp", "0"); setValue("agi-skp", "0"); + for (const special_name of specialNames) { + for (let i = 1; i < 6; i++) { //toggle all pressed buttons of the same powder special off + //name is same, power is i + let elem = document.getElementById(special_name.replace(" ", "_")+'-'+i); + if (elem.classList.contains("toggleOn")) { + elem.classList.remove("toggleOn"); + } + } + } + for (const [key, value] of damageMultipliers) { + let elem = document.getElementById(key + "-boost") + if (elem.classList.contains("toggleOn")) { + elem.classList.remove("toggleOn"); + } + } + + const nodes_to_reset = item_nodes.concat(powder_nodes.concat(edit_input_nodes)); + for (const node of nodes_to_reset) { + node.mark_dirty(); + } + powder_special_input.mark_dirty(); + boosts_node.mark_dirty(); + + for (const node of nodes_to_reset) { + node.update(); + } + powder_special_input.update(); + boosts_node.update(); + setValue("level-choice", "106"); location.hash = ""; - calculateBuild(); } function toggleID() { @@ -130,6 +158,15 @@ function toggle_spell_tab(tab) { } } +function toggle_boost_tab(tab) { + for (const i of skp_order) { + document.querySelector("#"+i+"-boost").style.display = "none"; + document.getElementById(i + "-boost-tab").classList.remove("selected-btn"); + } + document.querySelector("#"+tab+"-boost").style.display = ""; + document.getElementById(tab + "-boost-tab").classList.add("selected-btn"); + +} let tabs = ['overall-stats', 'offensive-stats', 'defensive-stats']; function show_tab(tab) { diff --git a/js/builder_graph.js b/js/builder_graph.js index 29ec4d6..ae36651 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -1,7 +1,136 @@ +let boosts_node; +/* Updates all spell boosts +*/ +function updateBoosts(buttonId) { + let elem = document.getElementById(buttonId); + if (elem.classList.contains("toggleOn")) { + elem.classList.remove("toggleOn"); + } else { + elem.classList.add("toggleOn"); + } + boosts_node.mark_dirty(); + boosts_node.update(); +} + +class BoostsInputNode extends ComputeNode { + 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]; + } +} + +let powder_special_input; +let specialNames = ["Quake", "Chain Lightning", "Curse", "Courage", "Wind Prison"]; + +function updatePowderSpecials(buttonId) { + //console.log(player_build.statMap); + + 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(); + powder_special_input.update(); +} + +class PowderSpecialInputNode extends ComputeNode { + 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; + } +} + +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"]); + } +} + /** * Node for getting an item's stats from an item input field. * - * Signature: ItemInputNode() => Item | null + * Signature: ItemInputNode(powdering: Optional[list[powder]]) => Item | null */ class ItemInputNode extends InputNode { /** @@ -18,36 +147,36 @@ class ItemInputNode extends InputNode { } compute_func(input_map) { - // built on the assumption of no one will type in CI/CR letter by letter + const powdering = input_map.get('powdering'); + // built on the assumption of no one will type in CI/CR letter by letter let item_text = this.input_field.value; if (!item_text) { return this.none_item; } let item; - - 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)); - } + 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)); } if (item) { + if (powdering !== undefined) { + item.statMap.set('powders', powdering); + } 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'); } - if (type_match) { return item; } + if (type_match) { + if (item.statMap.get('category') === 'armor' && powdering !== undefined) { + applyArmorPowders(item.statMap, powdering); + } + return item; + } } return null; } @@ -142,11 +271,11 @@ class WeaponInputDisplayNode extends ComputeNode { * Encode the build into a url-able string. * * Signature: BuildEncodeNode(build: Build, - helmet-powder: List[powder], - chestplate-powder: List[powder], - leggings-powder: List[powder], - boots-powder: List[powder], - weapon-powder: List[powder]) => str + * helmet-powder: List[powder], + * chestplate-powder: List[powder], + * leggings-powder: List[powder], + * boots-powder: List[powder], + * weapon-powder: List[powder]) => str */ class BuildEncodeNode extends ComputeNode { constructor() { super("builder-encode"); } @@ -227,7 +356,7 @@ class BuildAssembleNode extends ComputeNode { for (const item of equipments) { all_none = all_none && item.statMap.has('NONE'); } - if (all_none) { + if (all_none && !location.hash) { return null; } return new Build(level, equipments, [], weapon); @@ -314,17 +443,17 @@ function getDefenseStats(stats) { defenseStats.push(totalHp); //EHP let ehp = [totalHp, totalHp]; - let defMult = stats.get("classDef"); - ehp[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); - ehp[1] /= (1-def_pct)*(2-defMult); + let defMult = (2 - stats.get("classDef")) * (1 - stats.get("defBonus")); + ehp[0] /= (1-def_pct)*(1-agi_pct)*defMult; + ehp[1] /= (1-def_pct)*defMult; defenseStats.push(ehp); //HPR let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.); defenseStats.push(totalHpr); //EHPR let ehpr = [totalHpr, totalHpr]; - ehpr[0] /= (1-def_pct)*(1-agi_pct)*(2-defMult); - ehpr[1] /= (1-def_pct)*(2-defMult); + ehpr[0] /= (1-def_pct)*(1-agi_pct)*defMult; + ehpr[1] /= (1-def_pct)*defMult; defenseStats.push(ehpr); //skp stats defenseStats.push([ def_pct*100, agi_pct*100]); @@ -345,7 +474,6 @@ function getDefenseStats(stats) { * * Signature: SpellDamageCalcNode(weapon-input: Item, * stats: StatMap, - * weapon-powder: List[powder], * spell-info: [Spell, SpellParts]) => List[SpellDamage] */ class SpellDamageCalcNode extends ComputeNode { @@ -355,11 +483,10 @@ class SpellDamageCalcNode extends ComputeNode { compute_func(input_map) { const weapon = new Map(input_map.get('weapon-input').statMap); - const weapon_powder = input_map.get('weapon-powder'); - const damage_mult = 1; // TODO: hook up const spell_info = input_map.get('spell-info'); const spell_parts = spell_info[1]; const stats = input_map.get('stats'); + const damage_mult = stats.get('damageMultiplier'); const skillpoints = [ stats.get('str'), stats.get('dex'), @@ -367,8 +494,6 @@ class SpellDamageCalcNode extends ComputeNode { stats.get('def'), stats.get('agi') ]; - - weapon.set("powders", weapon_powder); let spell_results = [] for (const part of spell_parts) { @@ -441,7 +566,7 @@ function getMeleeStats(stats, weapon) { adjAtkSpd = 0; } - let damage_mult = 1; + let damage_mult = stats.get("damageMultiplier"); 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. @@ -485,7 +610,6 @@ class BuildDisplayNode extends ComputeNode { displaySetBonuses("set-info", build); let meleeStats = getMeleeStats(stats, build.weapon); // TODO: move weapon out? - console.log(meleeStats); displayMeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); displayDefenseStats(document.getElementById("defensive-stats"), stats); @@ -576,13 +700,11 @@ class DisplayBuildWarningsNode extends ComputeNode { if (build.level < item_lvl) { if (!lvlWarning) { lvlWarning = document.createElement("p"); - lvlWarning.classList.add("itemp"); - lvlWarning.classList.add("warning"); + lvlWarning.classList.add("itemp"); lvlWarning.classList.add("warning"); lvlWarning.textContent = "WARNING: A level " + build.level + " player cannot use some piece(s) of this build." } let baditem = document.createElement("p"); - baditem.classList.add("nocolor"); - baditem.classList.add("itemp"); + baditem.classList.add("nocolor"); baditem.classList.add("itemp"); baditem.textContent = item.get("displayName") + " requires level " + item_lvl + " to use."; lvlWarning.appendChild(baditem); } @@ -594,8 +716,7 @@ class DisplayBuildWarningsNode extends ComputeNode { const bonus = sets.get(setName).bonuses[count-1]; if (bonus["illegal"]) { let setWarning = document.createElement("p"); - setWarning.classList.add("itemp"); - setWarning.classList.add("warning"); + setWarning.classList.add("itemp"); setWarning.classList.add("warning"); setWarning.textContent = "WARNING: illegal item combination: " + setName summarybox.append(setWarning); } @@ -612,15 +733,26 @@ class AggregateStatsNode extends ComputeNode { constructor() { super("builder-aggregate-stats"); } compute_func(input_map) { - const build = input_map.get('build'); - const weapon = input_map.get('weapon'); + const build = input_map.get('build'); input_map.delete('build'); + const powder_boost = input_map.get('powder-boost'); input_map.delete('powder-boost'); + const potion_boost = input_map.get('potion-boost'); input_map.delete('potion-boost'); + const weapon = input_map.get('weapon'); input_map.delete('weapon'); + const output_stats = new Map(build.statMap); + output_stats.set("damageMultiplier", 1 + potion_boost[0]); + output_stats.set("defBonus", potion_boost[1]); for (const [k, v] of input_map.entries()) { - if (k === 'build') { - continue; - } output_stats.set(k, v); } + for (const [k, v] of powder_boost.entries()) { + if (output_stats.has(k)) { + output_stats.set(k, v + output_stats.get(k)); + } + else { + output_stats.set(k, v); + } + } + output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type"))); return output_stats; } @@ -656,6 +788,7 @@ class SkillPointSetterNode extends ComputeNode { } compute_func(input_map) { + console.log("a"); 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()) { @@ -750,6 +883,12 @@ function builder_graph_init() { build_encode_node.link_to(powder_node, input); } + 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'); + // Edit IDs setter declared up here to set ids so they will be populated by default. let edit_id_output = new EditableIDSetterNode(); edit_id_output.link_to(build_node); @@ -788,6 +927,19 @@ function builder_graph_init() { } level_input.update(); + // Powder specials. + powder_special_input = new PowderSpecialInputNode(); + 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'); + powder_special_input.update(); + + // Potion boost. + boosts_node = new BoostsInputNode(); + stat_agg_node.link_to(boosts_node, 'potion-boost'); + boosts_node.update(); + // Also do something similar for skill points for (let i = 0; i < 4; ++i) { @@ -798,7 +950,7 @@ function builder_graph_init() { let calc_node = new SpellDamageCalcNode(i); calc_node.link_to(item_nodes[8], 'weapon-input').link_to(stat_agg_node, 'stats') - .link_to(powder_nodes[4], 'weapon-powder').link_to(spell_node, 'spell-info'); + .link_to(spell_node, 'spell-info'); spelldmg_nodes.push(calc_node); let display_node = new SpellDisplayNode(i); diff --git a/js/damage_calc.js b/js/damage_calc.js index eef3825..490713f 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -72,9 +72,6 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, damages[element+1][1] += powder.max; } - - //console.log(tooltipinfo); - damages[0] = neutralRemainingRaw; let damageMult = damageMultiplier; diff --git a/js/display.js b/js/display.js index c87f689..61c57f0 100644 --- a/js/display.js +++ b/js/display.js @@ -1,18 +1,3 @@ -/** - * 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"]); - } -} function apply_elemental_format(p_elem, id, suffix) { suffix = (typeof suffix !== 'undefined') ? suffix : ""; @@ -192,6 +177,11 @@ function displayExpandedItem(item, parent_id){ if (item.get("category") === "weapon") { let stats = new Map(); stats.set("atkSpd", item.get("atkSpd")); + stats.set("eDamPct", 0); + stats.set("tDamPct", 0); + stats.set("wDamPct", 0); + stats.set("fDamPct", 0); + stats.set("aDamPct", 0); stats.set("damageBonus", [0, 0, 0, 0, 0]); //SUPER JANK @HPP PLS FIX @@ -991,9 +981,15 @@ function displayNextCosts(_stats, spell, spellIdx) { } function displayRolledID(item, id, elemental_format) { - let row = document.createElement('tr'); - let min_elem = document.createElement('td'); - min_elem.classList.add('left'); + let row = document.createElement('div'); + row.classList.add('col'); + + let item_div = document.createElement('div'); + item_div.classList.add('row'); + + let min_elem = document.createElement('div'); + min_elem.classList.add('col', 'text-start'); + min_elem.style.cssText += "flex-grow: 0"; let id_min = item.get("minRolls").get(id) let style = id_min < 0 ? "negative" : "positive"; if(reversedIDs.includes(id)){ @@ -1001,10 +997,11 @@ function displayRolledID(item, id, elemental_format) { } min_elem.classList.add(style); min_elem.textContent = id_min + idSuffixes[id]; - row.appendChild(min_elem); + item_div.appendChild(min_elem); - let desc_elem = document.createElement('td'); - desc_elem.classList.add('center'); + let desc_elem = document.createElement('div'); + desc_elem.classList.add('col', 'text-center');//, 'text-nowrap'); + desc_elem.style.cssText += "flex-grow: 1"; //TODO elemental format jank if (elemental_format) { apply_elemental_format(desc_elem, id); @@ -1012,18 +1009,20 @@ function displayRolledID(item, id, elemental_format) { else { desc_elem.textContent = idPrefixes[id]; } - row.appendChild(desc_elem); + item_div.appendChild(desc_elem); - let max_elem = document.createElement('td'); + let max_elem = document.createElement('div'); let id_max = item.get("maxRolls").get(id) - max_elem.classList.add('right'); + max_elem.classList.add('col', 'text-end'); + max_elem.style.cssText += "flex-grow: 0"; style = id_max < 0 ? "negative" : "positive"; - if(reversedIDs.includes(id)){ + if (reversedIDs.includes(id)) { style === "positive" ? style = "negative" : style = "positive"; } max_elem.classList.add(style); max_elem.textContent = id_max + idSuffixes[id]; - row.appendChild(max_elem); + item_div.appendChild(max_elem); + row.appendChild(item_div); return row; } @@ -1458,55 +1457,60 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){ } } -function displayPowderSpecials(parent_elem, powderSpecials, build) { - parent_elem.textContent = "Powder Specials"; +function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overall=false) { + const skillpoints = [ + stats.get('str'), + stats.get('dex'), + stats.get('int'), + stats.get('def'), + stats.get('agi') + ]; + parent_elem.textContent = "" + let title = document.createElement("b"); + title.textContent = "Powder Specials"; + parent_elem.appendChild(title); let specials = powderSpecials.slice(); - let stats = build.statMap; let expandedStats = new Map(); //each entry of powderSpecials is [ps, power] for (special of specials) { //iterate through the special and display its effects. let powder_special = document.createElement("p"); - powder_special.classList.add("left"); let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]); let specialTitle = document.createElement("p"); let specialEffects = document.createElement("p"); - specialTitle.classList.add("left"); - specialTitle.classList.add("itemp"); specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]); - specialEffects.classList.add("left"); - specialEffects.classList.add("itemp"); - specialEffects.classList.add("nocolor"); let effects = special[0]["weaponSpecialEffects"]; let power = special[1]; specialTitle.textContent = special[0]["weaponSpecialName"] + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : ""); - for (const [key,value] of effects) { - let effect = document.createElement("p"); - effect.classList.add("itemp"); - effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key); - if(key === "Damage"){ - effect.textContent += elementIcons[powderSpecialStats.indexOf(special[0])]; - } - if(special[0]["weaponSpecialName"] === "Wind Prison" && key === "Damage Boost") { - effect.textContent += " (only 1st hit)"; - } - specialEffects.appendChild(effect); - } + if (!overall || powderSpecialStats.indexOf(special[0]) == 2 || powderSpecialStats.indexOf(special[0]) == 3 || powderSpecialStats.indexOf(special[0]) == 4) { + for (const [key,value] of effects) { + let effect = document.createElement("p"); + effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key); + if(key === "Damage"){ + effect.textContent += elementIcons[powderSpecialStats.indexOf(special[0])]; + } + if(special[0]["weaponSpecialName"] === "Wind Prison" && key === "Damage Boost") { + effect.textContent += " (only 1st hit)"; + } + specialEffects.appendChild(effect); + } + } powder_special.appendChild(specialTitle); powder_special.appendChild(specialEffects); //if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage. let specialDamage = document.createElement("p"); + // specialDamage.classList.add("item-margin"); let spells = spell_table["powder"]; if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]); let part = spell["parts"][0]; let _results = calculateSpellDamage(stats, part.conversion, - stats.get("mdRaw"), stats.get("mdPct") + build.externalStats.get("mdPct"), - 0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)), build.externalStats);//part.multiplier[power] / 100 + stats.get("mdRaw"), stats.get("mdPct"), + 0, weapon, skillpoints, stats.get('damageMultiplier') * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100 - let critChance = skillPointsToPercentage(build.total_skillpoints[1]); + let critChance = skillPointsToPercentage(skillpoints[1]); let save_damages = []; let totalDamNormal = _results[0]; @@ -1521,59 +1525,69 @@ function displayPowderSpecials(parent_elem, powderSpecials, build) { let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; - let averageLabel = document.createElement("p"); - averageLabel.textContent = "Average: "+averageDamage.toFixed(2); - averageLabel.classList.add("damageSubtitle"); - specialDamage.append(averageLabel); - - - let nonCritLabel = document.createElement("p"); - nonCritLabel.textContent = "Non-Crit Average: "+nonCritAverage.toFixed(2); - nonCritLabel.classList.add("damageSubtitle"); - specialDamage.append(nonCritLabel); + let averageWrap = document.createElement("p"); + let averageLabel = document.createElement("span"); + averageLabel.textContent = "Average: "; - for (let i = 0; i < 6; i++){ - if (results[i][1] > 0){ - let p = document.createElement("p"); - p.classList.add("damagep"); - p.classList.add(damageClasses[i]); - p.textContent = results[i][0]+"-"+results[i][1]; - specialDamage.append(p); - } - } - let normalDamage = document.createElement("p"); - normalDamage.textContent = "Total: " + totalDamNormal[0].toFixed(2) + "-" + totalDamNormal[1].toFixed(2); - normalDamage.classList.add("itemp"); - specialDamage.append(normalDamage); + let averageLabelDmg = document.createElement("span"); + averageLabelDmg.classList.add("Damage"); + averageLabelDmg.textContent = averageDamage.toFixed(2); - let nonCritChanceLabel = document.createElement("p"); - nonCritChanceLabel.textContent = "Non-Crit Chance: " + ((1-critChance)*100).toFixed(2) + "%"; - specialDamage.append(nonCritChanceLabel); - - let critLabel = document.createElement("p"); - critLabel.textContent = "Crit Average: "+critAverage.toFixed(2); - critLabel.classList.add("damageSubtitle"); + averageWrap.appendChild(averageLabel); + averageWrap.appendChild(averageLabelDmg); + specialDamage.appendChild(averageWrap); - specialDamage.append(critLabel); - for (let i = 0; i < 6; i++){ - if (results[i][1] > 0){ - let p = document.createElement("p"); - p.classList.add("damagep"); - p.classList.add(damageClasses[i]); - p.textContent = results[i][2]+"-"+results[i][3]; - specialDamage.append(p); + if (!overall) { + let nonCritLabel = document.createElement("p"); + nonCritLabel.textContent = "Non-Crit Average: "+nonCritAverage.toFixed(2); + nonCritLabel.classList.add("damageSubtitle"); + nonCritLabel.classList.add("item-margin"); + specialDamage.append(nonCritLabel); + + for (let i = 0; i < 6; i++){ + if (results[i][1] > 0){ + let p = document.createElement("p"); + p.classList.add("damagep"); + p.classList.add(damageClasses[i]); + p.textContent = results[i][0]+"-"+results[i][1]; + specialDamage.append(p); + } } + let normalDamage = document.createElement("p"); + normalDamage.textContent = "Total: " + totalDamNormal[0].toFixed(2) + "-" + totalDamNormal[1].toFixed(2); + normalDamage.classList.add("itemp"); + specialDamage.append(normalDamage); + + let nonCritChanceLabel = document.createElement("p"); + nonCritChanceLabel.textContent = "Non-Crit Chance: " + ((1-critChance)*100).toFixed(2) + "%"; + specialDamage.append(nonCritChanceLabel); + + let critLabel = document.createElement("p"); + critLabel.textContent = "Crit Average: "+critAverage.toFixed(2); + critLabel.classList.add("damageSubtitle"); + critLabel.classList.add("item-margin"); + + specialDamage.append(critLabel); + for (let i = 0; i < 6; i++){ + if (results[i][1] > 0){ + let p = document.createElement("p"); + p.classList.add("damagep"); + p.classList.add(damageClasses[i]); + p.textContent = results[i][2]+"-"+results[i][3]; + specialDamage.append(p); + } + } + let critDamage = document.createElement("p"); + critDamage.textContent = "Total: " + totalDamCrit[0].toFixed(2) + "-" + totalDamCrit[1].toFixed(2); + critDamage.classList.add("itemp"); + specialDamage.append(critDamage); + + let critChanceLabel = document.createElement("p"); + critChanceLabel.textContent = "Crit Chance: " + (critChance*100).toFixed(2) + "%"; + specialDamage.append(critChanceLabel); + + save_damages.push(averageDamage); } - let critDamage = document.createElement("p"); - critDamage.textContent = "Total: " + totalDamCrit[0].toFixed(2) + "-" + totalDamCrit[1].toFixed(2); - critDamage.classList.add("itemp"); - specialDamage.append(critDamage); - - let critChanceLabel = document.createElement("p"); - critChanceLabel.textContent = "Crit Chance: " + (critChance*100).toFixed(2) + "%"; - specialDamage.append(critChanceLabel); - - save_damages.push(averageDamage); powder_special.append(specialDamage); } diff --git a/js/load_tome.js b/js/load_tome.js index 62bb135..e6fded5 100644 --- a/js/load_tome.js +++ b/js/load_tome.js @@ -25,7 +25,6 @@ async function load_tome_local() { console.log("Successfully read local tome db."); } get_tx.oncomplete = function(event) { - console.log('b'); console.log(request.readyState); tomes = request.result; init_tome_maps(); From d1a468a227a60909fc6da3699850bcc05ac27f2b Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 20 Jun 2022 10:52:46 -0700 Subject: [PATCH 035/155] Remove unused files (should be merged into builder.js... etc now.) --- js/sq2bs.js | 522 -------- js/sq2build.js | 692 ----------- js/sq2builder.js | 1127 ----------------- js/sq2display.js | 2407 ------------------------------------ js/sq2display_constants.js | 191 --- 5 files changed, 4939 deletions(-) delete mode 100644 js/sq2bs.js delete mode 100644 js/sq2build.js delete mode 100644 js/sq2builder.js delete mode 100644 js/sq2display.js delete mode 100644 js/sq2display_constants.js diff --git a/js/sq2bs.js b/js/sq2bs.js deleted file mode 100644 index dfdc701..0000000 --- a/js/sq2bs.js +++ /dev/null @@ -1,522 +0,0 @@ - -document.addEventListener('DOMContentLoaded', function() { - - for (const eq of equipment_keys) { - document.querySelector("#"+eq+"-choice").setAttribute("oninput", "update_field('"+ eq +"'); calcBuildSchedule();"); - document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); - } - - for (const eq of powderable_keys) { - document.querySelector("#"+eq+"-powder").setAttribute("oninput", "calcBuildSchedule(); update_field('"+ eq +"');"); - } - - for (const eq of tome_keys) { - document.querySelector("#" + eq + "-choice").setAttribute("oninput", "update_field('" + eq + "'); calcBuildSchedule();"); - document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); - } - - for (const i of spell_disp) { - document.querySelector("#"+i+"Avg").setAttribute("onclick", "toggle_spell_tab('"+i+"')"); - } - - document.querySelector("#level-choice").setAttribute("oninput", "calcBuildSchedule()") - document.querySelector("#weapon-choice").setAttribute("oninput", document.querySelector("#weapon-choice").getAttribute("oninput") + "resetArmorPowderSpecials();"); - // document.querySelector("#edit-IDs-button").setAttribute("onclick", "toggle_edit_id_tab()"); - - let skp_fields = document.getElementsByClassName("skp-update"); - - for (i = 0; i < skp_fields.length; i++) { - skp_fields[i].setAttribute("oninput", "updateStatSchedule()"); - } - - let masonry = Macy({ - container: "#masonry-container", - columns: 1, - mobileFirst: true, - breakAt: { - 1200: 4, - }, - margin: { - x: 20, - y: 20, - } - - }); - - let search_masonry = Macy({ - container: "#search-results", - columns: 1, - mobileFirst: true, - breakAt: { - 1200: 4, - }, - margin: { - x: 20, - y: 20, - } - - }); - - document.querySelector("#search-container").addEventListener("keyup", function(event) { - if (event.key === "Escape") { - document.querySelector("#search-container").style.display = "none"; - }; - }); - -}); - -// phanta scheduler -let calcBuildTask = null; -let updateStatTask = null; -let doSearchTask = null; - -function calcBuildSchedule(){ - if (calcBuildTask !== null) { - clearTimeout(calcBuildTask); - } - calcBuildTask = setTimeout(function(){ - calcBuildTask = null; - resetEditableIDs(); - calculateBuild(); - }, 500); -} - -function updateStatSchedule(){ - if (updateStatTask !== null) { - clearTimeout(updateStatTask); - } - updateStatTask = setTimeout(function(){ - updateStatTask = null; - updateStats(); - }, 500); -} - -function doSearchSchedule(){ - console.log("Search Schedule called"); - if (doSearchTask !== null) { - clearTimeout(doSearchTask); - } - doSearchTask = setTimeout(function(){ - doSearchTask = null; - doItemSearch(); - window.dispatchEvent(new Event('resize')); - }, 500); -} - -function sq2ResetFields(){ - for (let i in powderInputs) { - setValue(powderInputs[i], ""); - } - for (let i in equipmentInputs) { - setValue(equipmentInputs[i], ""); - } - - for (let i in tomeInputs) { - setValue(tomeInputs[i], ""); - } - setValue("str-skp", "0"); - setValue("dex-skp", "0"); - setValue("int-skp", "0"); - setValue("def-skp", "0"); - setValue("agi-skp", "0"); - setValue("level-choice", "106"); - location.hash = ""; - calculateBuild(); -} - -// equipment field dynamic styling -function update_field(field) { - // built on the assumption of no one will type in CI/CR letter by letter - // resets - document.querySelector("#"+field+"-choice").classList.remove("text-light", "is-invalid", 'Normal', 'Unique', 'Rare', 'Legendary', 'Fabled', 'Mythic', 'Set', 'Crafted', 'Custom'); - document.querySelector("#"+field+"-choice").classList.add("text-light"); - document.querySelector("#" + field + "-img").classList.remove('Normal-shadow', 'Unique-shadow', 'Rare-shadow', 'Legendary-shadow', 'Fabled-shadow', 'Mythic-shadow', 'Set-shadow', 'Crafted-shadow', 'Custom-shadow'); - - item = document.querySelector("#"+field+"-choice").value - let powder_slots; - let tier; - let category; - let type; - - // get item info - if (item.slice(0, 3) == "CI-") { - item = getCustomFromHash(item); - powder_slots = item.statMap.get("slots"); - tier = item.statMap.get("tier"); - category = item.statMap.get("category"); - type = item.statMap.get("type"); - } - else if (item.slice(0, 3) == "CR-") { - item = getCraftFromHash(item); - powder_slots = item.statMap.get("slots"); - tier = item.statMap.get("tier"); - category = item.statMap.get("category"); - type = item.statMap.get("type"); - } - else if (itemMap.get(item)) { - item = itemMap.get(item); - if (!item) {return false;} - powder_slots = item.slots; - tier = item.tier; - category = item.category; - type = item.type; - } - else if (tomeMap.get(item)) { - tome = tomeMap.get(item); - if (!tome) {return false;} - powder_slots = 0; - tier = tome.tier; - category = tome.category; - type = tome.type; - } - else { - // item not found - document.querySelector("#"+field+"-choice").classList.add("text-light"); - if (item) { document.querySelector("#"+field+"-choice").classList.add("is-invalid"); } - - /*if (!accessory_keys.contains(type.toLowerCase())) { - document.querySelector("#"+type+"-powder").disabled = true; - }*/ - return false; - } - - - if ((type != field.replace(/[0-9]/g, '')) && (category != field.replace(/[0-9]/g, ''))) { - document.querySelector("#"+field+"-choice").classList.add("text-light"); - if (item) { document.querySelector("#"+field+"-choice").classList.add("is-invalid"); } - - //document.querySelector("#"+equipment_keys[i]+"-powder").disabled = true; - return false; - } - - // set item color - document.querySelector("#"+field+"-choice").classList.add(tier); - document.querySelector("#"+field+"-img").classList.add(tier + "-shadow"); - - - - if (powderable_keys.includes(field)) { - // set powder slots - document.querySelector("#"+field+"-powder").setAttribute("placeholder", powder_slots+" slots"); - - if (powder_slots == 0) { - document.querySelector("#"+field+"-powder").disabled = true; - } else { - document.querySelector("#"+field+"-powder").disabled = false; - } - - // powder error handling - document.querySelector("#" + field + "-powder").classList.remove("is-invalid"); - let powder_string = document.querySelector("#"+field+"-powder").value; - - if (powder_string.length % 2 != 0 || powder_string.length / 2 > powder_slots) { - document.querySelector("#"+field+"-powder").classList.add("is-invalid"); - } else { - for (i = 0; i < powder_string.length / 2; i++) { - if (skp_elements.includes(powder_string.substring(i*2, i*2+2).split("")[0]) == false || isNaN(powder_string.substring(i*2, i*2+2).split("")[1]) || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) < 1 || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) > 6) { - document.querySelector("#"+field+"-powder").classList.add("is-invalid"); - } - } - }; - } - - // set weapon img - if (category == 'weapon') { - document.querySelector("#weapon-img").setAttribute('src', '../media/items/new/generic-'+type+'.png'); - } -} -/* tabulars | man i hate this code but too lazy to fix /shrug */ - -let tabs = ['overall-stats', 'offensive-stats', 'defensive-stats']; - -function show_tab(tab) { - //console.log(itemFilters) - - //hide all tabs, then show the tab of the div clicked and highlight the correct button - for (const i in tabs) { - document.querySelector("#" + tabs[i]).style.display = "none"; - document.getElementById("tab-" + tabs[i].split("-")[0] + "-btn").classList.remove("selected-btn"); - } - document.querySelector("#" + tab).style.display = ""; - document.getElementById("tab-" + tab.split("-")[0] + "-btn").classList.add("selected-btn"); -} - -function toggle_spell_tab(tab) { - let arrow_img = document.querySelector("#" + "arrow_" + tab + "Avg"); - if (document.querySelector("#"+tab).style.display == "none") { - document.querySelector("#"+tab).style.display = ""; - arrow_img.src = arrow_img.src.replace("down", "up"); - } else { - document.querySelector("#"+tab).style.display = "none"; - arrow_img.src = arrow_img.src.replace("up", "down"); - } -} - -function toggle_boost_tab(tab) { - for (const i of skp_order) { - document.querySelector("#"+i+"-boost").style.display = "none"; - document.getElementById(i + "-boost-tab").classList.remove("selected-btn"); - } - document.querySelector("#"+tab+"-boost").style.display = ""; - document.getElementById(tab + "-boost-tab").classList.add("selected-btn"); - -} - -// toggle tab -function toggle_tab(tab) { - if (document.querySelector("#"+tab).style.display == "none") { - document.querySelector("#"+tab).style.display = ""; - } else { - document.querySelector("#"+tab).style.display = "none"; - } -} - -function collapse_element(elmnt) { - elem_list = document.querySelector(elmnt).children; - if (elem_list) { - for (elem of elem_list) { - if (elem.classList.contains("no-collapse")) { continue; } - if (elem.style.display == "none") { - elem.style.display = ""; - } else { - elem.style.display = "none"; - } - } - } - // macy quirk - window.dispatchEvent(new Event('resize')); - // weird bug where display: none overrides?? - document.querySelector(elmnt).style.removeProperty('display'); -} - -// search misc -function set_item(item) { - document.querySelector("#search-container").style.display = "none"; - let type; - // if (!player_build) {return false;} - if (item.get("category") === "weapon") { - type = "weapon"; - } else if (item.get("type") === "ring") { - if (!document.querySelector("#ring1-choice").value) { - type = "ring1"; - } else { - type = "ring2"; - } - } else { - type = item.get("type"); - } - document.querySelector("#"+type+"-choice").value = item.get("displayName"); - calcBuildSchedule(); - update_field(type); -} - -// disable boosts - -function reset_powder_specials() { - let specials = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"] - for (const special of specials) { - for (i = 1; i < 6; i++) { - if (document.querySelector("#"+special+"-"+i).classList.contains("toggleOn")) { - document.querySelector("#"+special+"-"+i).classList.remove("toggleOn"); - } - } - } -} - -// autocomplete initialize -function init_autocomplete() { - let dropdowns = new Map() - for (const eq of equipment_keys) { - if (tome_keys.includes(eq)) { - continue; - } - // build dropdown - let item_arr = []; - if (eq == 'weapon') { - for (const weaponType of weapon_keys) { - for (const weapon of itemLists.get(weaponType)) { - let item_obj = itemMap.get(weapon); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { - continue; - } - item_arr.push(weapon); - } - } - } else { - for (const item of itemLists.get(eq.replace(/[0-9]/g, ''))) { - let item_obj = itemMap.get(item); - if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { - continue; - } - if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) { - continue; - } - item_arr.push(item) - } - } - - // create dropdown - dropdowns.set(eq, new autoComplete({ - data: { - src: item_arr - }, - selector: "#"+ eq +"-choice", - wrapper: false, - resultsList: { - maxResults: 1000, - tabSelect: true, - noResults: true, - class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", - element: (list, data) => { - // dynamic result loc - let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); - list.style.top = position.bottom + window.scrollY +"px"; - list.style.left = position.x+"px"; - list.style.width = position.width+"px"; - list.style.maxHeight = position.height * 2 +"px"; - - if (!data.results.length) { - message = document.createElement('li'); - message.classList.add('scaled-font'); - message.textContent = "No results found!"; - list.prepend(message); - } - }, - }, - resultItem: { - class: "scaled-font search-item", - selected: "dark-5", - element: (item, data) => { - item.classList.add(itemMap.get(data.value).tier); - }, - }, - events: { - input: { - selection: (event) => { - if (event.detail.selection.value) { - event.target.value = event.detail.selection.value; - } - update_field(eq); - calcBuildSchedule(); - }, - }, - } - })); - } - - for (const eq of tome_keys) { - // build dropdown - let tome_arr = []; - for (const tome of tomeLists.get(eq.replace(/[0-9]/g, ''))) { - let tome_obj = tomeMap.get(tome); - if (tome_obj["restrict"] && tome_obj["restrict"] === "DEPRECATED") { - continue; - } - //this should suffice for tomes - jank - if (tome_obj["name"].includes('No ' + eq.charAt(0).toUpperCase())) { - continue; - } - let tome_name = tome; - tome_arr.push(tome_name); - } - - // create dropdown - dropdowns.set(eq, new autoComplete({ - data: { - src: tome_arr - }, - selector: "#"+ eq +"-choice", - wrapper: false, - resultsList: { - maxResults: 1000, - tabSelect: true, - noResults: true, - class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", - element: (list, data) => { - // dynamic result loc - let position = document.getElementById(eq+'-dropdown').getBoundingClientRect(); - list.style.top = position.bottom + window.scrollY +"px"; - list.style.left = position.x+"px"; - list.style.width = position.width+"px"; - list.style.maxHeight = position.height * 2 +"px"; - - if (!data.results.length) { - message = document.createElement('li'); - message.classList.add('scaled-font'); - message.textContent = "No results found!"; - list.prepend(message); - } - }, - }, - resultItem: { - class: "scaled-font search-item", - selected: "dark-5", - element: (tome, data) => { - tome.classList.add(tomeMap.get(data.value).tier); - }, - }, - events: { - input: { - selection: (event) => { - if (event.detail.selection.value) { - event.target.value = event.detail.selection.value; - } - update_field(eq); - calcBuildSchedule(); - }, - }, - } - })); - } - - let filter_loc = ["filter1", "filter2", "filter3", "filter4"]; - for (const i of filter_loc) { - dropdowns.set(i+"-choice", new autoComplete({ - data: { - src: sq2ItemFilters, - }, - selector: "#"+i+"-choice", - wrapper: false, - resultsList: { - tabSelect: true, - noResults: true, - class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", - element: (list, data) => { - // dynamic result loc - console.log(i); - list.style.zIndex = "100"; - let position = document.getElementById(i+"-dropdown").getBoundingClientRect(); - window_pos = document.getElementById("search-container").getBoundingClientRect(); - list.style.top = position.bottom - window_pos.top + 5 +"px"; - list.style.left = position.x - window_pos.x +"px"; - list.style.width = position.width+"px"; - - if (!data.results.length) { - message = document.createElement('li'); - message.classList.add('scaled-font'); - message.textContent = "No filters found!"; - list.prepend(message); - } - }, - }, - resultItem: { - class: "scaled-font search-item", - selected: "dark-5", - }, - events: { - input: { - selection: (event) => { - if (event.detail.selection.value) { - event.target.value = event.detail.selection.value; - } - doSearchSchedule(); - }, - }, - } - })); - } -} - diff --git a/js/sq2build.js b/js/sq2build.js deleted file mode 100644 index 7551d34..0000000 --- a/js/sq2build.js +++ /dev/null @@ -1,692 +0,0 @@ - - -const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20], ["sword", 1.10]]); - -/** - * @description Error to catch items that don't exist. - * @module ItemNotFound - */ -class ItemNotFound { - /** - * @class - * @param {String} item the item name entered - * @param {String} type the type of item - * @param {Boolean} genElement whether to generate an element from inputs - * @param {String} override override for item type - */ - constructor(item, type, genElement, override) { - /** - * @public - * @type {String} - */ - this.message = `Cannot find ${override||type} named ${item}`; - if (genElement) - /** - * @public - * @type {Element} - */ - this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0]; - else - this.element = document.createElement("div"); - } -} - -/** - * @description Error to catch incorrect input. - * @module IncorrectInput - */ -class IncorrectInput { - /** - * @class - * @param {String} input the inputted text - * @param {String} format the correct format - * @param {String} sibling the id of the error node's sibling - */ - constructor(input, format, sibling) { - /** - * @public - * @type {String} - */ - this.message = `${input} is incorrect. Example: ${format}`; - /** - * @public - * @type {String} - */ - this.id = sibling; - } -} - -/** - * @description Error that inputs an array of items to generate errors of. - * @module ListError - * @extends Error - */ -class ListError extends Error { - /** - * @class - * @param {Array} errors array of errors - */ - constructor(errors) { - let ret = []; - if (typeof errors[0] == "string") { - super(errors[0]); - } else { - super(errors[0].message); - } - for (let i of errors) { - if (typeof i == "string") { - ret.push(new Error(i)); - } else { - ret.push(i); - } - } - /** - * @public - * @type {Object[]} - */ - this.errors = ret; - } -} - -/*Class that represents a wynn player's build. -*/ -class Build{ - - /** - * @description Construct a build. - * @param {Number} level : Level of the player. - * @param {String[]} equipment : List of equipment names that make up the build. - * In order: boots, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon. - * @param {Number[]} powders : Powder application. List of lists of integers (powder IDs). - * In order: boots, Chestplate, Leggings, Boots, Weapon. - * @param {Object[]} inputerrors : List of instances of error-like classes. - * - * @param {Object[]} tomes: List of tomes. - * In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome. - * 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building) - */ - constructor(level,equipment, powders, externalStats, inputerrors=[], tomes){ - - let errors = inputerrors; - //this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem). - this.craftedItems = []; - this.customItems = []; - // NOTE: powders is just an array of arrays of powder IDs. Not powder objects. - this.powders = powders; - if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") { - const helmet = itemMap.get(equipment[0]); - this.powders[0] = this.powders[0].slice(0,helmet.slots); - this.helmet = expandItem(helmet, this.powders[0]); - } else { - try { - let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined); - if (helmet.statMap.get("type") !== "helmet") { - throw new Error("Not a helmet"); - } - this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots")); - helmet.statMap.set("powders",this.powders[0].slice()); - this.helmet = helmet.statMap; - applyArmorPowders(this.helmet, this.powders[0]); - if (this.helmet.get("custom")) { - this.customItems.push(helmet); - } else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(helmet); - } - - } catch (Error) { - const helmet = itemMap.get("No Helmet"); - this.powders[0] = this.powders[0].slice(0,helmet.slots); - this.helmet = expandItem(helmet, this.powders[0]); - errors.push(new ItemNotFound(equipment[0], "helmet", true)); - } - } - if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") { - const chestplate = itemMap.get(equipment[1]); - this.powders[1] = this.powders[1].slice(0,chestplate.slots); - this.chestplate = expandItem(chestplate, this.powders[1]); - } else { - try { - let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined); - if (chestplate.statMap.get("type") !== "chestplate") { - throw new Error("Not a chestplate"); - } - this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots")); - chestplate.statMap.set("powders",this.powders[1].slice()); - this.chestplate = chestplate.statMap; - applyArmorPowders(this.chesplate, this.powders[1]); - if (this.chestplate.get("custom")) { - this.customItems.push(chestplate); - } else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(chestplate); - } - } catch (Error) { - console.log(Error); - const chestplate = itemMap.get("No Chestplate"); - this.powders[1] = this.powders[1].slice(0,chestplate.slots); - this.chestplate = expandItem(chestplate, this.powders[1]); - errors.push(new ItemNotFound(equipment[1], "chestplate", true)); - } - } - if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") { - const leggings = itemMap.get(equipment[2]); - this.powders[2] = this.powders[2].slice(0,leggings.slots); - this.leggings = expandItem(leggings, this.powders[2]); - } else { - try { - let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined); - if (leggings.statMap.get("type") !== "leggings") { - throw new Error("Not a leggings"); - } - this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots")); - leggings.statMap.set("powders",this.powders[2].slice()); - this.leggings = leggings.statMap; - applyArmorPowders(this.leggings, this.powders[2]); - if (this.leggings.get("custom")) { - this.customItems.push(leggings); - } else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(leggings); - } - } catch (Error) { - const leggings = itemMap.get("No Leggings"); - this.powders[2] = this.powders[2].slice(0,leggings.slots); - this.leggings = expandItem(leggings, this.powders[2]); - errors.push(new ItemNotFound(equipment[2], "leggings", true)); - } - } - if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") { - const boots = itemMap.get(equipment[3]); - this.powders[3] = this.powders[3].slice(0,boots.slots); - this.boots = expandItem(boots, this.powders[3]); - } else { - try { - let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined); - if (boots.statMap.get("type") !== "boots") { - throw new Error("Not a boots"); - } - this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots")); - boots.statMap.set("powders",this.powders[3].slice()); - this.boots = boots.statMap; - applyArmorPowders(this.boots, this.powders[3]); - if (this.boots.get("custom")) { - this.customItems.push(boots); - } else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(boots); - } - } catch (Error) { - const boots = itemMap.get("No Boots"); - this.powders[3] = this.powders[3].slice(0,boots.slots); - this.boots = expandItem(boots, this.powders[3]); - errors.push(new ItemNotFound(equipment[3], "boots", true)); - } - } - if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") { - const ring = itemMap.get(equipment[4]); - this.ring1 = expandItem(ring, []); - }else{ - try { - let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined); - if (ring.statMap.get("type") !== "ring") { - throw new Error("Not a ring"); - } - this.ring1 = ring.statMap; - if (this.ring1.get("custom")) { - this.customItems.push(ring); - } else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(ring); - } - } catch (Error) { - const ring = itemMap.get("No Ring 1"); - this.ring1 = expandItem(ring, []); - errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring")); - } - } - if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") { - const ring = itemMap.get(equipment[5]); - this.ring2 = expandItem(ring, []); - }else{ - try { - let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined); - if (ring.statMap.get("type") !== "ring") { - throw new Error("Not a ring"); - } - this.ring2 = ring.statMap; - if (this.ring2.get("custom")) { - this.customItems.push(ring); - } else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(ring); - } - } catch (Error) { - const ring = itemMap.get("No Ring 2"); - this.ring2 = expandItem(ring, []); - errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring")); - } - } - if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") { - const bracelet = itemMap.get(equipment[6]); - this.bracelet = expandItem(bracelet, []); - }else{ - try { - let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined); - if (bracelet.statMap.get("type") !== "bracelet") { - throw new Error("Not a bracelet"); - } - this.bracelet = bracelet.statMap; - if (this.bracelet.get("custom")) { - this.customItems.push(bracelet); - } else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(bracelet); - } - } catch (Error) { - const bracelet = itemMap.get("No Bracelet"); - this.bracelet = expandItem(bracelet, []); - errors.push(new ItemNotFound(equipment[6], "bracelet", true)); - } - } - if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") { - const necklace = itemMap.get(equipment[7]); - this.necklace = expandItem(necklace, []); - }else{ - try { - let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined); - if (necklace.statMap.get("type") !== "necklace") { - throw new Error("Not a necklace"); - } - this.necklace = necklace.statMap; - if (this.necklace.get("custom")) { - this.customItems.push(necklace); - } else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(necklace); - } - } catch (Error) { - const necklace = itemMap.get("No Necklace"); - this.necklace = expandItem(necklace, []); - errors.push(new ItemNotFound(equipment[7], "necklace", true)); - } - } - if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") { - const weapon = itemMap.get(equipment[8]); - this.powders[4] = this.powders[4].slice(0,weapon.slots); - this.weapon = expandItem(weapon, this.powders[4]); - }else{ - try { - let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined); - if (weapon.statMap.get("category") !== "weapon") { - throw new Error("Not a weapon"); - } - this.weapon = weapon.statMap; - if (this.weapon.get("custom")) { - this.customItems.push(weapon); - } else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(weapon); - } - this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots")); - this.weapon.set("powders",this.powders[4].slice()); - // document.getElementsByClassName("powder-specials")[0].style.display = "grid"; - } catch (Error) { - const weapon = itemMap.get("No Weapon"); - this.powders[4] = this.powders[4].slice(0,weapon.slots); - this.weapon = expandItem(weapon, this.powders[4]); - // document.getElementsByClassName("powder-specials")[0].style.display = "none"; - errors.push(new ItemNotFound(equipment[8], "weapon", true)); - } - } - - //cannot craft tomes - - if(tomeMap.get(tomes[0]) && tomeMap.get(tomes[0]).type === "weaponTome") { - const weaponTome1 = tomeMap.get(tomes[0]); - this.weaponTome1 = expandItem(weaponTome1, []); - } else { - try { - let weaponTome1 = getCustomFromHash(tomes[0]) ? getCustomFromHash(tomes[0]) : undefined; - if (weaponTome1.statMap.get("type") !== "weaponTome") { - throw new Error("Not a Weapon Tome"); - } - if (this.weaponTome1.get("custom")) { - this.customItems.push(weaponTome1); - } //can't craft tomes - - } catch (Error) { - const weaponTome1 = tomeMap.get("No Weapon Tome"); - this.weaponTome1 = expandItem(weaponTome1, []); - errors.push(new ItemNotFound(tomes[0], "weaponTome1", true)); - } - } - if(tomeMap.get(tomes[1]) && tomeMap.get(tomes[1]).type === "weaponTome") { - const weaponTome2 = tomeMap.get(tomes[1]); - this.weaponTome2 = expandItem(weaponTome2, []); - } else { - try { - let weaponTome2 = getCustomFromHash(tomes[1]) ? getCustomFromHash(tomes[1]) : undefined; - if (weaponTome2.statMap.get("type") !== "weaponTome") { - throw new Error("Not a Weapon Tome"); - } - if (this.weaponTome2.get("custom")) { - this.customItems.push(weaponTome2); - } //can't craft tomes - - } catch (Error) { - const weaponTome2 = tomeMap.get("No Weapon Tome"); - this.weaponTome2 = expandItem(weaponTome2, []); - errors.push(new ItemNotFound(tomes[1], "weaponTome2", true)); - } - } - if(tomeMap.get(tomes[2]) && tomeMap.get(tomes[2]).type === "armorTome") { - const armorTome1 = tomeMap.get(tomes[2]); - this.armorTome1 = expandItem(armorTome1, []); - } else { - try { - let armorTome1 = getCustomFromHash(tomes[2]) ? getCustomFromHash(tomes[2]) : undefined; - if (armorTome1.statMap.get("type") !== "armorTome") { - throw new Error("Not an Armor Tome"); - } - if (this.armorTome1.get("custom")) { - this.customItems.push(armorTome1); - } //can't craft tomes - - } catch (Error) { - const armorTome1 = tomeMap.get("No Armor Tome"); - this.armorTome1 = expandItem(armorTome1, []); - errors.push(new ItemNotFound(tomes[2], "armorTome1", true)); - } - } - if(tomeMap.get(tomes[3]) && tomeMap.get(tomes[3]).type === "armorTome") { - const armorTome2 = tomeMap.get(tomes[3]); - this.armorTome2 = expandItem(armorTome2, []); - } else { - try { - let armorTome2 = getCustomFromHash(tomes[3]) ? getCustomFromHash(tomes[3]) : undefined; - if (armorTome2.statMap.get("type") !== "armorTome") { - throw new Error("Not an Armor Tome"); - } - if (this.armorTome2.get("custom")) { - this.customItems.push(armorTome2); - } //can't craft tomes - - } catch (Error) { - const armorTome2 = tomeMap.get("No Armor Tome"); - this.armorTome2 = expandItem(armorTome2, []); - errors.push(new ItemNotFound(tomes[3], "armorTome2", true)); - } - } - if(tomeMap.get(tomes[4]) && tomeMap.get(tomes[4]).type === "armorTome") { - const armorTome3 = tomeMap.get(tomes[4]); - this.armorTome3 = expandItem(armorTome3, []); - } else { - try { - let armorTome3 = getCustomFromHash(tomes[4]) ? getCustomFromHash(tomes[4]) : undefined; - if (armorTome3.statMap.get("type") !== "armorTome") { - throw new Error("Not an Armor Tome"); - } - if (this.armorTome3.get("custom")) { - this.customItems.push(armorTome3); - } //can't craft tomes - - } catch (Error) { - const armorTome3 = tomeMap.get("No Armor Tome"); - this.armorTome3 = expandItem(armorTome3, []); - errors.push(new ItemNotFound(tomes[4], "armorTome3", true)); - } - } - if(tomeMap.get(tomes[5]) && tomeMap.get(tomes[5]).type === "armorTome") { - const armorTome4 = tomeMap.get(tomes[5]); - this.armorTome4 = expandItem(armorTome4, []); - } else { - try { - let armorTome4 = getCustomFromHash(tomes[5]) ? getCustomFromHash(tomes[5]) : undefined; - if (armorTome4.statMap.get("type") !== "armorTome") { - throw new Error("Not an Armor Tome"); - } - if (this.armorTome4.get("custom")) { - this.customItems.push(armorTome4); - } //can't craft tomes - - } catch (Error) { - const armorTome4 = tomeMap.get("No Armor Tome"); - this.armorTome4 = expandItem(armorTome4, []); - errors.push(new ItemNotFound(tomes[5], "armorTome4", true)); - } - } - if(tomeMap.get(tomes[6]) && tomeMap.get(tomes[6]).type === "guildTome") { - const guildTome1 = tomeMap.get(tomes[6]); - this.guildTome1 = expandItem(guildTome1, []); - } else { - try { - let guildTome1 = getCustomFromHash(tomes[6]) ? getCustomFromHash(tomes[6]) : undefined; - if (guildTome1.statMap.get("type") !== "guildTome1") { - throw new Error("Not an Guild Tome"); - } - if (this.guildTome1.get("custom")) { - this.customItems.push(guildTome1); - } //can't craft tomes - - } catch (Error) { - const guildTome1 = tomeMap.get("No Guild Tome"); - this.guildTome1 = expandItem(guildTome1, []); - errors.push(new ItemNotFound(tomes[6], "guildTome1", true)); - } - } - - //console.log(this.craftedItems) - - if (level < 1) { //Should these be constants? - this.level = 1; - } else if (level > 106) { - this.level = 106; - } else if (level <= 106 && level >= 1) { - this.level = level; - } else if (typeof level === "string") { - this.level = level; - errors.push(new IncorrectInput(level, "a number", "level-choice")); - } else { - errors.push("Level is not a string or number."); - } - document.getElementById("level-choice").value = this.level; - - this.availableSkillpoints = levelToSkillPoints(this.level); - this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ]; - this.tomes = [this.weaponTome1, this.weaponTome2, this.armorTome1, this.armorTome2, this.armorTome3, this.armorTome4, this.guildTome1]; - this.items = this.equipment.concat([this.weapon]).concat(this.tomes); - // return [equip_order, best_skillpoints, final_skillpoints, best_total]; - let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon); - console.log(result); - this.equip_order = result[0]; - // How many skillpoints the player had to assign (5 number) - this.base_skillpoints = result[1]; - // How many skillpoints the build ended up with (5 number) - this.total_skillpoints = result[2]; - // How many skillpoints assigned (1 number, sum of base_skillpoints) - this.assigned_skillpoints = result[3]; - this.activeSetCounts = result[4]; - - // For strength boosts like warscream, vanish, etc. - this.damageMultiplier = 1.0; - this.defenseMultiplier = 1.0; - - // For other external boosts ;-; - this.externalStats = externalStats; - - this.initBuildStats(); - - // Remove every error before adding specific ones - for (let i of document.getElementsByClassName("error")) { - i.textContent = ""; - } - this.errors = errors; - if (errors.length > 0) this.errored = true; - } - - /*Returns build in string format - */ - toString(){ - return [this.equipment,this.weapon,this.tomes].flat(); - } - - /* Getters */ - - getSpellCost(spellIdx, cost) { - return Math.max(1, this.getBaseSpellCost(spellIdx, cost)); - } - - getBaseSpellCost(spellIdx, cost) { - cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); - cost += this.statMap.get("spRaw"+spellIdx); - return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)); - } - - - /* Get melee stats for build. - Returns an array in the order: - */ - getMeleeStats(){ - const stats = this.statMap; - if (this.weapon.get("tier") === "Crafted") { - stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]); - } - let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier"); - if(adjAtkSpd > 6){ - adjAtkSpd = 6; - }else if(adjAtkSpd < 0){ - adjAtkSpd = 0; - } - - let damage_mult = 1; - if (this.weapon.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") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats); - - let dex = this.total_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]); - } - - /* - Get all defensive stats for this build. - */ - getDefenseStats(){ - const stats = this.statMap; - let defenseStats = []; - let def_pct = skillPointsToPercentage(this.total_skillpoints[3]); - let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]); - //total hp - let totalHp = stats.get("hp") + stats.get("hpBonus"); - if (totalHp < 5) totalHp = 5; - defenseStats.push(totalHp); - //EHP - let ehp = [totalHp, totalHp]; - let defMult = classDefenseMultipliers.get(this.weapon.get("type")); - ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); - ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); - defenseStats.push(ehp); - //HPR - let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.); - defenseStats.push(totalHpr); - //EHPR - let ehpr = [totalHpr, totalHpr]; - ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); - ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); - defenseStats.push(ehpr); - //skp stats - defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*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; - } - - /* Get all stats for this build. Stores in this.statMap. - @pre The build itself should be valid. No checking of validity of pieces is done here. - */ - initBuildStats(){ - - let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi"]; - - //Create a map of this build's stats - let statMap = new Map(); - - for (const staticID of staticIDs) { - statMap.set(staticID, 0); - } - statMap.set("hp", levelToHPBase(this.level)); - - let major_ids = new Set(); - for (const item of this.items){ - for (let [id, value] of item.get("maxRolls")) { - if (staticIDs.includes(id)) { - continue; - } - statMap.set(id,(statMap.get(id) || 0)+value); - } - for (const staticID of staticIDs) { - if (item.get(staticID)) { - statMap.set(staticID, statMap.get(staticID) + item.get(staticID)); - } - } - if (item.get("majorIds")) { - for (const major_id of item.get("majorIds")) { - major_ids.add(major_id); - } - } - } - statMap.set("activeMajorIDs", major_ids); - for (const [setName, count] of this.activeSetCounts) { - const bonus = sets[setName].bonuses[count-1]; - for (const id in bonus) { - if (skp_order.includes(id)) { - // pass. Don't include skillpoints in ids - } - else { - statMap.set(id,(statMap.get(id) || 0)+bonus[id]); - } - } - } - statMap.set("poisonPct", 100); - - // The stuff relevant for damage calculation!!! @ferricles - statMap.set("atkSpd", this.weapon.get("atkSpd")); - - for (const x of skp_elements) { - this.externalStats.set(x + "DamPct", 0); - } - this.externalStats.set("mdPct", 0); - this.externalStats.set("sdPct", 0); - this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]); - this.externalStats.set("defBonus",[0, 0, 0, 0, 0]); - this.externalStats.set("poisonPct", 0); - this.statMap = statMap; - - this.aggregateStats(); - } - - aggregateStats() { - let statMap = this.statMap; - statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]); - statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]); - statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]); - statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]); - statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type"))); - } -} diff --git a/js/sq2builder.js b/js/sq2builder.js deleted file mode 100644 index 31a0657..0000000 --- a/js/sq2builder.js +++ /dev/null @@ -1,1127 +0,0 @@ -const url_tag = location.hash.slice(1); -// console.log(url_base); -// console.log(url_tag); - - -const BUILD_VERSION = "7.0.19"; - -let player_build; - - -// THIS IS SUPER DANGEROUS, WE SHOULD NOT BE KEEPING THIS IN SO MANY PLACES -let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison", - "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", - "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", - "hprRaw", "hprPct", "hpBonus", "atkTier", - "spPct1", "spRaw1", "spPct2", "spRaw2", - "spPct3", "spRaw3", "spPct4", "spRaw4" ]; - -let editable_elems = []; - -for (let i of editable_item_fields) { - let elem = document.getElementById(i); - elem.addEventListener("change", (event) => { - elem.classList.add("highlight"); - }); - editable_elems.push(elem); -} - -for (let i of skp_order) { - let elem = document.getElementById(i+"-skp"); - elem.addEventListener("change", (event) => { - elem.classList.add("highlight"); - }); - editable_elems.push(elem); -} - -function clear_highlights() { - for (let i of editable_elems) { - i.classList.remove("highlight"); - } -} - - -let equipment_fields = [ - "helmet", - "chestplate", - "leggings", - "boots", - "ring1", - "ring2", - "bracelet", - "necklace", - "weapon" -]; -let tome_fields = [ - "weaponTome1", - "weaponTome2", - "armorTome1", - "armorTome2", - "armorTome3", - "armorTome4", - "guildTome1", -] -let equipment_names = [ - "Helmet", - "Chestplate", - "Leggings", - "Boots", - "Ring 1", - "Ring 2", - "Bracelet", - "Necklace", - "Weapon" -]; - -let tome_names = [ - "Weapon Tome", - "Weapon Tome", - "Armor Tome", - "Armor Tome", - "Armor Tome", - "Armor Tome", - "Guild Tome", -] -let equipmentInputs = equipment_fields.map(x => x + "-choice"); -let buildFields = equipment_fields.map(x => x+"-tooltip").concat(tome_fields.map(x => x + "-tooltip")); -let tomeInputs = tome_fields.map(x => x + "-choice"); - -let powderInputs = [ - "helmet-powder", - "chestplate-powder", - "leggings-powder", - "boots-powder", - "weapon-powder", -]; - -function getItemNameFromID(id) { - if (redirectMap.has(id)) { - return getItemNameFromID(redirectMap.get(id)); - } - return idMap.get(id); -} - -function getTomeNameFromID(id) { - if (tomeRedirectMap.has(id)) { - return getTomeNameFromID(tomeRedirectMap.get(id)); - } - return tomeIDMap.get(id); -} - -function parsePowdering(powder_info) { - // TODO: Make this run in linear instead of quadratic time... ew - let powdering = []; - for (let i = 0; i < 5; ++i) { - let powders = ""; - let n_blocks = Base64.toInt(powder_info.charAt(0)); - powder_info = powder_info.slice(1); - for (let j = 0; j < n_blocks; ++j) { - let block = powder_info.slice(0,5); - let six_powders = Base64.toInt(block); - for (let k = 0; k < 6 && six_powders != 0; ++k) { - powders += powderNames.get((six_powders & 0x1f) - 1); - six_powders >>>= 5; - } - powder_info = powder_info.slice(5); - } - powdering[i] = powders; - } - return [powdering, powder_info]; -} - -/* - * Populate fields based on url, and calculate build. - */ -function decodeBuild(url_tag) { - if (url_tag) { - //default values - let equipment = [null, null, null, null, null, null, null, null, null]; - let tomes = [null, null, null, null, null, null, null]; - let powdering = ["", "", "", "", ""]; - let info = url_tag.split("_"); - let version = info[0]; - let save_skp = false; - let skillpoints = [0, 0, 0, 0, 0]; - let level = 106; - - version_number = parseInt(version) - //equipment (items) - // TODO: use filters - if (version_number < 4) { - let equipments = info[1]; - for (let i = 0; i < 9; ++i ) { - let equipment_str = equipments.slice(i*3,i*3+3); - equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); - } - info[1] = equipments.slice(27); - } - else if (version_number == 4) { - let info_str = info[1]; - let start_idx = 0; - for (let i = 0; i < 9; ++i ) { - if (info_str.slice(start_idx,start_idx+3) === "CR-") { - equipment[i] = info_str.slice(start_idx, start_idx+20); - start_idx += 20; - } else { - let equipment_str = info_str.slice(start_idx, start_idx+3); - equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); - start_idx += 3; - } - } - info[1] = info_str.slice(start_idx); - } - else if (version_number <= 6) { - let info_str = info[1]; - let start_idx = 0; - for (let i = 0; i < 9; ++i ) { - if (info_str.slice(start_idx,start_idx+3) === "CR-") { - equipment[i] = info_str.slice(start_idx, start_idx+20); - start_idx += 20; - } else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") { - let len = Base64.toInt(info_str.slice(start_idx,start_idx+3)); - equipment[i] = info_str.slice(start_idx+3,start_idx+3+len); - start_idx += (3+len); - } else { - let equipment_str = info_str.slice(start_idx, start_idx+3); - equipment[i] = getItemNameFromID(Base64.toInt(equipment_str)); - start_idx += 3; - } - } - info[1] = info_str.slice(start_idx); - } - //constant in all versions - for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); - } - - //level, skill point assignments, and powdering - if (version_number == 1) { - let powder_info = info[1]; - let res = parsePowdering(powder_info); - powdering = res[0]; - } else if (version_number == 2) { - save_skp = true; - let skillpoint_info = info[1].slice(0, 10); - for (let i = 0; i < 5; ++i ) { - skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2)); - } - - let powder_info = info[1].slice(10); - let res = parsePowdering(powder_info); - powdering = res[0]; - } else if (version_number <= 6){ - level = Base64.toInt(info[1].slice(10,12)); - setValue("level-choice",level); - save_skp = true; - let skillpoint_info = info[1].slice(0, 10); - for (let i = 0; i < 5; ++i ) { - skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2)); - } - - let powder_info = info[1].slice(12); - - let res = parsePowdering(powder_info); - powdering = res[0]; - info[1] = res[1]; - } - // Tomes. - if (version_number == 6) { - //tome values do not appear in anything before v6. - for (let i = 0; i < 7; ++i) { - let tome_str = info[1].charAt(i); - setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str))); - } - info[1] = info[1].slice(7); - } - - for (let i in powderInputs) { - setValue(powderInputs[i], powdering[i]); - } - - calculateBuild(save_skp, skillpoints); - } -} - -/* Stores the entire build in a string using B64 encoding and adds it to the URL. -*/ -function encodeBuild() { - - if (player_build) { - let build_string; - - //V6 encoding - Tomes - build_version = 4; - build_string = ""; - tome_string = ""; - - let crafted_idx = 0; - let custom_idx = 0; - for (const item of player_build.items) { - - if (item.get("custom")) { - let custom = "CI-"+encodeCustom(player_build.customItems[custom_idx],true); - build_string += Base64.fromIntN(custom.length, 3) + custom; - custom_idx += 1; - build_version = Math.max(build_version, 5); - } else if (item.get("crafted")) { - build_string += "CR-"+encodeCraft(player_build.craftedItems[crafted_idx]); - crafted_idx += 1; - } else if (item.get("category") === "tome") { - let tome_id = item.get("id"); - if (tome_id <= 60) { - // valid normal tome. ID 61-63 is for NONE tomes. - build_version = Math.max(build_version, 6); - } - tome_string += Base64.fromIntN(tome_id, 1); - } else { - build_string += Base64.fromIntN(item.get("id"), 3); - } - } - - for (const skp of skp_order) { - build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048 - } - build_string += Base64.fromIntN(player_build.level, 2); - for (const _powderset of player_build.powders) { - let n_bits = Math.ceil(_powderset.length / 6); - build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders. - // Slice copy. - let powderset = _powderset.slice(); - while (powderset.length != 0) { - let firstSix = powderset.slice(0,6).reverse(); - let powder_hash = 0; - for (const powder of firstSix) { - powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first. - } - build_string += Base64.fromIntN(powder_hash, 5); - powderset = powderset.slice(6); - } - } - build_string += tome_string; - - return build_version.toString() + "_" + build_string; - } -} - -function calculateBuild(save_skp, skp){ - try { - resetEditableIDs(); - if (player_build) { - reset_powder_specials(); - updateBoosts("skip", false); - updatePowderSpecials("skip", false); - } - let weaponName = getValue(equipmentInputs[8]); - //bruh @hpp - if (weaponName.startsWith("Morph-")) { - let equipment = [ "Morph-Stardust", "Morph-Steel", "Morph-Iron", "Morph-Gold", "Morph-Topaz", "Morph-Emerald", "Morph-Amethyst", "Morph-Ruby", weaponName.substring(6) ]; - for (let i in equipment) { - setValue(equipmentInputs[i], equipment[i]); - } - } - - //updatePowderSpecials("skip"); //jank pt 1 - save_skp = (typeof save_skp !== 'undefined') ? save_skp : false; - /* TODO: implement level changing - Make this entire function prettier - */ - let equipment = [ null, null, null, null, null, null, null, null, null ]; - for (let i in equipment) { - let equip = getValue(equipmentInputs[i]).trim(); - if (equip === "") { - equip = "No " + equipment_names[i] - } - else { - setValue(equipmentInputs[i], equip); - } - equipment[i] = equip; - } - let powderings = []; - let errors = []; - for (const i in powderInputs) { - // read in two characters at a time. - // TODO: make this more robust. - let input = getValue(powderInputs[i]).trim(); - let powdering = []; - let errorederrors = []; - while (input) { - let first = input.slice(0, 2); - let powder = powderIDs.get(first); - if (powder === undefined) { - errorederrors.push(first); - } else { - powdering.push(powder); - } - input = input.slice(2); - } - if (errorederrors.length > 0) { - if (errorederrors.length > 1) - errors.push(new IncorrectInput(errorederrors.join(""), "t6w6", powderInputs[i])); - else - errors.push(new IncorrectInput(errorederrors[0], "t6 or e3", powderInputs[i])); - } - //console.log("POWDERING: " + powdering); - powderings.push(powdering); - } - let tomes = [ null, null, null, null, null, null, null]; - for (let i in tomes) { - let equip = getValue(tomeInputs[i]).trim(); - if (equip === "") { - equip = "No " + tome_names[i] - } - else { - setValue(tomeInputs[i], equip); - } - tomes[i] = equip; - } - - - let level = document.getElementById("level-choice").value; - player_build = new Build(level, equipment, powderings, new Map(), errors, tomes); - console.log(player_build); - - //isn't this deprecated? - for (let i of document.getElementsByClassName("hide-container-block")) { - i.style.display = "block"; - } - for (let i of document.getElementsByClassName("hide-container-grid")) { - i.style.display = "grid"; - } - - console.log(player_build.toString()); - displaysq2EquipOrder(document.getElementById("build-order"),player_build.equip_order); - - const assigned = player_build.base_skillpoints; - const skillpoints = player_build.total_skillpoints; - for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-base", "Original: " + skillpoints[i]); - } - - if (save_skp) { - // TODO: reduce duplicated code, @updateStats - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let manual_assigned = skp[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - } - - updateEditableIDs(); - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - console.log(error); - } -} - -function handleBuilderError(error) { - if (error instanceof ListError) { - for (let i of error.errors) { - if (i instanceof ItemNotFound) { - i.element.textContent = i.message; - } else if (i instanceof IncorrectInput) { - if (document.getElementById(i.id) !== null) { - document.getElementById(i.id).parentElement.querySelectorAll("p.error")[0].textContent = i.message; - } - } else { - let msg = i.stack; - let lines = msg.split("\n"); - let header = document.getElementById("header"); - header.textContent = ""; - for (const line of lines) { - let p = document.createElement("p"); - p.classList.add("itemp"); - p.textContent = line; - header.appendChild(p); - } - let p2 = document.createElement("p"); - p2.textContent = "If you believe this is an error, contact hppeng on forums or discord."; - header.appendChild(p2); - } - } - } else { - let msg = error.stack; - let lines = msg.split("\n"); - let header = document.getElementById("header"); - header.textContent = ""; - for (const line of lines) { - let p = document.createElement("p"); - p.classList.add("itemp"); - p.textContent = line; - header.appendChild(p); - } - let p2 = document.createElement("p"); - p2.textContent = "If you believe this is an error, contact hppeng on forums or discord."; - header.appendChild(p2); - } -} - -/* Updates all build statistics based on (for now) the skillpoint input fields and then calculates build stats. -*/ -function updateStats() { - - let specialNames = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"]; - for (const sName of specialNames) { - for (let i = 1; i < 6; i++) { - let elem = document.getElementById(sName + "-" + i); - let name = sName.replace("_", " "); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(sName)]; - console.log(special); - if (special["weaponSpecialEffects"].has("Damage Boost")) { - if (name === "Courage" || name === "Curse") { //courage is universal damage boost - //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100; - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1]; - } - } - } - } - } - - - - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let value = document.getElementById(skp_order[i] + "-skp").value; - if (value === ""){value = 0; setValue(skp_order[i] + "-skp", value)} - let manual_assigned = 0; - if (value.includes("+")) { - let skp = value.split("+"); - for (const s of skp) { - manual_assigned += parseInt(s,10); - } - } else { - manual_assigned = parseInt(value,10); - } - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - if(player_build){ - updatePowderSpecials("skip", false); - updateArmorPowderSpecials("skip", false); - updateBoosts("skip", false); - for (let id of editable_item_fields) { - player_build.statMap.set(id, parseInt(getValue(id))); - } - } - player_build.aggregateStats(); - console.log(player_build.statMap); - calculateBuildStats(); -} - - -/* Updates all IDs in the edit IDs section. Resets each input and original value text to the correct text according to the current build. -*/ -function updateEditableIDs() { - if (player_build) { - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - let val = player_build.statMap.get(id); - edit_input.value = val; - edit_input.placeholder = val; - - let value_label = document.getElementById(id + "-base"); - value_label.textContent = "Original Value: " + val; - //a hack to make resetting easier - value_label.value = val; - } - } -} - -/* Resets all IDs in the edit IDs section to their "original" values. -*/ -function resetEditableIDs() { - if (player_build) { - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - let value_label = document.getElementById(id + "-base"); - - edit_input.value = value_label.value; - edit_input.placeholder = value_label.value; - } - } else { - //no player build, reset to 0 - for (const id of editable_item_fields) { - let edit_input = document.getElementById(id); - - edit_input.value = 0; - edit_input.placeholder = 0; - } - } -} - -/* Updates all spell boosts -*/ -function updateBoosts(buttonId, recalcStats) { - let elem = document.getElementById(buttonId); - let name = buttonId.split("-")[0]; - if(buttonId !== "skip") { - if (elem.classList.contains("toggleOn")) { - player_build.damageMultiplier -= damageMultipliers.get(name); - if (name === "warscream") { - player_build.defenseMultiplier -= .20; - } - if (name === "vanish") { - player_build.defenseMultiplier -= .15; - } - elem.classList.remove("toggleOn"); - }else{ - player_build.damageMultiplier += damageMultipliers.get(name); - if (name === "warscream") { - player_build.defenseMultiplier += .20; - } - if (name === "vanish") { - player_build.defenseMultiplier += .15; - } - elem.classList.add("toggleOn"); - } - updatePowderSpecials("skip", false); //jank pt 1 - } else { - for (const [key, value] of damageMultipliers) { - let elem = document.getElementById(key + "-boost") - if (elem.classList.contains("toggleOn")) { - elem.classList.remove("toggleOn"); - player_build.damageMultiplier -= value; - if (key === "warscream") { player_build.defenseMultiplier -= .20 } - if (key === "vanish") { player_build.defenseMultiplier -= .15 } - } - } - } - if (recalcStats) { - calculateBuildStats(); - } -} - -/* Updates ACTIVE powder special boosts (weapons) -*/ -function updatePowderSpecials(buttonId, recalcStats) { - //console.log(player_build.statMap); - - let name = (buttonId).split("-")[0]; - let power = (buttonId).split("-")[1]; // [1, 5] - let specialNames = ["Quake", "Chain Lightning", "Curse", "Courage", "Wind Prison"]; - let powderSpecials = []; // [ [special, power], [special, power]] - - - if(name !== "skip"){ - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special.weaponSpecialEffects.has("Damage Boost")) { - name = name.replace("_", " "); - if (name === "Courage" || name === "Curse") { //courage and curse are universal damage boost - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - //poison? - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[power-1]; - } - } - } 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"); - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special.weaponSpecialEffects.has("Damage Boost")) { - name = name.replace("_", " "); //might be redundant - if (name === "Courage" || name === "Curse") { //courage is universal damage boost - //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100; - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]); - player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1]; - } - } - } - } - //toggle the pressed button on - elem.classList.add("toggleOn"); - } - } - - for (const sName of specialNames) { - for (let i = 1;i < 6; i++) { - if (document.getElementById(sName.replace(" ","_") + "-" + i).classList.contains("toggleOn")) { - let powderSpecial = powderSpecialStats[specialNames.indexOf(sName.replace("_"," "))]; - powderSpecials.push([powderSpecial, i]); - break; - } - } - } - - - if (name !== "skip") { - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { - let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))]; - if (special["weaponSpecialEffects"].has("Damage Boost")) { - let name = special["weaponSpecialName"]; - if (name === "Courage" || name === "Curse") { //courage and curse are is universal damage boost - player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - } else if (name === "Wind Prison") { - player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]); - player_build.externalStats.get("damageBonus")[4] += special.weaponSpecialEffects.get("Damage Boost")[power-1]; - } - } - } - } - - if (recalcStats) { - calculateBuildStats(); - } - displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); -} - - -/* Updates PASSIVE powder special boosts (armors) -*/ -function updateArmorPowderSpecials(elem_id, recalc_stats) { - //we only update the powder special + external stats if the player has a build - if (elem_id !== "skip") { - if (player_build !== undefined && player_build.weapon !== undefined && player_build.weapon.get("name") !== "No Weapon") { - 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 prev_label = document.getElementById(elem_id + "_prev"); - - let value = elem.value; - - //for use in editing build stats - let prev_value = prev_label.value; - let value_diff = value - prev_value; - - //update the "previous" label - prev_label.value = value; - - label.textContent = label.textContent.split(":")[0] + ": " + value; - - let dmg_id = elem_chars[skp_names.indexOf(wynn_elem)] + "DamPct"; - let new_dmgboost = player_build.externalStats.get(dmg_id) + value_diff; - - //update build external stats - the second one is the relevant one for damage calc purposes - player_build.externalStats.set(dmg_id, new_dmgboost); - player_build.externalStats.get("damageBonus")[skp_names.indexOf(wynn_elem)] = new_dmgboost; - - //update the slider's graphics - let bg_color = elem_colors[skp_names.indexOf(wynn_elem)]; - let pct = Math.round(100 * value / powderSpecialStats[skp_names.indexOf(wynn_elem)].cap); - elem.style.background = `linear-gradient(to right, ${bg_color}, ${bg_color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`; - - } - } else { - if (player_build !== undefined) { - for (let i = 0; i < skp_names.length; ++i) { - skp_name = skp_names[i]; - skp_char = elem_chars[i]; - player_build.externalStats.set(skp_char + "DamPct", player_build.externalStats.get(skp_char + "DamPct") - document.getElementById(skp_name+"_boost_armor").value); - player_build.externalStats.get("damageBonus")[i] -= document.getElementById(skp_name+"_boost_armor").value; - } - } - } - - - if (recalc_stats && player_build) { - //calc build stats and display powder special - calculateBuildStats(); - // displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true); - } - -} - -function resetArmorPowderSpecials() { - for (const skp of skp_names) { - document.getElementById(skp + "_boost_armor").value = 0; - document.getElementById(skp + "_boost_armor_prev").value = 0; - document.getElementById(skp + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`; - document.getElementById(skp + "_boost_armor_label").textContent = `% ${capitalizeFirst(elem_names[skp_names.indexOf(skp)])} Damage Boost: 0` - } -} - -/* Calculates all build statistics and updates the entire display. -*/ -function calculateBuildStats() { - const assigned = player_build.base_skillpoints; - const skillpoints = player_build.total_skillpoints; - let skp_effects = ["% more damage dealt.","% chance to crit.","% spell cost reduction.","% less damage taken.","% chance to dodge."]; - for (let i in skp_order){ //big bren - setText(skp_order[i] + "-skp-assign", "Assign: " + assigned[i]); - setValue(skp_order[i] + "-skp", skillpoints[i]); - let linebreak = document.createElement("br"); - linebreak.classList.add("itemp"); - document.getElementById(skp_order[i] + "-skp-label"); - 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[i] > 100) { - let skp_warning = document.createElement("p"); - skp_warning.classList.add("warning"); - skp_warning.classList.add("small-text") - skp_warning.textContent += "Cannot assign " + assigned[i] + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually."; - document.getElementById(skp_order[i]+"-warnings").textContent = '' - document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning); - } - } - - 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 " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: "; - let remainingSkpContent = document.createElement("b"); - remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints); - remainingSkpContent.classList.add(levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints < 0 ? "negative" : "positive"); - - remainingSkp.appendChild(remainingSkpTitle); - remainingSkp.appendChild(remainingSkpContent); - - - summarybox.append(skpRow); - summarybox.append(remainingSkp); - if(player_build.assigned_skillpoints > levelToSkillPoints(player_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 " + (player_build.level>101 ? "101+" : player_build.level) + ", there are only " + levelToSkillPoints(player_build.level) + " skill points available."; - summarybox.append(skpWarning); - summarybox.append(skpCount); - } - let lvlWarning; - for (const item of player_build.items) { - let item_lvl; - if (item.get("crafted")) { - //item_lvl = item.get("lvlLow") + "-" + item.get("lvl"); - item_lvl = item.get("lvlLow"); - } - else { - item_lvl = item.get("lvl"); - } - - if (player_build.level < item_lvl) { - if (!lvlWarning) { - lvlWarning = document.createElement("p"); - lvlWarning.classList.add("itemp"); - lvlWarning.classList.add("warning"); - lvlWarning.textContent = "WARNING: A level " + player_build.level + " player cannot use some piece(s) of this build." - } - let baditem = document.createElement("p"); - baditem.classList.add("nocolor"); - baditem.classList.add("itemp"); - baditem.textContent = item.get("displayName") + " requires level " + item.get("lvl") + " to use."; - lvlWarning.appendChild(baditem); - } - } - if(lvlWarning){ - summarybox.append(lvlWarning); - } - for (const [setName, count] of player_build.activeSetCounts) { - const bonus = sets[setName].bonuses[count-1]; - // console.log(setName); - if (bonus["illegal"]) { - let setWarning = document.createElement("p"); - setWarning.classList.add("itemp"); - setWarning.classList.add("warning"); - setWarning.textContent = "WARNING: illegal item combination: " + setName - summarybox.append(setWarning); - } - } - - for (let i in player_build.items) { - displaysq2ExpandedItem(player_build.items[i], buildFields[i]); - collapse_element("#"+equipment_keys[i]+"-tooltip"); - } - - displaysq2ArmorStats(player_build); - displaysq2BuildStats('overall-stats', player_build, build_all_display_commands); - displaysq2BuildStats("offensive-stats",player_build, build_offensive_display_commands); - displaysq2SetBonuses("set-info",player_build); - displaysq2WeaponStats(player_build); - - let meleeStats = player_build.getMeleeStats(); - displaysq2MeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); - - displaysq2DefenseStats(document.getElementById("defensive-stats"),player_build); - - displaysq2PoisonDamage(document.getElementById("build-poison-stats"),player_build); - - let spells = spell_table[player_build.weapon.get("type")]; - for (let i = 0; i < 4; ++i) { - let parent_elem = document.getElementById("spell"+i+"-info"); - let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaysq2SpellDamage(parent_elem, overallparent_elem, player_build, spells[i], i+1); - } - - location.hash = encodeBuild(); - clear_highlights(); -} - -function copyBuild() { - if (player_build) { - copyTextToClipboard(url_base+location.hash); - document.getElementById("copy-button").textContent = "Copied!"; - } -} - -function shareBuild() { - if (player_build) { - let text = url_base+location.hash+"\n"+ - "WynnBuilder build:\n"+ - "> "+player_build.helmet.get("displayName")+"\n"+ - "> "+player_build.chestplate.get("displayName")+"\n"+ - "> "+player_build.leggings.get("displayName")+"\n"+ - "> "+player_build.boots.get("displayName")+"\n"+ - "> "+player_build.ring1.get("displayName")+"\n"+ - "> "+player_build.ring2.get("displayName")+"\n"+ - "> "+player_build.bracelet.get("displayName")+"\n"+ - "> "+player_build.necklace.get("displayName")+"\n"+ - "> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]"; - copyTextToClipboard(text); - document.getElementById("share-button").textContent = "Copied!"; - } -} - -function populateBuildList() { - const buildList = document.getElementById("build-choice"); - const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); - - for (const buildName of Object.keys(savedBuilds).sort()) { - const buildOption = document.createElement("option"); - buildOption.setAttribute("value", buildName); - buildList.appendChild(buildOption); - } -} - -function saveBuild() { - if (player_build) { - const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); - const saveName = document.getElementById("build-name").value; - const encodedBuild = encodeBuild(); - if ((!Object.keys(savedBuilds).includes(saveName) - || document.getElementById("saved-error").textContent !== "") && encodedBuild !== "") { - savedBuilds[saveName] = encodedBuild.replace("#", ""); - window.localStorage.setItem("builds", JSON.stringify(savedBuilds)); - - document.getElementById("saved-error").textContent = ""; - document.getElementById("saved-build").textContent = "Build saved locally"; - - const buildList = document.getElementById("build-choice"); - const buildOption = document.createElement("option"); - buildOption.setAttribute("value", saveName); - buildList.appendChild(buildOption); - } else { - document.getElementById("saved-build").textContent = ""; - if (encodedBuild === "") { - document.getElementById("saved-error").textContent = "Empty build"; - } - else { - document.getElementById("saved-error").textContent = "Exists. Overwrite?"; - } - } - } -} - -function loadBuild() { - let savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); - let saveName = document.getElementById("build-name").value; - - if (Object.keys(savedBuilds).includes(saveName)) { - decodeBuild(savedBuilds[saveName]) - document.getElementById("loaded-error").textContent = ""; - document.getElementById("loaded-build").textContent = "Build loaded"; - } else { - document.getElementById("loaded-build").textContent = ""; - document.getElementById("loaded-error").textContent = "Build doesn't exist"; - } -} - -function resetFields(){ - for (let i in powderInputs) { - setValue(powderInputs[i], ""); - } - for (let i in equipmentInputs) { - setValue(equipmentInputs[i], ""); - } - setValue("str-skp", "0"); - setValue("dex-skp", "0"); - setValue("int-skp", "0"); - setValue("def-skp", "0"); - setValue("agi-skp", "0"); - setValue("level-choice", "106"); - location.hash = ""; - calculateBuild(); -} - -function toggleID() { - let button = document.getElementById("show-id-button"); - let targetDiv = document.getElementById("id-edit"); - if (button.classList.contains("toggleOn")) { //toggle the pressed button off - targetDiv.style.display = "none"; - button.classList.remove("toggleOn"); - } - else { - targetDiv.style.display = "block"; - button.classList.add("toggleOn"); - } -} - -function toggleButton(button_id) { - let button = document.getElementById(button_id); - if (button) { - if (button.classList.contains("toggleOn")) { - button.classList.remove("toggleOn"); - } else { - button.classList.add("toggleOn"); - } - } -} - -function optimizeStrDex() { - if (!player_build) { - return; - } - const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; - const base_skillpoints = player_build.base_skillpoints; - const max_str_boost = 100 - base_skillpoints[0]; - const max_dex_boost = 100 - base_skillpoints[1]; - if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable - - const base_total_skillpoints = player_build.total_skillpoints; - let str_bonus = remaining; - let dex_bonus = 0; - let best_skillpoints = player_build.total_skillpoints; - let best_damage = 0; - for (let i = 0; i <= remaining; ++i) { - let total_skillpoints = base_total_skillpoints.slice(); - total_skillpoints[0] += Math.min(max_str_boost, str_bonus); - total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); - - // Calculate total 3rd spell damage - let spell = spell_table[player_build.weapon.get("type")][2]; - const stats = player_build.statMap; - let critChance = skillPointsToPercentage(total_skillpoints[1]); - let save_damages = []; - 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; - } - } - } - let total_damage = 0; - for (const part of spell_parts) { - if (part.type === "damage") { - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw"), stats.get("sdPct") + player_build.externalStats.get("sdPct"), - part.multiplier / 100, player_build.weapon, total_skillpoints, - player_build.damageMultiplier, player_build.externalStats); - let totalDamNormal = _results[0]; - let totalDamCrit = _results[1]; - let results = _results[2]; - let tooltipinfo = _results[3]; - - for (let i = 0; i < 6; ++i) { - for (let j in results[i]) { - results[i][j] = results[i][j].toFixed(2); - } - } - let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; - let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; - let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; - - save_damages.push(averageDamage); - if (part.summary == true) { - total_damage = averageDamage; - } - } else if (part.type === "total") { - total_damage = 0; - for (let i in part.factors) { - total_damage += save_damages[i] * part.factors[i]; - } - } - } // END Calculate total 3rd spell damage (total_damage) - if (total_damage > best_damage) { - best_damage = total_damage; - best_skillpoints = total_skillpoints.slice(); - } - - str_bonus -= 1; - dex_bonus += 1; - - } - // TODO: reduce duplicated code, @calculateBuild - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; - for (let i in skp_order) { - let manual_assigned = best_skillpoints[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; - } - player_build.assigned_skillpoints += delta_total; - - try { - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - handleBuilderError(error); - } -} - -// TODO: Learn and use await -function init() { - console.log("builder.js init"); - init_autocomplete(); - decodeBuild(url_tag); - for (const i of equipment_keys) { - update_field(i); - } -} -function init2() { - load_ing_init(init); -} -function init3() { - load_tome_init(init2) -} - - -load_init(init3); diff --git a/js/sq2display.js b/js/sq2display.js deleted file mode 100644 index 10b1701..0000000 --- a/js/sq2display.js +++ /dev/null @@ -1,2407 +0,0 @@ -function displaysq2BuildStats(parent_id,build,command_group){ - // Commands to "script" the creation of nice formatting. - // #commands create a new element. - // !elemental is some janky hack for elemental damage. - // normals just display a thing. - - let display_commands = command_group; - //console.log(display_commands); - - - let parent_div = document.getElementById(parent_id); - // Clear the parent div. - if (parent_div != null) { - setHTML(parent_id, ""); - } - - let stats = build.statMap; - //console.log(build.statMap); - - let active_elem; - let elemental_format = false; - - //TODO this is put here for readability, consolidate with definition in build.js - let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"]; - - for (const command of display_commands) { - // style instructions - - if (command.charAt(0) === "#") { - if (command === "#defense-stats") { - displaysq2DefenseStats(parent_div, build, true); - } - } - if (command.charAt(0) === "!") { - // TODO: This is sooo incredibly janky..... - if (command === "!elemental") { - elemental_format = !elemental_format; - } - } - - // id instruction - else { - let id = command; - if (stats.get(id)) { - let style = null; - - // TODO: add pos and neg style - if (!staticIDs.includes(id)) { - style = "positive"; - if (stats.get(id) < 0) { - style = "negative"; - } - } - - // ignore - let id_val = stats.get(id); - if (reversedIDs.includes(id)) { - style === "positive" ? style = "negative" : style = "positive"; - } - if (id === "poison" && id_val > 0) { - id_val = Math.ceil(id_val*build.statMap.get("poisonPct")/100); - } - displaysq2FixedID(parent_div, id, id_val, elemental_format, style); - if (id === "poison" && id_val > 0) { - let row = document.createElement('div'); - row.classList.add("row") - let value_elem = document.createElement('div'); - value_elem.classList.add('col'); - value_elem.classList.add('text-end'); - - let prefix_elem = document.createElement('b'); - prefix_elem.textContent = "\u279C With Strength: "; - let number_elem = document.createElement('b'); - number_elem.classList.add(style); - number_elem.textContent = (id_val * (1+skillPointsToPercentage(build.total_skillpoints[0])) ).toFixed(0) + idSuffixes[id]; - value_elem.append(prefix_elem); - value_elem.append(number_elem); - row.appendChild(value_elem); - - parent_div.appendChild(row); - } - - // sp thingy - } else if (skp_order.includes(id)) { - let total_assigned = build.total_skillpoints[skp_order.indexOf(id)]; - let base_assigned = build.base_skillpoints[skp_order.indexOf(id)]; - let diff = total_assigned - base_assigned; - let style; - if (diff > 0) { - style = "positive"; - } else if (diff < 0) { - style = "negative"; - } - if (diff != 0) { - displaysq2FixedID(parent_div, id, diff, false, style); - } - } - } - } -} - -function displaysq2ExpandedItem(item, parent_id){ - // Commands to "script" the creation of nice formatting. - // #commands create a new element. - // !elemental is some janky hack for elemental damage. - // normals just display a thing. - if (item.get("category") === "weapon") { - let stats = new Map(); - stats.set("atkSpd", item.get("atkSpd")); - stats.set("damageBonus", [0, 0, 0, 0, 0]); - - //SUPER JANK @HPP PLS FIX - let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; - if (item.get("tier") !== "Crafted") { - stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]); - let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); - let damages = results[2]; - let total_damage = 0; - for (const i in damage_keys) { - total_damage += damages[i][0] + damages[i][1]; - item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]); - } - total_damage = total_damage / 2; - item.set("basedps", total_damage); - - } else { - stats.set("damageRaw", [item.get("nDamLow"), item.get("eDamLow"), item.get("tDamLow"), item.get("wDamLow"), item.get("fDamLow"), item.get("aDamLow")]); - stats.set("damageBases", [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]); - let resultsLow = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); - let damagesLow = resultsLow[2]; - stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]); - stats.set("damageBases", [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]); - let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); - let damages = results[2]; - console.log(damages); - - let total_damage_min = 0; - let total_damage_max = 0; - for (const i in damage_keys) { - total_damage_min += damagesLow[i][0] + damagesLow[i][1]; - total_damage_max += damages[i][0] + damages[i][1]; - item.set(damage_keys[i], damagesLow[i][0]+"-"+damagesLow[i][1]+"\u279c"+damages[i][0]+"-"+damages[i][1]); - } - total_damage_min = total_damage_min / 2; - total_damage_max = total_damage_max / 2; - item.set("basedps", [total_damage_min, total_damage_max]); - } - } else if (item.get("category") === "armor") { - } - - let display_commands = sq2_item_display_commands; - - // Clear the parent div. - setHTML(parent_id, ""); - let parent_div = document.getElementById(parent_id); - parent_div.classList.add("border", "border-2", "border-dark"); - - let fix_id = item.has("fixID") && item.get("fixID"); - let elemental_format = false; - for (let i = 0; i < display_commands.length; i++) { - const command = display_commands[i]; - if (command.charAt(0) === "!") { - // TODO: This is sooo incredibly janky..... - if (command === "!elemental") { - elemental_format = !elemental_format; - } - else if (command === "!spacer") { - let spacer = document.createElement('div'); - spacer.classList.add("row", "my-2"); - parent_div.appendChild(spacer); - continue; - } - } - else { - let id = command; - if(nonRolledIDs.includes(id)){//nonRolledID & non-0/non-null/non-und ID - if (!item.get(id)) { - if (! (item.get("crafted") && skp_order.includes(id) && - (item.get("maxRolls").get(id) || item.get("minRolls").get(id)))) { - continue; - } - } - if (id === "slots") { - let p_elem = document.createElement("div"); - p_elem.classList.add("col"); - - // PROPER POWDER DISPLAYING - let numerals = new Map([[1, "I"], [2, "II"], [3, "III"], [4, "IV"], [5, "V"], [6, "VI"]]); - - let powderPrefix = document.createElement("b"); - powderPrefix.textContent = "Powder Slots: " + item.get(id) + " ["; - p_elem.appendChild(powderPrefix); - - let powders = item.get("powders"); - for (let i = 0; i < powders.length; i++) { - let powder = document.createElement("b"); - powder.textContent = numerals.get((powders[i]%6)+1)+" "; - powder.classList.add(damageClasses[Math.floor(powders[i]/6)+1]+"_powder"); - p_elem.appendChild(powder); - } - - let powderSuffix = document.createElement("b"); - powderSuffix.textContent = "]"; - p_elem.appendChild(powderSuffix); - parent_div.appendChild(p_elem); - } else if (id === "set") { - if (item.get("hideSet")) { continue; } - - let p_elem = document.createElement("div"); - p_elem.classList.add("col"); - p_elem.textContent = "Set: " + item.get(id).toString(); - parent_div.appendChild(p_elem); - } else if (id === "majorIds") { - //console.log(item.get(id)); - for (let majorID of item.get(id)) { - let p_elem = document.createElement("div"); - p_elem.classList.add("col"); - - let title_elem = document.createElement("b"); - let b_elem = document.createElement("b"); - if (majorID.includes(":")) { - let name = majorID.substring(0, majorID.indexOf(":")+1); - let mid = majorID.substring(majorID.indexOf(":")+1); - if (name.charAt(0) !== "+") {name = "+" + name} - title_elem.classList.add("Legendary"); - title_elem.textContent = name; - b_elem.classList.add("Crafted"); - b_elem.textContent = mid; - p_elem.appendChild(title_elem); - p_elem.appendChild(b_elem); - } else { - let name = item.get(id).toString() - if (name.charAt(0) !== "+") {name = "+" + name} - b_elem.classList.add("Legendary"); - b_elem.textContent = name; - p_elem.appendChild(b_elem); - } - parent_div.appendChild(p_elem); - } - } else if (id === "lvl" && item.get("tier") === "Crafted") { - let p_elem = document.createElement("div"); - p_elem.classList.add("col"); - p_elem.textContent = "Combat Level Min: " + item.get("lvlLow") + "-" + item.get(id); - parent_div.appendChild(p_elem); - } else if (id === "displayName") { - let row = document.createElement("div"); - - let a_elem = document.createElement("a"); - row.classList.add("row", "justify-content-center"); - a_elem.classList.add("col-auto", "text-center", "item-title", "p-0"); - a_elem.classList.add(item.has("tier") ? item.get("tier").replace(" ","") : "Normal"); - // a_elem.style.textGrow = 1; - - row.appendChild(a_elem); - - /* - FUNCTIONALITY FOR THIS FEATURE HAS SINCE BEEN REMOVED (WITH SQ2). - IF WE WANT TO USE IT IN THE FUTURE, I'VE LEFT THE CODE TO ADD IT IN HERE - */ - - //allow the plus minus element to toggle upon click: âž•âž– - // let plusminus = document.createElement("div"); - // plusminus.id = parent_div.id.split("-")[0] + "-pm"; - // plusminus.classList.add("col", "plus_minus", "text_end"); - // plusminus.style.flexGrow = 0; - // plusminus.textContent = "\u2795"; - // row.appendChild(plusminus); - - if (item.get("custom")) { - a_elem.href = "../custom/#" + item.get("hash"); - a_elem.textContent = item.get("displayName"); - } else if (item.get("crafted")) { - a_elem.href = "../crafter/#" + item.get("hash"); - a_elem.textContent = item.get(id); - } else { - a_elem.href = "../item/#" + item.get("displayName"); - a_elem.textContent = item.get("displayName"); - } - parent_div.appendChild(row); - - let nolink_row = document.createElement("div"); - let p_elem = document.createElement("p"); - nolink_row.classList.add("row", "justify-content-center"); - nolink_row.style.display = "none"; - p_elem.classList.add("col-auto", "text-center", "item-title", "p-0"); - p_elem.classList.add(item.has("tier") ? item.get("tier").replace(" ","") : "Normal"); - if (item.get("custom")) { - p_elem.textContent = item.get("displayName"); - } else if (item.get("crafted")) { - p_elem.textContent = item.get(id); - } else { - p_elem.textContent = item.get("displayName"); - } - - nolink_row.appendChild(p_elem); - parent_div.appendChild(nolink_row); - - let img = document.createElement("img"); - if (item && item.has("type")) { - img.src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + item.get("type") + ".png"; - img.alt = item.get("type"); - img.style = " z=index: 1; position: relative;"; - let container = document.createElement("div"); - - let bckgrd = document.createElement("div"); - bckgrd.classList.add("col", "px-0", "d-flex", "align-items-center", "justify-content-center");// , "no-collapse"); - bckgrd.style = "border-radius: 50%;background-image: radial-gradient(closest-side, " + colorMap.get(item.get("tier")) + " 20%," + "hsl(0, 0%, 16%) 80%); margin-left: auto; margin-right: auto;" - bckgrd.classList.add("scaled-bckgrd"); - parent_div.appendChild(container); - container.appendChild(bckgrd); - bckgrd.appendChild(img); - } - } else { - let p_elem; - if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && parent_div.nodeName === "table") ) { //skp warp - p_elem = displaysq2FixedID(parent_div, id, item.get(id), elemental_format); - } else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") { - p_elem = displaysq2FixedID(parent_div, id, item.get(id+"Low")+"-"+item.get(id), elemental_format); - } - if (id === "lore") { - p_elem.style = "font-style: italic"; - } else if (skp_order.includes(id)) { //id = str, dex, int, def, or agi - if ( item.get("tier") !== "Crafted") { - row = document.createElement("div"); - row.classList.add("col"); - - let title = document.createElement("b"); - title.textContent = idPrefixes[id] + " "; - let boost = document.createElement("b"); - if (item.get(id) < 0) { - boost.classList.add("negative"); - } else { //boost = 0 SHOULD not come up - boost.classList.add("positive"); - } - boost.textContent = item.get(id); - row.appendChild(title); - row.appendChild(boost); - parent_div.appendChild(row); - } else if ( item.get("tier") === "Crafted") { - let row = displaysq2RolledID(item, id, elemental_format); - parent_div.appendChild(row); - } - } else if (id === "restrict") { - p_elem.classList.add("restrict"); - } - } - } - else if ( rolledIDs.includes(id) && - ((item.get("maxRolls") && item.get("maxRolls").get(id)) - || (item.get("minRolls") && item.get("minRolls").get(id)))) { - let style = "positive"; - if (item.get("minRolls").get(id) < 0) { - style = "negative"; - } - if(reversedIDs.includes(id)){ - style === "positive" ? style = "negative" : style = "positive"; - } - if (fix_id) { - p_elem = document.createElement("div"); - p_elem.classList.add("col", "text-nowrap"); - if (id == "dex") { - console.log("dex activated at fix_id") - } - displaysq2FixedID(p_elem, id, item.get("minRolls").get(id), elemental_format, style); - parent_div.appendChild(p_elem); - } - else { - let row = displaysq2RolledID(item, id, elemental_format); - parent_div.appendChild(row); - } - }else{ - // :/ - } - } - } - //Show powder specials ;-; - let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots", "ring", "bracelet", "necklace"]; - if(nonConsumables.includes(item.get("type"))) { - let powder_special = document.createElement("div"); - powder_special.classList.add("col"); - let powders = item.get("powders"); - let element = ""; - let power = 0; - for (let i = 0; i < powders.length; i++) { - let firstPowderType = skp_elements[Math.floor(powders[i]/6)]; - if (element !== "") break; - else if (powders[i]%6 > 2) { //t4+ - for (let j = i+1; j < powders.length; j++) { - let currentPowderType = skp_elements[Math.floor(powders[j]/6)] - if (powders[j] % 6 > 2 && firstPowderType === currentPowderType) { - element = currentPowderType; - power = Math.round(((powders[i] % 6 + powders[j] % 6 + 2) / 2 - 4) * 2); - break; - } - } - } - } - if (element !== "") {//powder special is "[e,t,w,f,a]+[0,1,2,3,4]" - let powderSpecial = powderSpecialStats[ skp_elements.indexOf(element)]; - let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]); - let specialTitle = document.createElement("span"); - let specialEffects = document.createElement("span"); - addClasses(specialTitle, [damageClasses[skp_elements.indexOf(element) + 1]]); - let effects; - if (item.get("category") === "weapon") {//weapon - effects = powderSpecial["weaponSpecialEffects"]; - specialTitle.textContent = powderSpecial["weaponSpecialName"]; - }else if (item.get("category") === "armor") {//armor - effects = powderSpecial["armorSpecialEffects"]; - specialTitle.textContent += powderSpecial["armorSpecialName"] + ": "; - } - for (const [key,value] of effects.entries()) { - if (key !== "Description") { - let effect = document.createElement("p"); - effect.classList.add("m-0"); - effect.textContent = key + ": " + value[power] + specialSuffixes.get(key); - if(key === "Damage"){ - effect.textContent += elementIcons[skp_elements.indexOf(element)]; - } - if (element === "w" && item.get("category") === "armor") { - effect.textContent += " / Mana Used"; - } - specialEffects.appendChild(effect); - }else{ - specialTitle.textContent += "[ " + effects.get("Description") + " ]"; - } - } - powder_special.appendChild(specialTitle); - powder_special.appendChild(specialEffects); - parent_div.appendChild(powder_special); - } - } - - if(item.get("tier") && item.get("tier") === "Crafted") { - let dura_elem = document.createElement("div"); - dura_elem.classList.add("col"); - let dura = []; - let suffix = ""; - if(nonConsumables.includes(item.get("type"))) { - dura = item.get("durability"); - dura_elem.textContent = "Durability: " - } else { - dura = item.get("duration"); - dura_elem.textContent = "Duration: " - suffix = " sec." - let charges = document.createElement("b"); - charges.textContent = "Charges: " + item.get("charges"); - parent_div.appendChild(charges); - } - - if (typeof(dura) === "string") { - dura_elem.textContent += dura + suffix; - } else { - dura_elem.textContent += dura[0]+"-"+dura[1] + suffix; - } - parent_div.append(dura_elem); - - } - //Show item tier - if (item.get("tier") && item.get("tier") !== " ") { - let item_desc_elem = document.createElement("div"); - item_desc_elem.classList.add("col"); - item_desc_elem.classList.add(item.get("tier")); - if (tome_types.includes(item.get("type"))) { - tome_type_map = new Map([["weaponTome", "Weapon Tome"],["armorTome", "Armor Tome"],["guildTome", "Guild Tome"]]); - item_desc_elem.textContent = item.get("tier")+" "+tome_type_map.get(item.get("type")); - } else { - item_desc_elem.textContent = item.get("tier")+" "+item.get("type"); - } - parent_div.append(item_desc_elem); - } - - //Show item hash if applicable - if (item.get("crafted") || item.get("custom")) { - let item_desc_elem = document.createElement("p"); - item_desc_elem.classList.add('itemp'); - item_desc_elem.style.maxWidth = "100%"; - item_desc_elem.style.wordWrap = "break-word"; - item_desc_elem.style.wordBreak = "break-word"; - item_desc_elem.textContent = item.get("hash"); - parent_div.append(item_desc_elem); - } - - if (item.get("category") === "weapon") { - let damage_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))]; - let total_damages = item.get("basedps"); - let base_dps_elem = document.createElement("p"); - base_dps_elem.classList.add("left"); - base_dps_elem.classList.add("itemp"); - if (item.get("tier") === "Crafted") { - let base_dps_min = total_damages[0] * damage_mult; - let base_dps_max = total_damages[1] * damage_mult; - - // base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3); - base_dps_elem.textContent = base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3); - } - else { - let bdps = total_damages * damage_mult; - base_dps_elem.textContent = (bdps ? bdps : 0); - } - parent_div.appendChild(document.createElement("p")); - parent_div.appendChild(base_dps_elem); - } -} - -function displaysq2RolledID(item, id, elemental_format) { - let row = document.createElement('div'); - row.classList.add('col'); - - let item_div = document.createElement('div'); - item_div.classList.add('row'); - - let min_elem = document.createElement('div'); - min_elem.classList.add('col', 'text-start'); - min_elem.style.cssText += "flex-grow: 0"; - let id_min = item.get("minRolls").get(id) - let style = id_min < 0 ? "negative" : "positive"; - if(reversedIDs.includes(id)){ - style === "positive" ? style = "negative" : style = "positive"; - } - min_elem.classList.add(style); - min_elem.textContent = id_min + idSuffixes[id]; - item_div.appendChild(min_elem); - - let desc_elem = document.createElement('div'); - desc_elem.classList.add('col', 'text-center');//, 'text-nowrap'); - desc_elem.style.cssText += "flex-grow: 1"; - //TODO elemental format jank - if (elemental_format) { - apply_sq2_elemental_format(desc_elem, id); - } - else { - desc_elem.textContent = idPrefixes[id]; - } - item_div.appendChild(desc_elem); - - let max_elem = document.createElement('div'); - let id_max = item.get("maxRolls").get(id) - max_elem.classList.add('col', 'text-end'); - max_elem.style.cssText += "flex-grow: 0"; - style = id_max < 0 ? "negative" : "positive"; - if (reversedIDs.includes(id)) { - style === "positive" ? style = "negative" : style = "positive"; - } - max_elem.classList.add(style); - max_elem.textContent = id_max + idSuffixes[id]; - item_div.appendChild(max_elem); - row.appendChild(item_div); - return row; -} - -function displaysq2WeaponStats(build) { - // let base_damage = build.get('damageRaw'); - let damage_keys = [ "nDam", "eDam", "tDam", "wDam", "fDam", "aDam" ]; - - // pP base calc (why do i still use pP) - let item = build.weapon; - let stats = new Map(); - stats.set("atkSpd", item.get("atkSpd")); - stats.set("damageBonus", [0, 0, 0, 0, 0]); - stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]); - - //needed for damage calc CR powders - if (build.weapon.get("tier") === "Crafted") { - stats.set("damageBases", [item.get("nDamBaseHigh"), item.get("eDamBaseHigh"), item.get("tDamBaseHigh"), item.get("wDamBaseHigh"), item.get("fDamBaseHigh"), item.get("aDamBaseHigh")]); - } - - let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, build.weapon, [0, 0, 0, 0, 0], 1, undefined); - let powdered_base = results[2]; - - const powdered_map = new Map(); - powdered_map.set('nDam', powdered_base[0][0]+'-'+powdered_base[0][1]); - powdered_map.set('eDam', powdered_base[1][0]+'-'+powdered_base[1][1]); - powdered_map.set('tDam', powdered_base[2][0]+'-'+powdered_base[2][1]); - powdered_map.set('wDam', powdered_base[3][0]+'-'+powdered_base[3][1]); - powdered_map.set('fDam', powdered_base[4][0]+'-'+powdered_base[4][1]); - powdered_map.set('aDam', powdered_base[5][0]+'-'+powdered_base[5][1]); - - // display - - //I think this is res's code? in any case it doesn't work - ferri - /* for (const i in damage_keys) { - document.getElementById(damage_keys[i]+"-base").textContent = powdered_map.get(damage_keys[i]); - } */ - - let tot = 0; - //sum up elemental damages to get sum of base dps's - for (let i = 0; i < 6; i++) { - tot += powdered_base[i][0] + powdered_base[i][1]; - } - tot /= 2; - let dps = Math.max(0, Math.round(tot * baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))] )); //atkspeeds - - // document.getElementById("weapon-dps").textContent = "base dps: " + (isNaN(dps) ? 0 : dps); - document.getElementById("weapon-dps").textContent = (isNaN(dps) ? 0 : dps); - document.getElementById("weapon-lv").textContent = item.get("lvl"); - - if (item.get("type")) { - document.getElementById("weapon-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + item.get("type") + ".png"; - } -} - -function displaysq2FixedID(active, id, value, elemental_format, style) { - if (style) { - let row = document.createElement('div'); - row.classList.add("row"); - let desc_elem = document.createElement('div'); - desc_elem.classList.add('col'); - desc_elem.classList.add('text-start'); - - if (elemental_format) { - apply_sq2_elemental_format(desc_elem, id); - } - else { - desc_elem.textContent = idPrefixes[id]; - } - row.appendChild(desc_elem); - - let value_elem = document.createElement('div'); - value_elem.classList.add('col'); - value_elem.classList.add('text-end'); - value_elem.classList.add(style); - value_elem.textContent = value + idSuffixes[id]; - row.appendChild(value_elem); - active.appendChild(row); - return row; - } - else { - // HACK TO AVOID DISPLAYING ZERO DAMAGE! TODO - if (value === "0-0" || value === "0-0\u279c0-0") { - return; - } - let p_elem = document.createElement('div'); - p_elem.classList.add('col'); - if (elemental_format) { - apply_sq2_elemental_format(p_elem, id, value); - } - else { - p_elem.textContent = idPrefixes[id].concat(value, idSuffixes[id]); - } - active.appendChild(p_elem); - return p_elem; - } -} - -function displaysq2PoisonDamage(overallparent_elem, build) { - overallparent_elem.textContent = ""; - - //Title - let title_elemavg = document.createElement("b"); - title_elemavg.textContent = "Poison Stats"; - overallparent_elem.append(title_elemavg); - - let overallpoisonDamage = document.createElement("p"); - let overallpoisonDamageFirst = document.createElement("span"); - let overallpoisonDamageSecond = document.createElement("span"); - let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct"))/100 /3); - overallpoisonDamageFirst.textContent = "Poison Tick: "; - overallpoisonDamageSecond.textContent = Math.max(poison_tick,0); - overallpoisonDamageSecond.classList.add("Damage"); - - overallpoisonDamage.appendChild(overallpoisonDamageFirst); - overallpoisonDamage.appendChild(overallpoisonDamageSecond); - overallparent_elem.append(overallpoisonDamage); -} - -function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ - // console.log("Melee Stats"); - // console.log(meleeStats); - let tooltipinfo = meleeStats[13]; - let attackSpeeds = ["Super Slow", "Very Slow", "Slow", "Normal", "Fast", "Very Fast", "Super Fast"]; - //let damagePrefixes = ["Neutral Damage: ","Earth Damage: ","Thunder Damage: ","Water Damage: ","Fire Damage: ","Air Damage: "]; - parent_elem.textContent = ""; - overallparent_elem.textContent = ""; - const stats = meleeStats.slice(); - - for (let i = 0; i < 6; ++i) { - for (let j in stats[i]) { - stats[i][j] = stats[i][j].toFixed(2); - } - } - for (let i = 6; i < 8; ++i) { - for (let j = 0; j < 2; j++) { - stats[i][j] = stats[i][j].toFixed(2); - } - } - for (let i = 8; i < 11; ++i){ - stats[i] = stats[i].toFixed(2); - } - //tooltipelem, tooltiptext - let tooltip; let tooltiptext; - - //title - let title_elem = document.createElement("p"); - title_elem.classList.add("title"); - title_elem.textContent = "Melee Stats"; - parent_elem.append(title_elem); - parent_elem.append(document.createElement("br")); - - //overall title - let title_elemavg = document.createElement("b"); - title_elemavg.textContent = "Melee Stats"; - overallparent_elem.append(title_elemavg); - - //average DPS - let averageDamage = document.createElement("p"); - averageDamage.classList.add("left"); - averageDamage.textContent = "Average DPS: " + stats[10]; - tooltiptext = `= ((${stats[8]} * ${(stats[6][2]).toFixed(2)}) + (${stats[9]} * ${(stats[7][2]).toFixed(2)}))` - tooltip = createTooltip(tooltip, "p", tooltiptext, averageDamage, ["melee-tooltip"]); - averageDamage.appendChild(tooltip); - parent_elem.append(averageDamage); - - //overall average DPS - let overallaverageDamage = document.createElement("p"); - let overallaverageDamageFirst = document.createElement("span"); - overallaverageDamageFirst.textContent = "Average DPS: " - - let overallaverageDamageSecond = document.createElement("span"); - overallaverageDamageSecond.classList.add("Damage"); - overallaverageDamageSecond.textContent = stats[10]; - overallaverageDamage.appendChild(overallaverageDamageFirst); - overallaverageDamage.appendChild(overallaverageDamageSecond); - - overallparent_elem.append(overallaverageDamage); - //overallparent_elem.append(document.createElement("br")); - - //attack speed - let atkSpd = document.createElement("p"); - atkSpd.classList.add("left"); - atkSpd.textContent = "Attack Speed: " + attackSpeeds[stats[11]]; - parent_elem.append(atkSpd); - parent_elem.append(document.createElement("br")); - - //overall attack speed - let overallatkSpd = document.createElement("p"); - let overallatkSpdFirst = document.createElement("span"); - overallatkSpdFirst.textContent = "Attack Speed: "; - let overallatkSpdSecond = document.createElement("span"); - overallatkSpdSecond.classList.add("Damage"); - overallatkSpdSecond.textContent = attackSpeeds[stats[11]]; - overallatkSpd.appendChild(overallatkSpdFirst); - overallatkSpd.appendChild(overallatkSpdSecond); - overallparent_elem.append(overallatkSpd); - - //Non-Crit: n->elem, total dmg, DPS - let nonCritStats = document.createElement("p"); - nonCritStats.classList.add("left"); - nonCritStats.textContent = "Non-Crit Stats: "; - nonCritStats.append(document.createElement("br")); - for (let i = 0; i < 6; i++){ - if(stats[i][1] != 0){ - let dmg = document.createElement("p"); - dmg.textContent = stats[i][0] + " \u2013 " + stats[i][1]; - dmg.classList.add(damageClasses[i]); - dmg.classList.add("itemp"); - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(0,2).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]); - nonCritStats.append(dmg); - } - } - - let normalDamage = document.createElement("p"); - normalDamage.textContent = "Total: " + stats[6][0] + " \u2013 " + stats[6][1]; - let tooltiparr = ["Min: = ", "Max: = "] - let arr = []; let arr2 = []; - for (let i = 0; i < 6; i++) { - if (stats[i][0] != 0) { - arr.push(stats[i][0]); - arr2.push(stats[i][1]); - } - } - tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + "); - tooltip = createTooltip(tooltip, "p", tooltiptext, normalDamage, ["melee-tooltip"]); - nonCritStats.append(normalDamage); - - let normalDPS = document.createElement("p"); - normalDPS.textContent = "Normal DPS: " + stats[8]; - normalDPS.classList.add("tooltip"); - tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, normalDPS, ["melee-tooltip"]); - nonCritStats.append(normalDPS); - - //overall average DPS - let singleHitDamage = document.createElement("p"); - let singleHitDamageFirst = document.createElement("span"); - singleHitDamageFirst.textContent = "Single Hit Average: "; - let singleHitDamageSecond = document.createElement("span"); - singleHitDamageSecond.classList.add("Damage"); - singleHitDamageSecond.textContent = stats[12].toFixed(2); - tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${stats[6][2].toFixed(2)} + ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${stats[7][2].toFixed(2)}`; - // tooltip = createTooltip(tooltip, "p", tooltiptext, singleHitDamage, ["melee-tooltip", "summary-tooltip"]); - - singleHitDamage.appendChild(singleHitDamageFirst); - singleHitDamage.appendChild(singleHitDamageSecond); - overallparent_elem.append(singleHitDamage); - - let normalChance = document.createElement("p"); - normalChance.textContent = "Non-Crit Chance: " + (stats[6][2]*100).toFixed(2) + "%"; - normalChance.append(document.createElement("br")); - normalChance.append(document.createElement("br")); - nonCritStats.append(normalChance); - - parent_elem.append(nonCritStats); - parent_elem.append(document.createElement("br")); - - //Crit: n->elem, total dmg, DPS - let critStats = document.createElement("p"); - critStats.classList.add("left"); - critStats.textContent = "Crit Stats: "; - critStats.append(document.createElement("br")); - for (let i = 0; i < 6; i++){ - if(stats[i][3] != 0) { - dmg = document.createElement("p"); - dmg.textContent = stats[i][2] + " \u2013 " + stats[i][3]; - dmg.classList.add(damageClasses[i]); - dmg.classList.add("itemp"); - tooltiptext = tooltipinfo.get("damageformulas")[i].slice(2,4).join("\n"); - tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]); - critStats.append(dmg); - } - } - let critDamage = document.createElement("p"); - critDamage.textContent = "Total: " + stats[7][0] + " \u2013 " + stats[7][1]; - tooltiparr = ["Min: = ", "Max: = "] - arr = []; arr2 = []; - for (let i = 0; i < 6; i++) { - if (stats[i][0] != 0) { - arr.push(stats[i][2]); - arr2.push(stats[i][3]); - } - } - tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + "); - tooltip = createTooltip(tooltip, "p", tooltiptext, critDamage, ["melee-tooltip"]); - - critStats.append(critDamage); - - let critDPS = document.createElement("p"); - critDPS.textContent = "Crit DPS: " + stats[9]; - tooltiptext = ` = ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`; - tooltip = createTooltip(tooltip, "p", tooltiptext, critDPS, ["melee-tooltip"]); - critStats.append(critDPS); - - let critChance = document.createElement("p"); - critChance.textContent = "Crit Chance: " + (stats[7][2]*100).toFixed(2) + "%"; - critChance.append(document.createElement("br")); - critChance.append(document.createElement("br")); - critStats.append(critChance); - - parent_elem.append(critStats); -} - -function displaysq2ArmorStats(build) { - let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace']; - - for (const i in armor_keys) { - document.getElementById(armor_keys[i]+'-health').textContent = build[armor_keys[i]].get('hp'); - document.getElementById(armor_keys[i]+'-lv').textContent = build[armor_keys[i]].get('lvl'); - } -} - -function displaysq2DefenseStats(parent_elem, build, insertSummary){ - let defenseStats = build.getDefenseStats(); - insertSummary = (typeof insertSummary !== 'undefined') ? insertSummary : false; - if (!insertSummary) { - parent_elem.textContent = ""; - } - const stats = defenseStats.slice(); - - // parent_elem.append(document.createElement("br")); - let statsTable = document.createElement("div"); - - //[total hp, ehp, total hpr, ehpr, [def%, agi%], [edef,tdef,wdef,fdef,adef]] - for(const i in stats){ - if(typeof stats[i] === "number"){ - stats[i] = stats[i].toFixed(2); - }else{ - for(const j in stats[i]){ - stats[i][j] = stats[i][j].toFixed(2); - } - } - } - - //total HP - let hpRow = document.createElement("div"); - hpRow.classList.add('row'); - let hp = document.createElement("div"); - hp.classList.add('col'); - hp.classList.add("Health"); - hp.classList.add("text-start"); - hp.textContent = "Total HP:"; - let boost = document.createElement("div"); - boost.classList.add('col'); - boost.textContent = stats[0]; - boost.classList.add("text-end"); - - hpRow.appendChild(hp); - hpRow.append(boost); - - if (insertSummary) { - parent_elem.appendChild(hpRow); - } else { - statsTable.appendChild(hpRow); - } - - let tooltip; let tooltiptext; - - let defMult = build.statMap.get("defMult"); - if (!defMult) {defMult = 1} - - //EHP - let ehpRow = document.createElement("div"); - ehpRow.classList.add("row"); - let ehp = document.createElement("div"); - ehp.classList.add("col"); - ehp.classList.add("text-start"); - ehp.textContent = "Effective HP:"; - - boost = document.createElement("div"); - boost.textContent = stats[1][0]; - boost.classList.add("col"); - boost.classList.add("text-end"); - tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - - ehpRow.appendChild(ehp); - ehpRow.append(boost); - - if (insertSummary) { - parent_elem.appendChild(ehpRow) - } else { - statsTable.append(ehpRow); - } - - ehpRow = document.createElement("div"); - ehpRow.classList.add("row"); - ehp = document.createElement("div"); - ehp.classList.add("col"); - ehp.classList.add("text-start"); - ehp.textContent = "Effective HP (no agi):"; - - boost = document.createElement("div"); - boost.textContent = stats[1][1]; - boost.classList.add("col"); - boost.classList.add("text-end"); - tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - - ehpRow.appendChild(ehp); - ehpRow.append(boost); - statsTable.append(ehpRow); - - //total HPR - let hprRow = document.createElement("div"); - hprRow.classList.add("row") - let hpr = document.createElement("div"); - hpr.classList.add("Health"); - hpr.classList.add("col"); - hpr.classList.add("text-start"); - hpr.textContent = "HP Regen (Total):"; - boost = document.createElement("div"); - boost.textContent = stats[2]; - boost.classList.add("col"); - boost.classList.add("text-end"); - - hprRow.appendChild(hpr); - hprRow.appendChild(boost); - - if (insertSummary) { - parent_elem.appendChild(hprRow); - } else { - statsTable.appendChild(hprRow); - } - - //EHPR - let ehprRow = document.createElement("div"); - ehprRow.classList.add("row") - let ehpr = document.createElement("div"); - ehpr.classList.add("col"); - ehpr.classList.add("text-start"); - ehpr.textContent = "Effective HP Regen:"; - - boost = document.createElement("div"); - boost.textContent = stats[3][0]; - boost.classList.add("col"); - boost.classList.add("text-end"); - tooltiptext = `= ${stats[2]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))` - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - - ehprRow.appendChild(ehpr); - ehprRow.append(boost); - statsTable.append(ehprRow); - /* - ehprRow = document.createElement("tr"); - ehpr = document.createElement("td"); - ehpr.classList.add("left"); - ehpr.textContent = "Effective HP Regen (no agi):"; - - boost = document.createElement("td"); - boost.textContent = stats[3][1]; - boost.classList.add("right"); - - ehprRow.appendChild(ehpr); - ehprRow.append(boost); - statsTable.append(ehprRow); */ - - //eledefs - let eledefs = stats[5]; - for (let i = 0; i < eledefs.length; i++){ - let eledefElemRow = document.createElement("div"); - eledefElemRow.classList.add("row") - - let eledef = document.createElement("div"); - eledef.classList.add("col"); - eledef.classList.add("text-start"); - let eledefTitle = document.createElement("span"); - eledefTitle.textContent = damageClasses[i+1]; - eledefTitle.classList.add(damageClasses[i+1]); - - let defense = document.createElement("span"); - defense.textContent = " Def (Total): "; - - eledef.appendChild(eledefTitle); - eledef.appendChild(defense); - eledefElemRow.appendChild(eledef); - - let boost = document.createElement("div"); - boost.textContent = eledefs[i]; - boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative"); - boost.classList.add("col"); - boost.classList.add("text-end"); - - let defRaw = build.statMap.get("defRaw")[i]; - let defPct = build.statMap.get("defBonus")[i]/100; - if (defRaw < 0) { - defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct; - tooltiptext = `= min(0, ${defRaw} * (1 ${defPct}))` - } else { - defPct >= 0 ? defPct = "+ " + defPct: defPct = "- " + defPct; - tooltiptext = `= ${defRaw} * (1 ${defPct})` - } - // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]); - - eledefElemRow.appendChild(boost); - - if (insertSummary) { - parent_elem.appendChild(eledefElemRow); - } else { - statsTable.appendChild(eledefElemRow); - } - } - - if (!insertSummary) { - //skp - let defRow = document.createElement("div"); - defRow.classList.add("row"); - let defElem = document.createElement("div"); - defElem.classList.add("col"); - defElem.classList.add("text-start"); - defElem.textContent = "Damage Absorbed %:"; - boost = document.createElement("div"); - boost.classList.add("col"); - boost.classList.add("text-end"); - boost.textContent = stats[4][0] + "%"; - defRow.appendChild(defElem); - defRow.appendChild(boost); - statsTable.append(defRow); - - let agiRow = document.createElement("div"); - agiRow.classList.add("row"); - let agiElem = document.createElement("div"); - agiElem.classList.add("col"); - agiElem.classList.add("text-start"); - agiElem.textContent = "Dodge Chance %:"; - boost = document.createElement("div"); - boost.classList.add("col"); - boost.classList.add("text-end"); - boost.textContent = stats[4][1] + "%"; - agiRow.appendChild(agiElem); - agiRow.appendChild(boost); - statsTable.append(agiRow); - } - - if (!insertSummary) { - parent_elem.append(statsTable); - } -} - -function displaysq2PowderSpecials(parent_elem, powderSpecials, build, overall=false) { - parent_elem.textContent = "" - let title = document.createElement("b"); - title.textContent = "Powder Specials"; - parent_elem.appendChild(title); - let specials = powderSpecials.slice(); - let stats = build.statMap; - let expandedStats = new Map(); - //each entry of powderSpecials is [ps, power] - for (special of specials) { - //iterate through the special and display its effects. - let powder_special = document.createElement("p"); - let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]); - let specialTitle = document.createElement("p"); - let specialEffects = document.createElement("p"); - specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]); - let effects = special[0]["weaponSpecialEffects"]; - let power = special[1]; - specialTitle.textContent = special[0]["weaponSpecialName"] + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : ""); - - if (!overall || powderSpecialStats.indexOf(special[0]) == 2 || powderSpecialStats.indexOf(special[0]) == 3 || powderSpecialStats.indexOf(special[0]) == 4) { - for (const [key,value] of effects) { - let effect = document.createElement("p"); - effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key); - if(key === "Damage"){ - effect.textContent += elementIcons[powderSpecialStats.indexOf(special[0])]; - } - if(special[0]["weaponSpecialName"] === "Wind Prison" && key === "Damage Boost") { - effect.textContent += " (only 1st hit)"; - } - specialEffects.appendChild(effect); - } - } - powder_special.appendChild(specialTitle); - powder_special.appendChild(specialEffects); - - //if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage. - let specialDamage = document.createElement("p"); - // specialDamage.classList.add("item-margin"); - let spells = spell_table["powder"]; - if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage - let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]); - let part = spell["parts"][0]; - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("mdRaw"), stats.get("mdPct"), - 0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100 - - let critChance = skillPointsToPercentage(build.total_skillpoints[1]); - let save_damages = []; - - let totalDamNormal = _results[0]; - let totalDamCrit = _results[1]; - let results = _results[2]; - for (let i = 0; i < 6; ++i) { - for (let j in results[i]) { - results[i][j] = results[i][j].toFixed(2); - } - } - let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; - let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; - let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; - - let averageWrap = document.createElement("p"); - let averageLabel = document.createElement("span"); - averageLabel.textContent = "Average: "; - - let averageLabelDmg = document.createElement("span"); - averageLabelDmg.classList.add("Damage"); - averageLabelDmg.textContent = averageDamage.toFixed(2); - - averageWrap.appendChild(averageLabel); - averageWrap.appendChild(averageLabelDmg); - specialDamage.appendChild(averageWrap); - - if (!overall) { - let nonCritLabel = document.createElement("p"); - nonCritLabel.textContent = "Non-Crit Average: "+nonCritAverage.toFixed(2); - nonCritLabel.classList.add("damageSubtitle"); - nonCritLabel.classList.add("item-margin"); - specialDamage.append(nonCritLabel); - - for (let i = 0; i < 6; i++){ - if (results[i][1] > 0){ - let p = document.createElement("p"); - p.classList.add("damagep"); - p.classList.add(damageClasses[i]); - p.textContent = results[i][0]+"-"+results[i][1]; - specialDamage.append(p); - } - } - let normalDamage = document.createElement("p"); - normalDamage.textContent = "Total: " + totalDamNormal[0].toFixed(2) + "-" + totalDamNormal[1].toFixed(2); - normalDamage.classList.add("itemp"); - specialDamage.append(normalDamage); - - let nonCritChanceLabel = document.createElement("p"); - nonCritChanceLabel.textContent = "Non-Crit Chance: " + ((1-critChance)*100).toFixed(2) + "%"; - specialDamage.append(nonCritChanceLabel); - - let critLabel = document.createElement("p"); - critLabel.textContent = "Crit Average: "+critAverage.toFixed(2); - critLabel.classList.add("damageSubtitle"); - critLabel.classList.add("item-margin"); - - specialDamage.append(critLabel); - for (let i = 0; i < 6; i++){ - if (results[i][1] > 0){ - let p = document.createElement("p"); - p.classList.add("damagep"); - p.classList.add(damageClasses[i]); - p.textContent = results[i][2]+"-"+results[i][3]; - specialDamage.append(p); - } - } - let critDamage = document.createElement("p"); - critDamage.textContent = "Total: " + totalDamCrit[0].toFixed(2) + "-" + totalDamCrit[1].toFixed(2); - critDamage.classList.add("itemp"); - specialDamage.append(critDamage); - - let critChanceLabel = document.createElement("p"); - critChanceLabel.textContent = "Crit Chance: " + (critChance*100).toFixed(2) + "%"; - specialDamage.append(critChanceLabel); - - save_damages.push(averageDamage); - } - - powder_special.append(specialDamage); - } - - parent_elem.appendChild(powder_special); - } -} - -function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx, weapon) { - parent_elem.textContent = ""; - - - let tooltip; let tooltiptext; - const stats = build.statMap; - let title_elem = document.createElement("p"); - - overallparent_elem.textContent = ""; - let title_elemavg = document.createElement("b"); - - if (spellIdx != 0) { - let first = document.createElement("span"); - first.textContent = spell.title + " ("; - title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here. - title_elemavg.appendChild(first); - - let second = document.createElement("span"); - second.textContent = build.getSpellCost(spellIdx, spell.cost); - second.classList.add("Mana"); - - let int_redux = skillPointsToPercentage(build.total_skillpoints[2]).toFixed(2); - let spPct_redux = (build.statMap.get("spPct" + spellIdx)/100).toFixed(2); - let spRaw_redux = (build.statMap.get("spRaw" + spellIdx)).toFixed(2); - spPct_redux >= 0 ? spPct_redux = "+ " + spPct_redux : spPct_redux = "- " + Math.abs(spPct_redux); - spRaw_redux >= 0 ? spRaw_redux = "+ " + spRaw_redux : spRaw_redux = "- " + Math.abs(spRaw_redux); - - // tooltiptext = `= max(1, floor((ceil(${spell.cost} * (1 - ${int_redux})) ${spRaw_redux}) * (1 ${spPct_redux})))`; - // tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spellcostcalc"]); - // second.appendChild(tooltip); - title_elem.appendChild(second.cloneNode(true)); - title_elemavg.appendChild(second); - - - let third = document.createElement("span"); - third.textContent = ") [Base: " + build.getBaseSpellCost(spellIdx, spell.cost) + " ]"; - title_elem.appendChild(third); - let third_summary = document.createElement("span"); - third_summary.textContent = ")"; - title_elemavg.appendChild(third_summary); - } - else { - title_elem.textContent = spell.title; - title_elemavg.textContent = spell.title; - } - - parent_elem.append(title_elem); - overallparent_elem.append(title_elemavg); - - overallparent_elem.append(displaysq2NextCosts(spell, build, weapon)); - - - let critChance = skillPointsToPercentage(build.total_skillpoints[1]); - - let save_damages = []; - - let part_divavg = document.createElement("p"); - overallparent_elem.append(part_divavg); - - 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; - } - } - } - //console.log(spell_parts); - - for (const part of spell_parts) { - let part_div = document.createElement("p"); - parent_elem.append(part_div); - - let subtitle_elem = document.createElement("p"); - subtitle_elem.textContent = part.subtitle; - part_div.append(subtitle_elem); - - if (part.type === "damage") { - //console.log(build.expandedStats); - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct"), - part.multiplier / 100, weapon, build.total_skillpoints, build.damageMultiplier); - let totalDamNormal = _results[0]; - let totalDamCrit = _results[1]; - let results = _results[2]; - let tooltipinfo = _results[3]; - - for (let i = 0; i < 6; ++i) { - for (let j in results[i]) { - results[i][j] = results[i][j].toFixed(2); - } - } - let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; - let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; - let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; - - let averageLabel = document.createElement("p"); - averageLabel.textContent = "Average: "+averageDamage.toFixed(2); - tooltiptext = ` = ((1 - ${critChance}) * ${nonCritAverage.toFixed(2)}) + (${critChance} * ${critAverage.toFixed(2)})` - // averageLabel.classList.add("damageSubtitle"); - // tooltip = createTooltip(tooltip, "p", tooltiptext, averageLabel, ["spell-tooltip"]); - part_div.append(averageLabel); - - - if (part.summary == true) { - let overallaverageLabel = document.createElement("p"); - let first = document.createElement("span"); - let second = document.createElement("span"); - first.textContent = part.subtitle + " Average: "; - second.textContent = averageDamage.toFixed(2); - overallaverageLabel.appendChild(first); - overallaverageLabel.appendChild(second); - // tooltip = createTooltip(tooltip, "p", tooltiptext, overallaverageLabel, ["spell-tooltip", "summary-tooltip"]); - second.classList.add("Damage"); - part_divavg.append(overallaverageLabel); - } - - function _damage_display(label_text, average, result_idx) { - let label = document.createElement("p"); - label.textContent = label_text+average.toFixed(2); - part_div.append(label); - - let arrmin = []; - let arrmax = []; - for (let i = 0; i < 6; i++){ - if (results[i][1] != 0){ - let p = document.createElement("p"); - p.classList.add(damageClasses[i]); - p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1]; - arrmin.push(results[i][result_idx]); - arrmax.push(results[i][result_idx + 1]); - part_div.append(p); - } - } - tooltiptext = ` = ((${arrmin.join(" + ")}) + (${arrmax.join(" + ")})) / 2`; - // tooltip = createTooltip(tooltip, "p", tooltiptext, label, ["spell-tooltip"]); - } - _damage_display("Non-Crit Average: ", nonCritAverage, 0); - _damage_display("Crit Average: ", critAverage, 2); - - save_damages.push(averageDamage); - } else if (part.type === "heal") { - let heal_amount = (part.strength * build.getDefenseStats()[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2); - tooltiptext = ` = ${part.strength} * ${build.getDefenseStats()[0]} * max(0.5, min(1.75, 1 + 0.5 * ${stats.get("wDamPct")/100}))`; - let healLabel = document.createElement("p"); - healLabel.textContent = heal_amount; - // healLabel.classList.add("damagep"); - // tooltip = createTooltip(tooltip, "p", tooltiptext, healLabel, ["spell-tooltip"]); - part_div.append(healLabel); - if (part.summary == true) { - let overallhealLabel = document.createElement("p"); - let first = document.createElement("span"); - let second = document.createElement("span"); - first.textContent = part.subtitle + ": "; - second.textContent = heal_amount; - overallhealLabel.appendChild(first); - second.classList.add("Set"); - overallhealLabel.appendChild(second); - part_divavg.append(overallhealLabel); - } - } else if (part.type === "total") { - let total_damage = 0; - tooltiptext = ""; - for (let i in part.factors) { - total_damage += save_damages[i] * part.factors[i]; - } - - let dmgarr = part.factors.slice(); - dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")"); - tooltiptext = " = " + dmgarr.join(" + "); - - - let averageLabel = document.createElement("p"); - averageLabel.textContent = "Average: "+total_damage.toFixed(2); - averageLabel.classList.add("damageSubtitle"); - tooltip = createTooltip(tooltip, "p", tooltiptext, averageLabel, ["spell-tooltip"]); - part_div.append(averageLabel); - - let overallaverageLabel = document.createElement("p"); - let overallaverageLabelFirst = document.createElement("span"); - let overallaverageLabelSecond = document.createElement("span"); - overallaverageLabelFirst.textContent = "Average: "; - overallaverageLabelSecond.textContent = total_damage.toFixed(2); - overallaverageLabelSecond.classList.add("Damage"); - - - overallaverageLabel.appendChild(overallaverageLabelFirst); - overallaverageLabel.appendChild(overallaverageLabelSecond); - part_divavg.append(overallaverageLabel); - } - } - - //up and down arrow - done ugly - let arrow = document.createElement("img"); - arrow.id = "arrow_" + overallparent_elem.id; - arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem"; - arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png"; - overallparent_elem.appendChild(arrow); -} - -function displaysq2EquipOrder(parent_elem, buildOrder){ - parent_elem.textContent = ""; - const order = buildOrder.slice(); - let title_elem = document.createElement("b"); - title_elem.textContent = "Equip order "; - title_elem.classList.add("Normal", "text-center"); - parent_elem.append(title_elem); - for (const item of order) { - let p_elem = document.createElement("b"); - p_elem.textContent = item.get("displayName"); - parent_elem.append(p_elem); - } -} - -function displaysq2NextCosts(spell, build, weapon) { - let int = build.total_skillpoints[2]; - let spells = spell_table[weapon.get("type")]; - - let row = document.createElement("div"); - row.classList.add("spellcost-tooltip"); - let init_cost = document.createElement("b"); - init_cost.textContent = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost); - init_cost.classList.add("Mana"); - let arrow = document.createElement("b"); - arrow.textContent = "\u279C"; - let next_cost = document.createElement("b"); - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1); - next_cost.classList.add("Mana"); - let int_needed = document.createElement("b"); - if (init_cost.textContent === "1") { - int_needed.textContent = ": n/a (+0)"; - }else { //do math - let target = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1; - let needed = int; - let noUpdate = false; - //forgive me... I couldn't inverse ceil, floor, and max. - while (build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) > target) { - if(needed > 150) { - noUpdate = true; - break; - } - needed++; - build.total_skillpoints[2] = needed; - } - let missing = needed - int; - //in rare circumstances, the next spell cost can jump. - if (noUpdate) { - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)-1); - }else { - next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)); - } - - - build.total_skillpoints[2] = int;//forgive me pt 2 - int_needed.textContent = ": " + (needed > 150 ? ">150" : needed) + " int (+" + (needed > 150 ? "n/a" : missing) + ")"; - } - - // row.appendChild(init_cost); - row.appendChild(arrow); - row.appendChild(next_cost); - row.appendChild(int_needed); - return row; -} - -function apply_sq2_elemental_format(p_elem, id, suffix) { - suffix = (typeof suffix !== 'undefined') ? suffix : ""; - // THIS IS SO JANK BUT IM TOO LAZY TO FIX IT TODO - let parts = idPrefixes[id].split(/ (.*)/); - let element_prefix = parts[0]; - let desc = parts[1]; - let i_elem = document.createElement('span'); - i_elem.classList.add(element_prefix); - i_elem.textContent = element_prefix; - p_elem.appendChild(i_elem); - - let i_elem2 = document.createElement('span'); - i_elem2.textContent = " " + desc + suffix; - p_elem.appendChild(i_elem2); -} - -function displaysq2SetBonuses(parent_id,build) { - setHTML(parent_id, ""); - let parent_div = document.getElementById(parent_id); - - let set_summary_elem = document.createElement('p'); - set_summary_elem.classList.add('text-center'); - set_summary_elem.textContent = "Set Bonuses"; - parent_div.append(set_summary_elem); - - for (const [setName, count] of build.activeSetCounts) { - const active_set = sets[setName]; - if (active_set["hidden"]) { continue; } - - let set_elem = document.createElement('p'); - set_elem.id = "set-"+setName; - set_summary_elem.append(set_elem); - - const bonus = active_set.bonuses[count-1]; - let mock_item = new Map(); - mock_item.set("fixID", true); - mock_item.set("displayName", setName+" Set: "+count+"/"+sets[setName].items.length); - let mock_minRolls = new Map(); - let mock_maxRolls = new Map(); - mock_item.set("minRolls", mock_minRolls); - mock_item.set("maxRolls", mock_maxRolls); - for (const id in bonus) { - if (rolledIDs.includes(id)) { - mock_minRolls.set(id, bonus[id]); - mock_maxRolls.set(id, bonus[id]); - } - else { - mock_item.set(id, bonus[id]); - } - } - mock_item.set("powders", []); - displaysq2ExpandedItem(mock_item, set_elem.id); - console.log(mock_item); - } -} - -function toggle_plus_minus(elem_id) { - let elem = document.getElementById(elem_id); - if (elem.classList.contains("plus_minus")) { - if (elem.textContent == "\u2795") { - elem.textContent = "\u2796"; - } else if (elem.textContent == "\u2796"){ - elem.textContent = "\u2795"; - } else { - // ???? - } - } -} - -// updates the powders within this element. -function updatePowders(elem_id) { - // let elem = document.getElementById(elem_id); - // for (let i = 0; i < elem.value.length - 1; i++) { - // if ('etwfa'.includes(elem.value.charAt(i)) && elem.value.charAt(i + 1) >= '1' && elem.value.charAt(i + 1) <= '6') { - // elem.value = elem.value.substring(0, i) + powder_chars['etwfa'.indexOf(elem.value.charAt(i))] + elem.value.substring(i + 2); - // } - // } -} - -/* -* Displays stats about a recipe that are NOT displayed in the craft stats. -* Includes: mat name and amounts, ingred names in an "array" with ingred effectiveness -*/ -function displaysq2RecipeStats(craft, parent_id) { - let elem = document.getElementById(parent_id); - if (!elem.classList.contains("col")) { - elem.classList.add("col"); - } - - //local vars - elem.textContent = ""; - recipe = craft["recipe"]; - mat_tiers = craft["mat_tiers"]; - ingreds = []; - for (const n of craft["ingreds"]) { - ingreds.push(n.get("name")); - } - let effectiveness = craft["statMap"].get("ingredEffectiveness"); - - let title = document.createElement("div"); - title.classList.add("row", "box-title", "fw-bold", "justify-content-center"); - title.textContent = "Recipe Stats"; - elem.appendChild(title); - - let mats = document.createElement("div"); - mats.classList.add("row"); - mats.textContent = "Crafting Materials: "; - elem.appendChild(mats); - - for (let i = 0; i < 2; i++) { - let tier = mat_tiers[i]; - let row = document.createElement("div"); - row.classList.add("row", "px-0", "mx-0"); - let b = document.createElement("div"); - let mat = recipe.get("materials")[i]; - b.textContent = "- " + mat.get("amount") + "x " + mat.get("item").split(" ").slice(1).join(" "); - b.classList.add("col"); - row.appendChild(b); - - let starsB = document.createElement("div"); - starsB.classList.add("T1-bracket", "col-auto", "px-0"); - starsB.textContent = "["; - row.appendChild(starsB); - for(let j = 0; j < 3; j ++) { - let star = document.createElement("div"); - star.classList.add("col-auto", "px-0"); - star.textContent = "\u272B"; - if(j < tier) { - star.classList.add("T1"); - } else { - star.classList.add("T0"); - } - row.append(star); - } - let starsE = document.createElement("div"); - starsE.classList.add("T1-bracket", "col-auto", "px-0"); - starsE.textContent = "]"; - row.appendChild(starsE); - - elem.appendChild(row); - } - - let ingredTable = document.createElement("div"); - ingredTable.classList.add("row"); - - for (let i = 0; i < 3; i++) { - let row = document.createElement("div"); - row.classList.add("row", "g-1", "justify-content-center"); - - - for (let j = 0; j < 2; j++) { - if (j == 1) { - let spacer = document.createElement("div"); - spacer.classList.add("col-1"); - row.appendChild(spacer); - } - let ingredName = ingreds[2 * i + j]; - let col = document.createElement("div"); - col.classList.add("col-5", "rounded", "dark-6", "border", "border-3", "dark-shadow"); - - let temp_row = document.createElement("div"); - temp_row.classList.add("row"); - col.appendChild(temp_row); - - let ingred_div = document.createElement("div"); - ingred_div.classList.add("col"); - ingred_div.textContent = ingredName; - temp_row.appendChild(ingred_div); - - let eff_div = document.createElement("div"); - eff_div.classList.add("col-auto"); - let e = effectiveness[2 * i + j]; - if (e > 0) { - eff_div.classList.add("positive"); - } else if (e < 0) { - eff_div.classList.add("negative"); - } - eff_div.textContent = "[" + e + "%]"; - - temp_row.appendChild(eff_div); - - row.appendChild(col); - } - ingredTable.appendChild(row); - } - elem.appendChild(ingredTable); -} - -/* -* Displays an ingredient in item format. -* However, an ingredient is too far from a normal item to display as one. -*/ -function displaysq2ExpandedIngredient(ingred, parent_id) { - let parent_elem = document.getElementById(parent_id); - parent_elem.textContent = ""; - - let item_order = [ - "dura", - "strReq", - "dexReq", - "intReq", - "defReq", - "agiReq" - ] - let consumable_order = [ - "dura", - "charges" - ] - let posMods_order = [ - "above", - "under", - "left", - "right", - "touching", - "notTouching" - ]; - let id_display_order = [ - "eDefPct", - "tDefPct", - "wDefPct", - "fDefPct", - "aDefPct", - "eDamPct", - "tDamPct", - "wDamPct", - "fDamPct", - "aDamPct", - "str", - "dex", - "int", - "agi", - "def", - "hpBonus", - "mr", - "ms", - "ls", - "hprRaw", - "hprPct", - "sdRaw", - "sdPct", - "mdRaw", - "mdPct", - "xpb", - "lb", - "lq", - "ref", - "thorns", - "expd", - "spd", - "atkTier", - "poison", - "spRegen", - "eSteal", - "spRaw1", - "spRaw2", - "spRaw3", - "spRaw4", - "spPct1", - "spPct2", - "spPct3", - "spPct4", - "jh", - "sprint", - "sprintReg", - "gXp", - "gSpd", - ]; - let active_elem; - let elemental_format = false; - let style; - for (const command of sq2_ing_display_order) { - if (command.charAt(0) === "!") { - // TODO: This is sooo incredibly janky..... - if (command === "!elemental") { - elemental_format = !elemental_format; - } - else if (command === "!spacer") { - let spacer = document.createElement('div'); - spacer.classList.add("row", "my-2"); - parent_elem.appendChild(spacer); - continue; - } - } else { - let div = document.createElement("div"); - div.classList.add("row"); - if (command === "displayName") { - div.classList.add("box-title"); - let title_elem = document.createElement("div"); - title_elem.classList.add("col-auto", "justify-content-center", "pr-1"); - title_elem.textContent = ingred.get("displayName"); - div.appendChild(title_elem); - - let tier = ingred.get("tier"); //tier in [0,3] - let begin = document.createElement("b"); - begin.classList.add("T"+tier+"-bracket", "col-auto", "px-0"); - begin.textContent = "["; - div.appendChild(begin); - - for (let i = 0; i < 3; i++) { - let tier_elem = document.createElement("b"); - if (i < tier) { - tier_elem.classList.add("T"+tier); - } else { - tier_elem.classList.add("T0"); - } - tier_elem.classList.add("px-0", "col-auto"); - tier_elem.textContent = "\u272B"; - div.appendChild(tier_elem); - } - let end = document.createElement("b"); - end.classList.add("T"+tier+"-bracket", "px-0", "col-auto"); - end.textContent = "]"; - div.appendChild(end); - }else if (command === "lvl") { - div.textContent = "Crafting Lvl Min: " + ingred.get("lvl"); - }else if (command === "posMods") { - for (const [key,value] of ingred.get("posMods")) { - let posModRow = document.createElement("div"); - posModRow.classList.add("row"); - if (value != 0) { - let posMod = document.createElement("div"); - posMod.classList.add("col-auto"); - posMod.textContent = posModPrefixes[key]; - posModRow.appendChild(posMod); - - let val = document.createElement("div"); - val.classList.add("col-auto", "px-0"); - val.textContent = value + posModSuffixes[key]; - if(value > 0) { - val.classList.add("positive"); - } else { - val.classList.add("negative"); - } - posModRow.appendChild(val); - div.appendChild(posModRow); - } - } - } else if (command === "itemIDs") { //dura, reqs - for (const [key,value] of ingred.get("itemIDs")) { - let idRow = document.createElement("div"); - idRow.classList.add("row"); - if (value != 0) { - let title = document.createElement("div"); - title.classList.add("col-auto"); - title.textContent = itemIDPrefixes[key]; - idRow.appendChild(title); - } - let desc = document.createElement("div"); - desc.classList.add("col-auto"); - if(value > 0) { - if(key !== "dura") { - desc.classList.add("negative"); - } else{ - desc.classList.add("positive"); - } - desc.textContent = "+"+value; - } else if (value < 0){ - if(key !== "dura") { - desc.classList.add("positive"); - } else{ - desc.classList.add("negative"); - } - desc.textContent = value; - } - if(value != 0){ - idRow.appendChild(desc); - } - div.appendChild(idRow); - } - } else if (command === "consumableIDs") { //dura, charges - for (const [key,value] of ingred.get("consumableIDs")) { - let idRow = document.createElement("div"); - idRow.classList.add("row"); - if (value != 0) { - let title = document.createElement("div"); - title.classList.add("col-auto"); - title.textContent = consumableIDPrefixes[key]; - idRow.appendChild(title); - } - let desc = document.createElement("div"); - desc.classList.add("col-auto"); - if(value > 0) { - desc.classList.add("positive"); - desc.textContent = "+"+value; - } else if (value < 0){ - desc.classList.add("negative"); - desc.textContent = value; - } - if(value != 0){ - idRow.appendChild(desc); - let suffix = document.createElement("div"); - suffix.classList.add("col-auto"); - suffix.textContent = consumableIDSuffixes[key]; - idRow.appendChild(suffix); - } - div.appendChild(idRow); - } - }else if (command === "skills") { - let row = document.createElement("div"); - row.classList.add("row"); - let title = document.createElement("div"); - title.classList.add("row"); - title.textContent = "Used in:"; - row.appendChild(title); - for(const skill of ingred.get("skills")) { - let skill_div = document.createElement("div"); - skill_div.classList.add("row"); - skill_div.textContent = skill.charAt(0) + skill.substring(1).toLowerCase(); - row.appendChild(skill_div); - } - div.appendChild(row); - } else if (command === "ids") { //warp - for (let [key,value] of ingred.get("ids").get("maxRolls")) { - if (value !== undefined && value != 0) { - let row = displaysq2RolledID(ingred.get("ids"), key, elemental_format); - row.classList.remove("col"); - row.classList.remove("col-12"); - div.appendChild(row); - } - } - } else {//this shouldn't be happening - } - - parent_elem.appendChild(div); - } - } -} - -//TODO: translate the below to BS - -/** Displays Additional Info for - * - * @param {String} elemID - the parent element's id - * @param {Map} item - the statMap of the item - * @returns - */ -function displaysq2AdditionalInfo(elemID, item) { - let parent_elem = document.getElementById(elemID); - - let title = document.createElement("div"); - title.classList.add("big-title", "justify-content-center"); - title.textContent = "Additional Info"; - parent_elem.appendChild(title); - - let droptype_elem = document.createElement("div"); - droptype_elem.classList.add("row"); - droptype_elem.textContent = "Drop type: " + (item.has("drop") ? item.get("drop"): "NEVER"); - parent_elem.appendChild(droptype_elem); - - let warning_elem = document.createElement("div"); - warning_elem.classList.add("row"); - warning_elem.textContent = "This page is incomplete. Will work on it later."; - parent_elem.appendChild(warning_elem); - - return; -} - -/** Displays the ID costs of an item - * - * @param {String} elemID - the id of the parent element. - * @param {Map} item - the statMap of an item. - */ - function displaysq2IDCosts(elemID, item) { - let parent_elem = document.getElementById(elemID); - let tier = item.get("tier"); - if ( (item.has("fixID") && item.get("fixID")) || ["Normal","Crafted","Custom","none", " ",].includes(item.get("tier"))) { - return; - } else { - /** Returns the number of inventory slots minimum an amount of emeralds would take up + the configuration of doing so. - * Returns an array of [invSpace, E, EB, LE, Stx LE] - * - * @param {number} ems - the total numerical value of emeralds to compact. - */ - function emsToInvSpace(ems) { - let stx = Math.floor(ems/262144); - ems -= stx*4096*64; - let LE = Math.floor(ems/4096); - ems -= LE*4096; - let EB = Math.floor(ems/64); - ems -= EB*64; - let e = ems; - return [ stx + Math.ceil(LE/64) + Math.ceil(EB/64) + Math.ceil(e/64) , e, EB, LE, stx]; - } - /** - * - * @param {String} tier - item tier - * @param {Number} lvl - item level - */ - function getIDCost(tier, lvl) { - switch (tier) { - case "Unique": - return Math.round(0.5*lvl + 3); - case "Rare": - return Math.round(1.2*lvl + 8); - case "Legendary": - return Math.round(4.5*lvl + 12); - case "Fabled": - return Math.round(12*lvl + 26); - case "Mythic": - return Math.round(18*lvl + 90); - case "Set": - return Math.round(1.5*lvl + 8) - default: - return -1; - } - } - - parent_elem.style = "display: visible"; - let lvl = item.get("lvl"); - if (typeof(lvl) === "string") { lvl = parseFloat(lvl); } - - let title_elem = document.createElement("div"); - title_elem.classList.add("big-title", "justify-content-center", "Set"); - title_elem.textContent = "Identification Costs"; - parent_elem.appendChild(title_elem); - - let grid_item = document.createElement("div"); - grid_item.classList.add("row", "g-3"); - parent_elem.appendChild(grid_item); - - let IDcost = getIDCost(tier, lvl); - let initIDcost = IDcost; - let invSpace = emsToInvSpace(IDcost); - let rerolls = 0; - - while(invSpace[0] <= 28 && IDcost > 0) { - let container_container = document.createElement("div"); - container_container.classList.add("col-lg-3", "col-sm-12"); - - let container = document.createElement("div"); - container.classList.add("col", "rounded", "border", "border-dark", "border-2"); - - container_container.appendChild(container); - - let container_title = document.createElement("div"); - container_title.classList.add("row", "box-title", "justify-content-center"); - - if (rerolls == 0) { - container_title.textContent = "Initial ID Cost: "; - } else { - container_title.textContent = "Reroll to [" + (rerolls+1) + "] Cost:"; - } - container.appendChild(container_title); - let total_cost_container = document.createElement("div"); - total_cost_container.classList.add("row"); - let total_cost_number = document.createElement("b"); - total_cost_number.classList.add("Set", "fw-bold", "col-6", "text-end"); - total_cost_number.textContent = IDcost + " "; - let total_cost_suffix = document.createElement("div"); - total_cost_suffix.classList.add("col-6", "text-start"); - total_cost_suffix.textContent = "emeralds." - total_cost_container.appendChild(total_cost_number); - total_cost_container.appendChild(total_cost_suffix); - container.appendChild(total_cost_container); - - let OR = document.createElement("div"); - OR.classList.add("row"); - container.appendChild(OR); - let OR_text = document.createElement("div"); - OR_text.classList.add("col", "text-center"); - OR_text.textContent = "OR"; - OR.appendChild(OR_text); - - let esuffixes = ["", "emeralds.", "EB.", "LE.", "stacks of LE."]; - for (let i = 4; i > 0; i--) { - let n_container = document.createElement("div"); - n_container.classList.add("row"); - let n_number = document.createElement("b"); - n_number.classList.add("Set", "fw-bold", "col-6", "text-end"); - n_number.textContent = invSpace[i] + " "; - let n_suffix = document.createElement("div"); - n_suffix.classList.add("col-6", "text-start"); - n_suffix.textContent = esuffixes[i]; - n_container.appendChild(n_number); - n_container.appendChild(n_suffix); - container.appendChild(n_container); - } - grid_item.appendChild(container_container); - - rerolls += 1; - IDcost = Math.round(initIDcost * (5 ** rerolls)); - invSpace = emsToInvSpace(IDcost); - } - } -} - -/** Displays all set bonuses (0/n, 1/n, ... n/n) for a given set - * - * @param {String} parent_id - id of the parent element - * @param {String} setName - the name of the set - */ - function displaysq2AllSetBonuses(parent_id, setName) { - let parent_elem = document.getElementById(parent_id); - parent_elem.style.display = ""; - let set = sets[setName]; - let title_elem = document.createElement("div"); - title_elem.textContent = setName + " Set Bonuses"; - title_elem.classList.add("Set", "big-title", "justify-content-center"); - parent_elem.appendChild(title_elem); - - let grid_elem = document.createElement("div"); - grid_elem.classList.add("row"); - parent_elem.appendChild(grid_elem); - - for (let i = 0; i < set.items.length; i++) { - - let set_elem = document.createElement('div'); - set_elem.classList.add("col-lg-3", "col-sm-12", "py-2", "my-1"); - grid_elem.appendChild(set_elem); - const bonus = set.bonuses[i]; - - let set_elem_display = document.createElement("div"); - set_elem_display.classList.add("rounded", "col", "g-0", "scaled-font", "border", "border-3", "border-dark", "dark-shadow", "dark-7", "p-3"); - set_elem_display.id = "set-"+setName+"-"+i; - set_elem.appendChild(set_elem_display); - - let mock_item = new Map(); - mock_item.set("fixID", true); - mock_item.set("tier", "Set"); - mock_item.set("displayName", setName+" Set: " + (i+1) + "/"+sets[setName].items.length); - // set_elem.textContent = mock_item.get("displayName"); - let mock_minRolls = new Map(); - let mock_maxRolls = new Map(); - mock_item.set("minRolls", mock_minRolls); - mock_item.set("maxRolls", mock_maxRolls); - for (const id in bonus) { - if (rolledIDs.includes(id)) { - mock_minRolls.set(id, bonus[id]); - mock_maxRolls.set(id, bonus[id]); - } - else { - mock_item.set(id, bonus[id]); - } - } - mock_item.set("powders", []); - displaysq2ExpandedItem(mock_item, set_elem_display.id); - } - -} - -/** Displays the individual probabilities of each possible value of each rollable ID for this item. - * - * @param {String} parent_id the document id of the parent element - * @param {String} item expandedItem object - * @param {String} amp the level of corkian amplifier used. 0 means no amp, 1 means Corkian Amplifier I, etc. [0,3] - */ -function displaysq2IDProbabilities(parent_id, item, amp) { - if (item.has("fixID") && item.get("fixID")) {return} - let parent_elem = document.getElementById(parent_id); - parent_elem.style.display = ""; - parent_elem.innerHTML = ""; - let title_elem = document.createElement("div"); - title_elem.textContent = "Identification Probabilities"; - title_elem.classList.add("row", "Legendary", "big-title", "justify-content-center"); - parent_elem.appendChild(title_elem); - - let disclaimer_elem = document.createElement("div"); - disclaimer_elem.classList.add("row", "justify-content-center"); - disclaimer_elem.textContent = "IDs are rolled on a uniform distribution. A chance of 0% means that either the minimum or maximum possible multiplier must be rolled to get this value." - parent_elem.appendChild(disclaimer_elem); - - let amp_row = document.createElement("div"); - amp_row.classList.add("row", "justify-content-center"); - amp_row.id = "amp_row"; - let amp_text = document.createElement("div"); - amp_text.classList.add("col-lg-2", "col-sm-3"); - amp_text.textContent = "Corkian Amplifier Used: " - amp_row.appendChild(amp_text); - - let amp_1 = document.createElement("button"); - amp_1.classList.add("col-lg-1", "col-sm-3", "border-dark", "text-light", "dark-5", "rounded", "scaled-font"); - amp_1.id = "cork_amp_1"; - amp_1.textContent = "I"; - amp_row.appendChild(amp_1); - let amp_2 = document.createElement("button"); - amp_2.classList.add("col-lg-1", "col-sm-3", "border-dark", "text-light", "dark-5", "rounded", "scaled-font"); - amp_2.id = "cork_amp_2"; - amp_2.textContent = "II"; - amp_row.appendChild(amp_2); - let amp_3 = document.createElement("button"); - amp_3.classList.add("col-lg-1", "col-sm-3", "border-dark", "text-light", "dark-5", "rounded", "scaled-font"); - amp_3.id = "cork_amp_3"; - amp_3.textContent = "III"; - amp_row.appendChild(amp_3); - amp_1.addEventListener("click", (event) => {toggleAmps(1)}); - amp_2.addEventListener("click", (event) => {toggleAmps(2)}); - amp_3.addEventListener("click", (event) => {toggleAmps(3)}); - parent_elem.appendChild(amp_row); - - if (amp != 0) {toggleButton("cork_amp_" + amp)} - - item_name = item.get("displayName"); - for (const [id,val] of Object.entries(itemMap.get(item_name))) { - if (rolledIDs.includes(id)) { - let min = item.get("minRolls").get(id); - let max = item.get("maxRolls").get(id); - - if (min != 0 || max != 0) { - //Apply corkian amps - if (val > 0) { - let base = itemMap.get(item_name)[id]; - if (reversedIDs.includes(id)) {max = Math.max( Math.round((0.3 + 0.05*amp) * base), 1)} - else {min = Math.max( Math.round((0.3 + 0.05*amp) * base), 1)} - } - - let row_elem = document.createElement("div"); - row_elem.classList.add("row"); - parent_elem.appendChild(row_elem); - - let base_and_range = document.createElement("div"); - base_and_range.classList.add("col-lg-4", "col-sm-12"); - - - let base_elem = document.createElement("div"); - let base_val = document.createElement("div"); - base_elem.classList.add("row"); - base_prefix = document.createElement("div"); - base_prefix.classList.add("col-auto"); - base_val.classList.add("col-auto"); - - base_prefix.textContent = idPrefixes[id] + "Base "; - base_val.textContent = val + idSuffixes[id]; - if (val > 0 == !reversedIDs.includes(id)) { - base_val.classList.add("positive"); - } else if (val > 0 == reversedIDs.includes(id)) { - base_val.classList.add("negative"); - } - base_elem.appendChild(base_prefix); - base_elem.appendChild(base_val); - - let range_elem = document.createElement("div"); - range_elem.classList.add("row", "justify-content-center"); - - range_elem.textContent = "[ " + min + idSuffixes[id] + ", " + max + idSuffixes[id] + " ]"; - if ( (min > 0 && max > 0 && !reversedIDs.includes(id)) || (min < 0 && max < 0 && reversedIDs.includes(id)) ) { - range_elem.classList.add("positive"); - } else if ( (min < 0 && max < 0 && !reversedIDs.includes(id)) || (min > 0 && max > 0 && reversedIDs.includes(id)) ) { - range_elem.classList.add("negative"); - } - - base_and_range.appendChild(base_elem); - base_and_range.appendChild(range_elem); - row_elem.appendChild(base_and_range); - - - let pdf_and_cdf = document.createElement("div"); - pdf_and_cdf.classList.add("col-lg-4", "col-sm-12"); - - let pdf_elem = document.createElement("div"); - pdf_elem.id = id + "-pdf"; - let cdf_elem = document.createElement("div"); - cdf_elem.id = id + "-cdf"; - pdf_elem.classList.add("row"); - cdf_elem.classList.add("row"); - - pdf_and_cdf.appendChild(pdf_elem); - pdf_and_cdf.appendChild(cdf_elem); - row_elem.appendChild(pdf_and_cdf); - - let input_sec = document.createElement("div"); - input_sec.classList.add("col-lg-4", "col-sm-12"); - - let title_input_slider = document.createElement("input"); - title_input_slider.classList.add("row"); - title_input_slider.type = "range"; - title_input_slider.id = id+"-slider"; - if (!reversedIDs.includes(id)) { - title_input_slider.step = 1; - title_input_slider.min = `${min}`; - title_input_slider.max = `${max}`; - title_input_slider.value = `${max}`; - } else { - title_input_slider.step = 1; - title_input_slider.min = `${-1*min}`; - title_input_slider.max = `${-1*max}`; - title_input_slider.value = `${-1*max}`; - } - let title_input_textbox = document.createElement("input"); - title_input_textbox.classList.add("row"); - title_input_textbox.type = "text"; - title_input_textbox.value = `${max}`; - title_input_textbox.id = id+"-textbox"; - title_input_textbox.classList.add("rounded", "border", "border-dark", "border-2", "dark-5", "text-light"); - input_sec.appendChild(title_input_slider); - input_sec.appendChild(title_input_textbox); - - row_elem.appendChild(input_sec); - - sq2StringPDF(id, max, val, amp); //val is base roll - sq2StringCDF(id, max, val, amp); //val is base roll - title_input_slider.addEventListener("change", (event) => { - let id_name = event.target.id.split("-")[0]; - let textbox_elem = document.getElementById(id_name+"-textbox"); - - if (reversedIDs.includes(id_name)) { - if (event.target.value < -1*min) { event.target.value = -1*min} - if (event.target.value > -1*max) { event.target.value = -1*max} - sq2StringPDF(id_name, -1*event.target.value, val, amp); //val is base roll - sq2StringCDF(id_name, -1*event.target.value, val, amp); //val is base roll - } else { - if (event.target.value < min) { event.target.value = min} - if (event.target.value > max) { event.target.value = max} - sq2StringPDF(id_name, 1*event.target.value, val, amp); //val is base roll - sq2StringCDF(id_name, 1*event.target.value, val, amp); //val is base roll - } - - if (textbox_elem && textbox_elem.value !== event.target.value) { - if (reversedIDs.includes(id_name)) { - textbox_elem.value = -event.target.value; - } else { - textbox_elem.value = event.target.value; - } - } - - - }); - title_input_textbox.addEventListener("change", (event) => { - let id_name = event.target.id.split("-")[0]; - if (reversedIDs.includes(id_name)) { - if (event.target.value > min) { event.target.value = min} - if (event.target.value < max) { event.target.value = max} - } else { - if (event.target.value < min) { event.target.value = min} - if (event.target.value > max) { event.target.value = max} - } - let slider_elem = document.getElementById(id_name+"-slider"); - if (slider_elem.value !== event.target.value) { - slider_elem.value = -event.target.value; - } - - sq2StringPDF(id_name, 1*event.target.value, val, amp); - sq2StringCDF(id_name, 1*event.target.value, val, amp); - }); - } - } - } -} - -//helper functions. id - the string of the id's name, val - the value of the id, base - the base value of the item for this id -function sq2StringPDF(id,val,base,amp) { - /** [0.3b,1.3b] positive normal - * [1.3b,0.3b] positive reversed - * [1.3b,0.7b] negative normal - * [0.7b,1.3b] negative reversed - * - * [0.3, 1.3] minr, maxr [0.3b, 1.3b] min, max - * the minr/maxr decimal roll that corresponds to val -> minround, maxround - */ - let p; let min; let max; let minr; let maxr; let minround; let maxround; - if (base > 0) { - minr = 0.3 + 0.05*amp; maxr = 1.3; - min = Math.max(1, Math.round(minr*base)); max = Math.max(1, Math.round(maxr*base)); - minround = (min == max) ? (minr) : ( Math.max(minr, (val-0.5) / base) ); - maxround = (min == max) ? (maxr) : ( Math.min(maxr, (val+0.5) / base) ); - } else { - minr = 1.3; maxr = 0.7; - min = Math.min(-1, Math.round(minr*base)); max = Math.min(-1, Math.round(maxr*base)); - minround = (min == max) ? (minr) : ( Math.min(minr, (val-0.5) / base) ); - maxround = (min == max) ? (maxr) : ( Math.max(maxr, (val+0.5) / base) ); - } - - p = Math.abs(maxround-minround)/Math.abs(maxr-minr)*100; - p = p.toFixed(3); - - let div1 = document.createElement("div"); - div1.textContent = "Roll exactly "; - div1.classList.add("col-auto", "px-0"); - let div2 = document.createElement("div"); - div2.textContent = val + idSuffixes[id]; - div2.classList.add("col-auto", "px-1"); - if (val > 0 == !reversedIDs.includes(id)) {div2.classList.add("positive")} - if (val > 0 == reversedIDs.includes(id)) {div2.classList.add("negative")} - let div3 = document.createElement("div"); - div3.textContent = ": " + p + "%"; - div3.classList.add("col-auto", "px-0"); - document.getElementById(id + "-pdf").innerHTML = ""; - document.getElementById(id + "-pdf").appendChild(div1); - document.getElementById(id + "-pdf").appendChild(div2); - document.getElementById(id + "-pdf").appendChild(div3); -} -function sq2StringCDF(id,val,base,amp) { - let p; let min; let max; let minr; let maxr; let minround; let maxround; - if (base > 0) { - minr = 0.3 + 0.05*amp; maxr = 1.3; - min = Math.max(1, Math.round(minr*base)); max = Math.max(1, Math.round(maxr*base)); - minround = (min == max) ? (minr) : ( Math.max(minr, (val-0.5) / base) ); - maxround = (min == max) ? (maxr) : ( Math.min(maxr, (val+0.5) / base) ); - } else { - minr = 1.3; maxr = 0.7; - min = Math.min(-1, Math.round(minr*base)); max = Math.min(-1, Math.round(maxr*base)); - minround = (min == max) ? (minr) : ( Math.min(minr, (val-0.5) / base) ); - maxround = (min == max) ? (maxr) : ( Math.max(maxr, (val+0.5) / base) ); - } - - if (reversedIDs.includes(id)) { - p = Math.abs(minr-maxround)/Math.abs(maxr-minr)*100; - } else { - p = Math.abs(maxr-minround)/Math.abs(maxr-minr)*100; - } - p = p.toFixed(3); - - let div1 = document.createElement("div"); - div1.textContent = "Roll "; - div1.classList.add("col-auto", "px-0"); - let div2 = document.createElement("div"); - div2.textContent = val + idSuffixes[id]; - div2.classList.add("col-auto", "px-1"); - if (val > 0 == !reversedIDs.includes(id)) {div2.classList.add("positive")} - if (val > 0 == reversedIDs.includes(id)) {div2.classList.add("negative")} - let div3 = document.createElement("div"); - div3.textContent= " or better: " + p + "%"; - div3.classList.add("col-auto", "px-0"); - document.getElementById(id + "-cdf").innerHTML = ""; - document.getElementById(id + "-cdf").appendChild(div1); - document.getElementById(id + "-cdf").appendChild(div2); - document.getElementById(id + "-cdf").appendChild(div3); -} diff --git a/js/sq2display_constants.js b/js/sq2display_constants.js deleted file mode 100644 index 6f352fd..0000000 --- a/js/sq2display_constants.js +++ /dev/null @@ -1,191 +0,0 @@ -let powder_chars = [ - '\u2724', - '\u2726', - '\u2749', - '\u2739', - '\u274b' -] -let subscript_nums = [ - '\u2081', - '\u2082', - '\u2083', - '\u2084', - '\u2085', - '\u2086', -] - -let skp_names = [ - 'str', - 'dex', - 'int', - 'def', - 'agi' -] - -let elem_chars = [ - 'e', - 't', - 'w', - 'f', - 'a' -] - -let elem_names = [ - 'earth', - 'thunder', - 'water', - 'fire', - 'air' -] - -let elem_colors = [ - "#00AA00", - "#FFFF55", - "#55FFFF", - "#FF5555", - "#FFFFFF" -] - -let item_types = [ - "Helmet", - "Chestplate", - "Leggings", - "Boots", - "Ring", - "Bracelet", - "Necklace", - "Dagger", - "Spear", - "Wand", - "Relik", - "Bow", - "Potion", - "Scroll", - "Food", - "Weapon Tome", - "Armor Tome", - "Guild Tome" -] - -let tome_types = ['weaponTome', 'armorTome', 'guildTome']; -let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1']; - -/* - * Display commands - */ -let build_all_display_commands = [ - "#defense-stats", - "str", "dex", "int", "def", "agi", - "mr", "ms", - "hprRaw", "hprPct", - "sdRaw", "sdPct", - "mdRaw", "mdPct", - "ref", "thorns", - "ls", - "poison", - "expd", - "spd", - "atkTier", - "!elemental", - "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", - "!elemental", - "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", - "rainbowRaw", - "sprint", "sprintReg", - "jh", - "xpb", "lb", "lq", - "spRegen", - "eSteal", - "gXp", "gSpd", -]; - -let build_offensive_display_commands = [ - "str", "dex", "int", "def", "agi", - "mr", "ms", - "sdRaw", "sdPct", - "mdRaw", "mdPct", - "ref", "thorns", - "ls", - "poison", - "expd", - "spd", - "atkTier", - "rainbowRaw", - "!elemental", - "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", - "!elemental", - "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", -]; - -let build_basic_display_commands = [ - '#defense-stats', - // defense stats [hp, ehp, hpr, ] - // "sPot", // base * atkspd + spell raws - // melee potential - // "mPot", // melee% * (base * atkspd) + melee raws - "mr", "ms", - "ls", - "poison", - "spd", - "atkTier", -] - -let sq2_item_display_commands = [ - "displayName", - "atkSpd", - "!elemental", - "hp", - "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", - "!spacer", - "fDef", "wDef", "aDef", "tDef", "eDef", - "!elemental", - "classReq", - "lvl", - "strReq", "dexReq", "intReq", "defReq","agiReq", - "!spacer", - "str", "dex", "int", "def", "agi", - "hpBonus", - "hprRaw", "hprPct", - "sdRaw", "sdPct", - "mdRaw", "mdPct", - "mr", "ms", - "ref", "thorns", - "ls", - "poison", - "expd", - "spd", - "atkTier", - "!elemental", - "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", - "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", - "!elemental", - "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", - "rainbowRaw", - "sprint", "sprintReg", - "jh", - "xpb", "lb", "lq", - "spRegen", - "eSteal", - "gXp", "gSpd", - "majorIds", - "!spacer", - "slots", - "!spacer", - "set", - "lore", - "quest", - "restrict" -]; - -let sq2_ing_display_order = [ - "displayName", //tier will be displayed w/ name - "!spacer", - "ids", - "!spacer", - "posMods", - "itemIDs", - "consumableIDs", - "!spacer", - "lvl", - "skills", -] \ No newline at end of file From 564e4709cd4f7635ce7d88f8ae90dee6f9df54dc Mon Sep 17 00:00:00 2001 From: reschan Date: Wed, 22 Jun 2022 09:34:06 +0700 Subject: [PATCH 036/155] change clickable arrow to a function --- js/sq2display.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/js/sq2display.js b/js/sq2display.js index 7dd1d27..ef8f77e 100644 --- a/js/sq2display.js +++ b/js/sq2display.js @@ -827,12 +827,7 @@ function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){ parent_elem.append(critStats); - //up and down arrow - done ugly - let arrow = document.createElement("img"); - arrow.id = "arrow_" + overallparent_elem.id; - arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem"; - arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png"; - overallparent_elem.appendChild(arrow); + addClickableArrow(overallparent_elem); } function displaysq2ArmorStats(build) { @@ -1401,12 +1396,7 @@ function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, sp } } - //up and down arrow - done ugly - let arrow = document.createElement("img"); - arrow.id = "arrow_" + overallparent_elem.id; - arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem"; - arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png"; - overallparent_elem.appendChild(arrow); + addClickableArrow(overallparent_elem); } function displaysq2EquipOrder(parent_elem, buildOrder){ @@ -2390,3 +2380,12 @@ function sq2StringCDF(id,val,base,amp) { document.getElementById(id + "-cdf").appendChild(div2); document.getElementById(id + "-cdf").appendChild(div3); } + +function addClickableArrow(elem) { + //up and down arrow - done ugly + let arrow = document.createElement("img"); + arrow.id = "arrow_" + elem.id; + arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem"; + arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png"; + elem.appendChild(arrow); +} From 6620d011e5877ca93be44a93fef78a10ab829da0 Mon Sep 17 00:00:00 2001 From: hppeng Date: Tue, 21 Jun 2022 20:41:40 -0700 Subject: [PATCH 037/155] Fix edit id resetting --- builder/index.html | 2 +- js/builder_graph.js | 35 ++++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/builder/index.html b/builder/index.html index bc5c7d3..7b4f294 100644 --- a/builder/index.html +++ b/builder/index.html @@ -431,7 +431,7 @@
-
diff --git a/js/builder_graph.js b/js/builder_graph.js index ae36651..688615a 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -758,19 +758,40 @@ class AggregateStatsNode extends ComputeNode { } } +let edit_id_output; +function resetEditableIDs() { + edit_id_output.notify(); +} /** * Set the editble id fields. * * Signature: EditableIDSetterNode(build: Build) => null */ class EditableIDSetterNode extends ComputeNode { - constructor() { super("builder-id-setter"); } + constructor(notify_nodes) { + super("builder-id-setter"); + this.notify_nodes = notify_nodes.slice(); + } 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) { - document.getElementById(id).value = build.statMap.get(id); + 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(); } } } @@ -784,7 +805,7 @@ class EditableIDSetterNode extends ComputeNode { class SkillPointSetterNode extends ComputeNode { constructor(notify_nodes) { super("builder-skillpoint-setter"); - this.notify_nodes = notify_nodes; + this.notify_nodes = notify_nodes.slice(); } compute_func(input_map) { @@ -889,10 +910,6 @@ function builder_graph_init() { item_nodes[3].link_to(powder_nodes[3], 'powdering'); item_nodes[8].link_to(powder_nodes[4], 'powdering'); - // Edit IDs setter declared up here to set ids so they will be populated by default. - let edit_id_output = new EditableIDSetterNode(); - edit_id_output.link_to(build_node); - // Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage let build_disp_node = new BuildDisplayNode() @@ -911,6 +928,10 @@ function builder_graph_init() { stat_agg_node.link_to(node, field); edit_input_nodes.push(node); } + // 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); + for (const skp of skp_order) { const elem = document.getElementById(skp+'-skp'); const node = new SumNumberInputNode('builder-'+skp+'-input', elem); From 361d4cca542c94b48b5d3c39e557185c09d2e7d9 Mon Sep 17 00:00:00 2001 From: ferricles Date: Tue, 21 Jun 2022 22:09:54 -0700 Subject: [PATCH 038/155] bruh --- atlas/index.html | 2 +- js/atlas.js | 4 ++++ media/audio/bruh_sound_effect.mp3 | Bin 0 -> 23853 bytes 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 media/audio/bruh_sound_effect.mp3 diff --git a/atlas/index.html b/atlas/index.html index 9275ce7..8bbc658 100644 --- a/atlas/index.html +++ b/atlas/index.html @@ -50,7 +50,7 @@
- +
diff --git a/js/atlas.js b/js/atlas.js index 7724c93..f901935 100644 --- a/js/atlas.js +++ b/js/atlas.js @@ -119,6 +119,10 @@ function runAtlas() { let center = [(at1[0]+at2[0])/2, (at1[1]+at2[1])/2 ]; if (Math.sqrt(((at2[1]+atlas2.vy) - (at1[1]+atlas1.vy))**2 + ((at2[0]+atlas2.vx) - (at1[0]+atlas1.vx))**2) < 2*r) { + //Play bruh sound effect + document.getElementById('bruh_sound_effect').play(); + document.getElementById('bruh_sound_effect').currentTime = 0; + if(Math.sqrt( (at2[1]-at1[1])**2 + (at2[0]-at1[0])**2 ) < 2*r ) {//check for collision //Move both away slightly - correct alg this time :) atlas1.style.left = parseFloat(atlas1.style.left.replace("px","")) + (at1[0]-center[0]) * 2 * r / Math.sqrt(dx**2 + dy**2) + "px"; diff --git a/media/audio/bruh_sound_effect.mp3 b/media/audio/bruh_sound_effect.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..2b245a321d004029cdf9ff02f698d4f31151d6d5 GIT binary patch literal 23853 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 Date: Tue, 21 Jun 2022 22:39:10 -0700 Subject: [PATCH 039/155] Small refactor to prepare for atree; fix reset health/lvl --- js/build.js | 4 - js/build2.js | 550 -------------------------------------- js/build_encode_decode.js | 18 +- js/builder.js | 6 +- js/builder_graph.js | 57 ++-- js/craft.js | 3 - js/display.js | 8 +- 7 files changed, 51 insertions(+), 595 deletions(-) delete mode 100644 js/build2.js diff --git a/js/build.js b/js/build.js index 78cf2ac..1760004 100644 --- a/js/build.js +++ b/js/build.js @@ -211,9 +211,5 @@ class Build{ let statMap = this.statMap; let weapon_stats = this.weapon.statMap; statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]); - statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]); - statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]); - statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]); - statMap.set("defMult", classDefenseMultipliers.get(weapon_stats.get("type"))); } } diff --git a/js/build2.js b/js/build2.js deleted file mode 100644 index b74e403..0000000 --- a/js/build2.js +++ /dev/null @@ -1,550 +0,0 @@ - - -const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20] ]); - -/** - * @description Error to catch items that don't exist. - * @module ItemNotFound - */ -class ItemNotFound { - /** - * @class - * @param {String} item the item name entered - * @param {String} type the type of item - * @param {Boolean} genElement whether to generate an element from inputs - * @param {String} override override for item type - */ - constructor(item, type, genElement, override) { - /** - * @public - * @type {String} - */ - this.message = `Cannot find ${override||type} named ${item}`; - if (genElement) - /** - * @public - * @type {Element} - */ - this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0]; - else - this.element = document.createElement("div"); - } -} - -/** - * @description Error to catch incorrect input. - * @module IncorrectInput - */ -class IncorrectInput { - /** - * @class - * @param {String} input the inputted text - * @param {String} format the correct format - * @param {String} sibling the id of the error node's sibling - */ - constructor(input, format, sibling) { - /** - * @public - * @type {String} - */ - this.message = `${input} is incorrect. Example: ${format}`; - /** - * @public - * @type {String} - */ - this.id = sibling; - } -} - -/** - * @description Error that inputs an array of items to generate errors of. - * @module ListError - * @extends Error - */ -class ListError extends Error { - /** - * @class - * @param {Array} errors array of errors - */ - constructor(errors) { - let ret = []; - if (typeof errors[0] == "string") { - super(errors[0]); - } else { - super(errors[0].message); - } - for (let i of errors) { - if (typeof i == "string") { - ret.push(new Error(i)); - } else { - ret.push(i); - } - } - /** - * @public - * @type {Object[]} - */ - this.errors = ret; - } -} - -/*Class that represents a wynn player's build. -*/ -class Build{ - - /** - * @description Construct a build. - * @param {Number} level : Level of the player. - * @param {String[]} equipment : List of equipment names that make up the build. - * In order: boots, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon. - * @param {Number[]} powders : Powder application. List of lists of integers (powder IDs). - * In order: boots, Chestplate, Leggings, Boots, Weapon. - * @param {Object[]} inputerrors : List of instances of error-like classes. - */ - constructor(level,equipment, powders, externalStats, inputerrors=[]){ - - let errors = inputerrors; - //this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem). - this.craftedItems = []; - this.customItems = []; - // NOTE: powders is just an array of arrays of powder IDs. Not powder objects. - this.powders = powders; - if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") { - const helmet = itemMap.get(equipment[0]); - this.powders[0] = this.powders[0].slice(0,helmet.slots); - this.helmet = expandItem(helmet, this.powders[0]); - } else { - try { - //let boots = getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : (getCustomFromHash(equipment[0])? getCustomFromHash(equipment[0]) : undefined); - let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined); - if (helmet.statMap.get("type") !== "helmet") { - throw new Error("Not a helmet"); - } - this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots")); - helmet.statMap.set("powders",this.powders[0].slice()); - helmet.applyPowders(); - this.helmet = helmet.statMap; - if (this.helmet.get("custom")) { - this.customItems.push(helmet); - } else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(helmet); - } - - } catch (Error) { - //console.log(Error); //fix - const helmet = itemMap.get("No Helmet"); - this.powders[0] = this.powders[0].slice(0,helmet.slots); - this.helmet = expandItem(helmet, this.powders[0]); - errors.push(new ItemNotFound(equipment[0], "helmet", true)); - } - } - if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") { - const chestplate = itemMap.get(equipment[1]); - this.powders[1] = this.powders[1].slice(0,chestplate.slots); - this.chestplate = expandItem(chestplate, this.powders[1]); - } else { - try { - let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined); - if (chestplate.statMap.get("type") !== "chestplate") { - throw new Error("Not a chestplate"); - } - this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots")); - chestplate.statMap.set("powders",this.powders[1].slice()); - chestplate.applyPowders(); - this.chestplate = chestplate.statMap; - if (this.chestplate.get("custom")) { - this.customItems.push(chestplate); - } else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(chestplate); - } - } catch (Error) { - const chestplate = itemMap.get("No Chestplate"); - this.powders[1] = this.powders[1].slice(0,chestplate.slots); - this.chestplate = expandItem(chestplate, this.powders[1]); - errors.push(new ItemNotFound(equipment[1], "chestplate", true)); - } - } - if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") { - const leggings = itemMap.get(equipment[2]); - this.powders[2] = this.powders[2].slice(0,leggings.slots); - this.leggings = expandItem(leggings, this.powders[2]); - } else { - try { - let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined); - if (leggings.statMap.get("type") !== "leggings") { - throw new Error("Not a leggings"); - } - this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots")); - leggings.statMap.set("powders",this.powders[2].slice()); - leggings.applyPowders(); - this.leggings = leggings.statMap; - if (this.leggings.get("custom")) { - this.customItems.push(leggings); - } else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(leggings); - } - } catch (Error) { - const leggings = itemMap.get("No Leggings"); - this.powders[2] = this.powders[2].slice(0,leggings.slots); - this.leggings = expandItem(leggings, this.powders[2]); - errors.push(new ItemNotFound(equipment[2], "leggings", true)); - } - } - if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") { - const boots = itemMap.get(equipment[3]); - this.powders[3] = this.powders[3].slice(0,boots.slots); - this.boots = expandItem(boots, this.powders[3]); - } else { - try { - let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined); - if (boots.statMap.get("type") !== "boots") { - throw new Error("Not a boots"); - } - this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots")); - boots.statMap.set("powders",this.powders[3].slice()); - boots.applyPowders(); - this.boots = boots.statMap; - console.log(boots); - if (this.boots.get("custom")) { - this.customItems.push(boots); - } else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(boots); - } - } catch (Error) { - const boots = itemMap.get("No Boots"); - this.powders[3] = this.powders[3].slice(0,boots.slots); - this.boots = expandItem(boots, this.powders[3]); - errors.push(new ItemNotFound(equipment[3], "boots", true)); - } - } - if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") { - const ring = itemMap.get(equipment[4]); - this.ring1 = expandItem(ring, []); - }else{ - try { - let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined); - if (ring.statMap.get("type") !== "ring") { - throw new Error("Not a ring"); - } - this.ring1 = ring.statMap; - if (this.ring1.get("custom")) { - this.customItems.push(ring); - } else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(ring); - } - } catch (Error) { - const ring = itemMap.get("No Ring 1"); - this.ring1 = expandItem(ring, []); - errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring")); - } - } - if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") { - const ring = itemMap.get(equipment[5]); - this.ring2 = expandItem(ring, []); - }else{ - try { - let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined); - if (ring.statMap.get("type") !== "ring") { - throw new Error("Not a ring"); - } - this.ring2 = ring.statMap; - if (this.ring2.get("custom")) { - this.customItems.push(ring); - } else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(ring); - } - } catch (Error) { - const ring = itemMap.get("No Ring 2"); - this.ring2 = expandItem(ring, []); - errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring")); - } - } - if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") { - const bracelet = itemMap.get(equipment[6]); - this.bracelet = expandItem(bracelet, []); - }else{ - try { - let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined); - if (bracelet.statMap.get("type") !== "bracelet") { - throw new Error("Not a bracelet"); - } - this.bracelet = bracelet.statMap; - if (this.bracelet.get("custom")) { - this.customItems.push(bracelet); - } else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(bracelet); - } - } catch (Error) { - const bracelet = itemMap.get("No Bracelet"); - this.bracelet = expandItem(bracelet, []); - errors.push(new ItemNotFound(equipment[6], "bracelet", true)); - } - } - if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") { - const necklace = itemMap.get(equipment[7]); - this.necklace = expandItem(necklace, []); - }else{ - try { - let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined); - if (necklace.statMap.get("type") !== "necklace") { - throw new Error("Not a necklace"); - } - this.necklace = necklace.statMap; - if (this.necklace.get("custom")) { - this.customItems.push(necklace); - } else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(necklace); - } - } catch (Error) { - const necklace = itemMap.get("No Necklace"); - this.necklace = expandItem(necklace, []); - errors.push(new ItemNotFound(equipment[7], "necklace", true)); - } - } - if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") { - const weapon = itemMap.get(equipment[8]); - this.powders[4] = this.powders[4].slice(0,weapon.slots); - this.weapon = expandItem(weapon, this.powders[4]); - if (equipment[8] !== "No Weapon") { - document.getElementsByClassName("powder-specials")[0].style.display = "grid"; - } else { - document.getElementsByClassName("powder-specials")[0].style.display = "none"; - } - }else{ - try { - let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined); - if (weapon.statMap.get("category") !== "weapon") { - throw new Error("Not a weapon"); - } - this.weapon = weapon.statMap; - if (this.weapon.get("custom")) { - this.customItems.push(weapon); - } else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority. - this.craftedItems.push(weapon); - } - this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots")); - this.weapon.set("powders",this.powders[4].slice()); - document.getElementsByClassName("powder-specials")[0].style.display = "grid"; - } catch (Error) { - const weapon = itemMap.get("No Weapon"); - this.powders[4] = this.powders[4].slice(0,weapon.slots); - this.weapon = expandItem(weapon, this.powders[4]); - document.getElementsByClassName("powder-specials")[0].style.display = "none"; - errors.push(new ItemNotFound(equipment[8], "weapon", true)); - } - } - //console.log(this.craftedItems) - - if (level < 1) { //Should these be constants? - this.level = 1; - } else if (level > 106) { - this.level = 106; - } else if (level <= 106 && level >= 1) { - this.level = level; - } else if (typeof level === "string") { - this.level = level; - errors.push(new IncorrectInput(level, "a number", "level-choice")); - } else { - errors.push("Level is not a string or number."); - } - document.getElementById("level-choice").value = this.level; - - this.availableSkillpoints = levelToSkillPoints(this.level); - this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ]; - this.items = this.equipment.concat([this.weapon]); - // return [equip_order, best_skillpoints, final_skillpoints, best_total]; - let result = calculate_skillpoints(this.equipment, this.weapon); - console.log(result); - this.equip_order = result[0]; - this.base_skillpoints = result[1]; - this.total_skillpoints = result[2]; - this.assigned_skillpoints = result[3]; - this.activeSetCounts = result[4]; - - // For strength boosts like warscream, vanish, etc. - this.damageMultiplier = 1.0; - this.defenseMultiplier = 1.0; - - // For other external boosts ;-; - this.externalStats = externalStats; - - this.initBuildStats(); - - // Remove every error before adding specific ones - for (let i of document.getElementsByClassName("error")) { - i.textContent = ""; - } - this.errors = errors; - if (errors.length > 0) this.errored = true; - } - - /*Returns build in string format - */ - toString(){ - return [this.equipment,this.weapon].flat(); - } - - /* Getters */ - - /* Get total health for build. - */ - - getSpellCost(spellIdx, cost) { - cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2]))); - cost = Math.max(0, Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100))); - return Math.max(1, cost + this.statMap.get("spRaw"+spellIdx)); - } - - - /* Get melee stats for build. - Returns an array in the order: - */ - getMeleeStats(){ - const stats = this.statMap; - if (this.weapon.get("tier") === "Crafted") { - stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]); - } - let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier"); - if(adjAtkSpd > 6){ - adjAtkSpd = 6; - }else if(adjAtkSpd < 0){ - adjAtkSpd = 0; - } - - let damage_mult = 1; - if (this.weapon.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") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats); - - let dex = this.total_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]); - } - - /* - Get all defensive stats for this build. - */ - getDefenseStats(){ - const stats = this.statMap; - let defenseStats = []; - let def_pct = skillPointsToPercentage(this.total_skillpoints[3]); - let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]); - //total hp - let totalHp = stats.get("hp") + stats.get("hpBonus"); - if (totalHp < 5) totalHp = 5; - defenseStats.push(totalHp); - //EHP - let ehp = [totalHp, totalHp]; - let defMult = classDefenseMultipliers.get(this.weapon.get("type")); - ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); - ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); - defenseStats.push(ehp); - //HPR - let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.); - defenseStats.push(totalHpr); - //EHPR - let ehpr = [totalHpr, totalHpr]; - ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier)); - ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier)); - defenseStats.push(ehpr); - //skp stats - defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*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; - } - - /* Get all stats for this build. Stores in this.statMap. - @pre The build itself should be valid. No checking of validity of pieces is done here. - */ - initBuildStats(){ - - let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"]; - - //Create a map of this build's stats - let statMap = new Map(); - - for (const staticID of staticIDs) { - statMap.set(staticID, 0); - } - statMap.set("hp", levelToHPBase(this.level)); - - let major_ids = new Set(); - for (const item of this.items){ - for (let [id, value] of item.get("maxRolls")) { - statMap.set(id,(statMap.get(id) || 0)+value); - } - for (const staticID of staticIDs) { - if (item.get(staticID)) { - statMap.set(staticID, statMap.get(staticID) + item.get(staticID)); - } - } - if (item.get("majorIds")) { - for (const majorID of item.get("majorIds")) { - major_ids.add(majorID); - } - } - } - statMap.set("activeMajorIDs", major_ids); - for (const [setName, count] of this.activeSetCounts) { - const bonus = sets[setName].bonuses[count-1]; - for (const id in bonus) { - if (skp_order.includes(id)) { - // pass. Don't include skillpoints in ids - } - else { - statMap.set(id,(statMap.get(id) || 0)+bonus[id]); - } - } - } - statMap.set("poisonPct", 100); - - // The stuff relevant for damage calculation!!! @ferricles - statMap.set("atkSpd", this.weapon.get("atkSpd")); - - for (const x of skp_elements) { - this.externalStats.set(x + "DamPct", 0); - } - this.externalStats.set("mdPct", 0); - this.externalStats.set("sdPct", 0); - this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]); - this.externalStats.set("defBonus",[0, 0, 0, 0, 0]); - this.externalStats.set("poisonPct", 0); - this.statMap = statMap; - - this.aggregateStats(); - } - - aggregateStats() { - let statMap = this.statMap; - statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]); - statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]); - statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]); - statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]); - statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type"))); - } -} diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 2601830..86aabd5 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -202,15 +202,15 @@ function shareBuild(build) { if (build) { let text = url_base+location.hash+"\n"+ "WynnBuilder build:\n"+ - "> "+build.helmet.statMap.get("displayName")+"\n"+ - "> "+build.chestplate.statMap.get("displayName")+"\n"+ - "> "+build.leggings.statMap.get("displayName")+"\n"+ - "> "+build.boots.statMap.get("displayName")+"\n"+ - "> "+build.ring1.statMap.get("displayName")+"\n"+ - "> "+build.ring2.statMap.get("displayName")+"\n"+ - "> "+build.bracelet.statMap.get("displayName")+"\n"+ - "> "+build.necklace.statMap.get("displayName")+"\n"+ - "> "+build.weapon.statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]"; + "> "+build.items[0].statMap.get("displayName")+"\n"+ + "> "+build.items[1].statMap.get("displayName")+"\n"+ + "> "+build.items[2].statMap.get("displayName")+"\n"+ + "> "+build.items[3].statMap.get("displayName")+"\n"+ + "> "+build.items[4].statMap.get("displayName")+"\n"+ + "> "+build.items[5].statMap.get("displayName")+"\n"+ + "> "+build.items[6].statMap.get("displayName")+"\n"+ + "> "+build.items[7].statMap.get("displayName")+"\n"+ + "> "+build.items[8].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]"; copyTextToClipboard(text); document.getElementById("share-button").textContent = "Copied!"; } diff --git a/js/builder.js b/js/builder.js index ec4f93f..34f7eab 100644 --- a/js/builder.js +++ b/js/builder.js @@ -96,18 +96,14 @@ function resetFields(){ } } - const nodes_to_reset = item_nodes.concat(powder_nodes.concat(edit_input_nodes)); + const nodes_to_reset = item_nodes.concat(powder_nodes).concat(edit_input_nodes).concat([powder_special_input, boosts_node]); for (const node of nodes_to_reset) { node.mark_dirty(); } - powder_special_input.mark_dirty(); - boosts_node.mark_dirty(); for (const node of nodes_to_reset) { node.update(); } - powder_special_input.update(); - boosts_node.update(); setValue("level-choice", "106"); location.hash = ""; diff --git a/js/builder_graph.js b/js/builder_graph.js index 688615a..dd2a66e 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -206,6 +206,11 @@ class ItemInputDisplayNode extends ComputeNode { 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'); + if (this.health_field) { + // Doesn't exist for weapons. + this.health_field.textContent = "0"; + } + this.level_field.textContent = "0"; if (!item) { this.input_field.classList.add("is-invalid"); return null; @@ -443,7 +448,7 @@ function getDefenseStats(stats) { defenseStats.push(totalHp); //EHP let ehp = [totalHp, totalHp]; - let defMult = (2 - stats.get("classDef")) * (1 - stats.get("defBonus")); + let defMult = (2 - stats.get("classDef")) * (2 - stats.get("defMultiplier")); ehp[0] /= (1-def_pct)*(1-agi_pct)*defMult; ehp[1] /= (1-def_pct)*defMult; defenseStats.push(ehp); @@ -725,33 +730,47 @@ class DisplayBuildWarningsNode extends ComputeNode { } /** - * Aggregate stats from the build and from inputs. + * Aggregate stats from all inputs (merges statmaps). * - * Signature: AggregateStatsNode(build: Build, *args) => StatMap + * Signature: AggregateStatsNode(*args) => StatMap */ class AggregateStatsNode extends ComputeNode { constructor() { super("builder-aggregate-stats"); } + 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)) { + output_stats.set(k2, v2 + output_stats.get(k2)); + } + 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"); } + compute_func(input_map) { const build = input_map.get('build'); input_map.delete('build'); - const powder_boost = input_map.get('powder-boost'); input_map.delete('powder-boost'); - const potion_boost = input_map.get('potion-boost'); input_map.delete('potion-boost'); const weapon = input_map.get('weapon'); input_map.delete('weapon'); const output_stats = new Map(build.statMap); - output_stats.set("damageMultiplier", 1 + potion_boost[0]); - output_stats.set("defBonus", potion_boost[1]); + output_stats.set("damageMultiplier", 1); + output_stats.set("defMultiplier", 1); for (const [k, v] of input_map.entries()) { output_stats.set(k, v); } - for (const [k, v] of powder_boost.entries()) { - if (output_stats.has(k)) { - output_stats.set(k, v + output_stats.get(k)); - } - else { - output_stats.set(k, v); - } - } output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type"))); return output_stats; @@ -919,13 +938,14 @@ function builder_graph_init() { // 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) let stat_agg_node = new AggregateStatsNode(); - stat_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); + let edit_agg_node = new AggregateEditableIDNode(); + edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); for (const field of editable_item_fields) { // Create nodes that listens to each editable id input, the node name should match the "id" const elem = document.getElementById(field); const node = new SumNumberInputNode('builder-'+field+'-input', elem); - stat_agg_node.link_to(node, field); + edit_agg_node.link_to(node, field); edit_input_nodes.push(node); } // Edit IDs setter declared up here to set ids so they will be populated by default. @@ -936,11 +956,12 @@ function builder_graph_init() { const elem = document.getElementById(skp+'-skp'); const node = new SumNumberInputNode('builder-'+skp+'-input', elem); - stat_agg_node.link_to(node, skp); + edit_agg_node.link_to(node, skp); build_encode_node.link_to(node, skp); build_warnings_node.link_to(node, skp); edit_input_nodes.push(node); } + stat_agg_node.link_to(edit_agg_node); build_disp_node.link_to(stat_agg_node, 'stats'); for (const input_node of item_nodes.concat(powder_nodes)) { diff --git a/js/craft.js b/js/craft.js index 1d3ae41..db30aef 100644 --- a/js/craft.js +++ b/js/craft.js @@ -170,7 +170,6 @@ class Craft{ statMap.set(e + "Dam", "0-0"); statMap.set(e + "DamLow", "0-0"); } - //statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]); statMap.set("category","weapon"); statMap.set("atkSpd",this.atkSpd); } @@ -381,12 +380,10 @@ class Craft{ statMap.set("reqs",[0,0,0,0,0]); statMap.set("skillpoints", [0,0,0,0,0]); - statMap.set("damageBonus",[0,0,0,0,0]); for (const e in skp_order) { statMap.set(skp_order[e], statMap.get("maxRolls").has(skp_order[e]) ? statMap.get("maxRolls").get(skp_order[e]) : 0); statMap.get("skillpoints")[e] = statMap.get("maxRolls").has(skp_order[e]) ? statMap.get("maxRolls").get(skp_order[e]) : 0; statMap.get("reqs")[e] = statMap.has(skp_order[e]+"Req") && !consumableTypes.includes(statMap.get("type"))? statMap.get(skp_order[e]+"Req") : 0; - statMap.get("damageBonus")[e] = statMap.has(skp_order[e]+"DamPct") ? statMap.get(skp_order[e]+"DamPct") : 0; } for (const id of rolledIDs) { if (statMap.get("minRolls").has(id)) { diff --git a/js/display.js b/js/display.js index 61c57f0..9ecb378 100644 --- a/js/display.js +++ b/js/display.js @@ -182,7 +182,6 @@ function displayExpandedItem(item, parent_id){ stats.set("wDamPct", 0); stats.set("fDamPct", 0); stats.set("aDamPct", 0); - stats.set("damageBonus", [0, 0, 0, 0, 0]); //SUPER JANK @HPP PLS FIX let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; @@ -1293,9 +1292,6 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){ statsTable.appendChild(hpRow); } - let defMult = statMap.get("defMult"); - if (!defMult) {defMult = 1} - //EHP let ehpRow = document.createElement("div"); ehpRow.classList.add("row"); @@ -1405,8 +1401,8 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){ boost.classList.add("col"); boost.classList.add("text-end"); - let defRaw = statMap.get("defRaw")[i]; - let defPct = statMap.get("defBonus")[i]/100; + let defRaw = statMap.get(skp_elements[i]+"Def"); + let defPct = statMap.get(skp_elements[i]+"DefPct")/100; if (defRaw < 0) { defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct; } else { From e4466d80952bd7d1b037d95bfe1a9dc9f0b1a216 Mon Sep 17 00:00:00 2001 From: hppeng Date: Tue, 21 Jun 2022 22:53:04 -0700 Subject: [PATCH 040/155] Add back morph easter egg --- js/builder_graph.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/js/builder_graph.js b/js/builder_graph.js index dd2a66e..9528be9 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -178,6 +178,24 @@ class ItemInputNode extends InputNode { return item; } } + 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); } + + // NOTE: DO NOT REORDER FOR PERFORMANCE REASONS + for (const node of item_nodes) { node.mark_dirty(); } + for (const node of item_nodes) { node.update(); } + } return null; } } From 9227dd0f56ab1672daa198b54fba8a66c6d0d4b1 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 01:25:19 -0700 Subject: [PATCH 041/155] Working armor special sliders (don't reset properly i think) --- builder/index.html | 20 ++++---- js/builder_graph.js | 105 +++++++++++++++++++++++++--------------- js/computation_graph.js | 2 + js/display_constants.js | 21 +++++--- 4 files changed, 94 insertions(+), 54 deletions(-) diff --git a/builder/index.html b/builder/index.html index 7b4f294..c30e554 100644 --- a/builder/index.html +++ b/builder/index.html @@ -944,27 +944,27 @@
-
-
-
-
-
@@ -1031,7 +1031,7 @@ Rage (Passive)
- +
@@ -1075,7 +1075,7 @@ Kill Streak (Passive)
- +
@@ -1119,7 +1119,7 @@ Concentration (Passive)
- +
@@ -1163,7 +1163,7 @@ Endurance (Passive)
- +
@@ -1207,7 +1207,7 @@ Dodge (Passive)
- +
diff --git a/js/builder_graph.js b/js/builder_graph.js index 9528be9..ed0ac00 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -1,18 +1,42 @@ -let boosts_node; -/* Updates all spell boosts -*/ -function updateBoosts(buttonId) { - let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { - elem.classList.remove("toggleOn"); - } else { - elem.classList.add("toggleOn"); +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; } - boosts_node.mark_dirty(); - boosts_node.update(); +})().update(); + +/* Updates PASSIVE powder special boosts (armors) +*/ +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(); } -class BoostsInputNode extends ComputeNode { + +let boosts_node = new (class extends ComputeNode { constructor() { super('builder-boost-input'); } compute_func(input_map) { @@ -28,14 +52,40 @@ class BoostsInputNode extends ComputeNode { } return [damage_boost, def_boost]; } +})().update(); + +/* Updates all spell boosts +*/ +function update_boosts(buttonId) { + let elem = document.getElementById(buttonId); + if (elem.classList.contains("toggleOn")) { + elem.classList.remove("toggleOn"); + } else { + elem.classList.add("toggleOn"); + } + boosts_node.mark_dirty().update(); } -let powder_special_input; let specialNames = ["Quake", "Chain Lightning", "Curse", "Courage", "Wind Prison"]; +let powder_special_input = new (class extends ComputeNode { + 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; + } +})(); function updatePowderSpecials(buttonId) { - //console.log(player_build.statMap); - let name = (buttonId).split("-")[0]; let power = (buttonId).split("-")[1]; // [1, 5] @@ -53,26 +103,7 @@ function updatePowderSpecials(buttonId) { elem.classList.add("toggleOn"); } - powder_special_input.mark_dirty(); - powder_special_input.update(); -} - -class PowderSpecialInputNode extends ComputeNode { - 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; - } + powder_special_input.mark_dirty().update(); } class PowderSpecialCalcNode extends ComputeNode { @@ -988,17 +1019,15 @@ function builder_graph_init() { level_input.update(); // Powder specials. - powder_special_input = new PowderSpecialInputNode(); 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'); + stat_agg_node.link_to(armor_powder_node, 'armor-powder'); powder_special_input.update(); // Potion boost. - boosts_node = new BoostsInputNode(); stat_agg_node.link_to(boosts_node, 'potion-boost'); - boosts_node.update(); // Also do something similar for skill points diff --git a/js/computation_graph.js b/js/computation_graph.js index f99a966..160c802 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -37,6 +37,7 @@ class ComputeNode { for (const child of this.children) { child.mark_input_clean(this.name, this.value); } + return this; } /** @@ -69,6 +70,7 @@ class ComputeNode { child.mark_dirty(); } } + return this; } /** diff --git a/js/display_constants.js b/js/display_constants.js index 4901e88..3ad22fb 100644 --- a/js/display_constants.js +++ b/js/display_constants.js @@ -212,20 +212,21 @@ let posModSuffixes = { let build_all_display_commands = [ "#defense-stats", "str", "dex", "int", "def", "agi", + "!spacer", "mr", "ms", "hprRaw", "hprPct", + "ls", "sdRaw", "sdPct", "mdRaw", "mdPct", - "ref", "thorns", - "ls", - "poison", - "expd", - "spd", - "atkTier", "!elemental", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "!elemental", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", + "atkTier", + "poison", + "ref", "thorns", + "expd", + "spd", "rainbowRaw", "sprint", "sprintReg", "jh", @@ -325,3 +326,11 @@ let sq2_ing_display_order = [ "lvl", "skills", ] + +let elem_colors = [ + "#00AA00", + "#FFFF55", + "#55FFFF", + "#FF5555", + "#FFFFFF" +] From 0d702149068c95c4ef3edc46b01279346104cebc Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 23 Jun 2022 16:15:06 +0700 Subject: [PATCH 042/155] implement auto connectors --- js/sq2bs.js | 222 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 154 insertions(+), 68 deletions(-) diff --git a/js/sq2bs.js b/js/sq2bs.js index 41612bd..9a21daf 100644 --- a/js/sq2bs.js +++ b/js/sq2bs.js @@ -546,7 +546,7 @@ function init_autocomplete() { } } -// atree parsing +// placeholder name, follow new schema function construct_AT(elem, tree) { console.log("constructing ability tree UI"); document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later @@ -562,8 +562,8 @@ function construct_AT(elem, tree) { let node = tree[i]; // create rows if not exist - if (document.getElementById("atree-row-" + node.row) == null) { - for (let j = 0; j <= node.row; j++) { + if (document.getElementById("atree-row-" + node.display.row) == null) { + for (let j = 0; j <= node.display.row; j++) { if (document.getElementById("atree-row-" + j) == null) { let row = document.createElement('div'); row.classList.add("row"); @@ -585,77 +585,163 @@ function construct_AT(elem, tree) { }; }; + let connector_list = [] + // create connectors based on parent location + for (let parent of node.parents) { + let parent_node = tree.find(object => { + return object.display_name === parent; + }); + + let connect_elem = document.createElement("div"); + connect_elem.style = "background-size: cover; width: 100%; height: 100%;"; + // connect up + for (let i = node.display.row - 1; i > parent_node.display.row; i--) { + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; + connector.id = "r" + i + "-c" + node.display.col + "-line" + document.getElementById("atree-row-" + i).children[node.display.col].appendChild(connector); + resolve_connector(document.getElementById("atree-row-" + i).children[node.display.col]); + } + // connect left + for (let i = parent_node.display.col + 1; i < node.display.col; i++) { + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; + connector.classList.add("rotate-90"); + connector.id = "r" + parent_node.display.row + "-c" + i + "-line" + document.getElementById("atree-row-" + parent_node.display.row).children[i].appendChild(connector); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i]); + } + + // connect right + for (let i = parent_node.display.col - 1; i > node.display.col; i--) { + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; + connector.classList.add("rotate-90"); + connector.id = "r" + parent_node.display.row + "-c" + i + "-line" + document.getElementById("atree-row-" + parent_node.display.row).children[i].appendChild(connector); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i]); + } + + // connect corners + if (parent_node.display.col > node.display.col && (parent_node.display.row != node.display.row)) { + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; + connector.classList.add("rotate-180"); + connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" + document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); + } + + if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; + connector.classList.add("rotate-270"); + connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" + document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); + } + } + // create node let node_elem = document.createElement('div') - node_elem.style = "background-image: url('" + node.image + "'); background-size: cover; width: 100%; height: 100%;"; - - if (node.connector && node.rotate != 0) { - node_elem.classList.add("rotate-" + node.rotate); - }; + node_elem.style = "background-image: url('../media/atree/node.png'); background-size: cover; width: 100%; height: 100%;"; // add tooltip - if (!node.connector) { - node_elem.addEventListener('mouseover', function(e) { - if (e.target !== this) {return;} - let tooltip = this.children[0]; - tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px"; - tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px"; - tooltip.style.display = "block"; - }); + node_elem.addEventListener('mouseover', function(e) { + if (e.target !== this) {return;} + let tooltip = this.children[0]; + tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px"; + tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px"; + tooltip.style.display = "block"; + }); - node_elem.addEventListener('mouseout', function(e) { - if (e.target !== this) {return;} - let tooltip = this.children[0]; + node_elem.addEventListener('mouseout', function(e) { + if (e.target !== this) {return;} + let tooltip = this.children[0]; + tooltip.style.display = "none"; + }); + + node_elem.classList.add("fake-button"); + + let active_tooltip = document.createElement('div'); + active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto"); + //was causing active element boxes to be 0 width + // active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; + active_tooltip.style.display = "none"; + + // tooltip text formatting + + let active_tooltip_title = document.createElement('b'); + active_tooltip_title.classList.add("scaled-font"); + active_tooltip_title.innerHTML = node.display_name; + + let active_tooltip_text = document.createElement('p'); + active_tooltip_text.classList.add("scaled-font-sm"); + active_tooltip_text.textContent = node.desc; + + active_tooltip.appendChild(active_tooltip_title); + active_tooltip.appendChild(active_tooltip_text); + + node_tooltip = active_tooltip.cloneNode(true); + + active_tooltip.id = "atree-ab-" + node.display_name.replaceAll(" ", ""); + + node_tooltip.style.position = "absolute"; + node_tooltip.style.zIndex = "100"; + + node_elem.appendChild(node_tooltip); + document.getElementById("atree-active").appendChild(active_tooltip); + + node_elem.addEventListener('click', function(e) { + if (e.target !== this) {return;} + let tooltip = document.getElementById("atree-ab-" + node.display_name.replaceAll(" ", "")); + if (tooltip.style.display == "block") { tooltip.style.display = "none"; - }); + this.classList.remove("atree-selected"); + this.style.backgroundImage = 'url("../media/atree/node.png")'; + } + else { + tooltip.style.display = "block"; + this.classList.add("atree-selected"); + this.style.backgroundImage = 'url("../media/atree/node-selected.png")'; + } - node_elem.classList.add("fake-button"); - - let active_tooltip = document.createElement('div'); - active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto"); - //was causing active element boxes to be 0 width - // active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; - active_tooltip.style.display = "none"; - - // tooltip text formatting - - let active_tooltip_title = document.createElement('b'); - active_tooltip_title.classList.add("scaled-font"); - active_tooltip_title.innerHTML = node.title.replaceAll("\n", "
"); - - let active_tooltip_text = document.createElement('p'); - active_tooltip_text.classList.add("scaled-font-sm"); - active_tooltip_text.textContent = node.desc; - - active_tooltip.appendChild(active_tooltip_title); - active_tooltip.appendChild(active_tooltip_text); - - node_tooltip = active_tooltip.cloneNode(true); - - active_tooltip.id = "atree-ab-" + node.title.replaceAll(" ", ""); - - node_tooltip.style.position = "absolute"; - node_tooltip.style.zIndex = "100"; - - node_elem.appendChild(node_tooltip); - document.getElementById("atree-active").appendChild(active_tooltip); - - node_elem.addEventListener('click', function(e) { - if (e.target !== this) {return;} - let tooltip = document.getElementById("atree-ab-" + node.title.replaceAll(" ", "")); - if (tooltip.style.display == "block") { - tooltip.style.display = "none"; - this.classList.remove("atree-selected"); - this.style.backgroundImage = 'url("../media/atree/node.png")'; - } - else { - tooltip.style.display = "block"; - this.classList.add("atree-selected"); - this.style.backgroundImage = 'url("../media/atree/node-selected.png")'; - } - }); - }; - - document.getElementById("atree-row-" + node.row).children[node.col].appendChild(node_elem); + toggle_connectors(connector_list); + }); + document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; }; + +// resolve connector conflict +function resolve_connector(elem) { + if (elem.children.length < 2) {return false;} + let line = 0; + let angle = 0; + let t = 0 + for (let child of elem.children) { + let type = child.id.split("-")[2] + if (type == "line") { + line += 1; + } else if (type == "angle") { + angle += 1; + } else if (type == "t") { + t += 1; + } + } + + let connect_elem = document.createElement("div"); + + if ((line == angle) || (angle == 1 && t == 1)) { + connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;" + connect_elem.classList.add("rotate-180") + connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-t" + elem.replaceChildren(connect_elem); + } + if (line > 1 && angle == 0) { + elem.replaceChildren(elem.children[0]) + } + if (t == 1 && line == 1) { + connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;" + elem.replaceChildren(connect_elem); + } +} From 3939f83ffc747f63fce5d5592b80288ef16bdf90 Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 23 Jun 2022 16:15:51 +0700 Subject: [PATCH 043/155] partially change to new format --- js/atree_constants.js | 4237 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 3991 insertions(+), 246 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 31c22eb..41cfb16 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -1,185 +1,1993 @@ const atrees = { "Archer": [ - {"title": "Arrow Bomb", "desc": "Throw a long-ranged arrow that explodes and deal high damage in a large area (self-damage for 30% of DPS) Mana cost: 50 Total damage: 180% (of DPS) - Neutral: 160% - Fire: 20% Range: 26 blocks AoE: 2.5 blocks (circular)", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 1, "col": 4, "rotate": 0}, - {"title": "Bow Proficiency", "desc": "Improve Main Attack damage and range w/ bow Main attack damage: +5% Main attack range: +6 blocks AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 2, "col": 5, "rotate": 90}, - {"title": "Cheaper Arrow Bomb", "desc": "Reduce Mana cost of Arrow Bomb Mana cost: -10 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 3, "col": 4, "rotate": 0}, - {"title": "Heart Shatter", "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage Total damage: +100% (of DPS) - Neutral: 100% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 5, "col": 4, "rotate": 0}, - {"title": "Escape", "desc": "Throw yourself backward to avoid danger (hold shift to cancel) Mana cost: 25 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 3, "rotate": 90}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 6, "col": 5, "rotate": 90}, - {"title": "Double Shots Ability\nBoltslinger Archetype", "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies) Blocks: - Power Shots AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, - {"title": "Power Shots Ability\nSharpshooter Archetype", "desc": "Main Attack arrows have increased speed and knockback Blocks: - Double Shots AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 6, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 7, "col": 2, "rotate": 0}, - {"title": "Arrow Storm", "desc": "Shoots 2 streams of 8 arrows, dealing damage to close mobs and pushing them back Mana cost: 40 Total damage: 40% (of DPS per arrow) - Neutral: 30% - Thunder: 10% Range: 16 blocks AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, - {"title": "Cheaper Escape", "desc": "Reduce mana cost of Escape Mana cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, - {"title": "Arrow Shield", "desc": "Create a shield around you that deal damage and knockback mobs when triggered (2 charges) Mana cost: 30 Total damage: 100% (of DPS) - Neutral: 90% - Air: 10% Duration: 60s AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 3, "rotate": 90}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 5, "rotate": 90}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 8, "col": 1, "rotate": 180}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 0, "rotate": 180}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 8, "col": 7, "rotate": 90}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 8, "col": 8, "rotate": 270}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 0, "rotate": 0}, - {"title": "Windy Feet Ability\nBoltslinger Archetype", "desc": "When casting Escape, give speed to yourself and nearby allies Effect: +20% Walk Speed Duration: 120s AoE: 8 blocks (circular) AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 9, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 2, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 6, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 9, "col": 8, "rotate": 0}, - {"title": "Air Mastery Ability\nBoltslinger Archetype", "desc": "Increases base damage from all Air attacks Air Damage: +3-4 Air Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, - {"title": "Thunder Mastery Ability\nBoltslinger Archetype", "desc": "Increases base damage from all Thunder attacks Thunder Damage: +1-8 Thunder Damage: +10% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 10, "col": 3, "rotate": 90}, + { + "display_name": "Arrow Shield", + "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", + "archetype": "", + "archetype_req": 0, + "parents": ["Power Shots", "Cheaper Escape"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 6 + }, + "properties": { + "duration": 60 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Shield", + "cost": 30, + "display_text": "Max Damage", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "", + "parts": [ + { + "name": "Shield Damage", + "type": "damage", + "multipliers": [90, 0, 0, 0, 0, 10] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Shield Damage": 2 + } + } + ] + } + ] + }, + + { + "display_name": "Escape", + "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", + "archetype": "", + "archetype_req": 0, + "parents": ["Heart Shatter"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 7, + "col": 4 + }, + "properties": { + "aoe": 0, + "range": 0 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Escape", + "cost": 25, + "display_text": "Max Damage", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [0, 0, 0, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] + } + ] + }, + { + "display_name": "Arrow Bomb", + "desc": "Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)", + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 0, + "col": 4 + }, + "properties": { + "aoe": 4.5, + "range": 26 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Bomb", + "cost": 50, + "display_text": "Average Damage", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Arrow Bomb", + "type": "damage", + "multipliers": [160, 0, 0, 0, 20, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Arrow Bomb": 1 + } + } + ] + } + ] + }, + { + "display_name": "Heart Shatter", + "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.", + "archetype": "", + "archetype_req": 0, + "parents": ["Bow Proficiency I"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 4, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + }, + { - {"image": "../media/atree/connect_c.png", "connector": true, "row": 10, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 10, "col": 5, "rotate": 90}, + } + ] + }, + { + "display_name": "Fire Creep", + "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", + "archetype": "", + "archetype_req": 0, + "parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 6 + }, + "properties": { + "aoe": 0.8, + "duration": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [30, 0, 0, 0, 20, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fire Creep": 15 + } + } + ] + }, + { + "display_name": "Bryophyte Roots", + "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.", + "archetype": "Trapper", + "archetype_req": 1, + "parents": ["Fire Creep", "Earth Mastery"], + "dependencies": ["Arrow Storm"], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 8 + }, + "properties": { + "aoe": 2, + "duration": 5, + "slowness": 0.4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Bryophyte Roots", + "cost": 0, + "multipliers": [40, 20, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Nimble String", + "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", + "archetype": "", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Arrow Rain"], + "dependencies": ["Arrow Storm"], + "blockers": ["Phantom Ray"], + "cost": 2, + "display": { + "row": 15, + "col": 2 + }, + "properties": { + "shootspeed": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [-15, 0, 0, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Stream", + "cost": 0, + "hits": { + "Single Arrow": 8 + } + } + ] + }, + { + "display_name": "Arrow Storm", + "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", + "archetype": "", + "archetype_req": 0, + "parents": ["Double Shots", "Cheaper Escape"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 2 + }, + "properties": { + "aoe": 0, + "range": 16 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Storm", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [30, 0, 10, 0, 0, 0] + }, + { + "name": "Single Stream", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Stream": 2 + } + } + ] + } + ] + }, + { + "display_name": "Guardian Angels", + "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)", + "archetype": "Boltslinger", + "archetype_req": 3, + "parents": ["Triple Shots", "Frenzy"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 1 + }, + "properties": { + "range": 4, + "duration": 60, + "shots": 8, + "count": 2 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Guardian Angels", + "cost": 30, + "display_text": "Total Damage Average", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [40, 0, 0, 0, 0, 20] + }, + { + "name": "Single Bow", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Bow": 2 + } + } + ] + } + ] + }, + { + "display_name": "Windy Feet", + "base_abil": "Escape", + "desc": "When casting Escape, give speed to yourself and nearby allies.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Storm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 1 + }, + "properties": { + "aoe": 8, + "duration": 120 + }, + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spd", + "value": 20 + } + ] + }, + { + "display_name": "Basaltic Trap", + "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)", + "archetype": "Trapper", + "archetype_req": 2, + "parents": ["Bryophyte Roots"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 8 + }, + "properties": { + "aoe": 7, + "traps": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [140, 30, 0, 0, 30, 0] + } + ] + }, + { + "display_name": "Windstorm", + "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", + "archetype": "", + "archetype_req": 0, + "parents": ["Guardian Angels"], + "dependencies": [], + "blockers": ["Phantom Ray"], + "cost": 2, + "display": { + "row": 21, + "col": 1 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [-11, 0, -7, 0, 0, 3] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 1 + } + } + ] + }, + { + "display_name": "Grappling Hook", + "base_abil": "Escape", + "desc": "When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Focus", "More Shields", "Cheaper Arrow Storm"], + "dependencies": [], + "blockers": ["Escape Artist"], + "cost": 2, + "display": { + "row": 21, + "col": 5 + }, + "properties": { + "range": 20 + }, + "effects": [ + ] + }, + { + "display_name": "Implosion", + "desc": "Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grappling Hook", "More Shields"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 6 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Twain's Arc", + "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", + "archetype": "Sharpshooter", + "archetype_req": 4, + "parents": ["More Focus"], + "dependencies": ["Focus"], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 4 + }, + "properties": { + "range": 64, + "focusReq": 2 + }, + "effects": [ - {"title": "Fire Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Fire attacks Fire Damage: +3-5 Fire Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, - {"title": "Earth Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Earth attacks Earth Damage: +2-4 Earth Damage: +20% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 0, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 2, "rotate": 0}, - {"title": "Water Mastery Ability\nSharpshooter Archetype", "desc": "Increases base damage from all Water attacks Water Damage: +2-4 Water Damage: +15% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 6, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 11, "col": 8, "rotate": 0}, - {"title": "Arrow Rain", "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies Total Damage: 200% (of DPS per arrow) - Neutral: 120% - Air: 80% AP: 2 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 12, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 1, "rotate": 90}, - {"title": "Nimble String", "desc": "Arrow Storm throws out +8 arrows per stream and shoot twice as fast Total Damage: -15% (of DPS per arrow) - Neutral: -15% Blocks: - Phantom Ray AP: 2 Req: Arrow Storm", "image": "../media/atree/node.png", "connector": false, "row": 12, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 6, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 12, "col": 8, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 0, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 2, "rotate": 0}, - {"title": "Phantom Ray", "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second Mana cost: -10 Total Damage: 30% (of DPS per hit) - Neutral: 25% - Water: 5% Range: 12 blocks Blocks: - Windstorm - Nimble String - Arrow Hurricane AP: 2 Req: Arrow Storm Min Sharpshooter: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 5, "rotate": 90}, - {"title": "Fire Creep\nSharpshooter Archetype", "desc": "Arrow Bomb will leak a trail of fire for 6s, damaging enemies that walk into it every 0.4s Total Damage: 50% (of DPS) - Neutral: 30% - Fire: 20% Duration: 6s AoE: 2 blocks (linear) AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 13, "col": 7, "rotate": 90}, - {"title": "Bryophyte Roots\nTrapper Archetype", "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s Total Damage: 60% (of DPS) - Neutral: 40% - Earth: 20% Effect: 40% Slowness to Enemies Duration: 5s AoE: 2 blocks (circular) AP: 2 Req: Arrow Storm Min Trapper: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 8}, - {"title": "Triple Shot\nBoltslinger Archetype", "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow AP: 1 Req: Double Shots", "image": "../media/atree/node.png", "connector": false, "row": 14, "col": 0}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 14, "col": 1, "rotate": 180}, - {"title": "Frenzy\nBoltslinger Archetype", "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 14, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 14, "col": 8, "rotate": 0}, - {"title": "Guardian Angels Ability", "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies (Arrow Shield will no longer push nearby enemies) Total Damage: 60% (of DPS per arrow) - Neutral: 40% - Air: 20% Range: 4 Blocks Duration: 60s AP: 2 Req: Arrow Shield Min Boltslinger: 0/3", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 3, "rotate": 180}, - {"title": "Focus Ability\nSharpshooter Archetype", "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once Damage Bonus: +35% (per focus) AP: 2 Min Sharpshooter: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 5, "rotate": 270}, - {"title": "Basaltic Trap Ability\nTrapper Archetype", "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies (Max 2 Traps) Total Damage: 200% (of DPS) - Neutral: 140% - Earth: 30% - Fire: 30% AoE: 7 Blocks (Circular) AP: 2 Min Trapper: 0/1", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 8}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 15, "col": 7, "rotate": 180}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 0, "rotate": 180}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 16, "col": 2, "rotate": 90}, - {"title": "Cheaper Arrow Storm", "desc": "Reduces the Mana cost of Arrow Storm. Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 3}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 4, "rotate": 180}, - {"title": "Grappling Hook Ability\nTrapper Archetype", "desc": "When casting Escape, you throw a hook that pulls you when hitting a block. If you hit a mob, pull them towards you instead. (Escape will not throw you backward anymore) Range: 20 blocks Blocks: - Escape Artist AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 16, "col": 6, "rotate": 180}, - {"title": "More Shields Ability", "desc": "Give +2 charges to Arrow Shield AP: 1 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 16, "col": 8, "rotate": 270}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 0, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 1, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 4, "rotate": 0}, - {"title": "Implosion Ability\nTrapper Archetype", "desc": "Arrow Bomb will pull enemies towards you. If a Trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage Total Damage: +40% (of DPS) - Neutral: +40% AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 17, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 17, "col": 7, "rotate": 0}, - {"title": "Patient Hunter Ability\nTrapper Archetype", "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%) AP: 2 Req: Basaltic Trap", "image": "../media/atree/node.png", "connector": false, "row": 17, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 0, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 1}, - {"title": "More Focus Ability\nSharpshooter Archetype", "desc": "Add +2 max Focus Damage Bonus: -5% (per focus) AP: 1 Req: Focus", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 18, "col": 7, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 0, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 19, "col": 7, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 1, "rotate": 180}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 20, "col": 3, "rotate": 90}, - {"title": "Twain's Arc Ability\nSharpshooter Archetype", "desc": "If you have 2+ Focus, holding shift will summon Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage) Total Damage: 200% (of DPS) - Neutral: 200% Range: 64 blocks AP: 2 Min Sharpshooter: 0/4 Req: Focus", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 20, "col": 5, "rotate": 180}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 6, "rotate": 270}, - {"title": "Bouncing Bomb Ability", "desc": "Arrow Bomb will bounce once when hitting a block or mob AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 20, "col": 8, "rotate": 270}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 0, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 4, "rotate": 0}, - {"title": "Scorched Earth Ability\nSharpshooter Archetype", "desc": "Fire Creep becomes much stronger Total Damage: +15% (of DPS) - Neutral: +10% - Fire: +5% Duration: 2s AoE: +0.4 Blocks (linear) AP: 1 Req: Fire Creep", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 21, "col": 6, "rotate": 0}, - {"title": "More Traps Ability\nTrapper Archetype", "desc": "Increase the maximum amount of active Traps you can have by +2 AP: 1 Req: Basaltic Trap", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 0, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 6, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 22, "col": 8, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 0}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 1, "rotate": 180}, - {"title": "Homing Shots Ability", "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 3, "rotate": 90}, - {"title": "Shocking Bomb Ability\nSharpshooter Archetype", "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder AP: 2 Min Sharpshooter: 0/5 Req: Arrow Bomb", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 23, "col": 5, "rotate": 180}, - {"title": "Better Arrow Shield Ability", "desc": "Arrow Shield will gain additonal AoE, knockback, and damage Total Damage: +40% (of DPS) - Neutral: +40% AoE: +1 Blocks (Circular) AP: 1 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 23, "col": 7, "rotate": 90}, - {"title": "Mana Trap Ability\nTrapper Archetype", "desc": "Your Traps will give you 4 Mana per second when you stay close to them Mana Cost: +10 Range: 12 Blocks AP: 2 Min Trapper: 0/5", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 0, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 2, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 5, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 24, "col": 8, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 1, "rotate": 90}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 2}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 25, "col": 4, "rotate": 180}, - {"title": "Initiator Ability\nSharpshooter Archetype", "desc": "If you do not damage an enemy for 5s for more, your next successful hit will deal +50% damage and add +1 Focus AP: 2 Req: Focus Min Sharpshooter: 0/5", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 25, "col": 6, "rotate": 90}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 25, "col": 7, "rotate": 180}, - {"title": "Cheaper Arrow Storm Ability", "desc": "Reduce the Mana cost of Arrow Storm Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 0, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 2, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 26, "col": 4, "rotate": 0}, - {"title": "Call of the Hound Ability\nTrapper Archetype", "desc": "Arrow Shield summons a Hound that will attack and drag aggressive mobs towards your traps Total Damage: 40% (of DPS) - Neutral: 40% AP: 2 Req: Arrow Shield", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 7}, - {"title": "Arrow Hurricane Ability\nBoltslinger Archetype", "desc": "Arrow Storm will shoot +2 stream of arrows Blocks: - Phantom Ray AP: 2 Min Boltslinger: 0/8", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, - {"image": "../media/atree/connect_t.png", "connector": true, "row": 27, "col": 1, "rotate": 180}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 3, "rotate": 90}, - {"title": "Cheaper Arrow Shield Ability", "desc": "Reduce the Mana cost of Arrow Shield Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 27, "col": 5, "rotate": 270}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 27, "col": 7, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 4, "rotate": 0}, - {"title": "Decimator Ability\nSharpshooter Archetype", "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%) AP: 2 Req: Phantom Ray", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 28, "col": 6, "rotate": 90}, - {"title": "Cheaper Escape Ability", "desc": "Reduce the Mana cost of Escape Mana Cost: -5 AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 28, "col": 8, "rotate": 270}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 1, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 29, "col": 7, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 8}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 0, "rotate": 180}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 2, "rotate": 270}, - {"title": "Crepuscular Ray Ability\nSharpshooter Archetype", "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus While in that state, you will lose 1 Focus per second Total Damage: 15% (of DPS per arrrow) - Neutral: 10% - Water: 5% AP: 2 Req: Arrow Storm Min Sharpshooter: 0/8", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 6, "rotate": 180}, - {"title": "Grape Bomb Ability", "desc": "Arrow Bomb will throw 3 additional smaller bombs when exploding Total Damage: 40% (of DPS) - Neutral: 30% - Fire: 10% AoE: 2 Blocks (Circular) AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 30, "col": 8, "rotate": 270}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 2, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 4, "rotate": 0}, - {"title": "Tangled Traps Ability\nTrapper Archetype", "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s Total Damage: 40% (of DPS) - Neutral: 20% - Air: 20% AP: 2", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 31, "col": 7, "rotate": 0}, - {"title": "Stringer Patient Hunter Ability\nTrapper Archetype", "desc": "Add +80% Max Damage to Patient Hunter AP: 1", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 2, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 4, "rotate": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 32, "col": 7, "rotate": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 1, "rotate": 180}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 3, "rotate": 90}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 4}, - {"image": "../media/atree/connect_angle.png", "connector": true, "row": 33, "col": 5, "rotate": 270}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 33, "col": 7, "rotate": 0}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 1}, - {"title": "text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "row": 34, "col": 6, "rotate": 90}, - {"title": "Minefield Ability\nTrapper Archetype", "desc": "Allow you to place +6 Traps, but with reduced damage and range Total Damage: -80% (of DPS) - Neutral: -80% AoE: -2 Blocks (Circular) AP: 2 Req: Basaltic Trap Min Trapper: 0/10", "image": "../media/atree/node.png", "connector": false, "row": 34, "col": 7}, + { + "type": "replace_spell", + "name": "Twain's Arc", + "cost": 0, + "display_text": "Twain's Arc", + "base_spell": 5, + "spell_type": "damage", + "scaling": "melee", + "display": "Twain's Arc Damage", + "parts": [ + { + "name": "Twain's Arc Damage", + "type": "damage", + "multipliers": [200, 0, 0, 0, 0, 0] + } + ] + } + ] + }, + { + "display_name": "Fierce Stomp", + "desc": "When using Escape, hold shift to quickly drop down and deal damage.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Refined Gunpowder", "Traveler"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 1 + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fierce Stomp": 1 + } + } + ] + }, + { + "display_name": "Scorched Earth", + "desc": "Fire Creep become much stronger.", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Twain's Arc"], + "dependencies": ["Fire Creep"], + "blockers": [], + "cost": 1, + "display": { + "row": 27, + "col": 5 + }, + "properties": { + "duration": 2, + "aoe": 0.4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [10, 0, 0, 0, 5, 0] + } + ] + }, + { + "display_name": "Leap", + "desc": "When you double tap jump, leap foward. (2s Cooldown)", + "archetype": "Boltslinger", + "archetype_req": 5, + "parents": ["Refined Gunpowder", "Homing Shots"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 0 + }, + "properties": { + "cooldown": 2 + }, + "effects": [ + + ] + }, + { + "display_name": "Shocking Bomb", + "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.", + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": ["Twain's Arc", "Better Arrow Shield", "Homing Shots"], + "dependencies": ["Arrow Bomb"], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 4 + }, + "properties": { + "gravity": 0 + }, + "effects": [ + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + } + ] + }, + { + "display_name": "Mana Trap", + "desc": "Your Traps will give you 4 Mana per second when you stay close to them.", + "archetype": "Trapper", + "archetype_req": 5, + "parents": ["More Traps", "Better Arrow Shield"], + "dependencies": ["Fire Creep"], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 8 + }, + "properties": { + "range": 12, + "manaRegen": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 10, + "multipliers": [0, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Escape Artist", + "desc": "When casting Escape, release 100 arrows towards the ground.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Better Guardian Angels", "Leap"], + "dependencies": [], + "blockers": ["Grappling Hook"], + "cost": 2, + "display": { + "row": 32, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Escape Artist", + "cost": 0, + "multipliers": [30, 0, 10, 0, 0, 0] + } + ] + }, + { + "display_name": "Initiator", + "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": ["Shocking Bomb", "Better Arrow Shield"], + "dependencies": ["Focus"], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 5 + }, + "properties": { + "focus": 1, + "timer": 5 + }, + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "damPct", + "value": 50 + } + ] + }, + { + "display_name": "Call of the Hound", + "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Initiator", "Cheaper Arrow Storm"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 2, + "display": { + "row": 33, + "col": 7 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Call of the Hound", + "cost": 0, + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Arrow Hurricane", + "desc": "Arrow Storm will shoot +2 stream of arrows.", + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": ["Precise Shot", "Escape Artist"], + "dependencies": [], + "blockers": ["Phantom Ray"], + "cost": 2, + "display": { + "row": 34, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 2 + } + } + ] + }, + { + "display_name": "Geyser Stomp", + "desc": "Fierce Stomp will create geysers, dealing more damage and vertical knockback.", + "archetype": "", + "archetype_req": 0, + "parents": ["Shrapnel Bomb"], + "dependencies": ["Fierce Stomp"], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 1 + }, + "properties": { + "aoe": 1 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [0, 0, 0, 50, 0, 0] + } + ] + }, + { + "display_name": "Crepuscular Ray", + "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.", + "archetype": "Sharpshooter", + "archetype_req": 10, + "parents": ["Cheaper Arrow Shield"], + "dependencies": ["Arrow Storm"], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 4 + }, + "properties": { + "focusReq": 5, + "focusRegen": -1 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Crepuscular Ray", + "base_spell": 5, + "spell_type": "damage", + "scaling": "spell", + "display": "One Focus", + "cost": 0, + + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [10, 0, 0, 5, 0, 0] + }, + { + "name": "One Focus", + "type": "total", + "hits": { + "Single Arrow": 20 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "One Focus": 7 + } + } + ] + } + ] + }, + { + "display_name": "Grape Bomb", + "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "More Shields"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 7 + }, + "properties": { + "miniBombs": 3, + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Grape Bomb", + "cost": 0, + "multipliers": [30, 0, 0, 0, 10, 0] + } + ] + }, + { + "display_name": "Tangled Traps", + "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grape Bomb"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 2, + "display": { + "row": 39, + "col": 6 + }, + "properties": { + "attackSpeed": 0.2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Tangled Traps", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 20] + } + ] + }, + { + "display_name": "Snow Storm", + "desc": "Enemies near you will be slowed down.", + "archetype": "", + "archetype_req": 0, + "parents": ["Geyser Stomp", "More Focus"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 40, + "col": 4 + }, + "properties": { + "range": 2.5, + "slowness": 0.3 + } + }, + { + "display_name": "All-Seeing Panoptes", + "desc": "Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.", + "archetype": "Boltslinger", + "archetype_req": 11, + "parents": ["Snow Storm"], + "dependencies": ["Guardian Angels"], + "blockers": [], + "cost": 2, + "display": { + "row": 41, + "col": 1 + }, + "properties": { + "range": 10, + "shots": 5 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [0, 0, 0, 0, 20, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 5 + } + } + ] + }, + { + "display_name": "Minefield", + "desc": "Allow you to place +6 Traps, but with reduced damage and range.", + "archetype": "Trapper", + "archetype_req": 10, + "parents": ["Grape Bomb", "Cheaper Arrow Bomb"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 2, + "display": { + "row": 41, + "col": 7 + }, + "properties": { + "aoe": -2, + "traps": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [-80, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Bow Proficiency I", + "desc": "Improve your Main Attack's damage and range when using a bow.", + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Bomb"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 4 + }, + "properties": { + "mainAtk_range": 6 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ] + }, + { + "display_name": "Cheaper Arrow Bomb", + "desc": "Reduce the Mana cost of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": ["Bow Proficiency I"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 6 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -10 + } + ] + }, + { + "display_name": "Cheaper Arrow Storm", + "desc": "Reduce the Mana cost of Arrow Storm.", + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "Windstorm", "Focus"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 21, + "col": 3 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ] + }, + { + "display_name": "Cheaper Escape", + "desc": "Reduce the Mana cost of Escape.", + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Storm", "Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ] + }, + { + "display_name": "Earth Mastery", + "desc": "Increases your base damage from all Earth attacks", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 8 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "eDamPct", + "value": 20 + }, + { + "type": "stat", + "name": "eDam", + "value": [2, 4] + } + ] + } + ] + }, + { + "display_name": "Thunder Mastery", + "desc": "Increases your base damage from all Thunder attacks", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Storm", "Fire Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "tDamPct", + "value": 10 + }, + { + "type": "stat", + "name": "tDam", + "value": [1, 8] + } + ] + } + ] + }, + { + "display_name": "Water Mastery", + "desc": "Increases your base damage from all Water attacks", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Escape", "Thunder Mastery", "Fire Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 14, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "wDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "wDam", + "value": [2, 4] + } + ] + } + ] + }, + { + "display_name": "Air Mastery", + "desc": "Increases base damage from all Air attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Arrow Storm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "aDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "aDam", + "value": [3, 4] + } + ] + } + ] + }, + { + "display_name": "Fire Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "fDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "fDam", + "value": [3, 5] + } + ] + } + ] + }, + { + "display_name": "More Shields", + "desc": "Give +2 charges to Arrow Shield.", + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "Basaltic Trap"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 1, + "display": { + "row": 21, + "col": 7 + }, + "properties": { + "shieldCharges": 2 + } + }, + { + "display_name": "Stormy Feet", + "desc": "Windy Feet will last longer and add more speed.", + "archetype": "", + "archetype_req": 0, + "parents": ["Windstorm"], + "dependencies": ["Windy Feet"], + "blockers": [], + "cost": 1, + "display": { + "row": 23, + "col": 1 + }, + "properties": { + "duration": 60 + }, + "effects": [ + { + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spdPct", + "value": 20 + } + ] + } + ] + }, + { + "display_name": "Refined Gunpowder", + "desc": "Increase the damage of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": ["Windstorm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [50, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "More Traps", + "desc": "Increase the maximum amount of active Traps you can have by +2.", + "archetype": "Trapper", + "archetype_req": 10, + "parents": ["Bouncing Bomb"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 1, + "display": { + "row": 27, + "col": 8 + }, + "properties": { + "traps": 2 + } + }, + { + "display_name": "Better Arrow Shield", + "desc": "Arrow Shield will gain additional area of effect, knockback and damage.", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Mana Trap", "Shocking Bomb", "Twain's Arc"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 6 + }, + "properties": { + "aoe": 1 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Shield", + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Better Leap", + "desc": "Reduce leap's cooldown by 1s.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Leap", "Homing Shots"], + "dependencies": ["Leap"], + "blockers": [], + "cost": 1, + "display": { + "row": 30, + "col": 1 + }, + "properties": { + "cooldown": -1 + } + }, + { + "display_name": "Better Guardian Angels", + "desc": "Your Guardian Angels can shoot +4 arrows before disappearing.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Escape Artist", "Homing Shots"], + "dependencies": ["Guardian Angels"], + "blockers": [], + "cost": 1, + "display": { + "row": 32, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 4 + } + } + ] + }, + { + "display_name": "Cheaper Arrow Storm (2)", + "desc": "Reduce the Mana cost of Arrow Storm.", + "archetype": "", + "archetype_req": 0, + "parents": ["Initiator", "Mana Trap"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 32, + "col": 8 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ] + }, + { + "display_name": "Precise Shot", + "desc": "+30% Critical Hit Damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Bomb"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 2 + }, + "properties": { + "mainAtk_range": 6 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdCritPct", + "value": 30 + } + ] + } + ] + }, + { + "display_name": "Cheaper Arrow Shield (2)", + "desc": "Reduce the Mana cost of Arrow Shield.", + "archetype": "", + "archetype_req": 0, + "parents": ["Precise Shot", "Initiator"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ] + }, + { + "display_name": "Rocket Jump", + "desc": "Arrow Bomb's self-damage will knockback you farther away.", + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Arrow Storm", "Initiator"], + "dependencies": ["Arrow Bomb"], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 6 + }, + "properties": { + } + }, + { + "display_name": "Cheaper Escape (2)", + "desc": "Reduce the Mana cost of Escape.", + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Storm", "Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 7 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ] + }, + { + "display_name": "Stronger Hook", + "desc": "Increase your Grappling Hook's range, speed and strength.", + "archetype": "Trapper", + "archetype_req": 5, + "parents": ["Cheaper Escape"], + "dependencies": ["Grappling Hook"], + "blockers": [], + "cost": 1, + "display": { + "row": 36, + "col": 8 + }, + "properties": { + "range": 8 + } + }, + { + "display_name": "Cheaper Arrow Bomb (2)", + "desc": "Reduce the Mana cost of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": ["More Focus (2)", "Minefield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 41, + "col": 5 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ] + }, + { + "display_name": "Bouncing Bomb", + "desc": "Arrow Bomb will bounce once when hitting a block or enemy", + "archetype": "", + "archetype_req": 0, + "parents": ["More Shields"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 7 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Arrow Bomb": 2 + } + } + ] + }, + { + "display_name": "Homing Shots", + "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity", + "archetype": "", + "archetype_req": 0, + "parents": ["Leap", "Shocking Bomb"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 2 + }, + "properties": { + + }, + "effects": [ + + ] + }, + { + "display_name": "Shrapnel Bomb", + "desc": "Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area", + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": ["Arrow Hurricane", "Precise Shot"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 1 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Shrapnel Bomb", + "cost": 0, + "multipliers": [40, 0, 0, 0, 20, 0] + } + ] + }, + { + "display_name": "Elusive", + "desc": "If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Geyser Stomp"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 30, + "col": 0 + }, + "properties": { + + }, + "effects": [ + + ] + }, + { + "display_name": "Double Shots", + "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Escape"], + "dependencies": [], + "blockers": ["Power Shots"], + "cost": 1, + "display": { + "row": 7, + "col": 2 + }, + "properties": { + "arrow": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 0, + "target_part": "Melee Damage", + "cost": 0, + "multipliers": 0.7 + } + ] + }, + { + "display_name": "Triple Shots", + "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Rain", "Frenzy"], + "dependencies": ["Double Shots"], + "blockers": [], + "cost": 1, + "display": { + "row": 14, + "col": 0 + }, + "properties": { + "arrow": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 0, + "target_part": "Melee Damage", + "cost": 0, + "multipliers": 0.7 + } + ] + }, + { + "display_name": "Power Shots", + "desc": "Main Attack arrows have increased speed and knockback", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Escape"], + "dependencies": [], + "blockers": ["Double Shots"], + "cost": 1, + "display": { + "row": 7, + "col": 6 + }, + "properties": { + + }, + "effects": [ + + ] + }, + { + "display_name": "Focus", + "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once", + "archetype": "Sharpshooter", + "archetype_req": 2, + "parents": ["Phantom Ray"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [35], + "max": 3 + } + ] + }, + { + "display_name": "More Focus", + "desc": "Add +2 max Focus", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Arrow Storm", "Grappling Hook"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 18, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [35], + "max": 5 + } + ] + }, + { + "display_name": "More Focus (2)", + "desc": "Add +2 max Focus", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Crepuscular Ray"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 32, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [35], + "max": 7 + } + ] + }, + { + "display_name": "Traveler", + "desc": "For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)", + "archetype": "", + "archetype_req": 0, + "parents": ["Refined Gunpowder", "Twain's Arc"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 20, + "col": 2 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "spd" + } + ], + "output": { + "type": "stat", + "name": "sdRaw" + }, + "scaling": [1], + "max": 100 + } + ] + }, + { + "display_name": "Patient Hunter", + "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%)", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["More Shields"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 2, + "display": { + "row": 17, + "col": 8 + }, + "properties": { + "max": 80 + }, + "effects": [ + + ] + }, + { + "display_name": "Stronger Patient Hunter", + "desc": "Add +80% Max Damage to Patient Hunter", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grape Bomb"], + "dependencies": ["Patient Hunter"], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 8 + }, + "properties": { + "max": 80 + }, + "effects": [ + + ] + }, + { + "display_name": "Frenzy", + "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Triple Shots", "Nimble String"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 14, + "col": 2 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Hits dealt", + "output": { + "type": "stat", + "name": "spd" + }, + "scaling": [6], + "max": 200 + } + ] + }, + { + "display_name": "Phantom Ray", + "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Water Mastery", "Fire Creep"], + "dependencies": ["Arrow Storm"], + "blockers": ["Windstorm", "Nimble String", "Arrow Hurricane"], + "cost": 2, + "display": { + "row": 16, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "replace_spell", + "name": "Phantom Ray", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [25, 0, 5, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Arrow": 16 + } + } + ] + } + ] + }, + { + "display_name": "Arrow Rain", + "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Nimble String", "Air Mastery"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Arrow Rain", + "cost": 0, + "multipliers": [120, 0, 0, 0, 0, 80] + } + ] + } ], + "Assassin": [ {"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4}, @@ -350,76 +2158,2013 @@ const atrees = {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8}, ], "Warrior": [ - {"row": 0, "col": 4, "name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area"}, - {"row": 2, "col": 2, "name": "Cheaper Bash", "desc": "Reduce the Mana cost of Bash"}, - {"row": 2, "col": 4, "name": "Spear Proficiency 1", "desc": "Improve your Main Attack's damage and range w/ spear"}, - {"row": 4, "col": 4, "name": "Double Bash", "desc": "Bash will hit a second time at a farther range"}, - {"row": 6, "col": 2, "name": "Heavy Impact", "desc": "After using Charge, violently crash down into the ground and deal damage. (Does not work if Flying Kick is unlocked)"}, - {"row": 6, "col": 4, "name": "Charge", "desc": "Charge forward at high speed (hold shift to cancel)"}, - {"row": 6, "col": 6, "name": "Tougher Skin", "desc": "Harden your skin and become permanently +5% more resistant. For every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)"}, - {"row": 7, "col": 0, "name": "Vehement", "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%). Damage Bonus: +5 (Raw)"}, - {"row": 8, "col": 2, "name": "Uppercut", "desc": "Rocket enemies in the air and deal massive damage"}, - {"row": 8, "col": 4, "name": "Cheaper Charge", "desc": "Reduce the Mana cost of Charge"}, - {"row": 8, "col": 6, "name": "War Scream", "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies"}, - {"row": 10, "col": 0, "name": "Earth Mastery", "desc": "Increases base damage from all Earth attacks"}, - {"row": 10, "col": 2, "name": "Thunder Mastery", "desc": "Increases base damage from all Thunder attacks"}, - {"row": 10, "col": 6, "name": "Air Mastery", "desc": "Increases base damage from all Air attacks"}, - {"row": 10, "col": 8, "name": "Fire Mastery", "desc": "Increases base damage from all Fire attacks"}, - {"row": 11, "col": 4, "name": "Water Mastery", "desc": "Increases base damage from all Water attacks"}, - {"row": 12, "col": 0, "name": "Quadruple Bash", "desc": "Bash will hit 4 times at an even larger range"}, - {"row": 12, "col": 2, "name": "Fireworks", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage"}, - {"row": 12, "col": 6, "name": "Flyby Jab", "desc": "Damage enemies in your way when using Charge"}, - {"row": 12, "col": 8, "name": "Flaming Uppercut", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds"}, - {"row": 13, "col": 4, "name": "Half-Moon Swipe", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water"}, - {"row": 13, "col": 7, "name": "Iron Lungs", "desc": "War scream deals more damage"}, - {"row": 15, "col": 2, "name": "Generalist", "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana"}, - {"row": 15, "col": 4, "name": "Counter", "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back"}, - {"row": 15, "col": 7, "name": "Mantle of the Bovemists", "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)"}, - {"row": 16, "col": 1, "name": "Bak'al's Grasp", "desc": "After casting war Scream, become Corrupted (15s Cooldown). You cannot heal while in that state. While Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)."}, - {"row": 17, "col": 0, "name": "Spear Proficiency 2", "desc": "Improve your Main Attack's damage and range w/ spear"}, - {"row": 17, "col": 3, "name": "Cheaper Uppercut", "desc": "Reduce the Mana Cost of Uppercut"}, - {"row": 17, "col": 5, "name": "Aerodynamics", "desc": "During Charge, you can steer and change direction"}, - {"row": 17, "col": 7, "name": "Provoke", "desc": "Mobs damaged by War Scream will target only you for at least 5s. Reduce the Mana cost of War Scream."}, - {"row": 18, "col": 2, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 18, "col": 6, "name": "Air Shout", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times."}, - {"row": 20, "col": 0, "name": "Enraged Blow", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)"}, - {"row": 20, "col": 3, "name": "Flying Kick", "desc": "While using Charge, mobs hit will halt your momentum and get knocked back"}, - {"row": 20, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 20, "col": 8, "name": "Manachism", "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 mana (1s Cooldown)"}, - {"row": 22, "col": 0, "name": "Boiling Blood", "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds"}, - {"row": 22, "col": 2, "name": "Ragnarokkr", "desc": "War Scream becomes deafening, increasing its range and giving damage bonus to players"}, - {"row": 22, "col": 4, "name": "Ambidextrous", "desc": "Increase your change to attack with Counter by 30%"}, - {"row": 22, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 22, "col": 8, "name": "Stronger Bash", "desc": "Increase the damage of Bash"}, - {"row": 23, "col": 1, "name": "Massacre", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar"}, - {"row": 23, "col": 5, "name": "Collide", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage"}, - {"row": 23, "col": 7, "name": "Rejuvenating Skin", "desc": "Regain back 30% of the damage you take as healing over 30s"}, - {"row": 24, "col": 2, "name": "Comet", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage"}, - {"row": 26, "col": 0, "name": "Uncontainable Corruption", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1"}, - {"row": 26, "col": 2, "name": "Radiant Devotee", "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)"}, - {"row": 26, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 26, "col": 7, "name": "Mythril Skin", "desc": "Gain +5% Base Resistance and become immune to knockback"}, - {"row": 27, "col": 1, "name": "Armour Breaker", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage"}, - {"row": 27, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 27, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 27, "col": 8, "name": "Sparking Hope", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies"}, - {"row": 28, "col": 0, "name": "Massive Bash", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)"}, - {"row": 28, "col": 2, "name": "Tempest", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area"}, - {"row": 28, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 29, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 29, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 29, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 31, "col": 0, "name": "Cheaper War Scream", "desc": "Reduce the Mana cost of War Scream"}, - {"row": 31, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 32, "col": 1, "name": "Blood KO", "desc": "Gonna have to rewrite this one chief"}, - {"row": 32, "col": 3, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 32, "col": 5, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 32, "col": 7, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 34, "col": 1, "name": "Blood Pact", "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)"}, - {"row": 34, "col": 4, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 34, "col": 6, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 34, "col": 8, "name": "TEXT", "desc": "IDFK MMMM"}, - {"row": 35, "col": 0, "name": "TEXT", "desc": "IDFK MMMM"} + { + "display_name": "Bash", + "desc": "Violently bash the ground, dealing high damage in a large area", + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 0, + "col": 4 + }, + "properties": { + "aoe": 4, + "range": 3 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Bash", + "cost": 45, + "display_text": "Total Damage Average", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Hit", + "type": "damage", + "multipliers": [130, 20, 0, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Hit": 1 + } + } + ] + } + ] + }, + { + "display_name": "Spear Proficiency 1", + "desc": "Improve your Main Attack's damage and range w/ spear", + "archetype": "", + "archetype_req": 0, + "parents": ["Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 4 + }, + "properties": { + "melee_range": 1 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ] + }, + + { + "display_name": "Cheaper Bash", + "desc": "Reduce the Mana cost of Bash", + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 1"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 2 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -10 + } + ] + }, + { + "display_name": "Double Bash", + "desc": "Bash will hit a second time at a farther range", + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 1"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 4, + "col": 4 + }, + "properties": { + "range": 3 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "name": "Single Hit", + "value": 1 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [-50, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Charge", + "desc": "Charge forward at high speed (hold shift to cancel)", + "archetype": "", + "archetype_req": 0, + "parents": ["Double Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 6, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "replace_spell", + "name": "Charge", + "cost": 25, + "display_text": "Total Damage Average", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [0, 0, 0, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] + } + ] + }, + + { + "display_name": "Heavy Impact", + "desc": "After using Charge, violently crash down into the ground and deal damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Charge"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 6, + "col": 2 + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Heavy Impact", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Vehement", + "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Heavy Impact"], + "dependencies": [], + "blockers": ["Tougher Skin"], + "cost": 1, + "display": { + "row": 7, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "mdPct" + }, + { + "type": "stat", + "name": "mdRaw" + } + ], + "output": { + "type": "stat", + "name": "spd" + }, + "scaling": [1, 1], + "max": 20 + } + ] + }, + + { + "display_name": "Tougher Skin", + "desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Charge"], + "dependencies": [], + "blockers": ["Vehement"], + "cost": 1, + "display": { + "row": 6, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "baseResist", + "value": "5" + } + ] + }, + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hprRaw" + }, + { + "type": "stat", + "name": "hprPct" + } + ], + "output": { + "type": "stat", + "name": "hpBonus" + }, + "scaling": [10, 10], + "max": 100 + } + ] + }, + + { + "display_name": "Uppercut", + "desc": "Rocket enemies in the air and deal massive damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Vehement"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 2 + }, + "properties": { + "aoe": 3, + "range": 5 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Uppercut", + "cost": 45, + "display_text": "Total Damage Average", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "total", + "parts": [ + { + "name": "Uppercut", + "type": "damage", + "multipliers": [150, 50, 50, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Uppercut": 1 + } + } + ] + } + ] + }, + + { + "display_name": "Cheaper Charge", + "desc": "Reduce the Mana cost of Charge", + "archetype": "", + "archetype_req": 0, + "parents": ["Uppercut", "War Scream"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ] + }, + + { + "display_name": "War Scream", + "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies", + "archetype": "", + "archetype_req": 0, + "parents": ["Tougher Skin"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 6 + }, + "properties": { + "duration": 30, + "aoe": 12, + "defense_bonus": 10 + }, + "effects": [ + { + "type": "replace_spell", + "name": "War Scream", + "cost": 35, + "display_text": "War Scream", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage Average", + "parts": [ + { + "name": "War Scream", + "type": "damage", + "multipliers": [50, 0, 0, 0, 50, 0] + } + ] + } + ] + }, + + { + "display_name": "Earth Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "eDamPct", + "value": 20 + }, + { + "type": "stat", + "name": "eDam", + "value": [2, 4] + } + ] + } + ] + }, + + { + "display_name": "Thunder Mastery", + "desc": "Increases base damage from all Thunder attacks", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uppercut", "Air Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "tDamPct", + "value": 10 + }, + { + "type": "stat", + "name": "tDam", + "value": [1, 8] + } + ] + } + ] + }, + + { + "display_name": "Water Mastery", + "desc": "Increases base damage from all Water attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Cheaper Charge", "Thunder Mastery", "Air Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 11, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "wDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "wDam", + "value": [2, 4] + } + ] + } + ] + }, + + { + "display_name": "Air Mastery", + "desc": "Increases base damage from all Air attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["War Scream", "Thunder Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "aDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "aDam", + "value": [3, 4] + } + ] + } + ] + }, + + { + "display_name": "Fire Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["War Scream"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 8 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "fDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "fDam", + "value": [3, 5] + } + ] + } + ] + }, + + { + "display_name": "Quadruple Bash", + "desc": "Bash will hit 4 times at an even larger range", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Earth Mastery", "Fireworks"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 0 + }, + "properties": { + "range": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Hit": 2 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [-20, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Fireworks", + "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Quadruple Bash"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fireworks", + "cost": 0, + "multipliers": [80, 0, 20, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fireworks": 1 + } + } + ] + }, + + { + "display_name": "Half-Moon Swipe", + "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Water Mastery"], + "dependencies": ["Uppercut"], + "blockers": [], + "cost": 2, + "display": { + "row": 13, + "col": 4 + }, + "properties": { + "range": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": -10, + "multipliers": [-70, 0, 0, 0, 0, 0] + }, + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "water" + } + ] + }, + + { + "display_name": "Flyby Jab", + "desc": "Damage enemies in your way when using Charge", + "archetype": "", + "archetype_req": 0, + "parents": ["Air Mastery", "Flaming Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 6 + }, + "properties": { + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Flyby Jab", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 40] + } + ] + }, + + { + "display_name": "Flaming Uppercut", + "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Fire Mastery", "Flyby Jab"], + "dependencies": ["Uppercut"], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 8 + }, + "properties": { + "duration": 3, + "tick": 0.6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Flaming Uppercut", + "cost": 0, + "multipliers": [0, 0, 0, 0, 50, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Flaming Uppercut Total Damage", + "cost": 0, + "hits": { + "Flaming Uppercut": 5 + } + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Flaming Uppercut": 5 + } + } + ] + }, + + { + "display_name": "Iron Lungs", + "desc": "War Scream deals more damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Flyby Jab", "Flaming Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 7 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "War Scream", + "cost": 0, + "multipliers": [30, 0, 0, 0, 0, 30] + } + ] + }, + + { + "display_name": "Generalist", + "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana", + "archetype": "Battle Monk", + "archetype_req": 3, + "parents": ["Counter"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 2 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Counter", + "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Half-Moon Swipe"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 4 + }, + "properties": { + "chance": 30 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Counter", + "cost": 0, + "multipliers": [60, 0, 20, 0, 0, 20] + } + ] + }, + + { + "display_name": "Mantle of the Bovemists", + "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)", + "archetype": "Paladin", + "archetype_req": 3, + "parents": ["Iron Lungs"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 7 + }, + "properties": { + "mantle_charge": 3 + }, + "effects": [ + + ] + }, + + { + "display_name": "Bak'al's Grasp", + "desc": "After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)", + "archetype": "Fallen", + "archetype_req": 2, + "parents": ["Quadruple Bash", "Fireworks"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 1 + }, + "properties": { + "cooldown": 15 + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "raw" + }, + "scaling": [4], + "slider_step": 2, + "max": 120 + } + ] + }, + + { + "display_name": "Spear Proficiency 2", + "desc": "Improve your Main Attack's damage and range w/ spear", + "archetype": "", + "archetype_req": 0, + "parents": ["Bak'al's Grasp", "Cheaper Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 0 + }, + "properties": { + "melee_range": 1 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ] + }, + + { + "display_name": "Cheaper Uppercut", + "desc": "Reduce the Mana Cost of Uppercut", + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 3 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ] + }, + + { + "display_name": "Aerodynamics", + "desc": "During Charge, you can steer and change direction", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Cheaper Uppercut", "Provoke"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 17, + "col": 5 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Provoke", + "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Aerodynamics", "Mantle of the Bovemists"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 7 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ] + }, + + { + "display_name": "Precise Strikes", + "desc": "+30% Critical Hit Damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Uppercut", "Spear Proficiency 2"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 18, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "critDmg", + "value": 30 + } + ] + } + ] + }, + + { + "display_name": "Air Shout", + "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", + "archetype": "", + "archetype_req": 0, + "parents": ["Aerodynamics", "Provoke"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 18, + "col": 6 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Air Shout", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 5] + } + ] + }, + + { + "display_name": "Enraged Blow", + "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Spear Proficiency 2"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hpBonus" + } + ], + "output": { + "type": "stat", + "name": "dmgPct" + }, + "scaling": [2], + "max": 200 + } + ] + }, + + { + "display_name": "Flying Kick", + "desc": "When using Charge, mobs hit will halt your momentum and get knocked back", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Cheaper Uppercut", "Stronger Mantle"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 3 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Flying Kick", + "cost": 0, + "multipliers": [120, 0, 0, 10, 0, 20] + } + ] + }, + + { + "display_name": "Stronger Mantle", + "desc": "Add +2 additional charges to Mantle of the Bovemists", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Manachism", "Flying Kick"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 20, + "col": 6 + }, + "properties": { + "mantle_charge": 2 + }, + "effects": [ + + ] + }, + + { + "display_name": "Manachism", + "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)", + "archetype": "Paladin", + "archetype_req": 3, + "parents": ["Stronger Mantle", "Provoke"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 8 + }, + "properties": { + "cooldown": 1 + }, + "effects": [ + + ] + }, + + { + "display_name": "Boiling Blood", + "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds", + "archetype": "", + "archetype_req": 0, + "parents": ["Enraged Blow", "Ragnarokkr"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Boiling Blood", + "cost": 0, + "multipliers": [25, 0, 0, 0, 5, 0] + } + ] + }, + + { + "display_name": "Ragnarokkr", + "desc": "War Scream become deafening, increasing its range and giving damage bonus to players", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Boiling Blood", "Flying Kick"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 2 + }, + "properties": { + "damage_bonus": 30, + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": 10 + } + ] + }, + + { + "display_name": "Ambidextrous", + "desc": "Increase your chance to attack with Counter by +30%", + "archetype": "", + "archetype_req": 0, + "parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"], + "dependencies": ["Counter"], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 4 + }, + "properties": { + "chance": 30 + }, + "effects": [ + + ] + }, + + { + "display_name": "Burning Heart", + "desc": "For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Ambidextrous", "Stronger Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hpBonus" + } + ], + "output": { + "type": "stat", + "name": "fDamPct" + }, + "scaling": [2], + "max": 100, + "slider_step": 100 + } + ] + }, + + { + "display_name": "Stronger Bash", + "desc": "Increase the damage of Bash", + "archetype": "", + "archetype_req": 0, + "parents": ["Burning Heart", "Manachism"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 8 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [30, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Intoxicating Blood", + "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", + "archetype": "Fallen", + "archetype_req": 5, + "parents": ["Ragnarokkr", "Boiling Blood"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 1 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Comet", + "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Ragnarokkr"], + "dependencies": ["Fireworks"], + "blockers": [], + "cost": 2, + "display": { + "row": 24, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Comet", + "cost": 0, + "multipliers": [80, 20, 0, 0, 0, 0] + }, + { + "type":"add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Comet": 1 + } + } + ] + }, + + { + "display_name": "Collide", + "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", + "archetype": "Battle Monk", + "archetype_req": 4, + "parents": ["Ambidextrous", "Burning Heart"], + "dependencies": ["Flying Kick"], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 5 + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Collide", + "cost": 0, + "multipliers": [100, 0, 0, 0, 50, 0] + } + ] + }, + + { + "display_name": "Rejuvenating Skin", + "desc": "Regain back 30% of the damage you take as healing over 30s", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Burning Heart", "Stronger Bash"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 7 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Uncontainable Corruption", + "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", + "archetype": "", + "archetype_req": 0, + "parents": ["Boiling Blood", "Radiant Devotee"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 0 + }, + "properties": { + "cooldown": -5 + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "raw" + }, + "scaling": [1], + "slider_step": 2, + "max": 50 + } + ] + }, + + { + "display_name": "Radiant Devotee", + "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Whirlwind Strike", "Uncontainable Corruption"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "inputs": [ + { + "type": "stat", + "name": "ref" + } + ], + "output": { + "type": "stat", + "name": "mr" + }, + "scaling": [1], + "max": 10, + "slider_step": 4 + } + ] + }, + + { + "display_name": "Whirlwind Strike", + "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": ["Ambidextrous", "Radiant Devotee"], + "dependencies": ["Uppercut"], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 4 + }, + "properties": { + "range": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": 0, + "multipliers": [0, 0, 0, 0, 0, 50] + } + ] + }, + + { + "display_name": "Mythril Skin", + "desc": "Gain +5% Base Resistance and become immune to knockback", + "archetype": "Paladin", + "archetype_req": 6, + "parents": ["Rejuvenating Skin"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 7 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "baseResist", + "value": 5 + } + ] + } + ] + }, + + { + "display_name": "Armour Breaker", + "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uncontainable Corruption", "Radiant Devotee"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 1 + }, + "properties": { + "duration": 5 + }, + "effects": [ + + ] + }, + + { + "display_name": "Shield Strike", + "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Mythril Skin", "Sparkling Hope"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Shield Strike", + "cost": 0, + "multipliers": [60, 0, 20, 0, 0, 0] + } + ] + }, + + { + "display_name": "Sparkling Hope", + "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Mythril Skin"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 8 + }, + "properties": { + "aoe": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Sparkling Hope", + "cost": 0, + "multipliers": [10, 0, 5, 0, 0, 0] + } + ] + }, + + { + "display_name": "Massive Bash", + "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", + "archetype": "Fallen", + "archetype_req": 8, + "parents": ["Tempest", "Uncontainable Corruption"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "bashAoE" + }, + "scaling": [1], + "max": 10, + "slider_step": 3 + } + ] + }, + + { + "display_name": "Tempest", + "desc": "War Scream will ripple the ground and deal damage 3 times in a large area", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Massive Bash", "Spirit of the Rabbit"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 2 + }, + "properties": { + "aoe": 16 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Tempest", + "cost": "0", + "multipliers": [30, 10, 0, 0, 0, 10] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Tempest Total Damage", + "cost": "0", + "hits": { + "Tempest": 3 + } + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Total Damage", + "cost": "0", + "hits": { + "Tempest": 3 + } + } + ] + }, + + { + "display_name": "Spirit of the Rabbit", + "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": ["Tempest", "Whirlwind Strike"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 28, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + }, + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "spd", + "value": 20 + } + ] + } + ] + }, + + { + "display_name": "Massacre", + "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", + "archetype": "Fallen", + "archetype_req": 5, + "parents": ["Tempest", "Massive Bash"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 1 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Axe Kick", + "desc": "Increase the damage of Uppercut, but also increase its mana cost", + "archetype": "", + "archetype_req": 0, + "parents": ["Tempest", "Spirit of the Rabbit"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 3 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": 10, + "multipliers": [100, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Radiance", + "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", + "archetype": "Paladin", + "archetype_req": 2, + "parents": ["Spirit of the Rabbit", "Cheaper Bash 2"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 5 + }, + "properties": { + "cooldown": 15 + }, + "effects": [ + + ] + }, + + { + "display_name": "Cheaper Bash 2", + "desc": "Reduce the Mana cost of Bash", + "archetype": "", + "archetype_req": 0, + "parents": ["Radiance", "Shield Strike", "Sparkling Hope"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 7 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ] + }, + + { + "display_name": "Cheaper War Scream", + "desc": "Reduce the Mana cost of War Scream", + "archetype": "", + "archetype_req": 0, + "parents": ["Massive Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ] + }, + + { + "display_name": "Discombobulate", + "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", + "archetype": "Battle Monk", + "archetype_req": 12, + "parents": ["Thunderclap"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 31, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Hits dealt", + "output": { + "type": "stat", + "name": "rainrawButDifferent" + }, + "scaling": [2], + "max": 50 + } + ] + }, + + { + "display_name": "Thunderclap", + "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", + "archetype": "Battle Monk", + "archetype_req": 8, + "parents": ["Spirit of the Rabbit"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 31, + "col": 4 + }, + "properties": { + "aoe": 2 + }, + "effects": [ + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + } + ] + }, + + { + "display_name": "Cyclone", + "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Thunderclap"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 32, + "col": 5 + }, + "properties": { + "aoe": 4, + "duration": 20 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Cyclone", + "cost": 0, + "multipliers": [10, 0, 0, 0, 5, 10] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Cyclone Total Damage", + "cost": 0, + "hits": { + "Cyclone": 40 + } + + } + ] + }, + + { + "display_name": "Second Chance", + "desc": "When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)", + "archetype": "Paladin", + "archetype_req": 12, + "parents": ["Cheaper Bash 2"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 7 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Blood Pact", + "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", + "archetype": "", + "archetype_req": 10, + "parents": ["Cheaper War Scream"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 34, + "col": 1 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Haemorrhage", + "desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Blood Pact"], + "dependencies": ["Blood Pact"], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 2 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Brink of Madness", + "desc": "If your health is 25% full or less, gain +40% Resistance", + "archetype": "", + "archetype_req": 0, + "parents": ["Blood Pact", "Cheaper Uppercut 2"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 35, + "col": 4 + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Cheaper Uppercut 2", + "desc": "Reduce the Mana cost of Uppercut", + "archetype": "", + "archetype_req": 0, + "parents": ["Second Chance", "Brink of Madness"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ] + }, + + { + "display_name": "Martyr", + "desc": "When you receive a fatal blow, all nearby allies become invincible", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Second Chance"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 35, + "col": 8 + }, + "properties": { + "duration": 3, + "aoe": 12 + }, + "effects": [ + + ] + } ], "Mage": [], "Shaman": [] From 6ea34708a6de2ed906efe382aa4dda57105d3de9 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 02:23:56 -0700 Subject: [PATCH 044/155] Fully working tomes !!! --- js/build.js | 14 ++++++++++++-- js/build_encode_decode.js | 2 +- js/builder.js | 10 +++++++++- js/builder_graph.js | 40 ++++++++++++++++++++++++++++++++------- js/load_tome.js | 24 +++++++++++------------ tomes.json | 20 ++++++++++++++++++++ 6 files changed, 87 insertions(+), 23 deletions(-) diff --git a/js/build.js b/js/build.js index 1760004..987aac3 100644 --- a/js/build.js +++ b/js/build.js @@ -155,10 +155,12 @@ class Build{ */ initBuildStats(){ - let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi"]; + let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"]; //Create a map of this build's stats let statMap = new Map(); + statMap.set("damageMultiplier", 1); + statMap.set("defMultiplier", 1); for (const staticID of staticIDs) { statMap.set(staticID, 0); @@ -176,7 +178,15 @@ class Build{ } for (const staticID of staticIDs) { if (item_stats.get(staticID)) { - statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID)); + if (staticID === "dmgMobs") { + statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID)); + } + else if (staticID === "defMobs") { + statMap.set('defMultiplier', statMap.get('defMultiplier') * item_stats.get(staticID)); + } + else { + statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID)); + } } } if (item_stats.get("majorIds")) { diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 86aabd5..c12dae0 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -157,7 +157,7 @@ function encodeBuild(build, powders, skillpoints) { } else if (item.statMap.get("crafted")) { build_string += "CR-"+encodeCraft(item); } else if (item.statMap.get("category") === "tome") { - let tome_id = item.get("id"); + let tome_id = item.statMap.get("id"); if (tome_id <= 60) { // valid normal tome. ID 61-63 is for NONE tomes. build_version = Math.max(build_version, 6); diff --git a/js/builder.js b/js/builder.js index 34f7eab..cf997f3 100644 --- a/js/builder.js +++ b/js/builder.js @@ -95,8 +95,15 @@ function resetFields(){ elem.classList.remove("toggleOn"); } } + for (const elem of skp_order) { + console.log(document.getElementById(elem + "_boost_armor").value); + document.getElementById(elem + "_boost_armor").value = 0; + document.getElementById(elem + "_boost_armor_prev").value = 0; + document.getElementById(elem + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`; + document.getElementById(elem + "_boost_armor_label").textContent = `% ${damageClasses[skp_order.indexOf(elem)+1]} Damage Boost: 0`; + } - const nodes_to_reset = item_nodes.concat(powder_nodes).concat(edit_input_nodes).concat([powder_special_input, boosts_node]); + const nodes_to_reset = item_nodes.concat(powder_nodes).concat(edit_input_nodes).concat([powder_special_input, boosts_node, armor_powder_node]); for (const node of nodes_to_reset) { node.mark_dirty(); } @@ -317,6 +324,7 @@ function init_autocomplete() { if (event.detail.selection.value) { event.target.value = event.detail.selection.value; } + event.target.dispatchEvent(new Event('input')); }, }, } diff --git a/js/builder_graph.js b/js/builder_graph.js index ed0ac00..cfb8491 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -259,7 +259,10 @@ class ItemInputDisplayNode extends ComputeNode { // Doesn't exist for weapons. this.health_field.textContent = "0"; } - this.level_field.textContent = "0"; + if (this.level_field) { + // Doesn't exist for tomes. + this.level_field.textContent = "0"; + } if (!item) { this.input_field.classList.add("is-invalid"); return null; @@ -274,7 +277,10 @@ class ItemInputDisplayNode extends ComputeNode { // Doesn't exist for weapons. this.health_field.textContent = item.statMap.get('hp'); } - this.level_field.textContent = item.statMap.get('lvl'); + if (this.level_field) { + // Doesn't exist for tomes. + this.level_field.textContent = item.statMap.get('lvl'); + } this.image.classList.add(tier + "-shadow"); return null; } @@ -401,7 +407,14 @@ class BuildAssembleNode extends ComputeNode { input_map.get('ring1-input'), input_map.get('ring2-input'), input_map.get('bracelet-input'), - input_map.get('necklace-input') + 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') ]; let weapon = input_map.get('weapon-input'); let level = input_map.get('level-input'); @@ -759,7 +772,7 @@ class DisplayBuildWarningsNode extends ComputeNode { } let baditem = document.createElement("p"); baditem.classList.add("nocolor"); baditem.classList.add("itemp"); - baditem.textContent = item.get("displayName") + " requires level " + item_lvl + " to use."; + baditem.textContent = item.statMap.get("displayName") + " requires level " + item_lvl + " to use."; lvlWarning.appendChild(baditem); } } @@ -791,7 +804,13 @@ class AggregateStatsNode extends ComputeNode { for (const [k, v] of input_map.entries()) { for (const [k2, v2] of v.entries()) { if (output_stats.has(k2)) { - output_stats.set(k2, v2 + output_stats.get(k2)); + // 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)); + } } else { output_stats.set(k2, v2); @@ -815,8 +834,6 @@ class AggregateEditableIDNode extends ComputeNode { const weapon = input_map.get('weapon'); input_map.delete('weapon'); const output_stats = new Map(build.statMap); - output_stats.set("damageMultiplier", 1); - output_stats.set("defMultiplier", 1); for (const [k, v] of input_map.entries()) { output_stats.set(k, v); } @@ -944,6 +961,15 @@ function builder_graph_init() { //new PrintNode(eq+'-debug').link_to(item_input); //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); } + 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); + } // weapon image changer node. let weapon_image = document.getElementById("weapon-img"); diff --git a/js/load_tome.js b/js/load_tome.js index e6fded5..3b44842 100644 --- a/js/load_tome.js +++ b/js/load_tome.js @@ -1,4 +1,4 @@ -const TOME_DB_VERSION = 1; +const TOME_DB_VERSION = 2; // @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA let tdb; @@ -120,6 +120,11 @@ async function load_tome_init() { }); } +let none_tomes = [ + ["tome", "weaponTome", "No Weapon Tome"], + ["tome", "armorTome", "No Armor Tome"], + ["tome", "guildTome", "No Guild Tome"] +]; function init_tome_maps() { //warp tomeMap = new Map(); @@ -131,17 +136,12 @@ function init_tome_maps() { tomeLists.set(it, []); } - let noneTomes = [ - ["tome", "weaponTome", "No Weapon Tome"], - ["tome", "armorTome", "No Armor Tome"], - ["tome", "guildTome", "No Guild Tome"] - ]; for (let i = 0; i < 3; i++) { let tome = Object(); tome.slots = 0; - tome.category = noneTomes[i][0]; - tome.type = noneTomes[i][1]; - tome.name = noneTomes[i][2]; + tome.category = none_tomes[i][0]; + tome.type = none_tomes[i][1]; + tome.name = none_tomes[i][2]; tome.displayName = tome.name; tome.set = null; tome.quest = null; @@ -160,14 +160,14 @@ function init_tome_maps() { //dependency - load.js clean_item(tome); - noneTomes[i] = tome; + none_tomes[i] = tome; } - tomes = tomes.concat(noneTomes); + tomes = tomes.concat(none_tomes); for (const tome of tomes) { if (tome.remapID === undefined) { tomeLists.get(tome.type).push(tome.displayName); tomeMap.set(tome.displayName, tome); - if (noneTomes.includes(tome)) { + if (none_tomes.includes(tome)) { tomeIDMap.set(tome.id, ""); } else { diff --git a/tomes.json b/tomes.json index 913efa4..0b4c213 100644 --- a/tomes.json +++ b/tomes.json @@ -9,6 +9,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defmobs": 3, "thorns": 6, "ref": 6, "hpBonus": 120, @@ -23,6 +24,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "thorns": 8, "ref": 8, "fixID": false, @@ -36,6 +38,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defMobs": 3, "exploding": 5, "mdPct": 5, "hpBonus": 120, @@ -50,6 +53,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "thorns": 6, "reflection": 6, "fixID": false, @@ -63,6 +67,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defMobs": 3, "sdPct": 5, "hpBonus": 120, "fixID": false, @@ -76,6 +81,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "sdPct": 6, "fixID": false, "id": 5 @@ -88,6 +94,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defMobs": 3, "hprRaw": 15, "hpBonus": 120, "fixID": false, @@ -101,6 +108,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "hprRaw": 60, "fixID": false, "id": 7 @@ -113,6 +121,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defMobs": 3, "ls": 25, "hpBonus": 120, "fixID": false, @@ -126,6 +135,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "ls": 85, "fixID": false, "id": 9 @@ -138,6 +148,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defMobs": 3, "lb": 5, "hpBonus": 120, "fixID": false, @@ -151,6 +162,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "lb": 6, "fixID": false, "id": 11 @@ -163,6 +175,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, + "defMobs": 3, "spd": 5, "hpBonus": 120, "fixID": false, @@ -176,6 +189,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 5, "spd": 6, "fixID": false, "id": 13 @@ -188,6 +202,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 8, "eDefPct": 10, "hpBonus": 150, "fixID": false, @@ -201,6 +216,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 8, "tDefPct": 10, "hpBonus": 150, "fixID": false, @@ -214,6 +230,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 8, "wDefPct": 10, "hpBonus": 150, "fixID": false, @@ -227,6 +244,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 8, "fDefPct": 10, "hpBonus": 150, "fixID": false, @@ -240,6 +258,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 8, "aDefPct": 10, "hpBonus": 150, "fixID": false, @@ -253,6 +272,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 100, + "defMobs": 8, "eDefPct": 6, "tDefPct": 6, "wDefPct": 6, From 12e4c1e88c976670d1c72ca0a13bac58a74a07be Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 03:16:36 -0700 Subject: [PATCH 045/155] Revert to current dev branch behaviour somewhat --- js/builder.js | 2 +- js/builder_graph.js | 1 - js/computation_graph.js | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/js/builder.js b/js/builder.js index f497224..96513d8 100644 --- a/js/builder.js +++ b/js/builder.js @@ -293,7 +293,7 @@ function init_autocomplete() { resultsList: { maxResults: 1000, tabSelect: true, - noResults: true, + noResults: false, class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm", element: (list, data) => { // dynamic result loc diff --git a/js/builder_graph.js b/js/builder_graph.js index cfb8491..51a4d6c 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -894,7 +894,6 @@ class SkillPointSetterNode extends ComputeNode { } compute_func(input_map) { - console.log("a"); 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()) { diff --git a/js/computation_graph.js b/js/computation_graph.js index c0337ec..90fe08c 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -105,7 +105,7 @@ class ComputeNode { * * @param node : ComputeNode to schedule an update for. */ -function calcSchedule(node) { +function calcSchedule(node, timeout) { if (node.update_task !== null) { clearTimeout(node.update_task); } @@ -113,7 +113,7 @@ function calcSchedule(node) { node.update_task = setTimeout(function() { node.update(); node.update_task = null; - }, 500); + }, timeout); } class PrintNode extends ComputeNode { @@ -139,8 +139,8 @@ class InputNode extends ComputeNode { constructor(name, input_field) { super(name); this.input_field = input_field; - //this.input_field.addEventListener("input", () => calcSchedule(this)); - this.input_field.addEventListener("change", () => calcSchedule(this)); + this.input_field.addEventListener("input", () => calcSchedule(this, 5000)); + this.input_field.addEventListener("change", () => calcSchedule(this, 500)); //calcSchedule(this); Manually fire first update for better control } From ef5dd8f6c6065dc38b085f5af075a4d301c93345 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 03:42:18 -0700 Subject: [PATCH 046/155] HOTFIX: tome patch --- js/build.js | 9 ++++----- js/build_encode_decode.js | 8 ++++---- js/builder_graph.js | 16 ++++++++++------ 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/js/build.js b/js/build.js index b95a569..ccd25a6 100644 --- a/js/build.js +++ b/js/build.js @@ -105,7 +105,7 @@ class Build{ * In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome. * 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building) */ - constructor(level, items, tomes, weapon){ + constructor(level, items, weapon){ if (level < 1) { //Should these be constants? this.level = 1; @@ -123,13 +123,12 @@ class Build{ this.availableSkillpoints = levelToSkillPoints(this.level); this.equipment = items; - this.tomes = tomes; this.weapon = weapon; - this.items = this.equipment.concat([this.weapon]).concat(this.tomes); + this.items = this.equipment.concat([this.weapon]); // return [equip_order, best_skillpoints, final_skillpoints, best_total]; // calc skillpoints requires statmaps only - let result = calculate_skillpoints(this.equipment.concat(this.tomes).map((x) => x.statMap), this.weapon.statMap); + let result = calculate_skillpoints(this.equipment.map((x) => x.statMap), this.weapon.statMap); this.equip_order = result[0]; // How many skillpoints the player had to assign (5 number) this.base_skillpoints = result[1]; @@ -145,7 +144,7 @@ class Build{ /*Returns build in string format */ toString(){ - return [this.equipment,this.weapon,this.tomes].flat(); + return [this.equipment,this.weapon].flat(); } diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index c12dae0..618bb71 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -119,11 +119,11 @@ function decodeBuild(url_tag) { // Tomes. if (version == 6) { //tome values do not appear in anything before v6. - for (let i = 0; i < 7; ++i) { + for (let i in tomes) { let tome_str = info[1].charAt(i); - for (let i in tomes) { - setValue(tomeInputs[i], getTomeNameFromID(Base64.toInt(tome_str))); - } + let tome_name = getTomeNameFromID(Base64.toInt(tome_str)); + console.log(tome_name); + setValue(tomeInputs[i], tome_name); } info[1] = info[1].slice(7); } diff --git a/js/builder_graph.js b/js/builder_graph.js index 51a4d6c..f4d41ea 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -382,8 +382,6 @@ class URLUpdateNode extends ComputeNode { * Create a "build" object from a set of equipments. * Returns a new Build object, or null if all items are NONE items. * - * TODO: add tomes - * * Signature: BuildAssembleNode(helmet-input: Item, * chestplate-input: Item, * leggings-input: Item, @@ -426,7 +424,7 @@ class BuildAssembleNode extends ComputeNode { if (all_none && !location.hash) { return null; } - return new Build(level, equipments, [], weapon); + return new Build(level, equipments, weapon); } } @@ -1007,8 +1005,6 @@ function builder_graph_init() { let build_disp_node = new BuildDisplayNode() build_disp_node.link_to(build_node, 'build'); - let build_warnings_node = new DisplayBuildWarningsNode(); - build_warnings_node.link_to(build_node, 'build'); // 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) let stat_agg_node = new AggregateStatsNode(); @@ -1026,14 +1022,15 @@ function builder_graph_init() { edit_id_output = new EditableIDSetterNode(edit_input_nodes); // Makes shallow copy of list. edit_id_output.link_to(build_node); + let skp_inputs = []; for (const skp of skp_order) { const elem = document.getElementById(skp+'-skp'); const node = new SumNumberInputNode('builder-'+skp+'-input', elem); edit_agg_node.link_to(node, skp); build_encode_node.link_to(node, skp); - build_warnings_node.link_to(node, skp); edit_input_nodes.push(node); + skp_inputs.push(node); } stat_agg_node.link_to(edit_agg_node); build_disp_node.link_to(stat_agg_node, 'stats'); @@ -1079,6 +1076,13 @@ function builder_graph_init() { let skp_output = new SkillPointSetterNode(edit_input_nodes); skp_output.link_to(build_node); + 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(); + // 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 From 5f9c0d5185ea08b8dfa43b8513df5715f37b2073 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 03:55:13 -0700 Subject: [PATCH 047/155] HOTFIX: str/dex optimizer working now --- js/builder_graph.js | 7 +++---- js/display.js | 1 - js/optimize.js | 30 +++++++++--------------------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index f4d41ea..5cddf8d 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -223,9 +223,8 @@ class ItemInputNode extends InputNode { for (const [i, x] of zip2(equipment_inputs, replace_items)) { setValue(i, x); } - // NOTE: DO NOT REORDER FOR PERFORMANCE REASONS - for (const node of item_nodes) { node.mark_dirty(); } - for (const node of item_nodes) { node.update(); } + for (const node of item_nodes) { calcSchedule(node, 10); } + return this.compute_func(input_map); } return null; } @@ -942,6 +941,7 @@ let item_nodes = []; let powder_nodes = []; let spelldmg_nodes = []; let edit_input_nodes = []; +let skp_inputs = []; function builder_graph_init() { // Phase 1/2: Set up item input, propagate updates, etc. @@ -1022,7 +1022,6 @@ function builder_graph_init() { edit_id_output = new EditableIDSetterNode(edit_input_nodes); // Makes shallow copy of list. edit_id_output.link_to(build_node); - let skp_inputs = []; for (const skp of skp_order) { const elem = document.getElementById(skp+'-skp'); const node = new SumNumberInputNode('builder-'+skp+'-input', elem); diff --git a/js/display.js b/js/display.js index 11afa6a..8f7017d 100644 --- a/js/display.js +++ b/js/display.js @@ -51,7 +51,6 @@ function displaySetBonuses(parent_id,build) { } mock_item.set("powders", []); displayExpandedItem(mock_item, set_elem.id); - console.log(mock_item); } } diff --git a/js/optimize.js b/js/optimize.js index fdce392..2343d47 100644 --- a/js/optimize.js +++ b/js/optimize.js @@ -19,7 +19,7 @@ function optimizeStrDex() { total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); // Calculate total 3rd spell damage - let spell = spell_table[player_build.weapon.get("type")][2]; + let spell = spell_table[player_build.weapon.statMap.get("type")][2]; const stats = player_build.statMap; let critChance = skillPointsToPercentage(total_skillpoints[1]); let save_damages = []; @@ -41,8 +41,7 @@ function optimizeStrDex() { if (part.type === "damage") { let _results = calculateSpellDamage(stats, part.conversion, stats.get("sdRaw"), stats.get("sdPct"), - part.multiplier / 100, player_build.weapon, total_skillpoints, - player_build.damageMultiplier, player_build.externalStats); + part.multiplier / 100, player_build.weapon.statMap, total_skillpoints, 1); let totalDamNormal = _results[0]; let totalDamCrit = _results[1]; let results = _results[2]; @@ -75,27 +74,16 @@ function optimizeStrDex() { str_bonus -= 1; dex_bonus += 1; - } - // TODO: reduce duplicated code, @calculateBuild - let skillpoints = player_build.total_skillpoints; - let delta_total = 0; + console.log(best_skillpoints); + + // TODO do not merge for performance reasons for (let i in skp_order) { - let manual_assigned = best_skillpoints[i]; - let delta = manual_assigned - skillpoints[i]; - skillpoints[i] = manual_assigned; - player_build.base_skillpoints[i] += delta; - delta_total += delta; + skp_inputs[i].input_field.value = best_skillpoints[i]; + skp_inputs[i].mark_dirty(); } - player_build.assigned_skillpoints += delta_total; - - try { - calculateBuildStats(); - if (player_build.errored) - throw new ListError(player_build.errors); - } - catch (error) { - handleBuilderError(error); + for (let i in skp_order) { + skp_inputs[i].update(); } } From d8d5b6caf643b6b9abb05e643a4229b7d74a66df Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 07:29:25 -0700 Subject: [PATCH 048/155] Minor code cleanup combine code in corners case combine cases in horiz. drawing --- js/display_atree.js | 41 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 0758189..551a233 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -54,18 +54,10 @@ function construct_AT(elem, tree) { document.getElementById("atree-row-" + i).children[node.display.col].appendChild(connector); resolve_connector(document.getElementById("atree-row-" + i).children[node.display.col]); } - // connect left - for (let i = parent_node.display.col + 1; i < node.display.col; i++) { - let connector = connect_elem.cloneNode() - connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; - connector.classList.add("rotate-90"); - connector.id = "r" + parent_node.display.row + "-c" + i + "-line" - document.getElementById("atree-row-" + parent_node.display.row).children[i].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i]); - } - - // connect right - for (let i = parent_node.display.col - 1; i > node.display.col; i--) { + // connect horizontally + let left = Math.min(parent_node.display.col, node.display.col); + let max = Math.max(parent_node.display.col, node.display.col); + for (let i = min + 1; i < max; i++) { let connector = connect_elem.cloneNode() connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; connector.classList.add("rotate-90"); @@ -75,23 +67,18 @@ function construct_AT(elem, tree) { } // connect corners - if (parent_node.display.col > node.display.col && (parent_node.display.row != node.display.row)) { - let connector = connect_elem.cloneNode() - connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - connector.classList.add("rotate-180"); - connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" - document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); - } - if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { - let connector = connect_elem.cloneNode() - connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - connector.classList.add("rotate-270"); - connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" - document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; + connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" + document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); + if (parent_node.display.col > node.display.col && (parent_node.display.row != node.display.row)) { + connector.classList.add("rotate-180"); } + else {//if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { + connector.classList.add("rotate-270"); + } + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); } // create node From e0f2dde9535d896016bcb79ee8c10912f91b2977 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 07:42:58 -0700 Subject: [PATCH 049/155] Fix typo --- js/display_atree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/display_atree.js b/js/display_atree.js index 551a233..162488e 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -55,7 +55,7 @@ function construct_AT(elem, tree) { resolve_connector(document.getElementById("atree-row-" + i).children[node.display.col]); } // connect horizontally - let left = Math.min(parent_node.display.col, node.display.col); + let min = Math.min(parent_node.display.col, node.display.col); let max = Math.max(parent_node.display.col, node.display.col); for (let i = min + 1; i < max; i++) { let connector = connect_elem.cloneNode() From b5f05cb1e6b23ff6187e0a8134d346c415c67db5 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 07:43:32 -0700 Subject: [PATCH 050/155] Whoops -- missing case --- js/display_atree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/display_atree.js b/js/display_atree.js index 162488e..85c2872 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -75,7 +75,7 @@ function construct_AT(elem, tree) { if (parent_node.display.col > node.display.col && (parent_node.display.row != node.display.row)) { connector.classList.add("rotate-180"); } - else {//if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { + else if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { connector.classList.add("rotate-270"); } resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); From c42e96d3607f62165330c5828e3807fa1a1e59a9 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 07:53:55 -0700 Subject: [PATCH 051/155] Finally fixed bugs -- please test your code before pushing bad hpp --- js/display_atree.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 85c2872..8d2cf7a 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -68,17 +68,19 @@ function construct_AT(elem, tree) { // connect corners - let connector = connect_elem.cloneNode() - connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" - document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); - if (parent_node.display.col > node.display.col && (parent_node.display.row != node.display.row)) { - connector.classList.add("rotate-180"); + if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) { + let connector = connect_elem.cloneNode() + connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; + connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" + document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); + if (parent_node.display.col > node.display.col) { + connector.classList.add("rotate-180"); + } + else {// if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { + connector.classList.add("rotate-270"); + } + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); } - else if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { - connector.classList.add("rotate-270"); - } - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); } // create node From d68af2f4c4ef837281e7f124b4d1fbc5a69fa4a5 Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 23 Jun 2022 22:00:04 +0700 Subject: [PATCH 052/155] fix archer tree --- js/atree_constants.js | 175 ++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 73 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 41cfb16..55e0032 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -1,15 +1,15 @@ const atrees = { "Archer": [ - { + { "display_name": "Arrow Shield", "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", - "archetype": "", - "archetype_req": 0, - "parents": ["Power Shots", "Cheaper Escape"], + "archetype": "", + "archetype_req": 0, + "parents": ["Power Shots", "Cheaper Escape"], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 9, "col": 6 @@ -18,17 +18,17 @@ const atrees = "duration": 60 }, "effects": [ - { + { "type": "replace_spell", "name": "Arrow Shield", "cost": 30, - "display_text": "Max Damage", - "base_spell": 4, - "spell_type": "damage", + "display_text": "Max Damage", + "base_spell": 4, + "spell_type": "damage", "scaling": "spell", - "display": "", + "display": "", "parts": [ - { + { "name": "Shield Damage", "type": "damage", "multipliers": [90, 0, 0, 0, 0, 10] @@ -44,7 +44,7 @@ const atrees = } ] }, - + { "display_name": "Escape", "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", @@ -422,7 +422,7 @@ const atrees = "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", "archetype": "", "archetype_req": 0, - "parents": ["Guardian Angels"], + "parents": ["Guardian Angels", "Cheaper Arrow Storm"], "dependencies": [], "blockers": ["Phantom Ray"], "cost": 2, @@ -480,7 +480,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 23, + "row": 22, "col": 6 }, "properties": {}, @@ -499,12 +499,12 @@ const atrees = "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", "archetype": "Sharpshooter", "archetype_req": 4, - "parents": ["More Focus"], + "parents": ["More Focus", "Traveler"], "dependencies": ["Focus"], "blockers": [], "cost": 2, "display": { - "row": 26, + "row": 25, "col": 4 }, "properties": { @@ -542,7 +542,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 27, + "row": 26, "col": 1 }, "properties": { @@ -577,7 +577,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 27, + "row": 26 , "col": 5 }, "properties": { @@ -624,7 +624,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 29, + "row": 28, "col": 4 }, "properties": { @@ -648,7 +648,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 29, + "row": 28, "col": 8 }, "properties": { @@ -675,7 +675,7 @@ const atrees = "blockers": ["Grappling Hook"], "cost": 2, "display": { - "row": 32, + "row": 31, "col": 0 }, "properties": { @@ -695,12 +695,12 @@ const atrees = "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", "archetype": "Sharpshooter", "archetype_req": 5, - "parents": ["Shocking Bomb", "Better Arrow Shield"], + "parents": ["Shocking Bomb", "Better Arrow Shield", "Cheaper Arrow Storm (2)"], "dependencies": ["Focus"], "blockers": [], "cost": 2, "display": { - "row": 32, + "row": 31, "col": 5 }, "properties": { @@ -721,12 +721,12 @@ const atrees = "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", "archetype": "Trapper", "archetype_req": 0, - "parents": ["Initiator", "Cheaper Arrow Storm"], + "parents": ["Initiator", "Cheaper Arrow Storm (2)"], "dependencies": ["Arrow Shield"], "blockers": [], "cost": 2, "display": { - "row": 33, + "row": 32, "col": 7 }, "properties": { @@ -751,7 +751,7 @@ const atrees = "blockers": ["Phantom Ray"], "cost": 2, "display": { - "row": 34, + "row": 33, "col": 0 }, "properties": {}, @@ -777,7 +777,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 38, + "row": 37, "col": 1 }, "properties": { @@ -803,7 +803,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 38, + "row": 37, "col": 4 }, "properties": { @@ -849,12 +849,12 @@ const atrees = "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", "archetype": "", "archetype_req": 0, - "parents": ["Grappling Hook", "More Shields"], + "parents": ["Cheaper Escape (2)"], "dependencies": [], "blockers": [], "cost": 2, "display": { - "row": 38, + "row": 37, "col": 7 }, "properties": { @@ -881,7 +881,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 39, + "row": 38, "col": 6 }, "properties": { @@ -902,13 +902,13 @@ const atrees = "desc": "Enemies near you will be slowed down.", "archetype": "", "archetype_req": 0, - "parents": ["Geyser Stomp", "More Focus"], + "parents": ["Geyser Stomp", "More Focus (2)"], "dependencies": [], "blockers": [], "cost": 2, "display": { - "row": 40, - "col": 4 + "row": 39, + "col": 2 }, "properties": { "range": 2.5, @@ -925,7 +925,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 41, + "row": 40, "col": 1 }, "properties": { @@ -956,12 +956,12 @@ const atrees = "desc": "Allow you to place +6 Traps, but with reduced damage and range.", "archetype": "Trapper", "archetype_req": 10, - "parents": ["Grape Bomb", "Cheaper Arrow Bomb"], + "parents": ["Grape Bomb", "Cheaper Arrow Bomb (2)"], "dependencies": ["Basaltic Trap"], "blockers": [], "cost": 2, "display": { - "row": 41, + "row": 40, "col": 7 }, "properties": { @@ -1299,7 +1299,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 26, + "row": 25, "col": 0 }, "properties": {}, @@ -1323,7 +1323,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 27, + "row": 26, "col": 8 }, "properties": { @@ -1340,7 +1340,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 29, + "row": 28, "col": 6 }, "properties": { @@ -1365,7 +1365,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 30, + "row": 29, "col": 1 }, "properties": { @@ -1382,7 +1382,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 32, + "row": 31, "col": 2 }, "properties": { @@ -1409,7 +1409,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 32, + "row": 31, "col": 8 }, "properties": { @@ -1427,12 +1427,12 @@ const atrees = "desc": "+30% Critical Hit Damage", "archetype": "", "archetype_req": 0, - "parents": ["Arrow Bomb"], + "parents": ["Better Guardian Angels", "Cheaper Arrow Shield", "Arrow Hurricane"], "dependencies": [], "blockers": [], "cost": 1, "display": { - "row": 34, + "row": 33, "col": 2 }, "properties": { @@ -1452,7 +1452,7 @@ const atrees = ] }, { - "display_name": "Cheaper Arrow Shield (2)", + "display_name": "Cheaper Arrow Shield", "desc": "Reduce the Mana cost of Arrow Shield.", "archetype": "", "archetype_req": 0, @@ -1461,7 +1461,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 34, + "row": 33, "col": 4 }, "properties": { @@ -1479,12 +1479,12 @@ const atrees = "desc": "Arrow Bomb's self-damage will knockback you farther away.", "archetype": "", "archetype_req": 0, - "parents": ["Cheaper Arrow Storm", "Initiator"], + "parents": ["Cheaper Arrow Storm (2)", "Initiator"], "dependencies": ["Arrow Bomb"], "blockers": [], "cost": 1, "display": { - "row": 34, + "row": 33, "col": 6 }, "properties": { @@ -1495,12 +1495,12 @@ const atrees = "desc": "Reduce the Mana cost of Escape.", "archetype": "", "archetype_req": 0, - "parents": ["Arrow Storm", "Arrow Shield"], + "parents": ["Call of the Hound", "Decimator"], "dependencies": [], "blockers": [], "cost": 1, "display": { - "row": 35, + "row": 34, "col": 7 }, "properties": { @@ -1519,12 +1519,12 @@ const atrees = "desc": "Increase your Grappling Hook's range, speed and strength.", "archetype": "Trapper", "archetype_req": 5, - "parents": ["Cheaper Escape"], + "parents": ["Cheaper Escape (2)"], "dependencies": ["Grappling Hook"], "blockers": [], "cost": 1, "display": { - "row": 36, + "row": 35, "col": 8 }, "properties": { @@ -1541,7 +1541,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 41, + "row": 40, "col": 5 }, "properties": { @@ -1565,7 +1565,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 20, + "row": 25, "col": 7 }, "properties": { @@ -1593,7 +1593,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 23, + "row": 28, "col": 2 }, "properties": { @@ -1613,7 +1613,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 27, + "row": 34, "col": 1 }, "properties": { @@ -1639,7 +1639,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 30, + "row": 38, "col": 0 }, "properties": { @@ -1685,7 +1685,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 14, + "row": 17, "col": 0 }, "properties": { @@ -1731,7 +1731,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 15, + "row": 19, "col": 4 }, "properties": { @@ -1762,7 +1762,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 18, + "row": 22, "col": 4 }, "properties": { @@ -1788,12 +1788,12 @@ const atrees = "desc": "Add +2 max Focus", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": ["Crepuscular Ray"], + "parents": ["Crepuscular Ray", "Snow Storm"], "dependencies": [], "blockers": [], "cost": 1, "display": { - "row": 32, + "row": 39, "col": 4 }, "properties": { @@ -1824,7 +1824,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 20, + "row": 25, "col": 2 }, "properties": { @@ -1859,7 +1859,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 17, + "row": 22, "col": 8 }, "properties": { @@ -1879,7 +1879,7 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 31, + "row": 38, "col": 8 }, "properties": { @@ -1899,7 +1899,7 @@ const atrees = "blockers": [], "cost": 2, "display": { - "row": 14, + "row": 17, "col": 2 }, "properties": { @@ -1985,6 +1985,35 @@ const atrees = "multipliers": [120, 0, 0, 0, 0, 80] } ] + }, + { + "display_name": "Decimator", + "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Arrow Shield"], + "dependencies": ["Phantom Ray"], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 5 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Phantom Ray hits", + "output": { + "type": "stat", + "name": "PhRayDmg" + }, + "scaling": 10, + "max": 50 + } + ] } ], @@ -2341,13 +2370,13 @@ const atrees = "desc": "After using Charge, violently crash down into the ground and deal damage", "archetype": "", "archetype_req": 0, - "parents": ["Charge"], + "parents": ["Uppercut"], "dependencies": [], "blockers": [], "cost": 1, "display": { - "row": 6, - "col": 2 + "row": 9, + "col": 1 }, "properties": { "aoe": 4 @@ -2368,13 +2397,13 @@ const atrees = "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", "archetype": "Fallen", "archetype_req": 0, - "parents": ["Heavy Impact"], + "parents": ["Charge"], "dependencies": [], "blockers": ["Tougher Skin"], "cost": 1, "display": { - "row": 7, - "col": 0 + "row": 6, + "col": 2 }, "properties": { }, From 39bbf45b12c0d57af2afd25a0dcb284987bf85af Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 23 Jun 2022 22:00:15 +0700 Subject: [PATCH 053/155] change connector logic --- js/display_atree.js | 81 +++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 29 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 0758189..19e3f22 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -14,28 +14,36 @@ function construct_AT(elem, tree) { let node = tree[i]; // create rows if not exist - if (document.getElementById("atree-row-" + node.display.row) == null) { - for (let j = 0; j <= node.display.row; j++) { - if (document.getElementById("atree-row-" + j) == null) { - let row = document.createElement('div'); - row.classList.add("row"); - row.id = "atree-row-" + j; - //was causing atree rows to be 0 height - console.log(elem.scrollWidth / 9); - row.style.minHeight = elem.scrollWidth / 9 + "px"; - //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; + let missing_rows = [node.display.row]; + + for (let parent of node.parents) { + missing_rows.push(tree.find(object => {return object.display_name === parent;}).display.row); + } + for (let missing_row of missing_rows) { + if (document.getElementById("atree-row-" + missing_row) == null) { + for (let j = 0; j <= missing_row; j++) { + if (document.getElementById("atree-row-" + j) == null) { + let row = document.createElement('div'); + row.classList.add("row"); + row.id = "atree-row-" + j; + //was causing atree rows to be 0 height + console.log(elem.scrollWidth / 9); + row.style.minHeight = elem.scrollWidth / 9 + "px"; + //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; - for (let k = 0; k < 9; k++) { - col = document.createElement('div'); - col.classList.add('col', 'px-0'); - col.style.minHeight = elem.scrollWidth / 9 + "px"; - row.appendChild(col); + for (let k = 0; k < 9; k++) { + col = document.createElement('div'); + col.classList.add('col', 'px-0'); + col.style.minHeight = elem.scrollWidth / 9 + "px"; + row.appendChild(col); + }; + elem.appendChild(row); }; - elem.appendChild(row); }; }; - }; + } + let connector_list = [] // create connectors based on parent location @@ -52,7 +60,7 @@ function construct_AT(elem, tree) { connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; connector.id = "r" + i + "-c" + node.display.col + "-line" document.getElementById("atree-row-" + i).children[node.display.col].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + i).children[node.display.col]); + resolve_connector(document.getElementById("atree-row-" + i).children[node.display.col], node); } // connect left for (let i = parent_node.display.col + 1; i < node.display.col; i++) { @@ -61,7 +69,7 @@ function construct_AT(elem, tree) { connector.classList.add("rotate-90"); connector.id = "r" + parent_node.display.row + "-c" + i + "-line" document.getElementById("atree-row-" + parent_node.display.row).children[i].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i]); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i], node); } // connect right @@ -71,7 +79,7 @@ function construct_AT(elem, tree) { connector.classList.add("rotate-90"); connector.id = "r" + parent_node.display.row + "-c" + i + "-line" document.getElementById("atree-row-" + parent_node.display.row).children[i].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i]); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i], node); } // connect corners @@ -81,7 +89,7 @@ function construct_AT(elem, tree) { connector.classList.add("rotate-180"); connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col], node); } if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { @@ -90,7 +98,7 @@ function construct_AT(elem, tree) { connector.classList.add("rotate-270"); connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col], node); } } @@ -165,11 +173,12 @@ function construct_AT(elem, tree) { }; // resolve connector conflict -function resolve_connector(elem) { +function resolve_connector(elem, node) { if (elem.children.length < 2) {return false;} let line = 0; let angle = 0; - let t = 0 + let t = 0; + let c = 0; for (let child of elem.children) { let type = child.id.split("-")[2] if (type == "line") { @@ -178,22 +187,36 @@ function resolve_connector(elem) { angle += 1; } else if (type == "t") { t += 1; + } else if (type == "c") { + c += 1; } } let connect_elem = document.createElement("div"); - if ((line == angle) || (angle == 1 && t == 1)) { + //if ((line == 1 && angle == 1) || (line == 2 && angle == 2) || (line == 1 && angle == 2) || (angle == 1 && t == 1)) { + // connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;" + // connect_elem.classList.add("rotate-180") + // connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-t" + // elem.replaceChildren(connect_elem); + //} + //if (line > 1 && angle == 0) { + // elem.replaceChildren(elem.children[0]) + //} + //if (t == 1 && line == 1) { + // connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;" + // elem.replaceChildren(connect_elem); + //} + if ((line == 1 && angle == 1)) { connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;" connect_elem.classList.add("rotate-180") connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-t" elem.replaceChildren(connect_elem); } - if (line > 1 && angle == 0) { - elem.replaceChildren(elem.children[0]) - } - if (t == 1 && line == 1) { + if (node.parents.length == 3 && t == 1) { connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;" + connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-c" elem.replaceChildren(connect_elem); } + elem.replaceChildren(elem.children[0]) } From bb98b2c5e43f382e0063e5622fc3cf818d951a49 Mon Sep 17 00:00:00 2001 From: reschan Date: Thu, 23 Jun 2022 22:11:47 +0700 Subject: [PATCH 054/155] fix archer tree (2) --- js/display_atree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/display_atree.js b/js/display_atree.js index 6e072cb..86d776b 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -87,7 +87,7 @@ function construct_AT(elem, tree) { else {// if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { connector.classList.add("rotate-270"); } - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col]); + resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col], node); } } From fe3445c16b1558785cfc52475b732a8543b215ed Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 09:36:50 +0700 Subject: [PATCH 055/155] fix: auto connector logic --- js/display_atree.js | 116 +++++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 86d776b..82b2a94 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -1,4 +1,5 @@ -// placeholder name, follow new schema +let atree_map; +let atree_connectors_map; function construct_AT(elem, tree) { console.log("constructing ability tree UI"); document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later @@ -10,6 +11,12 @@ function construct_AT(elem, tree) { active_row.textContent = "Active:"; document.getElementById("atree-active").appendChild(active_row); + atree_map = new Map(); + atree_connectors_map = new Map() + for (let i of tree) { + atree_map.set(i.display_name, {display: i.display, parents: i.parents, connectors: []}); + } + for (let i = 0; i < tree.length; i++) { let node = tree[i]; @@ -31,12 +38,13 @@ function construct_AT(elem, tree) { row.style.minHeight = elem.scrollWidth / 9 + "px"; //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; - for (let k = 0; k < 9; k++) { col = document.createElement('div'); col.classList.add('col', 'px-0'); col.style.minHeight = elem.scrollWidth / 9 + "px"; row.appendChild(col); + + atree_connectors_map.set(j + "," + k, []) }; elem.appendChild(row); }; @@ -45,49 +53,47 @@ function construct_AT(elem, tree) { } - let connector_list = [] + let connector_list = []; // create connectors based on parent location for (let parent of node.parents) { - let parent_node = tree.find(object => { - return object.display_name === parent; - }); + let parent_node = atree_map.get(parent); let connect_elem = document.createElement("div"); connect_elem.style = "background-size: cover; width: 100%; height: 100%;"; // connect up for (let i = node.display.row - 1; i > parent_node.display.row; i--) { - let connector = connect_elem.cloneNode() + let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; - connector.id = "r" + i + "-c" + node.display.col + "-line" - document.getElementById("atree-row-" + i).children[node.display.col].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + i).children[node.display.col], node); + atree_map.get(node.display_name).connectors.push(i + "," + node.display.col); + atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line"}); + resolve_connector(i + "," + node.display.col, node); } // connect horizontally let min = Math.min(parent_node.display.col, node.display.col); let max = Math.max(parent_node.display.col, node.display.col); for (let i = min + 1; i < max; i++) { - let connector = connect_elem.cloneNode() + let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; connector.classList.add("rotate-90"); - connector.id = "r" + parent_node.display.row + "-c" + i + "-line" - document.getElementById("atree-row-" + parent_node.display.row).children[i].appendChild(connector); - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[i], node); + atree_map.get(node.display_name).connectors.push(parent_node.display.row + "," + i); + atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line"}); + resolve_connector(parent_node.display.row + "," + i, node); } // connect corners if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) { - let connector = connect_elem.cloneNode() + let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - connector.id = "r" + parent_node.display.row + "-c" + node.display.col + "-angle" - document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col].appendChild(connector); + atree_map.get(node.display_name).connectors.push(parent_node.display.row + "," + node.display.col); + atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle"}); if (parent_node.display.col > node.display.col) { connector.classList.add("rotate-180"); } else {// if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { connector.classList.add("rotate-270"); } - resolve_connector(document.getElementById("atree-row-" + parent_node.display.row).children[node.display.col], node); + resolve_connector(parent_node.display.row + "," + node.display.col, node); } } @@ -159,53 +165,55 @@ function construct_AT(elem, tree) { }); document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; + + atree_render_connection(); }; // resolve connector conflict -function resolve_connector(elem, node) { - if (elem.children.length < 2) {return false;} - let line = 0; - let angle = 0; - let t = 0; - let c = 0; - for (let child of elem.children) { - let type = child.id.split("-")[2] - if (type == "line") { - line += 1; - } else if (type == "angle") { - angle += 1; - } else if (type == "t") { - t += 1; - } else if (type == "c") { - c += 1; +function resolve_connector(pos, node) { + if (atree_connectors_map.get(pos).length < 2) {return false;} + + let line = false; + let angle = false; + let t = false; + for (let i of atree_connectors_map.get(pos)) { + if (i.type == "line") { + line += true; + } else if (i.type == "angle") { + angle += true; + } else if (i.type == "t") { + t += true; } } let connect_elem = document.createElement("div"); - //if ((line == 1 && angle == 1) || (line == 2 && angle == 2) || (line == 1 && angle == 2) || (angle == 1 && t == 1)) { - // connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;" - // connect_elem.classList.add("rotate-180") - // connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-t" - // elem.replaceChildren(connect_elem); - //} - //if (line > 1 && angle == 0) { - // elem.replaceChildren(elem.children[0]) - //} - //if (t == 1 && line == 1) { - // connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;" - // elem.replaceChildren(connect_elem); - //} - if ((line == 1 && angle == 1)) { + if ((line && angle)) { connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;" connect_elem.classList.add("rotate-180") - connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-t" - elem.replaceChildren(connect_elem); + atree_connectors_map.set(pos, [{connector: connect_elem, type: "t"}]) } - if (node.parents.length == 3 && t == 1) { + if (node.parents.length == 3 && t && atree_same_row(node)) { connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;" - connect_elem.id = elem.children[0].id.split("-")[0] + "-" + elem.children[0].id.split("-")[1] + "-c" - elem.replaceChildren(connect_elem); + atree_connectors_map.set(pos, [{connector: connect_elem, type: "c"}]) + } + // override the conflict with the first children + atree_connectors_map.set(pos, [atree_connectors_map.get(pos)[0]]) +} + +// check if a node doesn't have same row w/ its parents (used to solve conflict) +function atree_same_row(node) { + for (let i of node.parents) { + if (node.display.row == atree_map.get(i).display.row) { return false; } + } + return true; +} + +// draw the connector onto the screen +function atree_render_connection() { + for (let i of atree_connectors_map.keys()) { + if (atree_connectors_map.get(i).length != 0) { + document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector) + } } - elem.replaceChildren(elem.children[0]) } From debbeee9f81faaffd94ea517d4a56239d52735c2 Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 09:49:24 +0700 Subject: [PATCH 056/155] for reference when creating beta atree data --- js/atree_constants_old.js | 171 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 js/atree_constants_old.js diff --git a/js/atree_constants_old.js b/js/atree_constants_old.js new file mode 100644 index 0000000..e325247 --- /dev/null +++ b/js/atree_constants_old.js @@ -0,0 +1,171 @@ +const atrees_old = { + "Assassin": [ + {"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4}, + {"title": "Dagger Proficiency I", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 2, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 3, "col": 4}, + {"title": "Double Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 5, "col": 4}, + {"title": "Dash", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 2}, + {"title": "Smoke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 6}, + {"title": "Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 1}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 8, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 6}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 8, "col": 8}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 8}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 1}, + {"title": "Backstab", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 90, "row": 10, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 10, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 7}, + {"title": "Fatality", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 11, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 0}, + {"title": "Violent Vortex", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 11, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 2}, + {"title": "Vanish", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 13, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 13, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 7}, + {"title": "Lacerate", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 15, "col": 1}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 8}, + {"title": "Wall of Smoke", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 8}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 16, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 0}, + {"title": "Silent Killer", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 16, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 2}, + {"title": "Shadow Travel", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 5}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 8}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 8}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 4}, + {"title": "Exploding Clones", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 19, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 18, "col": 6}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 7}, + {"title": "Weightless", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 20, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 20, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 4}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 20, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 21, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 6}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 8}, + {"title": "Dancing Blade", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 21, "col": 0}, + {"title": "Spin Attack Damage", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 21, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 3}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 22, "col": 1}, + {"title": "Marked", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 5}, + {"title": "Shurikens", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 8}, + {"title": "Far Reach", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, + {"title": "Stronger Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 5}, + {"title": "Psithurism", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 1}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 1}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 3}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 4}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 5}, + {"title": "Choke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 6}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 7}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 5}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 25, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 0}, + {"title": "Death Magnet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 25, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 2}, + {"title": "Cheaper Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 7}, + {"title": "Parry", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 7}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 1}, + {"title": "Fatal Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 3}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 3}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 27, "col": 6}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 6}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 27, "col": 8}, + {"title": "Wall Jump", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 8}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 28, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 0}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 1}, + {"title": "Harvester", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 4}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 7}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 7}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7 }, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 30, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 2 }, + {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 30, "col": 5}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 30, "col": 6}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 5}, + {"title": "Ricochet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 1}, + {"title": "Satsujin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 1}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 4}, + {"title": "Forbidden Art", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 4}, + {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 7}, + {"title": "Jasmine Bloom", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 7}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 32, "col": 0}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 0}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 2}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, + {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 5}, + {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8}, + ] +} \ No newline at end of file From 32ee15dc2b023cb0fe3c22026c5cb587087e0404 Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 09:49:54 +0700 Subject: [PATCH 057/155] delete empty atrees, move assassin to old --- js/atree_constants.js | 172 ------------------------------------------ 1 file changed, 172 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 55e0032..add793f 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -2016,176 +2016,6 @@ const atrees = ] } ], - - "Assassin": [ - {"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4}, - {"title": "Dagger Proficiency I", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 2, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 3, "col": 4}, - {"title": "Double Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 5, "col": 4}, - {"title": "Dash", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 2}, - {"title": "Smoke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 6}, - {"title": "Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 8, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 6}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 8, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 8}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 1}, - {"title": "Backstab", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 90, "row": 10, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 10, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 7}, - {"title": "Fatality", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 11, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 0}, - {"title": "Violent Vortex", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 11, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 2}, - {"title": "Vanish", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 13, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 13, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 7}, - {"title": "Lacerate", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 15, "col": 1}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 8}, - {"title": "Wall of Smoke", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 8}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 16, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 0}, - {"title": "Silent Killer", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 16, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 2}, - {"title": "Shadow Travel", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 8}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 8}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 4}, - {"title": "Exploding Clones", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 19, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 18, "col": 6}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 7}, - {"title": "Weightless", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 20, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 20, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 4}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 20, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 21, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 6}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 8}, - {"title": "Dancing Blade", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 21, "col": 0}, - {"title": "Spin Attack Damage", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 21, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 3}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 22, "col": 1}, - {"title": "Marked", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 5}, - {"title": "Shurikens", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 8}, - {"title": "Far Reach", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, - {"title": "Stronger Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 5}, - {"title": "Psithurism", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 1}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 1}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 5}, - {"title": "Choke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 6}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 7}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 5}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 25, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 0}, - {"title": "Death Magnet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 25, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 2}, - {"title": "Cheaper Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 7}, - {"title": "Parry", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 7}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 1}, - {"title": "Fatal Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 3}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 27, "col": 6}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 6}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 27, "col": 8}, - {"title": "Wall Jump", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 8}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 28, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 1}, - {"title": "Harvester", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 7}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7 }, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 30, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 2 }, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 30, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 30, "col": 6}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 5}, - {"title": "Ricochet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 1}, - {"title": "Satsujin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 4}, - {"title": "Forbidden Art", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 7}, - {"title": "Jasmine Bloom", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 32, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8}, - ], "Warrior": [ { "display_name": "Bash", @@ -4195,8 +4025,6 @@ const atrees = ] } ], - "Mage": [], - "Shaman": [] } const atree_example = [ From cba1bb5eb09baca8095e1a5fecfa46b01687975a Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 09:50:18 +0700 Subject: [PATCH 058/155] catch if given atree does not exist --- js/display_atree.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/display_atree.js b/js/display_atree.js index 82b2a94..8f0c08f 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -4,6 +4,8 @@ function construct_AT(elem, tree) { console.log("constructing ability tree UI"); document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later elem.innerHTML = ""; //reset the atree in the DOM + + if (tree === undefined) {return false;} // add in the "Active" title to atree let active_row = document.createElement("div"); From e86152af181b7b82f819c317648db05ab22dd09a Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 09:51:43 +0700 Subject: [PATCH 059/155] delete nonexist function call --- js/display_atree.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 8f0c08f..0c5254a 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -162,8 +162,6 @@ function construct_AT(elem, tree) { this.classList.add("atree-selected"); this.style.backgroundImage = 'url("../media/atree/node-selected.png")'; } - - toggle_connectors(connector_list); }); document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; From da97f593abd0b3face2b56e5aec4af206629e03e Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 10:01:31 +0700 Subject: [PATCH 060/155] remove old script --- py_script/atree_csv_to_json.py | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 py_script/atree_csv_to_json.py diff --git a/py_script/atree_csv_to_json.py b/py_script/atree_csv_to_json.py deleted file mode 100644 index 4c63d06..0000000 --- a/py_script/atree_csv_to_json.py +++ /dev/null @@ -1,24 +0,0 @@ -import csv -import json -import re - -with open('atree.csv', newline='') as csvfile: - res = "" - reader = csv.DictReader(csvfile) - for row in reader: - if not row["connector"]: - row["connector"] = False - else: - row["connector"] = True - row["row"] = int(row["row"]) - row["col"] = int(row["col"]) - if row["rotate"].isdigit(): - row["rotate"] = int(row["rotate"]) - else: - row.pop("rotate") - row["desc"] = re.sub("\n", " ", row["desc"]) - - resjson = json.dumps(row) - res += str(resjson) + ",\n" - - print(res) From beb47c7f3d361a817b5b1bfdd162b02988eb160e Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 20:01:31 -0700 Subject: [PATCH 061/155] Minified atree constants file; remove unused (old) script --- builder/index.html | 2 +- js/atree_constants_min.js | 2 ++ py_script/atree_csv_to_json.py | 24 ------------------------ 3 files changed, 3 insertions(+), 25 deletions(-) create mode 100644 js/atree_constants_min.js delete mode 100644 py_script/atree_csv_to_json.py diff --git a/builder/index.html b/builder/index.html index b0c21b7..1a14ed3 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1404,7 +1404,7 @@ - + diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js new file mode 100644 index 0000000..9379944 --- /dev/null +++ b/js/atree_constants_min.js @@ -0,0 +1,2 @@ +// Minified version of js/atree_constants.js +const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Assassin:[{title:"Spin Attack",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:4},{title:"Dagger Proficiency I",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:4},{title:"Double Spin",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:5,col:4},{title:"Dash",desc:"desc",image:"../media/atree/node.png",connector:!1,row:6,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:6,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:6,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:6,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:6,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:7,col:2},{title:"Smoke Bomb",desc:"desc",image:"../media/atree/node.png",connector:!1,row:8,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:7,col:6},{title:"Multihit",desc:"desc",image:"../media/atree/node.png",connector:!1,row:8,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:8,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:1},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:8,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:6},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:8,col:8},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:8},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:8},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:10,col:1},{title:"Backstab",desc:"desc",image:"../media/atree/node.png",connector:!1,row:11,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:90,row:10,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:10,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:11,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:10,col:7},{title:"Fatality",desc:"desc",image:"../media/atree/node.png",connector:!1,row:11,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:11,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:0},{title:"Violent Vortex",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:11,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:2},{title:"Vanish",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:13,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:14,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:15,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:14,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:15,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:13,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:14,col:7},{title:"Lacerate",desc:"desc",image:"../media/atree/node.png",connector:!1,row:15,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:15,col:1},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:16,col:1},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:15,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:16,col:5},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:15,col:8},{title:"Wall of Smoke",desc:"desc",image:"../media/atree/node.png",connector:!1,row:16,col:8},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:16,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:0},{title:"Silent Killer",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:16,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:2},{title:"Shadow Travel",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:5},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:8},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:8},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:18,col:4},{title:"Exploding Clones",desc:"desc",image:"../media/atree/node.png",connector:!1,row:19,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:18,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:19,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:20,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:19,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:20,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:18,col:6},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:18,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:19,col:7},{title:"Weightless",desc:"desc",image:"../media/atree/node.png",connector:!1,row:20,col:7},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:20,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:20,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:1},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:20,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:4},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:20,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:21,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:6},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:20,col:8},{title:"Dancing Blade",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:8},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:21,col:0},{title:"Spin Attack Damage",desc:"desc",image:"../media/atree/node.png",connector:!1,row:22,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:21,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:22,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:22,col:1},{title:"Marked",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:22,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:23,col:5},{title:"Shurikens",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:23,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:22,col:8},{title:"Far Reach",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:8},{title:"Stronger Multihit",desc:"desc",image:"../media/atree/node.png",connector:!1,row:24,col:5},{title:"Psithurism",desc:"desc",image:"../media/atree/node.png",connector:!1,row:24,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:24,col:1},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:1},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:24,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:25,col:5},{title:"Choke Bomb",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:6},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:25,col:7},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:8},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:26,col:5},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:25,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:0},{title:"Death Magnet",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:25,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:2},{title:"Cheaper Multihit",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:7},{title:"Parry",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:7},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:27,col:1},{title:"Fatal Spin",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:1},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:27,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:27,col:6},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:6},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:27,col:8},{title:"Wall Jump",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:8},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:28,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:29,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:29,col:1},{title:"Harvester",desc:"desc",image:"../media/atree/node.png",connector:!1,row:30,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:28,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:29,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:30,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:28,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:29,col:7},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:30,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:30,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:31,col:2},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:30,col:5},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:30,col:6},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:31,col:5},{title:"Ricochet",desc:"desc",image:"../media/atree/node.png",connector:!1,row:31,col:8},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:31,col:1},{title:"Satsujin",desc:"desc",image:"../media/atree/node.png",connector:!1,row:32,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:31,col:4},{title:"Forbidden Art",desc:"desc",image:"../media/atree/node.png",connector:!1,row:32,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:31,col:7},{title:"Jasmine Bloom",desc:"desc",image:"../media/atree/node.png",connector:!1,row:32,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:32,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:32,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:2},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:32,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:8},],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:[],blockers:[],cost:1,display:{row:20,col:6},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Thunderclap"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:31,col:4},properties:{aoe:2},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Thunderclap"],dependencies:[],blockers:[],cost:1,display:{row:32,col:5},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8},properties:{duration:3,aoe:12},effects:[]}],Mage:[],Shaman:[]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] diff --git a/py_script/atree_csv_to_json.py b/py_script/atree_csv_to_json.py deleted file mode 100644 index 4c63d06..0000000 --- a/py_script/atree_csv_to_json.py +++ /dev/null @@ -1,24 +0,0 @@ -import csv -import json -import re - -with open('atree.csv', newline='') as csvfile: - res = "" - reader = csv.DictReader(csvfile) - for row in reader: - if not row["connector"]: - row["connector"] = False - else: - row["connector"] = True - row["row"] = int(row["row"]) - row["col"] = int(row["col"]) - if row["rotate"].isdigit(): - row["rotate"] = int(row["rotate"]) - else: - row.pop("rotate") - row["desc"] = re.sub("\n", " ", row["desc"]) - - resjson = json.dumps(row) - res += str(resjson) + ",\n" - - print(res) From 3f3ed18455946a04431d5c498ba05d51fe2ac933 Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 20:10:02 -0700 Subject: [PATCH 062/155] Fix minified file / update --- js/atree_constants_min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js index 9379944..714b6d0 100644 --- a/js/atree_constants_min.js +++ b/js/atree_constants_min.js @@ -1,2 +1,2 @@ // Minified version of js/atree_constants.js -const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Assassin:[{title:"Spin Attack",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:4},{title:"Dagger Proficiency I",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:4},{title:"Double Spin",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:5,col:4},{title:"Dash",desc:"desc",image:"../media/atree/node.png",connector:!1,row:6,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:6,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:6,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:6,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:6,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:7,col:2},{title:"Smoke Bomb",desc:"desc",image:"../media/atree/node.png",connector:!1,row:8,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:7,col:6},{title:"Multihit",desc:"desc",image:"../media/atree/node.png",connector:!1,row:8,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:8,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:1},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:8,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:6},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:8,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:8,col:8},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:8},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:10,col:8},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:10,col:1},{title:"Backstab",desc:"desc",image:"../media/atree/node.png",connector:!1,row:11,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:9,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:90,row:10,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:10,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:11,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:10,col:7},{title:"Fatality",desc:"desc",image:"../media/atree/node.png",connector:!1,row:11,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:11,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:0},{title:"Violent Vortex",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:11,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:2},{title:"Vanish",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:13,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:13,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:14,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:15,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:14,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:15,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:12,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:13,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:14,col:7},{title:"Lacerate",desc:"desc",image:"../media/atree/node.png",connector:!1,row:15,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:15,col:1},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:16,col:1},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:15,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:16,col:5},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:15,col:8},{title:"Wall of Smoke",desc:"desc",image:"../media/atree/node.png",connector:!1,row:16,col:8},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:16,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:0},{title:"Silent Killer",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:16,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:2},{title:"Shadow Travel",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:5},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:17,col:8},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:18,col:8},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:18,col:4},{title:"Exploding Clones",desc:"desc",image:"../media/atree/node.png",connector:!1,row:19,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:18,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:19,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:20,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:19,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:20,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:18,col:6},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:18,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:19,col:7},{title:"Weightless",desc:"desc",image:"../media/atree/node.png",connector:!1,row:20,col:7},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:20,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:20,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:1},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:20,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:4},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:20,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:21,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:6},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:20,col:8},{title:"Dancing Blade",desc:"desc",image:"../media/atree/node.png",connector:!1,row:21,col:8},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:21,col:0},{title:"Spin Attack Damage",desc:"desc",image:"../media/atree/node.png",connector:!1,row:22,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:21,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:22,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:22,col:1},{title:"Marked",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:22,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:23,col:5},{title:"Shurikens",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:6},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:23,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:22,col:8},{title:"Far Reach",desc:"desc",image:"../media/atree/node.png",connector:!1,row:23,col:8},{title:"Stronger Multihit",desc:"desc",image:"../media/atree/node.png",connector:!1,row:24,col:5},{title:"Psithurism",desc:"desc",image:"../media/atree/node.png",connector:!1,row:24,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:24,col:1},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:1},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:3},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:24,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:4},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:25,col:5},{title:"Choke Bomb",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:6},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:25,col:7},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:25,col:8},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:26,col:5},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:25,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:0},{title:"Death Magnet",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:25,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:2},{title:"Cheaper Multihit",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:26,col:7},{title:"Parry",desc:"desc",image:"../media/atree/node.png",connector:!1,row:27,col:7},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:27,col:1},{title:"Fatal Spin",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:1},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:27,col:3},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:27,col:6},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:6},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:27,col:8},{title:"Wall Jump",desc:"desc",image:"../media/atree/node.png",connector:!1,row:28,col:8},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:28,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:29,col:0},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:29,col:1},{title:"Harvester",desc:"desc",image:"../media/atree/node.png",connector:!1,row:30,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:28,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:29,col:4},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:30,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:28,col:7},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:29,col:7},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:30,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:30,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:31,col:2},{image:"../media/atree/connect_t.png",connector:!0,rotate:180,row:30,col:5},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:30,col:6},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:31,col:5},{title:"Ricochet",desc:"desc",image:"../media/atree/node.png",connector:!1,row:31,col:8},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:31,col:1},{title:"Satsujin",desc:"desc",image:"../media/atree/node.png",connector:!1,row:32,col:1},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:31,col:4},{title:"Forbidden Art",desc:"desc",image:"../media/atree/node.png",connector:!1,row:32,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:31,col:7},{title:"Jasmine Bloom",desc:"desc",image:"../media/atree/node.png",connector:!1,row:32,col:7},{image:"../media/atree/connect_angle.png",connector:!0,rotate:180,row:32,col:0},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:0},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:32,col:2},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:2},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:32,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:5},{title:"Text",desc:"desc",image:"../media/atree/node.png",connector:!1,row:33,col:8},],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:[],blockers:[],cost:1,display:{row:20,col:6},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Thunderclap"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:31,col:4},properties:{aoe:2},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Thunderclap"],dependencies:[],blockers:[],cost:1,display:{row:32,col:5},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8},properties:{duration:3,aoe:12},effects:[]}],Mage:[],Shaman:[]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] +const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:[],blockers:[],cost:1,display:{row:20,col:6},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Thunderclap"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:31,col:4},properties:{aoe:2},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Thunderclap"],dependencies:[],blockers:[],cost:1,display:{row:32,col:5},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8},properties:{duration:3,aoe:12},effects:[]}]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] From 14ab20c4464fe96cf4ad7e3185974ce79443cb0f Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 20:18:08 -0700 Subject: [PATCH 063/155] Remove extra prints, make response time of nodes much faster hides a bug that we have where two updates are scheduled if you hit enter and tab... no....... --- js/builder_graph.js | 2 +- js/computation_graph.js | 4 ++-- js/display_atree.js | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index a3e0052..899fd6d 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -178,6 +178,7 @@ class ItemInputNode extends InputNode { } compute_func(input_map) { + console.log("Item update...." + Date.now()); const powdering = input_map.get('powdering'); // built on the assumption of no one will type in CI/CR letter by letter @@ -337,7 +338,6 @@ class WeaponInputDisplayNode extends ComputeNode { toggle_tab('atree-dropdown'); toggleButton('toggle-atree'); } - console.log(document.getElementById("toggle-atree").classList.contains("toggleOn")); } } diff --git a/js/computation_graph.js b/js/computation_graph.js index 90fe08c..0066186 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -139,8 +139,8 @@ class InputNode extends ComputeNode { constructor(name, input_field) { super(name); this.input_field = input_field; - this.input_field.addEventListener("input", () => calcSchedule(this, 5000)); - this.input_field.addEventListener("change", () => calcSchedule(this, 500)); + this.input_field.addEventListener("input", () => calcSchedule(this, 500)); + this.input_field.addEventListener("change", () => calcSchedule(this, 5)); //calcSchedule(this); Manually fire first update for better control } diff --git a/js/display_atree.js b/js/display_atree.js index 0c5254a..888383e 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -36,7 +36,6 @@ function construct_AT(elem, tree) { row.classList.add("row"); row.id = "atree-row-" + j; //was causing atree rows to be 0 height - console.log(elem.scrollWidth / 9); row.style.minHeight = elem.scrollWidth / 9 + "px"; //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; From 623037eda0394e4a04c4d969970ac928c3b65edb Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 22:33:17 -0700 Subject: [PATCH 064/155] HOTFIX: display weapon as the last part of copy for sharing --- js/build_encode_decode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/build_encode_decode.js b/js/build_encode_decode.js index 618bb71..c7c42c4 100644 --- a/js/build_encode_decode.js +++ b/js/build_encode_decode.js @@ -210,7 +210,7 @@ function shareBuild(build) { "> "+build.items[5].statMap.get("displayName")+"\n"+ "> "+build.items[6].statMap.get("displayName")+"\n"+ "> "+build.items[7].statMap.get("displayName")+"\n"+ - "> "+build.items[8].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]"; + "> "+build.items[15].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]"; copyTextToClipboard(text); document.getElementById("share-button").textContent = "Copied!"; } From 2b187743959f9faf62a751de29ca81cd0cd48acc Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 03:35:03 -0700 Subject: [PATCH 065/155] Wynn2 damage calculation --- js/build.js | 22 ++-- js/build_utils.js | 40 ++++++- js/builder_graph.js | 24 ++-- js/damage_calc.js | 272 ++++++++++++++++++++++++++++++-------------- js/display.js | 61 ++-------- js/optimize.js | 8 +- 6 files changed, 267 insertions(+), 160 deletions(-) diff --git a/js/build.js b/js/build.js index ccd25a6..fc57316 100644 --- a/js/build.js +++ b/js/build.js @@ -155,6 +155,17 @@ class Build{ let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"]; + let must_ids = [ + "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 + ] + //Create a map of this build's stats let statMap = new Map(); statMap.set("damageMultiplier", 1); @@ -163,6 +174,9 @@ class Build{ for (const staticID of staticIDs) { statMap.set(staticID, 0); } + for (const staticID of must_ids) { + statMap.set(staticID, 0); + } statMap.set("hp", levelToHPBase(this.level)); let major_ids = new Set(); @@ -211,13 +225,5 @@ class Build{ statMap.set("atkSpd", this.weapon.statMap.get("atkSpd")); this.statMap = statMap; - - this.aggregateStats(); - } - - aggregateStats() { - let statMap = this.statMap; - let weapon_stats = this.weapon.statMap; - statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]); } } diff --git a/js/build_utils.js b/js/build_utils.js index a92d38a..80db2f1 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -66,12 +66,23 @@ let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tom let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ]; let skpReqs = skp_order.map(x => x + "Req"); -let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "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", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "dmgMobs", "defMobs"]; +let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "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", "id", "majorIds", "damMobs", "defMobs", + +// 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 +]; let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ] //File reading for ID translations for JSON purposes let reversetranslations = new Map(); -let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]); +let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]); //does not include dmgMobs (wep tomes) and defMobs (armor tomes) for (const [k, v] of translations) { reversetranslations.set(v, k); @@ -103,7 +114,17 @@ let nonRolledIDs = [ "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", - "majorIds"]; + "majorIds", +// wynn2 damages. +"eDamAddMin","eDamAddMax", +"tDamAddMin","tDamAddMax", +"wDamAddMin","wDamAddMax", +"fDamAddMin","fDamAddMax", +"aDamAddMin","aDamAddMax", +"nDamAddMin","nDamAddMax", // neutral which is now an element +"damAddMin","damAddMax", // all +"rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). +]; let rolledIDs = [ "hprPct", "mr", @@ -131,13 +152,22 @@ let rolledIDs = [ "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", - "rainbowRaw", + "pDamRaw", "sprint", "sprintReg", "jh", "lq", "gXp", - "gSpd" + "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 ]; let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; diff --git a/js/builder_graph.js b/js/builder_graph.js index 899fd6d..63d08d1 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -313,9 +313,10 @@ class ItemDisplayNode extends ComputeNode { */ class WeaponInputDisplayNode extends ComputeNode { - constructor(name, image_field) { + constructor(name, image_field, dps_field) { super(name); this.image = image_field; + this.dps_field = dps_field; } compute_func(input_map) { @@ -324,6 +325,12 @@ class WeaponInputDisplayNode extends ComputeNode { const type = item.statMap.get('type'); this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png'); + let dps = get_base_dps(item.statMap); + if (isNaN(dps)) { + dps = dps[1]; + if (isNaN(dps)) dps = 0; + } + this.dps_field.textContent = dps; //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")) { @@ -577,9 +584,11 @@ class SpellDamageCalcNode extends ComputeNode { 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"), - part.multiplier / 100, weapon, skillpoints, damage_mult); + let tmp_conv = []; + for (let i in part.conversion) { + tmp_conv.push(part.conversion[i] * part.multiplier/100); + } + let results = calculateSpellDamage(stats, weapon, tmp_conv, true); spell_results.push(results); } else if (part.type === "heal") { // TODO: wynn2 formula @@ -651,8 +660,7 @@ function getMeleeStats(stats, weapon) { //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 results = calculateSpellDamage(stats, weapon_stats, [100, 0, 0, 0, 0, 0], false, true); let dex = skillpoints[1]; @@ -973,7 +981,6 @@ function builder_graph_init() { //new PrintNode(eq+'-debug').link_to(item_input); //document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm'); } - 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"); @@ -985,7 +992,8 @@ function builder_graph_init() { // weapon image changer node. let weapon_image = document.getElementById("weapon-img"); - new WeaponInputDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]); + let weapon_dps = document.getElementById("weapon-dps"); + new WeaponInputDisplayNode('weapon-type', weapon_image, weapon_dps).link_to(item_nodes[8]); // Level input node. let level_input = new InputNode('level-input', document.getElementById('level-choice')); diff --git a/js/damage_calc.js b/js/damage_calc.js index 490713f..058ba96 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -1,31 +1,67 @@ const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]); -// Calculate spell damage given a spell elemental conversion table, and a spell multiplier. -// If spell mult is 0, its melee damage and we don't multiply by attack speed. -function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) { - let buildStats = new Map(stats); - //6x for damages, normal min normal max crit min crit max - let powders = weapon.get("powders").slice(); - - // Array of neutral + ewtfa damages. Each entry is a pair (min, max). - let damages = []; - const rawDamages = buildStats.get("damageRaw"); - for (let i = 0; i < rawDamages.length; i++) { - const damage_vals = rawDamages[i].split("-").map(Number); - damages.push(damage_vals); +// GRR THIS MUTATES THE ITEM +function get_base_dps(item) { + const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))]; + //SUPER JANK @HPP PLS FIX + let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; + if (item.get("tier") !== "Crafted") { + let weapon_result = apply_weapon_powder(item); + let damages = weapon_result[0]; + let total_damage = 0; + for (const i in damage_keys) { + total_damage += damages[i][0] + damages[i][1]; + item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]); + } + total_damage = total_damage / 2; + return total_damage * attack_speed_mult; + } else { + let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; + let results_low = apply_weapon_powder(item, base_low); + let damage_low = results_low[2]; + let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; + let results_high = apply_weapon_powder(item, base_high); + let damage_high = results_high[2]; + + let total_damage_min = 0; + let total_damage_max = 0; + for (const i in damage_keys) { + total_damage_min += damage_low[i][0] + damage_low[i][1]; + total_damage_max += damage_high[i][0] + damage_high[i][1]; + item.set(damage_keys[i], damage_low[i][0]+"-"+damage_low[i][1]+"\u279c"+damage_high[i][0]+"-"+damage_high[i][1]); + } + total_damage_min = attack_speed_mult * total_damage_min / 2; + total_damage_max = attack_speed_mult * total_damage_max / 2; + return [total_damage_min, total_damage_max]; } +} + +/** + * weapon: Weapon to apply powder to + * damageBases: used by crafted + */ +function apply_weapon_powder(weapon, damageBases) { + let powders = weapon.get("powders").slice(); + + // Array of neutral + ewtfa damages. Each entry is a pair (min, max). + let damages = [ + weapon.get('nDam').split('-').map(Number), + weapon.get('eDam').split('-').map(Number), + weapon.get('tDam').split('-').map(Number), + weapon.get('wDam').split('-').map(Number), + weapon.get('fDam').split('-').map(Number), + weapon.get('aDam').split('-').map(Number) + ]; // Applying spell conversions let neutralBase = damages[0].slice(); let neutralRemainingRaw = damages[0].slice(); - //powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do. //Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA //1st round - apply each as ingred, 2nd round - apply as normal - if (weapon.get("tier") === "Crafted") { - let damageBases = buildStats.get("damageBases").slice(); + if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) { for (const p of powders.concat(weapon.get("ingredPowders"))) { let powder = powderStats[p]; //use min, max, and convert let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error @@ -34,26 +70,13 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 ); } //update all damages - if(!weapon.get("custom")) { - for (let i = 0; i < damages.length; i++) { - damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)]; - } + for (let i = 0; i < damages.length; i++) { + damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)]; } - neutralRemainingRaw = damages[0].slice(); neutralBase = damages[0].slice(); } - for (let i = 0; i < 5; ++i) { - let conversionRatio = spellConversions[i+1]/100; - let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); - let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]); - damages[i+1][0] = Math.floor(round_near(damages[i+1][0] + min_diff)); - damages[i+1][1] = Math.floor(round_near(damages[i+1][1] + max_diff)); - neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); - neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); - } - //apply powders to weapon for (const powderID of powders) { const powder = powderStats[powderID]; @@ -72,69 +95,148 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, damages[element+1][1] += powder.max; } + // The ordering of these two blocks decides whether neutral is present when converted away or not. + let present_elements = [] + for (const damage of damages) { + present_elements.push(damage[1] > 0); + } + + // The ordering of these two blocks decides whether neutral is present when converted away or not. damages[0] = neutralRemainingRaw; + return [damages, present_elements]; +} - let damageMult = damageMultiplier; - let melee = false; - // If we are doing melee calculations: - if (spellMultiplier == 0) { - spellMultiplier = 1; - melee = true; - } - else { - damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]; - } - //console.log(damages); - //console.log(damageMult); - rawModifier *= spellMultiplier * damageMultiplier; - let totalDamNorm = [0, 0]; - let totalDamCrit = [0, 0]; - let damages_results = []; - // 0th skillpoint is strength, 1st is dex. - let str = total_skillpoints[0]; - let strBoost = 1 + skillPointsToPercentage(str); - if(!melee){ - let baseDam = rawModifier * strBoost; - let baseDamCrit = rawModifier * (1 + strBoost); - totalDamNorm = [baseDam, baseDam]; - totalDamCrit = [baseDamCrit, baseDamCrit]; - } - let staticBoost = (pctModifier / 100.); - let skillBoost = [0]; - for (let i in total_skillpoints) { - skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get(skp_elements[i]+"DamPct") / 100.); +function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) { + // TODO: Roll all the loops together maybe + + // Array of neutral + ewtfa damages. Each entry is a pair (min, max). + // 1. Get weapon damage (with powders). + let weapon_result = apply_weapon_powder(weapon); + let weapon_damages = weapon_result[0]; + let present = weapon_result[1]; + + // 2. Conversions. + // 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here. + let damages = []; + const neutral_convert = conversions[0] / 100; + let weapon_min = 0; + let weapon_max = 0; + for (const damage of weapon_damages) { + let min_dmg = damage[0] * neutral_convert; + let max_dmg = damage[1] * neutral_convert; + damages.push([min_dmg, max_dmg]); + weapon_min += damage[0]; + weapon_max += damage[1]; } + // 2.2. Next, apply elemental conversions using damage computed in step 1.1. + // Also, track which elements are present. (Add onto those present in the weapon itself.) + for (let i = 1; i <= 5; ++i) { + if (conversions[i] > 0) { + damages[i][0] += conversions[i]/100 * weapon_min; + damages[i][1] += conversions[i]/100 * weapon_max; + present[i] = true; + } + } + + // Also theres prop and rainbow!! + const damage_elements = ['n'].concat(skp_elements); // netwfa + + if (!ignore_speed) { + // 3. Apply attack speed multiplier. Ignored for melee single hit + const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(weapon.get("atkSpd"))]; + for (let i = 0; i < 6; ++i) { + damages[i][0] *= attack_speed_mult; + damages[i][1] *= attack_speed_mult; + } + } + + // 4. Add additive damage. TODO: Is there separate additive damage? + for (let i = 0; i < 6; ++i) { + if (present[i]) { + damages[i][0] += stats.get(damage_elements[i]+'DamAddMin'); + damages[i][1] += stats.get(damage_elements[i]+'DamAddMax'); + } + } + + // 5. ID bonus. + let specific_boost_str = 'Md'; + if (use_spell_damage) { + specific_boost_str = 'Sd'; + } + // 5.1: %boost application + let skill_boost = [0]; // no neutral skillpoint booster + for (const skp of skp_order) { + skill_boost.push(skillPointsToPercentage(stats.get(skp))); + } + let static_boost = (stats.get(specific_boost_str.toLowerCase()+'Pct') + stats.get('damPct')) / 100; + + // These do not count raw damage. I think. Easy enough to change + let total_min = 0; + let total_max = 0; for (let i in damages) { - let damageBoost = 1 + skillBoost[i] + staticBoost; - damages_results.push([ - Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min - Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max - Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min - Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max - ]); - totalDamNorm[0] += damages_results[i][0]; - totalDamNorm[1] += damages_results[i][1]; - totalDamCrit[0] += damages_results[i][2]; - totalDamCrit[1] += damages_results[i][3]; + let damage_prefix = damage_elements[i] + specific_boost_str; + let damageBoost = 1 + skill_boost[i] + static_boost + (stats.get(damage_prefix+'Pct')/100); + damages[i][0] *= Math.max(damageBoost, 0); + damages[i][1] *= Math.max(damageBoost, 0); + // Collect total damage post %boost + total_min += damages[i][0]; + total_max += damages[i][1]; } - if (melee) { - totalDamNorm[0] += Math.max(strBoost*rawModifier, -damages_results[0][0]); - totalDamNorm[1] += Math.max(strBoost*rawModifier, -damages_results[0][1]); - totalDamCrit[0] += Math.max((strBoost+1)*rawModifier, -damages_results[0][2]); - totalDamCrit[1] += Math.max((strBoost+1)*rawModifier, -damages_results[0][3]); + + let total_elem_min = total_min - damages[0][0]; + let total_elem_max = total_max - damages[0][1]; + + // 5.2: Raw application. + let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw'); + let rainbow_raw = stats.get('r'+specific_boost_str+'Raw') + stats.get('rDamRaw'); + for (let i in damages) { + let damages_obj = damages[i]; + let damage_prefix = damage_elements[i] + specific_boost_str; + // Normie raw + let raw_boost = 0; + if (present[i]) { + raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw'); + } + // Next, rainraw and propRaw + let new_min = damages_obj[0] + raw_boost + (damages_obj[0] / total_min) * prop_raw; + let new_max = damages_obj[1] + raw_boost + (damages_obj[1] / total_max) * prop_raw; + if (i != 0) { // rainraw + new_min += (damages_obj[0] / total_elem_min) * rainbow_raw; + new_max += (damages_obj[1] / total_elem_max) * rainbow_raw; + } + damages_obj[0] = new_min; + damages_obj[1] = new_max; } - damages_results[0][0] += strBoost*rawModifier; - damages_results[0][1] += strBoost*rawModifier; - damages_results[0][2] += (strBoost + 1)*rawModifier; - damages_results[0][3] += (strBoost + 1)*rawModifier; - if (totalDamNorm[0] < 0) totalDamNorm[0] = 0; - if (totalDamNorm[1] < 0) totalDamNorm[1] = 0; - if (totalDamCrit[0] < 0) totalDamCrit[0] = 0; - if (totalDamCrit[1] < 0) totalDamCrit[1] = 0; + // 6. Strength boosters + // str/dex, as well as any other mutually multiplicative effects + let strBoost = 1 + skill_boost[1]; + let total_dam_norm = [0, 0]; + let total_dam_crit = [0, 0]; + let damages_results = []; + const damage_mult = stats.get("damageMultiplier"); - return [totalDamNorm, totalDamCrit, damages_results]; + for (const damage of damages) { + const res = [ + damage[0] * strBoost * damage_mult, // Normal min + damage[1] * strBoost * damage_mult, // Normal max + damage[0] * (strBoost + 1) * damage_mult, // Crit min + damage[1] * (strBoost + 1) * damage_mult, // Crit max + ]; + damages_results.push(res); + total_dam_norm[0] += res[0]; + total_dam_norm[1] += res[1]; + total_dam_crit[0] += res[2]; + total_dam_crit[1] += res[3]; + } + + if (total_dam_norm[0] < 0) total_dam_norm[0] = 0; + if (total_dam_norm[1] < 0) total_dam_norm[1] = 0; + if (total_dam_crit[0] < 0) total_dam_crit[0] = 0; + if (total_dam_crit[1] < 0) total_dam_crit[1] = 0; + + return [total_dam_norm, total_dam_crit, damages_results]; } diff --git a/js/display.js b/js/display.js index 8f7017d..631f0c0 100644 --- a/js/display.js +++ b/js/display.js @@ -174,50 +174,7 @@ function displayExpandedItem(item, parent_id){ // !elemental is some janky hack for elemental damage. // normals just display a thing. if (item.get("category") === "weapon") { - let stats = new Map(); - stats.set("atkSpd", item.get("atkSpd")); - stats.set("eDamPct", 0); - stats.set("tDamPct", 0); - stats.set("wDamPct", 0); - stats.set("fDamPct", 0); - stats.set("aDamPct", 0); - - //SUPER JANK @HPP PLS FIX - let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; - if (item.get("tier") !== "Crafted") { - stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]); - let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); - let damages = results[2]; - let total_damage = 0; - for (const i in damage_keys) { - total_damage += damages[i][0] + damages[i][1]; - item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]); - } - total_damage = total_damage / 2; - item.set("basedps", total_damage); - - } else { - stats.set("damageRaw", [item.get("nDamLow"), item.get("eDamLow"), item.get("tDamLow"), item.get("wDamLow"), item.get("fDamLow"), item.get("aDamLow")]); - stats.set("damageBases", [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]); - let resultsLow = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); - let damagesLow = resultsLow[2]; - stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]); - stats.set("damageBases", [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]); - let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined); - let damages = results[2]; - console.log(damages); - - let total_damage_min = 0; - let total_damage_max = 0; - for (const i in damage_keys) { - total_damage_min += damagesLow[i][0] + damagesLow[i][1]; - total_damage_max += damages[i][0] + damages[i][1]; - item.set(damage_keys[i], damagesLow[i][0]+"-"+damagesLow[i][1]+"\u279c"+damages[i][0]+"-"+damages[i][1]); - } - total_damage_min = total_damage_min / 2; - total_damage_max = total_damage_max / 2; - item.set("basedps", [total_damage_min, total_damage_max]); - } + item.set('basedps', get_base_dps(item)); } else if (item.get("category") === "armor") { } @@ -555,19 +512,18 @@ function displayExpandedItem(item, parent_id){ } if (item.get("category") === "weapon") { - let damage_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))]; let total_damages = item.get("basedps"); let base_dps_elem = document.createElement("p"); base_dps_elem.classList.add("left"); base_dps_elem.classList.add("itemp"); if (item.get("tier") === "Crafted") { - let base_dps_min = total_damages[0] * damage_mult; - let base_dps_max = total_damages[1] * damage_mult; + let base_dps_min = total_damages[0]; + let base_dps_max = total_damages[1]; base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3); } else { - base_dps_elem.textContent = "Base DPS: "+(total_damages * damage_mult); + base_dps_elem.textContent = "Base DPS: "+(total_damages); } parent_div.appendChild(document.createElement("p")); parent_div.appendChild(base_dps_elem); @@ -1502,9 +1458,12 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]); let part = spell["parts"][0]; - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("mdRaw"), stats.get("mdPct"), - 0, weapon, skillpoints, stats.get('damageMultiplier') * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100 + + let tmp_conv = []; + for (let i in part.conversion) { + tmp_conv.push(part.conversion[i] * part.multiplier[power-1]); + } + let _results = calculateSpellDamage(stats, weapon, tmp_conv, false); let critChance = skillPointsToPercentage(skillpoints[1]); let save_damages = []; diff --git a/js/optimize.js b/js/optimize.js index 2343d47..988da77 100644 --- a/js/optimize.js +++ b/js/optimize.js @@ -39,9 +39,11 @@ function optimizeStrDex() { let total_damage = 0; for (const part of spell_parts) { if (part.type === "damage") { - let _results = calculateSpellDamage(stats, part.conversion, - stats.get("sdRaw"), stats.get("sdPct"), - part.multiplier / 100, player_build.weapon.statMap, total_skillpoints, 1); + let tmp_conv = []; + for (let i in part.conversion) { + tmp_conv.push(part.conversion[i] * part.multiplier); + } + let _results = calculateSpellDamage(stats, player_build.weapon.statMap, tmp_conv, true); let totalDamNormal = _results[0]; let totalDamCrit = _results[1]; let results = _results[2]; From 83157f22c87772c44bf3fcd467f7178eec084647 Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 04:30:47 -0700 Subject: [PATCH 066/155] Add some atree images (not using wynn ones) --- media/atree/highlight_angle.png | Bin 0 -> 1042 bytes media/atree/highlight_c.png | Bin 0 -> 1027 bytes media/atree/highlight_line.png | Bin 0 -> 974 bytes media/atree/highlight_t.png | Bin 0 -> 632 bytes media/atree/node_0.png | Bin 0 -> 5644 bytes media/atree/node_0_blocked.png | Bin 0 -> 5163 bytes media/atree/node_1.png | Bin 0 -> 5384 bytes media/atree/node_1_blocked.png | Bin 0 -> 5066 bytes media/atree/node_2.png | Bin 0 -> 5421 bytes media/atree/node_2_blocked.png | Bin 0 -> 5111 bytes media/atree/node_3.png | Bin 0 -> 5218 bytes media/atree/node_3_blocked.png | Bin 0 -> 4913 bytes media/atree/node_4.png | Bin 0 -> 5277 bytes media/atree/node_4_blocked.png | Bin 0 -> 5031 bytes media/atree/node_highlight.png | Bin 0 -> 23515 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 media/atree/highlight_angle.png create mode 100644 media/atree/highlight_c.png create mode 100644 media/atree/highlight_line.png create mode 100644 media/atree/highlight_t.png create mode 100644 media/atree/node_0.png create mode 100644 media/atree/node_0_blocked.png create mode 100644 media/atree/node_1.png create mode 100644 media/atree/node_1_blocked.png create mode 100644 media/atree/node_2.png create mode 100644 media/atree/node_2_blocked.png create mode 100644 media/atree/node_3.png create mode 100644 media/atree/node_3_blocked.png create mode 100644 media/atree/node_4.png create mode 100644 media/atree/node_4_blocked.png create mode 100644 media/atree/node_highlight.png diff --git a/media/atree/highlight_angle.png b/media/atree/highlight_angle.png new file mode 100644 index 0000000000000000000000000000000000000000..accb200b454435f89aae297ec862ec8b779e7f17 GIT binary patch literal 1042 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fn>1)7d$|)7e=epeR2rGbfdS zLF4??iQXD6filPH)An3CaY#p{d$&Z`6oF|{O0JnbpQ3hrKi52~+V^{3j?UDzn?Br- z(8*bIGwO!cQX8*nVPQwycKv&P?ut*=l;Gu`Ut2%_ZS~w*;6%F5>9z0JPM@>fxN(J< zwfXw1djq^X6qvWXUN(E~9J$`$!kT-5ORwG5n|k!WTe8y2SpiEq`DaF|tM5^{vwz-c zt$V_Jvrkt!{qWgwc_KHLy~bUQ533(-InOv%vh2~u#D^O;Ow&tV+;cYdxX?S{J{6tl zPt2QH{WR_csS#h^u)SWrwCDK}(uheI{gn5p~_ zbJ3l*gLMZl$lwFt-|xEXSiipXIya-8|L>dH^1IkxY<|xPHnC)PGsBmg-yt>ul`;H! zQw_1=?mGpB+HbZHv-8RY8ti`018Z^EZvEnizR~}S>ysPGuf64a)ttZjw(J%5`(fLy zSMcv!n^%6};qrTu4P|BwZ*v%K-DKFZnK2`s`NBE21#@{FtfdEyL@#qq?O(Rc#4M*N SH@bF%{O0NE=d#Wzp$P!rB&AFM literal 0 HcmV?d00001 diff --git a/media/atree/highlight_c.png b/media/atree/highlight_c.png new file mode 100644 index 0000000000000000000000000000000000000000..1aa028df6daccfbde7f4c9662ab8550ccfbd9431 GIT binary patch literal 1027 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fn=|)7d$|)7e=epeR2rGbfdS zLF4??iMAex14LT=gSRS)vRZdoq^!`35)dt%q9JzBDN}pPtAtON>@zM+ShB-{YaJ_l zz1V6#y>q*)c5pR4;O|&GdGey}DHZWS8G>3r9(=MpSN*);er-d5w&B@VGZ;+nnh6Jb z`lN>)J)phof&J!tJ5H%WD+h zZah`ly*X^v2DkZtS`V(-H|dGhRf9zlPCSJj0RfJJsy8|=tec#q==eNH`u~L2{gb^F z!v%X}xHX)c-~2lG|H1d1wRf&8nJzM=;hgyKpAw8ryB+qvvbcY|FW8>(&-nvW?|qq` zec+Ay%d5-8?3jLSYwUi#Y}p&e>DC7xOjew|N&Dx@mKeSY`^EXE7?`&^oS$XB{g|bn ze1pi@te6jA&EJ|V6^nn6j4;s1XI zZEYZPJ!eH1kYX(f@(cbC1Ps5o@dX0~I14-?iy0XB4ude`@%$AjzzF^6>Eakt5%>0% zp`Wv(2#ce1&41(OrG4Uy1d~=z_S~~6?&$xIv%da#_w!@&`S0(mj5r*uIe=y{FqAx1 z1k)ELJ2KpI1Tzt$Y%!W z`Eoh*eCGVv$`_mG_f>t_ew}T>UNeTbTNyLf^Ey=LGTh2$z7Qwf@a-1E7W`8Bu5N!1 z`loFB>9q#v)fdbEZ?@;SV1Bsy-v4jWgTm9m&>pkw*5Ogt(uJ=1)7d$|)7e=epeR2rGbfdS zLF4??iQXD6filPH)An3CaY#p{d$&Z`6oF|{O0JnbpQ3hrKi52~+V^{3j?UDzn?Br- z(8*bIGwO!cQX8*nVPQwycKv&P?ut*=l;Gu`Ut2%_ZS~w*;6%F5>9z0JPM@>fxN(J< zwfXw1djq^X6qvWXUN(E~9J$`$!kT-5ORwG5n|k!WTe8y2SpiEq`DaF|tM5^{vwz-c zt$V_Jvrkt!{qWgwc_KHLy~bUQ533(-InOv%vh2~u#D^O;Ow&tV+;cYdxX?S{J{6tl zPt2QH{WR_cM1MG7z~PkFjHhE&A8y`Y%q z%*exfaLNCF<#}4VWjBKl=ooHneE#)JRc+P(KevD9ub=G5Fsm784g-Tjyc{S|Ff{nF zF=p^FftU=nl`LS|N|I%PB?p-K<*6W;evu;3@Js>ByfEDzOfMKU3>Xg73bPNb4Cg;{ YAANO5o_mvf87P1~UHx3vIVCg!0No2qo&W#< literal 0 HcmV?d00001 diff --git a/media/atree/highlight_t.png b/media/atree/highlight_t.png new file mode 100644 index 0000000000000000000000000000000000000000..7629abf158e6fb5e152a4eeed06327a39f7d5be3 GIT binary patch literal 632 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBK7W8uh%1n0P*P&}|DQoy8^~PGS&5c$z5c&nooe6r>t*2je`RJ2 zZ*v%K-DKFZnK2`s`NBE21#@{Ftfd?9Nz`f@MaOe_My<_nBUTIPmQYQ{w!((<&-JGt V%6&c*-$ literal 0 HcmV?d00001 diff --git a/media/atree/node_0.png b/media/atree/node_0.png new file mode 100644 index 0000000000000000000000000000000000000000..538b41afe28fec84f738fd98993d1ded17d05e4b GIT binary patch literal 5644 zcmX|Fc|26@`#v)UV+bARspInQ-n&oiUzI@|~W1ONcsdb*k>0004p z5CF~r-tIguvOBaqOmx(NvQ8m7Xu#}=*N6b{J^{IRj~%qR+;uHH0D!0Y@Pf3u6+Hj| z-fTTh;td~b25l>W*G!~!<)gkmU0lof93O#j;{see_E~gJl=d}U19@52l*q&yiunRh zmo9oHOS~UrmCDW@Mk0w{%Y%#8>)L1SDyudxj;eSRcGcN@xBJbWWxv%KfS>X2On)e{-w?yNhWU$u)s^Q0FZorvHzC+BqOe^ERpgI2QF z_!@KKPOp~D%TPBl0?wVq@fG>&@>qo77yn*Oocl?|XV}iH0&RZ;#W$Q-;q%_f!OhLh z#nG{PDu^Di8JmML|420Q5;Beiin`1vOxIVIPjICFd9pmIbX8Q#k?imYIqCtrzpozU zaMhu1C}B@RsKOCh#zA3#u#c>Y5mT9nW}2>JLz=qIg*K+0K3L6+&T8tqlt484r6N8l zeSHFpCK_2K1>Hl4z=&^h_HwvmvYMp%P(-7sp|)M=??nkU(I;WPtiSvl6M9J+xC9D+ zL_z^Z7QVWPG~R>fIh;x?v?*|1F=a02646ryuB*#2GIOHGv}QRzu#n*BvF{?shrMRg zy{K!}8n_a?QcJ?Gzp}6on;ND}-8-3~IDPzumV~}GefWdFocYWo6<>JBg}p=!-lX(8 zIWI!DkhsU%e7_^(f^B9~t%p{}* zSBU(+3pH{5wu7&)|3`^^(KkQ)1uO*gLv z()0FPBi1?ND=(lBd`>%ch@=J>f5T}-U8$e&Q!E_oTe;=%nTt-TB3BUyEpGowIxkQpD&YP-eSa}M+nqZ-{w2(AtRc|ufVFSem8mxU z_1W=GindA9A~ba$b&mCtU&XVinzb|Ek*D|KC-`E=lR+zcWW7$p$rQZh?b|Lgvt5%x zG&6ty+GC?%#U1bNY{Y&X%6bhYITP5ipw;-`BE`DVF z5o^y^5Uvw1BzwEcRl#|%OhNF{!*>~>4G9kY2$IwW-NHc_Xc5Ve*i1jF2XRGh9&7RlYr0Qf_{rRIc!P#$y(H({r%h%;wzg zJ03fnCw&@6zzR+OIA30o8@QcgmFg%=yhq-El%AM>mPZJzsc<|Yia9jUu z!H%-_K~&N5+3V>J(;aE{uC>jVTjC*&fSGy@26KPUHo`8%Tu5wdcs^7}Al9@8GT|34NL{pRnCcyRL zlIb(ar8+j4Utfm}Qf3d~Lk6?9?FC^qp^au?(=aYbdHZ)Wo#4u{<(X9HGTAQtL7m0B zyCJlST=s^#%3W*mWlX^RZ?Upfqkbj(mbE-|j=kQ<|0Gy50cGv;Z(3(X5~XMzsS53v z_t$$jQyYf%PxA#S2y9n!`~Lm$%5G({#mkSNIBxjSij=}b>c*45gmI=tAhD_TS%Cxb zJHB_Okm}_TP-9wmhvL}7CFQ&3+4v4Gh;^sB_5px-*`>CXUcJ_-%1EZ=bZqSR3^l&L z-};kNB;f9q-32)h=JLH6H>&O{DV#fJ zrw%{eumrro#g3&)YN0Yk! z59`U^W~Cm#-s{P;3ib4Yw4X834GwN0|G=8e%GJpGTVI5?EA!12m)&zB^<%Q*8xKH71oaLq5vagSf%4pCiozAfD%!u`b5Z?YRHT}L=OZqwn~Fi@hqyOhMYSCMZn zO4(fVu+`Q%<^Cs5afsg7S8STGbQ<1ef1VNi1cl#w=eypm!!Y#|XU$fhBG>a;3KGuX zD}S62B$chrvjQ3Rh0H&>=+~EAQW

{^-mbjJcAG%8G-Nv&XW^)|};SEAc^=-qi=J z-HtHS6^BTa$YKCis@#fHcrRv?qRLR9IxyUV0BmR_aYC|8QAJEm%6-%~1UY;{(7ATorY`2jeI^d8W8u4GM_sUXvURWaIXS*tzaM`xX zD)&W7XZ^hgJuL5@&=;G``8Z{MmmUqP-#_2TG5+*!yjJjOwOB~v%kZo=Ed3`Yh@Lyc zC7yoPFw;P3aImy}=GCgM=+D>_atngar!&?y|EOy?I z{Hn8CWLc-X8CfG&pnz?eFqqoEk?&+~Bp$mu()WM_o?S2WGQ&#~I2ZO77$};Rg|}ojqZ< zHmYam`Y$$HatrKNPUz&qbAvZ$U(;zRo_n`Gm&VD}a%3p^U6f4O|Df*-)SPa#B$$1g zKMOcf|BAWD{=42L^#o#hWGZ=L2m|~)Xm|KZRqcCMD!*ISu;Wy#YNW``Yf+k7z*eN5 zKprUiGJ@ar)FSi(YWXK#DK)n{bWv`ybm9d525?#w@{klAisJ$C+^zY+5gXMXi6}qU zW)K{H|1>Mk0Nu_9eie$V8pvwP-g}0GGf*-z4x?fWC+&6nBdEX4P6IX6rmX0!-TgJ4 zwct`V%(mY3{xaAN7$Hd-;?#vt(_Yn|SN)I8Q-_lpaZ*h7;V+q)^HXsJ5TUU}IPmn+ z{;Z{};ZBD%tuH^02x3^LHvv#uU%Y~Vd}2+f#Fz3xHyU_mL0@zO;RXf)#Cx2DHfDQo z{_Ujf^;-YWBx{yte_}nhoysbM&VIuc_zA;F$KqJ2*vD&zEXM>+E5g}M=CKYgi+$<% z(;`c&fmdiya#0$ok8f3Idy)zU@#a(k-+=G$Yjy7K6Jtm) z0w<{f303c%j{}NaBn2uErX4S3nt}cQoE#$@tF?zhYVBs;*#158*R_!tK6NJ`` z^5{4)hbRJoX$?Nu*Wnz;0aukyMQgR*wgTY5JlG;H5CYxj`p!WciNH%CRj;3AY-E0= z(F{20dGk}$@F#SO`&8S}_^RC7zy+Izk!{oW;y?vD%68nxJBLktduuG1QHO46{9m@S zh{g~$PNj2*agZ!*<`$&{w$}zg8&|;19u2UQyAMDf(P@qYg#5P_D@&T&s%W*|9_h89 zL)Ck+0(2t~?6*PC)Q`%WPJJ$YN$CvsHA=SrnMNMG30k-rpLdLoe!fc~Rx# zTSihB`o_3{0mxfwM#xyuZfX59*zcS_1&%^XTSk;*Iv_yJ1S+3DM0N7GFuT{|^n>G$ z0^ANP=Ek7Su0>0m>^(x(-dSSRwyZ>%(Nl)vkjU@1z!-p(cAmCfE z`tya|pTgS#SZV8@ky!$D0~dq|K+Q;A@gXPrB_Wg>xV!nrd(^kp3mBne4Nx;4)#mA4 zDRe83Z}GzO0qiM!neqbM0`hGjE__>Gqz!KiY7&=(;Hor#lw7C4BbZ@QQl#7K&{PV)Tcam;ppR$G%I_vZAewYx%;LWyh((`I5L> zbqy263hd|t$Q63h1jnllxDs%Y~@|-enZrk`X6h1!wdv)OEX%jaD z&VVg7FA(Zix_%_2J69)dqWNiBGGD5Mw5Oxt)*W%eM%k6i1MPwo21R}jP`b}XJ)=%j zS_fMhoJXKelKHrd^{Uou)&lG8 z9xiQ^z3H~8i(lr|vHhRS9ybGJMF+S&bTmaEJ1QJ|4a%+S(<$dJJ<+xaSc#wwmiwP~ z*xrv(1H#eE<)EIho26z?XFol8ac#OoE>}D5F{|M9`Vl;XJ}Btp^eQWGpVcoO#3gx{ zh2|W`KL)SwPIqOw*>$Enavb3w-@7I|+fDQPntI(@t>vjP#kV7#bxa(L?O^TfTtXi# z&w6tuu;!4TzPb4C)kQ^=BeL8z(`04dvE34TuWYOimrG%C0olkX0w{wRP2-20pyKZ2 z*JqzJ+K!hl&KIj?1+%Jue{xBqDd^RT+)3=VfpU`2&qz|YqiM{ z8WABXY^A5fh)AEI(!y)kYhnwzZEIc@c4uC~TM}njWp*mSDcG@`U9KC;eyfR4B1Fcq zM{HNtbwtUd-^~2?d89FQ@v=)__vuk^XI-!lG$fMSeI5?S>D&U#xP!WCMWF9F8?PJ( z&Sg7>*04o{Ub(S{U}q1xbRv#lw#kdz@4;^nSgwJplc9cpTS}Ahtrwah)hmn-8Yvr^ z*VC!dE=N^v)zn)^)+1<=K0~|A{x`d|puYPk4A%Tyz7aKEV@t!PR%CIkw+>%6SQ+~g#{ffdC4olXp(`>}|#+sGsj$p;Hb)T~?{H2WnCm_l%t z**rrA5E3TZnR>(@F>WgX$e)WJ7~?eq;HLm4#(qVA!4w!^BdteW1acOnNF&eu*Hd(% zO&E0tGnfzGX(VlL(>D7bE{5fC4B9|K4-v(KW~&19m{? zc`4=mB7jy0B2pqJQ~Lgc$OsdR^YWQzo&iPM`p2McwG|nE^b8<(%nG4Ro`7#3nR&dp zMU0IAhhWqNVr-uCfQ?Lac9aa#(*V;W-MQ+qqHhTWA5UE!im~az0P1iY_Jh&h^G}Cg zd6WEVGDV&PtZ`f31zt*?0{90J3Lca)&vZa=;lRaHF)h-LD4_TRR2_|Tw2%z?u6`Kx z#*fD<`Zo`w=4ppDr5N~C5-1T+cZvPAZ_-KM@^u;;vSFM2i zQP}d=`xE+$b7#5^qs&Q67LA#WvJnI#NoZJ$$&@jrVJ9Ee|sc#0ZKNY!{}Bj#7-g*V^n6S^(pqkYl4R!G+Tod>iy{O z2vO>C2sSzT82l8b&V4bGgq0NtX(p_KHgBDKLu4#g-Y+|xXzs`ziAL57%{XfM-221p z$A9Cv7D4mYNWZ)Fw{WbA#e0~#w!73nB&?cRG#^L0|2gVhfHrh!cz8G+AC1VF*BPDK f-)hI*XQ4)BQYXENt%Sf6TtH9jx@MXB-O&F7-D$q8 literal 0 HcmV?d00001 diff --git a/media/atree/node_0_blocked.png b/media/atree/node_0_blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..e62898d6afbba094ba0c18a0e828e861b68624b6 GIT binary patch literal 5163 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 TdDC;+?IkeMGt;fmc8d8w)7HIR literal 0 HcmV?d00001 diff --git a/media/atree/node_1_blocked.png b/media/atree/node_1_blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..f7b0e5ad463971b1895660ba297875f1a0b14f56 GIT binary patch literal 5066 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 literal 0 HcmV?d00001 diff --git a/media/atree/node_2_blocked.png b/media/atree/node_2_blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..97bcb14caa107bf7d44c1cf332b2417cb369df0d GIT binary patch literal 5111 zcmX9icRUpC|IeMnWknx*i$Z28WZfAVsjMQhM_G}`ndi)qoDoVy*`s71l9iAV$=>Vi zkaf;+zw7(^<9R*L`~7-9@AbT&_xpK2F@|?^80om_004}7x?08nKqyNHfYVSW%cq48 z|3TiyIvU_pAMYv!fIU*btqwprmY!rsO@Yrm(KYu5fT``@fOL2kIs))toSv5YT|ev1 zsTgmbW&e&H+F9yzvqFilw2XecDDSY0)TQHMrhJu><==FXzafeTZz(U-c&PXW|K5@6 zNOihd@l8M14+A@sggk`Uj50?%a0J?-X0}A-9DiweaYH)+c5l}+>9gkr z;Au<}DIcA(4|%~gop7q}-GXX69eMNxGVr2ww#=r}ILBC0leya$AX_#2=HJ50JdZCv8qDAo~Sba2E6Le7HMTLlyhra>kDeb5q1 zU=8}>w?a3%%=7v39V3FcIK zo_{V=6NyZ>zSS{buswGpO;GptRPJ1)?3ysEybqOL2qcaOw@Pma6+!sN3MA^e!A16C zOi0m|+?s+e2I;LjGkN0hq&-J*^fzq_4MA~(SMxd>xk9j{Z4nB};<;dSaB%y{nHniH zoq$);Z|3OTxqYPVI86K+LNu=IDjw4}?T#J-Sews!*&zQNkEY)A)J+Fyb#Jx6EJ6g&+wn4s z0LcU%&g9j}3zhi3?=7*s85?h}eY?>wG%E_kPC1Xhc4nfg65b{Rd02jr+P$KZkUY7A zb1bI9%S;tHVwVIPglRi2jLC2~p+X)oVVPW;gn5ag(h%g%p?Tx1j?Q<-O9-ju=Xl4$ zB)KNW5xab`_2*}uN^s&GqB<~*Z($4&19DD#dwbk&PP+lv3uQlPCxzW&>+6o33}Z0+ z-s4GhLsUvh1RmzzA5Yvr(TPy+9H&NV2G_ISr$z2O*L;EYb+^v%c(i2yC!N7myT@~W zwRx3^pS>y3N|ZtVW_N_TZrB<$;0(|v!=2}BR1Zrf;}8;ct}QC*lFCER{6C+(EZvwE zg+tj(KsF)V=;nstiVL+iB2#AOo`l54JU!t(c!i)A*)IV5L%ibcPW3giBG$_Nu?0AXvv zQ2Wq2q2x$FzvhZF|LxkRz6Pl^O+HFto-6R6zZ2AcX8`^b@~gG!ioc1+okrHN)dt+y zN43;IgRiMWjkGXzERh?qhIoFD>R3ukBOB2@omR20t*=ifk$^+rspb+%9=hfBu&RLvMq9?k=4J;$>CY5DlEQ= zW|a*v`VG0kan~(IpYAJs1LS#Zx@Np&5&d3GkxJM6;iT?VKLe<*qc=C+e}k4D5#`5EtNFAa^&C_{+5 z<270a^~0XE5w?3$Lj~4#&MqdWpcEBX!bYT<_t`X`=osvr9IOe5 zoq}E_sm`C$O{Vmwe#7PTE3BZ3%P89TYx>~z$XXTt)O@+-QR4^)e@mIW4^ zLuM>F>rIc{sV!Z`hHB}+I9a-iuuq4`#C~XEr~BD;1WmiI;5|W&TLaGIEO~@ixCF^w z!!wEaV`KfwySG;;CzlWSR||+9na45an!ItQqKixPWltYwH^@x|dxQQ{RQAUvbneKo zGU40U-GdL4>FAbH^}5x`249LJ5dBnlf^rqU;&*EoEz#qpEsXg~tEjmZN zKrYY)KXQv&DX_R(6j&BkpL&O66+d`ZFr@X+^xYf(uG05!zXL@%a={5A54DxIZxH{& zfj)yxwth&K56PiKoOwMzYn-&gk*K2F_J&&lj*;HeSH|@r{G}OWI*)eDE2A#XppIck z+5p+ro2OirQ6d8i*8=2BeN4{+JXuNwHMcaL=p-`OGt2ho4bFCWW2h)~6^!=}gh->> zN`+$fy3O`VMy|a1q|i~#4(X42cXPhV=I`yZeH71iKrITNoxqUv0gL2cqt__ByG^4O ziHq1??sOL*OZKdg-Dl7_HE|5^yK}K)S zuT^%G#Tw6B(Cg=6Tw(5e_|QG^;?KS>B5&3(=9(5Ok6<|Oey)4a>`;EvTdp?>Zo2=e zUhsRC;ewQdX17o>(_%4oWmFXx4_8_!q`pBLh79jg$r9AB^tvu%!agILJM;!42eI81 zZ;l!8et5&<8~Ikvk$M^zy7cqRTwpv;b$o;8{MQ1dkj|e=dIfxh*AV zGpB&lsb_e3viKX`>f_>OZ5Sp|fJ;GNSFZ89gWAuu`rj;oDU-Y$$<)lQNp%ad1_vE` zD|zE)w_2Dq!`mi>n*{-HQoLE5DA;Td1Gbl; z;4>!#-0vZRQN{a-uJ~`IE$k_-?UG}?M4+fRmX`BA$vj-Dcu3AB#^pjjh`*V_)GYsH zxd0AmmBWMoU*f`#MDsAIJC|UX^MF@J)Tt8oAmeU)Z_*US`njD@im{i*9L&Y16x@6R zFM-t5)Se`(n@ho!Fdkke);vGlm+OK#`b@tJZ?mzCtd@d6PaldxnZ8(ix{`F_O@EdI?H#1=Ly{?cEV7a?f zMTv{`cBE?w&Mq}U`}x=hmC-tf;p`Q_tuk(vv>>SyJ?OgbY!&-l`C+fxM;a6*2CdJ2 z_NsCpPLMx#y0b4Y4ANDm&9CX}CWqfL`r{ewG;F8?u9}Namu*VwruF`f9oh_0h9UpU zq>lrE=w&Yf^m`Do^oL@EGys&TUIhX7jJ;;Cg3z74!w^f9NfMrdYF9 znc|~ZT;8YoPoQ@U8q)KroEN&Qv2Lh%^@`1|SrDYz_C}v*n=8kqtbxOM-3p9QDe5{jzpX zGlLOJo(v$Ks_MOP*OX{&FZX=uB5dCbi9 zpvcr8X>Eq~#2Tcw$4s}c=2RmFi3(mO4L(4%-+>3XDLI9R1@pa)&{v&6(?R%tDQ$%WXpn*H=a; zZ#dP#u@YZM|6lCD_`{v@pvsdXbIsRP1jPL#nKALlZ**p9O)wah(XYZb9Cabc*Jv^H z*S5=IncBqE=lA~CvGhwOcXiga#*thPvMxN}x{5sy5BRump;E~rv|N9*7eYwL zj5Jznhb-YaffdFeplB_#_vhl~^N`|R7q8TJYK>+@OSSvxO5gabd2H0gd_FzAbJVb9 zmB!=8%dX{B8V0_7gmp-R@2jlV!%uDZ>aEr%x>wNnOd$|ZKnu6YILYf_kbbSRk>h=` z6^n9oS(o@Tv!z+K+4PqU#4@jB^7G*C-%p6_dSIF<-tSAcTaBNbB553Cv_AU1m6GFyfWK=QJCim=g+Ef?mLp2cK(gVXwsDNuZYNQ= zG}b0XFYk8#SDS^6`U;3RIR!AY@0GC}Cy>m6nzsCil77HdAHNu8%=g&!Gd? zRn67DF0dLr-MVJj_ovHvRnC8Xpy@|P_g3fBM;WoxZ71%v>Jn?ZbZNo!#JLZxow+^O znZnyF--}J~3Oji@jp&c{(<;yEaNl?lBI_>tR%#v3f#B@H@D~ndjy@tX z9NNnc&q|R^FFY3rMjzX4rXLhrLClNp_IR?;GS5AT zGfNmt^yeyK@3Dz6(D82+$eLzc&9+Rc{)MVeeD?vXZJ5*acMtJI31r$h@B9ZYzYk47 z)rY?s=)RJ`mCzD8XvoPUt!UNyiox_LUZ!zSL&gcUxQ!DDeBNMuX{%eW-DO7~n^8w{ zX&Ic>DVtiEG0gh3KMoPo5X`^IS~2FnsYJyXRk|w>d@E$F32{7Z=GJa`JWxA)w6&lq zwP(M1IaZZFjQ1oZ3m85+fI|V?z|fnlG*Uo6xt3rEg5WtdBwr9)1ehzbACT*tl>tcH zQN_@*0I4jP2}F?7ZrIq64+sG*iRe{jO1IV5%CAMGmq^3Q41=B<4sFs_M}b?^m?zXq z&T`biUaVMMZbcl)X9CIAF{(O!W=D-V4+Gc_l4!OgR?Gqf85F?B2)f%O)yINP|U#3 z;U?)dU>E^PIiAm(m0zc6_5n4~`a30P7^Ml-g9~oX*I*AF=nO}mUdn#WB>1wPHN?iX zJc|;Cko!XM?PWwhN_-=W+p7upc%euZsBWGjM^mgm5Yqrf=MLx1yy(CUxFB1SA?cFG zzfB?*#<4c-mjSB~M(d{WP6>{NVpf8fo&=h0`JW!=gksvu=J`SKW4KrtI7$6IZ>9(T z$09Y!l5|P@AB%6=X{}~4g&#%!d88lZB92a}0)~8=NER4eKRwpw!ey|wLc{!~iVba* zL`BgFZe{Vp=7K6TFZO@N7`Suo z^W;&Oz@fPLitT%AYZrR|$@OtuD4Y@7gk;x8Va~%h>~{-iI-@|LFdR9jP%i$DTz_o( zPp!QJ5Hl0XhKD)s%FauyueA*SqcM(cMRgqfZxz;h7zGHS>&%8Q^)G&sLG53+Y=x_| zbrP6azaq?c%l6qhICxMDR=(9aD@^yv>Q@6M@zsY_tHCw=JUKCy;S9nN@J9^tbT!|0(s x=7k8+*~{y)CwmIJ)`G!!?#cdzgibP5T|n7n+_v>F<+Bdx-MFLmNy9em{{ZY6o#Oxi literal 0 HcmV?d00001 diff --git a/media/atree/node_3.png b/media/atree/node_3.png new file mode 100644 index 0000000000000000000000000000000000000000..b55c8963146856bb2783d0b1cf7084c10b16ee62 GIT binary patch literal 5218 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 literal 0 HcmV?d00001 diff --git a/media/atree/node_3_blocked.png b/media/atree/node_3_blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..26f82cd074326890caadb16f51eb1cf8cc9390b8 GIT binary patch literal 4913 zcmW+)XH*kP6WxShLJt_4v`~~HMX6GR&;&$jN>NI9Qi9SYB2q#GK?O8+5EMcaR3sD? zMY_^Eh&(|`Kq*o}=mC;^yzj@J-8*M?=FZ(8J7?}X*je#$AL0f8@Yz^fH~|3JKSBWF z!2afOwb=hZ6yaoL21*3e>K0EAj> zEKJWwc`c3I4fPnl)=KXT{-p89^6B?e?U9TB+N8+w^&JZSCBm8cDu7kQ$WMSNPkt;- z{1l#2ec_Pd{TBmH7U6=2QzX@=Mk1sqtiMuKL*uu`c9|f}U&%20Pxi#gtvbEX1H5l0 zRre}Si@6IoWclW(h{T*v)9YMGe$x9Vfp(kjsyR-a5_Va+P8;V7Ev&4mksR-knlAUQ znQU&WnX6P;F8PfVO6!KM$jvHV>mPObZR3aJPaAx((&T^Cx&MpP3bTM0))JH)b$rm) zW4#U)YoZLjNzW3}@V;8klaoL?Vv0U?|KL0+J`pE^#B7JcpsL71sgT|EL-mybvYh$XB(Ov!1Ye7s{i87#HE z{Y^PmKoItlv3^TA76}WEy^$A}n$xspdkejir|S{_sB34geG47NXg|InFO*~6(5!fh zDlaIL*ziHof-WySVb)NnI9R!@9Q&+>(Z^NhxG zdrE0PDw006{;4=#599@PoDWRT5od0vvX|L=)kE}xy+=l-3%E^R= zot+9MBvOsQEu;JT3gX|rNhQMh)57KrKj5e6`H$X+Ky{6kQe>XG6e7!(AdY)sX3k~} zui)nNugcB{h`ThRSwQipG*s6Jks@PPsSoQQ0F+6McOc>kL#Ak(DlB16E@2;Q7SQ-9 z0!^gmxRAH0)zUsS3k4ZPno#oe!NP4SPgYBGe=xjR;8wLNocmq7oFUQaqGW@M=aD+I zg@R%*w^NY8ylKnLbJIx=qDwcS!nO&Ic|x%J3kb*V-nPwe(zkZTz0ZOiuc< zuHCh@8N;jW?7h0;Dc5rYXKU2Pnqyok2T0VI>&qFT*!qz17uo#M@*NKgjI|a(26HW! z^IF8ErY_rXwe~cVi{814xnqs#1L8q6T2||-Iqm_eq?)Jz!R%sz+8)L8^QF2YNffOR z;o!Du)+!@n%^Pp*S59YlRvrFMD0}bAI+HE43u9b)JzO^x zx&I*IgO{RQQ>GRkKlcAOH;?A$YLL)dD8K#aKKjFZPs|J8y_6%Xwl|E!b?eQ}aE~=s zwLh-ZTHD3-c6}Z5OhD&4aS*5M{i|KH;;&C9e?Bz6HRft%HS_p`PGwB2BW*KPimHrBPHo%(lX4XV<-|t7|S`TjIBJ#~t5I zd+SuT;V>m@SiGxzsq@h!$2=@s`w%s>&;GVL`Wk$n#RC!cu3o6q9@o8`>`Yp3Z#~mW8Z|O9(3GK+R_KeF{!&M;iksg#B|v8mzBEL| zCx?*IGHBzU1Yt)nE$Aa-ID!0$BWf(#M77@${cq`?EiW9{+nAD*eNgVT1X+sbZS2x$ zrUbhF#yNXUkX&3&ocY)Mg&(jb@@nGsS6Fh+;dQy!(SB3?i!W){qYw4{m??mwn$+e17;#LQCs=tp_!S_iCjO;8}MaR`9kh)nG&wOYVGGV4q zdTK-Am|b>C$O-yGubA57`r@&+LU}%1$cNdBYr0uyT?Ts=H7K4w%6+3r>N(&J#+_Y% z!2|!-X{wo`QQ>lLGlAiA%+K<}q>slphgKears${&=mXp{C7 z>}*DS(%2(VZ>)82e9e_|@fF9M8IBzjq~M%MV5AT>Mu5a8PeI^0m#e_P>pYPMV;Ieb z;)U=`7zSRwRwIT#1H!ro@Xf-d=N-lG4$#UJOF$3>2rr%?WJfF(sXowBWTQ?Mf z6$WzhDV@F|;!!XQt7$ZD;KT!2Pj-Z*{?!hl;d z0qoji*RNhGUG=KB0JreB#g+#njJ$A_TZf^Txt*L;0wcdAw;>$?%F$}Mgl5#9gM{?V zA&4v#(-lS)prqNt8%|B?LosRL0c}U4hbAxBbC8)vIXsZb1slvk7}0e-j3%YNEkx!( zRv)>AO=bjy5nWBwj)~Q1bv+~kc`R?a{G!zOS_F*feQX=|V9RJS4$RqLrdwO7C$|&U zK+g>sz~kf7ICN=Jw7M=r6#__tfY^!v4L&e1d7Q@Ky)oJFqD=Pp%X6hH$rWR;X=m4E zoWr@&BMw1&bD{0Tc*m(HO|14?xWxFV-d;g4b?4yCnuw+tX*BpoZMTWysXK85Kw-oa zla;_rUs_UIU~}th4B-nBbar-9)#?|eq?4;A6I=Qo++a;vM)ii`L0)(may&tR-?#m` z^&A`l=mDXghPd5|@4H`n?4kr8%@_3YqQjo@5Ru-U^^&6OH4cL>zi9UGs_`S0`Krouz#<|8Ka8V3&`*w zF@1CUwN2*loDiLE?crvP;o-!it>lsHc2f4ty&f^D=b|-y<$-y^)2)E`3GFDEFLEGy z8y|d6o!o@qWRGbvv3R5^Gb=wUj>!M9kjGntTrJ%(f;H5i*m&djs>+zo^^2BIopRw~&+Zolg7Y|-v6nlK zx(dWF?pjn^k-pC*?N+ixf=o&2)evIR$AS&^pOypf;^Rf1?JUVZk0Tnd*R1yb^sY$p zYM#&ExOS1P>v$#owJPMKN?FUWt^0HxZ1tRR(@O;80a03b`?V_Pwn6zS(~;C?jUL>w zx_Y@Gk{djO2+k?Lk|`+(6YLjyXWP%79XX`9hev+!7q3;G%Ur`29GuKbN-e_q1&FpN zG6(J&U7d#-41Sok{}hSIUH8oq@SDL6QYzIyjD**Y1b?sibdPG2{|}*CXO=s+nH{RR z7D{Ilo_mjUw8e1|?GGd7V5@_wEF3D{Ke~Hes>+!*UMn$f_5~tN9)9(zYink<``B$R ztwJjg*2^i{QM+AA^jTL`j!OgPjuQK#S!p_8`?!4l&kyt&&8c3nLld3!yx(zcEQwRg9;r|f8x_C`^hq?26KoDl+2_|4kX8UER;ab>T75S0nui zDoGSIlcKFt>Ast;j+NIf9Ieu;ZHG`gS)tD)gK2IHFk4@;woNg^>-BxB@vQP`D zCZ?UamzFVHC=rE=d=`Q(sATo}zN38ELlI(6_6&3va5&&0;{7^H6kWZNP5vp?((%a^4iIJhs)^ zefueYdBD&T6clwP9Og>UJB%p3RUa|b(SDcF{8-UH^=#c4g|WTB(^4P>+17%8Cjx66 zo4*{-8NHviRMZNV%B0o$bm)ri?lUZU zcp*in>?nz;H}IDo=GPpn+B;vh`a+5Ii|_sSK&%aSraBqDL(Tiy?-*fk0A1ppQR&lM z2%=!$mWtYYhG@G@&nl$;#9G>M+dRD(b&#{assF=jk%Yudq7|vD*YCiP_|Qk1(g1DB z-S{M5&-iX`*Sbu*&yc)S^bv(-Z1KMbqX(X4tCJN*gy&vSyYF&o$tCIO==z78PCw@C zojb2e8J-?AzrL&amF;L|gWEaR6R2e2Qdys}p1>R6^iWax9u`ASyIotw+h-wf7qdxJYFNQYEk~%Q`bfrp|5+}9!2g8 zSoS2jiHs`E#*rJz3r5VBENylj^PYf{XNvwd7a`o=rC{#bpk2IG`KWB?Pi!Fem<+R6-KElV0XJql5J2F3JLgfO$-#6067em@u`0_mfhmZ3n!L!6r5 z5CjRulvlXbRl-2fvn_>8o0Rw7=Eyc{FNyef70bYl8!$TX1O8mVt+?1mYvC~Fpe+QQ zD|eCoss@S?gksKEiqxybay1ZuH_3G$pZtTQHRZeph@c?y|{B38) zO6^?;M$}KZe(D(7A+O>KH4jQG1W0??u^W!I#3TDWi@Sc*zn%jQw?GZfoPIQL6S(m= zh<-%XtH^_DW1Ia!MP+bWdO0lM?qNCy~Xb;^nUR^)D;K@VPtjRS z2@93YFBJ6A3Xk`e%%e2Fct86TM$p5_aT>yC64<1OTUxfz%0_x@_ zhV~&>mdAblU1yy+)@GM#N(S5p-5%cWY>cDU&pK-vE7csX3%wk33uj%ky<`{8x; zvZ@CiL<>88C^&o9R5xIDX{2H5(Q2I@JC~xaNWSnpN6NAw$_P5@3F^}Krd#HS4!^AL zA$f+a!=nWkl^xHiT7GyvrH-uo7K%{p)9XA}Yr(?Dkac@4v4V5YmfOreT%MLf)=B8Z z3VNjxsgi_M@iwx%lQZ{ROMZ_=^(~bo17%}s*jFE9|NT@f4AS$gy3LHr(e5dN6Z3;A znyHy`UN7wFihk`%m)r3lLBJZMEB2pd#e-p90?IBp25jMOEE)c^gc{<~78D@_Re5;S zX$8_fOG;W>nr5@`bQf&rxCKhyjRThCoH-!PQkD7#yW9r;2Ii{S;0LrdE7MI z+hSAd;lFQJ`#CJ_c_5;bBAh63o0ABSFk`t0|8uX959hyWC`SnB4^xjJN-D)fClIFL zYXr`5%-AGgNqJr4m}l;~NhqkaFQQ|UK%}Gn)#ET@S2Q?LMzVIuj(JKk9j)$hD607_ zR=~g{l?#oG7fzgtKkv&Ae^gsrK_bVoJdT%3^hKEbqZHB64pSmfoA9f05ecQu1dM>Y zKoaVjvNam{N;r0kL%!n#y!h`mEJG?wD;8sa1(Ad*5!gf~v?XKKWBQB5p;t*jz(5ZD z9F6?V6sPc7Bwjn7dW8RpQUv0SQl}8#Z9#}HWyh#HcY!Ffr_Hx$wpHBd3d=9tXsxW#Bq4^fjykU zTPNG)vKAH(OG1&0)q**9Mj-GrX^@B6wLt=OeUP#bHVH2c$h82o$VRA0v2)F|~U>EJgZ454W< z0L$Rp2!7<7nZH!r)b3MEJ)Xrgukl~VqoyCh+Y!{_{ad_wu4j!ZbB|^Xab{i~D>9OK zy?rXy*t?h?$n8JOVVl(NH=nl~51iT0ZPfNRX7s!F4Sn?ao2%ETLpoPF4uzs|SK{#1 zq>{xJPui3jh{Y#IEI#57`u?|P!0c3`dY~KQxT|c9cv;ll<;=?yMH7Cn&|LQi<-qfW zKJ{*GAV4x`o?F$mFRTgM@t$9|Y8m{`j&b~HS?vRA52-}Hwb3}7IgqNhmgwk)&tRD22^3IPAH2I4p4zfHUrvzq>-n~l zLN|Xa;U-4>5UJJ@Rr`bwF1c|GuC!Nw^7VOt4Z+s zbvNXC?8o=L<`VA8XoPN$hdbW6R(WKZm9MZAv%R)H2@U3{#Y}U*M6a1MLE)Kk`|pYB zdKhH$bkpIwBS2(NuY*9^jWu0d{mLFpSGno=TSjpl(_Li_k1{S|2T7ATy|JcMDCHi8 z`MuF_9nWs7URwE){`aX>eR=*zpbkSJ<5bjArFie|xd#*Uu^@rStUqbhJsYxr8vk9W ztWTBFa?oMbn2O-XK$plG6JhJ3wcMQW&vWehF}KMofnO0_ zS&K>MtY%)x`73R{&PD%h!GWhvqE+_0ROkQxZmmu7A}i>7gse75XXcIybz>BRTELJ5 zFpZ2?kY>i879bfra@6)|>c7H{ct!6)Q(wK9bT|}6orLID&i?pw!X?{9up2MdPw-Y-iB*{>B{1=>9Mqu-pCHVyJs0{mdcguq;Pr`6p= zAIx&=Of4~Dz6LJB!U`ZuUgZua+5A80evh1&OZ^j1s71@YEw@rKJRH=z6AHm^k=Rrs z^xM;BiO8Cng11)lBa$!SGF5K)QXa6(jA&gw_UxxHpYJ;{jqRV@y=BxxT2b z3DP#3D3bedMpfXZV}q7@&yT2;_h&%QzUGH&=FaNl%y%&tGrxtes?QXd3S+-n%udw9 zFuTk%6F&p53O)I2I?$W(x?*odFW__*-n<#Byz;i30EUjNKa zuBH*i#$S6+&-&+K6g>fM-`#9oxVLc-wOl(L_wjdxKcR)>g;A=ka#kk7ybA+zR$H-* zJHd+K@weYf(L497?g_$V9N-7+aRc&U`sFkY2_+p{+taxiH_$8bRf;|!b_OdQi^jr4}?9DPe zrONV&M|c>da05;5IF!PpK05c-YN()1qwuIUh?-^o`EUAZIiJ$CJYn3?uHRb}k&z>i0DQA|d;cGa@TH z#3U2mEm$@Z#C3(AZ5fRBWoA(`$a+vX9FNvZRL#Je`}BOmOFk3oVyX0_#hH$uOA`eZ zBrY|;D)hR&+(AVsKlU0z2V8244;zZ`wp6O*mcSdXQto3iZTraKO33`dmfgG~e~=5(kKgv^R@ zL*~8Cl@sA_ZXY!mQheOsqzb<0>nTcBmIfZtf!M1zgl48Y_H3RE1aV^vH6d_?^PUM! zukS!ajDbj~yD#xWo<6HI`c~%bm4qniE;n26!|_D{>OEP~*A96&PPFNxjzGzcsSk_< zXD;7N-QSb~u~SA4y3ib}TReS5k(6x=D;GvPzBd-WO>dFwbDyHhHlV3H2ZOtz(bn$T zGLJD1JKy-TzG@x%6bmYX+3p&TWZkA|({6G=F#{P&{9D~+brxtWSEmu)U71n;eU>YA=vA)4@Rf>K6&i? z5D8)uo$#-vz|Fu&X`2xzj0&-K+vXZu7}FOGZmwzI=t|!?9)A8bh(5ZbZoOY=%=$K{ z>!ruI{df~jB(zTZrP$u)abym(L;PtGP6{YakNdVgiBHTZ{2gxVwvPkdmKT{~0~*?R zc5VHF+Of0jQ_D4$@s*3d*C0F?pz+XkVE(USvx(LU`xQ&Og{Mw<7!p_oGS2b1VN%9l z;ZpY9Kjt&p73?BUfjU%SYDbcF!>kO8+={8VoMmkt^zEjo1m^Ivsjym0(0L_8*RHo` z&F5=rGOE>5tmgD@y5Erx+-ZyVU)}kInepb)h(GON*eQHC@YW3@CBuFR1I_5^{crD1 z&(!jOB@S=AS^9fbNhdv|VdoRsip9exX3}O!0u2&*~k+apGA^i;)yya1ZO<@)bv#M*7O)2q5U!>ySG4<775GmcYaAzoJz#KlrrS-1$Y&S8ye7 z>u5&-e|#Ee%6>#rs_M@k+^{F}dOiQA;YGjCQ9Pt80CyAzDYzE;cf|T>Ga(ESH?y*O zp?6KzR_kXuPu1z<)3Z?2?a5V16G6r9SFHOMLnx1~=&Fj0omM;&IQ3s>#eLD&^et!^ zkThpzSZ_zJ$bIKqvni!Vw#Hk~11@%Jh|MpqIOyH|oe~0nrC~+`LyrH zw;g7#*7xC*$)vokg42sYjk_+9_uO*CHjf_TtC{!G_regNG3|R-)5{F{2~*pUGN3Ql zwc4wruV&Kc*hx7>>#y`f6}5N2;#2;ao4eO@GqY2JOxW;?khV@`rUQ&+na|O01f4 zKAX2Q$fvzAV%;m8EV8ag3!E+Z90b&2@tPJ#j*g#+#?7W!3wzGYBiQTTY5eh<&b&Na zHu1zR%Lmy!8~oSnaRk+y%Kx-1=MF0-FL8K|*`Lh(`J#WFgWg0%=?H#RyCE~5zms7V zs|2`@<%tx!q_4)#P`|I8b+L#W2E(5SpSLsrqH9ywb)BsxL}{lFmOuGt==rS53U+ct z;6&|0_Vg|}nOfkye`DJCmZmMC_9F(w7WNA2rk6K8;%V80ooxxx)xL`Tr}BR2qkQDO zc%FB|HloXxC&pg1U%WlLFC!}fj1jT1s%L-a#fd{de?R`ps>Emcr%vFFTULxXCcPwY zgqc@7MG+U&+z6Z-;IXLY7ff!($s(>i*zX#2S8D;+cl(;@e1PvmU+ff|`rFvqqES|E zB&)bTgoEmoE|gZ==fbpF&)vGC6S>`MV{sdqRr|ZrU;5&VYfjvi#ga@a-v0yf9q*Nl zHzenR$7h#;h>=vWmO%RF<1?LK8rNwmKIYXMwN!oki1*vg8*BgjSYG*rZt&6I#(tW0 zJ9ETpd5AwNIG;(ee!F(>?9M5($jUEKNDKh<&Z;-0&ws?CHO%FJ|A(|60i%#Qm>V#b z1vH7;%aKQJA7V$k*Kqi&LJL=t$@YLO7BQIaC;^Osl+%?9lPB3gf;x%A=Avy0XD|X9 z(oriBIq-G`s0o6#C*?6`SocFdf&RkzO#yOjCBPdSHtx%ipm;PK+~TaKJJamI5-&*L zF@TY~SD+Ye!?gXbC!i-N+(h@qZX1;iED4*gHx!!su(Z6TUQBsuaUqb!b*(=Op}-CT z!l3itW1d2@S+gPAZtYnaaB|44{bA(3(lQjEj1|N-<r zE7TVpGfsjF2xwi$wV$Wv7tZ}Ypz*e#*pQwsLC7v~z!+I^Zia<8Avhx>;3UOa90+n2 zo53VWK^Wed6A~wtpeZJ;Z&xE}&kc@5V{GN_``Ys!Nc?j5!;rwQK^)3uh*UmvxZvuj zChkOke5#ZTmcD|!foQ4mjr-AN=7==pNy@*}w8>u3P>%$sQ@BgUhcO%1s2*W4h9V(f zuToFlnzq8tXyD+6ZHVZ4Ck-?L`rEV7i(B6ei3H!ZZld|v2XT$OJPz49HNVplv3e?; z6S2#~=oeb6%@Z$6ETlrPYh0r>9CiGAKef5Cm7-`iA5Pt>m2BtSERem5Ret_~cC~1z zEqLL`i(%Y7N%l=TA5vfFKfPfUShS!-4F-yqsZ)*vihA1ihhewE_#Pr7jSzuut3te@ l!X}%f`bT?v6eN`i^PFt8J$irgIqR1qFh6H)Qe%XR{y+Usv^f9( literal 0 HcmV?d00001 diff --git a/media/atree/node_4_blocked.png b/media/atree/node_4_blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..d8592524a3d817a46ddde156f81dc8af720c5e5e GIT binary patch literal 5031 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 literal 0 HcmV?d00001 diff --git a/media/atree/node_highlight.png b/media/atree/node_highlight.png new file mode 100644 index 0000000000000000000000000000000000000000..76fa3f815860e7a0a055b808cdc9770b48b80320 GIT binary patch literal 23515 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 ax@94jVrlnX4i5^s0Vjtu$0^4G@c##|V&2~X literal 0 HcmV?d00001 From 2e287447ff03abbe0e93f07613151ab485dc9305 Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 04:48:48 -0700 Subject: [PATCH 067/155] Add some display info to atree --- js/atree_constants.js | 263 ++++++++++++++++++++++++++---------------- 1 file changed, 162 insertions(+), 101 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index add793f..26dc309 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -2028,7 +2028,8 @@ const atrees = "cost": 1, "display": { "row": 0, - "col": 4 + "col": 4, + "icon": "node_4" }, "properties": { "aoe": 4, @@ -2072,7 +2073,8 @@ const atrees = "cost": 1, "display": { "row": 2, - "col": 4 + "col": 4, + "icon": "node_0" }, "properties": { "melee_range": 1 @@ -2102,7 +2104,8 @@ const atrees = "cost": 1, "display": { "row": 2, - "col": 2 + "col": 2, + "icon": "node_0" }, "properties": { @@ -2126,7 +2129,8 @@ const atrees = "cost": 1, "display": { "row": 4, - "col": 4 + "col": 4, + "icon": "node_1" }, "properties": { "range": 3 @@ -2163,7 +2167,8 @@ const atrees = "cost": 1, "display": { "row": 6, - "col": 4 + "col": 4, + "icon": "node_4" }, "properties": { }, @@ -2206,7 +2211,8 @@ const atrees = "cost": 1, "display": { "row": 9, - "col": 1 + "col": 1, + "icon": "node_1" }, "properties": { "aoe": 4 @@ -2233,7 +2239,8 @@ const atrees = "cost": 1, "display": { "row": 6, - "col": 2 + "col": 2, + "icon": "node_0" }, "properties": { }, @@ -2272,7 +2279,8 @@ const atrees = "cost": 1, "display": { "row": 6, - "col": 6 + "col": 6, + "icon": "node_0" }, "properties": { }, @@ -2321,7 +2329,8 @@ const atrees = "cost": 1, "display": { "row": 8, - "col": 2 + "col": 2, + "icon": "node_4" }, "properties": { "aoe": 3, @@ -2366,7 +2375,8 @@ const atrees = "cost": 1, "display": { "row": 8, - "col": 4 + "col": 4, + "icon": "node_0" }, "properties": { }, @@ -2390,7 +2400,8 @@ const atrees = "cost": 1, "display": { "row": 8, - "col": 6 + "col": 6, + "icon": "node_4" }, "properties": { "duration": 30, @@ -2429,7 +2440,8 @@ const atrees = "cost": 1, "display": { "row": 10, - "col": 0 + "col": 0, + "icon": "node_0" }, "properties": { }, @@ -2463,7 +2475,8 @@ const atrees = "cost": 1, "display": { "row": 10, - "col": 2 + "col": 2, + "icon": "node_0" }, "properties": { }, @@ -2497,7 +2510,8 @@ const atrees = "cost": 1, "display": { "row": 11, - "col": 4 + "col": 4, + "icon": "node_0" }, "properties": { }, @@ -2531,7 +2545,8 @@ const atrees = "cost": 1, "display": { "row": 10, - "col": 6 + "col": 6, + "icon": "node_0" }, "properties": { }, @@ -2565,7 +2580,8 @@ const atrees = "cost": 1, "display": { "row": 10, - "col": 8 + "col": 8, + "icon": "node_0" }, "properties": { }, @@ -2599,7 +2615,8 @@ const atrees = "cost": 2, "display": { "row": 12, - "col": 0 + "col": 0, + "icon": "node_1" }, "properties": { "range": 6 @@ -2635,7 +2652,8 @@ const atrees = "cost": 2, "display": { "row": 12, - "col": 2 + "col": 2, + "icon": "node_1" }, "properties": { }, @@ -2670,7 +2688,8 @@ const atrees = "cost": 2, "display": { "row": 13, - "col": 4 + "col": 4, + "icon": "node_1" }, "properties": { "range": 4 @@ -2702,7 +2721,8 @@ const atrees = "cost": 2, "display": { "row": 12, - "col": 6 + "col": 6, + "icon": "node_1" }, "properties": { "aoe": 2 @@ -2729,7 +2749,8 @@ const atrees = "cost": 2, "display": { "row": 12, - "col": 8 + "col": 8, + "icon": "node_1" }, "properties": { "duration": 3, @@ -2775,7 +2796,8 @@ const atrees = "cost": 1, "display": { "row": 13, - "col": 7 + "col": 7, + "icon": "node_0" }, "properties": { }, @@ -2801,7 +2823,8 @@ const atrees = "cost": 2, "display": { "row": 15, - "col": 2 + "col": 2, + "icon": "node_3" }, "properties": { }, @@ -2821,7 +2844,8 @@ const atrees = "cost": 2, "display": { "row": 15, - "col": 4 + "col": 4, + "icon": "node_1" }, "properties": { "chance": 30 @@ -2848,7 +2872,8 @@ const atrees = "cost": 2, "display": { "row": 15, - "col": 7 + "col": 7, + "icon": "node_3" }, "properties": { "mantle_charge": 3 @@ -2869,7 +2894,8 @@ const atrees = "cost": 2, "display": { "row": 16, - "col": 1 + "col": 1, + "icon": "node_3" }, "properties": { "cooldown": 15 @@ -2901,7 +2927,8 @@ const atrees = "cost": 1, "display": { "row": 17, - "col": 0 + "col": 0, + "icon": "node_0" }, "properties": { "melee_range": 1 @@ -2931,7 +2958,8 @@ const atrees = "cost": 1, "display": { "row": 17, - "col": 3 + "col": 3, + "icon": "node_0" }, "properties": { }, @@ -2955,7 +2983,8 @@ const atrees = "cost": 2, "display": { "row": 17, - "col": 5 + "col": 5, + "icon": "node_1" }, "properties": { }, @@ -2975,7 +3004,8 @@ const atrees = "cost": 1, "display": { "row": 17, - "col": 7 + "col": 7, + "icon": "node_1" }, "properties": { }, @@ -2999,7 +3029,8 @@ const atrees = "cost": 1, "display": { "row": 18, - "col": 2 + "col": 2, + "icon": "node_0" }, "properties": { }, @@ -3028,7 +3059,8 @@ const atrees = "cost": 2, "display": { "row": 18, - "col": 6 + "col": 6, + "icon": "node_1" }, "properties": { @@ -3055,7 +3087,8 @@ const atrees = "cost": 2, "display": { "row": 20, - "col": 0 + "col": 0, + "icon": "node_2" }, "properties": { }, @@ -3090,7 +3123,8 @@ const atrees = "cost": 2, "display": { "row": 20, - "col": 3 + "col": 3, + "icon": "node_1" }, "properties": { }, @@ -3111,12 +3145,13 @@ const atrees = "archetype": "Paladin", "archetype_req": 0, "parents": ["Manachism", "Flying Kick"], - "dependencies": [], + "dependencies": ["Mantle of the Bovemists"], "blockers": [], "cost": 1, "display": { "row": 20, - "col": 6 + "col": 6, + "icon": "node_0" }, "properties": { "mantle_charge": 2 @@ -3137,7 +3172,8 @@ const atrees = "cost": 2, "display": { "row": 20, - "col": 8 + "col": 8, + "icon": "node_2" }, "properties": { "cooldown": 1 @@ -3158,7 +3194,8 @@ const atrees = "cost": 2, "display": { "row": 22, - "col": 0 + "col": 0, + "icon": "node_1" }, "properties": { }, @@ -3184,7 +3221,8 @@ const atrees = "cost": 2, "display": { "row": 22, - "col": 2 + "col": 2, + "icon": "node_2" }, "properties": { "damage_bonus": 30, @@ -3210,7 +3248,8 @@ const atrees = "cost": 1, "display": { "row": 22, - "col": 4 + "col": 4, + "icon": "node_0" }, "properties": { "chance": 30 @@ -3231,7 +3270,8 @@ const atrees = "cost": 1, "display": { "row": 22, - "col": 6 + "col": 6, + "icon": "node_0" }, "properties": { }, @@ -3267,7 +3307,8 @@ const atrees = "cost": 1, "display": { "row": 22, - "col": 8 + "col": 8, + "icon": "node_0" }, "properties": { }, @@ -3293,7 +3334,8 @@ const atrees = "cost": 2, "display": { "row": 23, - "col": 1 + "col": 1, + "icon": "node_1" }, "properties": { }, @@ -3313,7 +3355,8 @@ const atrees = "cost": 2, "display": { "row": 24, - "col": 2 + "col": 2, + "icon": "node_1" }, "properties": { }, @@ -3348,7 +3391,8 @@ const atrees = "cost": 2, "display": { "row": 23, - "col": 5 + "col": 5, + "icon": "node_1" }, "properties": { "aoe": 4 @@ -3375,7 +3419,8 @@ const atrees = "cost": 2, "display": { "row": 23, - "col": 7 + "col": 7, + "icon": "node_3" }, "properties": { }, @@ -3395,7 +3440,8 @@ const atrees = "cost": 1, "display": { "row": 26, - "col": 0 + "col": 0, + "icon": "node_0" }, "properties": { "cooldown": -5 @@ -3427,7 +3473,8 @@ const atrees = "cost": 1, "display": { "row": 26, - "col": 2 + "col": 2, + "icon": "node_0" }, "properties": { }, @@ -3462,7 +3509,8 @@ const atrees = "cost": 2, "display": { "row": 26, - "col": 4 + "col": 4, + "icon": "node_1" }, "properties": { "range": 2 @@ -3489,7 +3537,8 @@ const atrees = "cost": 2, "display": { "row": 26, - "col": 7 + "col": 7, + "icon": "node_1" }, "properties": { }, @@ -3518,7 +3567,8 @@ const atrees = "cost": 2, "display": { "row": 27, - "col": 1 + "col": 1, + "icon": "node_2" }, "properties": { "duration": 5 @@ -3539,7 +3589,8 @@ const atrees = "cost": 2, "display": { "row": 27, - "col": 6 + "col": 6, + "icon": "node_1" }, "properties": { }, @@ -3565,7 +3616,8 @@ const atrees = "cost": 2, "display": { "row": 27, - "col": 8 + "col": 8, + "icon": "node_2" }, "properties": { "aoe": 6 @@ -3592,7 +3644,8 @@ const atrees = "cost": 2, "display": { "row": 28, - "col": 0 + "col": 0, + "icon": "node_2" }, "properties": { }, @@ -3623,7 +3676,8 @@ const atrees = "cost": 2, "display": { "row": 28, - "col": 2 + "col": 2, + "icon": "node_1" }, "properties": { "aoe": 16 @@ -3668,7 +3722,8 @@ const atrees = "cost": 1, "display": { "row": 28, - "col": 4 + "col": 4, + "icon": "node_0" }, "properties": { }, @@ -3702,7 +3757,8 @@ const atrees = "cost": 2, "display": { "row": 29, - "col": 1 + "col": 1, + "icon": "node_1" }, "properties": { }, @@ -3722,7 +3778,8 @@ const atrees = "cost": 1, "display": { "row": 29, - "col": 3 + "col": 3, + "icon": "node_0" }, "properties": { }, @@ -3748,7 +3805,8 @@ const atrees = "cost": 2, "display": { "row": 29, - "col": 5 + "col": 5, + "icon": "node_2" }, "properties": { "cooldown": 15 @@ -3769,7 +3827,8 @@ const atrees = "cost": 1, "display": { "row": 29, - "col": 7 + "col": 7, + "icon": "node_0" }, "properties": { }, @@ -3793,7 +3852,8 @@ const atrees = "cost": 1, "display": { "row": 31, - "col": 0 + "col": 0, + "icon": "node_0" }, "properties": { }, @@ -3817,7 +3877,8 @@ const atrees = "cost": 2, "display": { "row": 31, - "col": 2 + "col": 2, + "icon": "node_3" }, "properties": { }, @@ -3841,22 +3902,30 @@ const atrees = "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", "archetype": "Battle Monk", "archetype_req": 8, - "parents": ["Spirit of the Rabbit"], + "parents": ["Cyclone"], "dependencies": [], "blockers": [], "cost": 2, "display": { - "row": 31, - "col": 4 - }, - "properties": { - "aoe": 2 + "row": 32, + "col": 5, + "icon": "node_1" }, + "properties": {}, "effects": [ { "type": "convert_spell_conv", "target_part": "all", "conversion": "thunder" + }, + { + "type": "raw_stat", + "bonuses": [{ + "type": "prop", + "abil_name": "Bash", + "name": "aoe", + "value": 3 + }] } ] }, @@ -3871,8 +3940,9 @@ const atrees = "blockers": [], "cost": 1, "display": { - "row": 32, - "col": 5 + "row": 31, + "col": 4 + "icon": "node_1" }, "properties": { "aoe": 4, @@ -3910,13 +3980,11 @@ const atrees = "cost": 2, "display": { "row": 32, - "col": 7 + "col": 7, + "icon": "node_3" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [] }, { @@ -3930,13 +3998,11 @@ const atrees = "cost": 2, "display": { "row": 34, - "col": 1 + "col": 1, + "icon": "node_3" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [] }, { @@ -3950,13 +4016,11 @@ const atrees = "cost": 1, "display": { "row": 35, - "col": 2 + "col": 2, + "icon": "node_1" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [] }, { @@ -3970,13 +4034,11 @@ const atrees = "cost": 2, "display": { "row": 35, - "col": 4 + "col": 4, + "icon": "node_2" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [] }, { @@ -3990,10 +4052,10 @@ const atrees = "cost": 1, "display": { "row": 35, - "col": 6 - }, - "properties": { + "col": 6, + "icon": "node_0" }, + "properties": {}, "effects": [ { "type": "add_spell_prop", @@ -4014,15 +4076,14 @@ const atrees = "cost": 2, "display": { "row": 35, - "col": 8 + "col": 8, + "icon": "node_1" }, "properties": { "duration": 3, "aoe": 12 }, - "effects": [ - - ] + "effects": [] } ], } From eb84f1de6e1d40e40aa03b6d9406e6bf7039fafc Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 04:56:56 -0700 Subject: [PATCH 068/155] Rudimentary display code and repair atree file --- js/atree_constants.js | 6 +++--- js/atree_constants_min.js | 3 +-- js/display_atree.js | 6 +++++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 26dc309..765249f 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -3871,7 +3871,7 @@ const atrees = "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", "archetype": "Battle Monk", "archetype_req": 12, - "parents": ["Thunderclap"], + "parents": ["Cyclone"], "dependencies": [], "blockers": [], "cost": 2, @@ -3935,13 +3935,13 @@ const atrees = "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", "archetype": "Battle Monk", "archetype_req": 0, - "parents": ["Thunderclap"], + "parents": ["Spirit of the Rabbit"], "dependencies": [], "blockers": [], "cost": 1, "display": { "row": 31, - "col": 4 + "col": 4, "icon": "node_1" }, "properties": { diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js index 714b6d0..73d3e29 100644 --- a/js/atree_constants_min.js +++ b/js/atree_constants_min.js @@ -1,2 +1 @@ -// Minified version of js/atree_constants.js -const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:[],blockers:[],cost:1,display:{row:20,col:6},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Thunderclap"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:31,col:4},properties:{aoe:2},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Thunderclap"],dependencies:[],blockers:[],cost:1,display:{row:32,col:5},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8},properties:{duration:3,aoe:12},effects:[]}]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] +const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4,icon:"node_4"},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4,icon:"node_1"},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4,icon:"node_4"},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2,icon:"node_4"},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6,icon:"node_4"},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0,icon:"node_1"},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4,icon:"node_1"},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6,icon:"node_1"},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8,icon:"node_1"},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2,icon:"node_3"},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4,icon:"node_1"},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7,icon:"node_3"},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1,icon:"node_3"},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5,icon:"node_1"},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:["Mantle of the Bovemists"],blockers:[],cost:1,display:{row:20,col:6,icon:"node_0"},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8,icon:"node_2"},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2,icon:"node_2"},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4,icon:"node_0"},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0,icon:"node_0"},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4,icon:"node_1"},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7,icon:"node_1"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1,icon:"node_2"},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8,icon:"node_2"},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2,icon:"node_1"},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5,icon:"node_2"},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2,icon:"node_3"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:32,col:5,icon:"node_1"},properties:{},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"},{type:"raw_stat",bonuses:[{type:"prop",abil_name:"Bash",name:"aoe",value:3}]}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:31,col:4,icon:"node_1"},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1,icon:"node_3"},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2,icon:"node_1"},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4,icon:"node_2"},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8,icon:"node_1"},properties:{duration:3,aoe:12},effects:[]}]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] \ No newline at end of file diff --git a/js/display_atree.js b/js/display_atree.js index 888383e..044939b 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -100,7 +100,11 @@ function construct_AT(elem, tree) { // create node let node_elem = document.createElement('div') - node_elem.style = "background-image: url('../media/atree/node.png'); background-size: cover; width: 100%; height: 100%;"; + let icon = node.display.icon; + if (icon === undefined) { + icon = "node"; + } + node_elem.style = "background-image: url('../media/atree/"+icon+".png'); background-size: cover; width: 100%; height: 100%;"; // add tooltip node_elem.addEventListener('mouseover', function(e) { From 8ce25cf2c92befa89fe2e678fc1358b7904cd410 Mon Sep 17 00:00:00 2001 From: reschan Date: Fri, 24 Jun 2022 19:04:58 +0700 Subject: [PATCH 069/155] space out and confine active abil window --- builder/index.html | 2 +- js/display_atree.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/index.html b/builder/index.html index 1a14ed3..f26eb5e 100644 --- a/builder/index.html +++ b/builder/index.html @@ -619,7 +619,7 @@

-
+
diff --git a/js/display_atree.js b/js/display_atree.js index 0c5254a..f90536a 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -121,7 +121,7 @@ function construct_AT(elem, tree) { node_elem.classList.add("fake-button"); let active_tooltip = document.createElement('div'); - active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto"); + active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow"); //was causing active element boxes to be 0 width // active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; active_tooltip.style.display = "none"; From 39e6ade1421ef8e3c19e24fddaec270789f515cf Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 06:06:24 -0700 Subject: [PATCH 070/155] HOTFIX: Patch dps_vis --- js/dps_vis.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/dps_vis.js b/js/dps_vis.js index 85386b0..2a7828d 100644 --- a/js/dps_vis.js +++ b/js/dps_vis.js @@ -213,14 +213,14 @@ function redraw(data) { let tier_mod = tiers_mod.get(tier); let y_max = baseline_y.map(x => 2.1*x*tier_mod*type_mod); let y_min = baseline_y.map(x => 2.0*x*tier_mod*type_mod); - line_top.datum(zip(baseline_x, y_max)) + line_top.datum(zip2(baseline_x, y_max)) .attr("fill", "none") .attr("stroke", d => colorMap.get(tier)) .attr("d", d3.line() .x(function(d) { return x(d[0]) }) .y(function(d) { return y(d[1]) }) ) - line_bot.datum(zip(baseline_x, y_min)) + line_bot.datum(zip2(baseline_x, y_min)) .attr("fill", "none") .attr("stroke", d => colorMap.get(tier)) .attr("d", d3.line() From 17311ff3b19f07f1a751283ebd25340ff2e4c1ac Mon Sep 17 00:00:00 2001 From: ferricles Date: Fri, 24 Jun 2022 19:19:15 -0700 Subject: [PATCH 071/155] first batch of generic assert tests --- js/utils.js | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/js/utils.js b/js/utils.js index fed7df8..a14afff 100644 --- a/js/utils.js +++ b/js/utils.js @@ -412,4 +412,101 @@ async function hardReload() { function capitalizeFirst(str) { return str[0].toUpperCase() + str.substring(1); -} \ No newline at end of file +} + +/** https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript + * If we ever want to write something that needs to import other js files + */ + const getScript = url => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + + script.onerror = reject + + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + + script.onload = script.onreadystatechange = null + + resolve() + } + + document.head.appendChild(script) + }) + +/* +GENERIC TEST FUNCTIONS +*/ +/** The generic assert function. Fails on all "false-y" values. Useful for non-object equality checks, boolean value checks, and existence checks. + * + * @param {*} arg - argument to assert. + * @param {String} msg - the error message to throw. + */ + function assert(arg, msg) { + if (!arg) { + throw new Error(msg ? msg : "Assert failed."); + } +} + +/** Asserts object equality of the 2 parameters. For loose and strict asserts, use assert(). + * + * @param {*} arg1 - first argument to compare. + * @param {*} arg2 - second argument to compare. + * @param {String} msg - the error message to throw. + */ +function assert_equals(arg1, arg2, msg) { + if (!Object.is(arg1, arg2)) { + throw new Error(msg ? msg : "Assert Equals failed. " + arg1 + " is not " + arg2 + "."); + } +} + +/** Asserts object inequality of the 2 parameters. For loose and strict asserts, use assert(). + * + * @param {*} arg1 - first argument to compare. + * @param {*} arg2 - second argument to compare. + * @param {String} msg - the error message to throw. + */ + function assert_not_equals(arg1, arg2, msg) { + if (Object.is(arg1, arg2)) { + throw new Error(msg ? msg : "Assert Not Equals failed. " + arg1 + " is " + arg2 + "."); + } +} + +/** Asserts proximity between 2 arguments. Should be used for any floating point datatype. + * + * @param {*} arg1 - first argument to compare. + * @param {*} arg2 - second argument to compare. + * @param {Number} epsilon - the margin of error (<= del difference is ok). + * @param {String} msg - the error message to throw. + */ +function assert_near(arg1, arg2, epsilon, msg) { + if (Math.abs(arg1 - arg2) > epsilon) { + throw new Error(msg ? msg : "Assert Near failed. " + arg1 + " is not within " + epsilon + " of " + arg2 + "."); + } +} + +/** Asserts that the input argument is null. + * + * @param {*} arg - the argument to test for null. + * @param {String} msg - the error message to throw. + */ +function assert_null(arg, msg) { + if (arg !== null) { + throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not null."); + } +} + +/** Asserts that the input argument is undefined. + * + * @param {*} arg - the argument to test for undefined. + * @param {String} msg - the error message to throw. + */ + function assert_undefined(arg, msg) { + if (arg !== undefined) { + throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not undefined."); + } +} + From 2000d381a8cbfec64bfd3c0909e0edd5e16e1eee Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 20:26:26 -0700 Subject: [PATCH 072/155] Incremental progress --- builder/doc.html | 1436 +++++++++++++++++++++++++++++++++++++++ js/computation_graph.js | 2 + 2 files changed, 1438 insertions(+) create mode 100644 builder/doc.html diff --git a/builder/doc.html b/builder/doc.html new file mode 100644 index 0000000..da5e87a --- /dev/null +++ b/builder/doc.html @@ -0,0 +1,1436 @@ + + + + + + + + + + + WynnBuilder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + diff --git a/js/computation_graph.js b/js/computation_graph.js index 0066186..283f54a 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -1,3 +1,4 @@ +let all_nodes = []; class ComputeNode { /** * Make a generic compute node. @@ -16,6 +17,7 @@ class ComputeNode { this.dirty = true; this.inputs_dirty = new Map(); this.inputs_dirty_count = 0; + all_nodes.push(this); } /** From 61dfe2de65f4afa067eb37b2a24b8d73448972a1 Mon Sep 17 00:00:00 2001 From: ferricles Date: Fri, 24 Jun 2022 21:01:54 -0700 Subject: [PATCH 073/155] assert error --- js/utils.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/js/utils.js b/js/utils.js index a14afff..f3352fd 100644 --- a/js/utils.js +++ b/js/utils.js @@ -510,3 +510,16 @@ function assert_null(arg, msg) { } } +/** Asserts that there is an error when a callback function is run. + * + * @param {Function} func_binding - a function binding to run. Can be passed in with func.bind(null, arg1, ..., argn) + * @param {String} msg - the error message to throw. + */ +function assert_error(func_binding, msg) { + try { + func_binding(); + console.trace(msg ? msg : "Function didn't throw an error."); + } catch (err) { + return; + } +} From 8805521ce257d523a6feb1742c40e50cf8cdd961 Mon Sep 17 00:00:00 2001 From: ferricles Date: Fri, 24 Jun 2022 21:24:22 -0700 Subject: [PATCH 074/155] assert error change for error thrown, formatted getScript, added default epsilon for assert near --- js/utils.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/js/utils.js b/js/utils.js index f3352fd..7efdd21 100644 --- a/js/utils.js +++ b/js/utils.js @@ -417,25 +417,25 @@ function capitalizeFirst(str) { /** https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript * If we ever want to write something that needs to import other js files */ - const getScript = url => new Promise((resolve, reject) => { - const script = document.createElement('script') - script.src = url - script.async = true - - script.onerror = reject - - script.onload = script.onreadystatechange = function() { - const loadState = this.readyState - - if (loadState && loadState !== 'loaded' && loadState !== 'complete') return - - script.onload = script.onreadystatechange = null - - resolve() +const getScript = url => new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = url; + script.async = true; + + script.onerror = reject; + + script.onload = script.onreadystatechange = function () { + const loadState = this.readyState; + + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + + script.onload = script.onreadystatechange = null; + + resolve(); } - - document.head.appendChild(script) - }) + + document.head.appendChild(script); +}) /* GENERIC TEST FUNCTIONS @@ -479,10 +479,10 @@ function assert_equals(arg1, arg2, msg) { * * @param {*} arg1 - first argument to compare. * @param {*} arg2 - second argument to compare. - * @param {Number} epsilon - the margin of error (<= del difference is ok). + * @param {Number} epsilon - the margin of error (<= del difference is ok). Defaults to -1E5. * @param {String} msg - the error message to throw. */ -function assert_near(arg1, arg2, epsilon, msg) { +function assert_near(arg1, arg2, epsilon = 1E-5, msg) { if (Math.abs(arg1 - arg2) > epsilon) { throw new Error(msg ? msg : "Assert Near failed. " + arg1 + " is not within " + epsilon + " of " + arg2 + "."); } @@ -518,8 +518,8 @@ function assert_null(arg, msg) { function assert_error(func_binding, msg) { try { func_binding(); - console.trace(msg ? msg : "Function didn't throw an error."); } catch (err) { return; } + throw new Error(msg ? msg : "Function didn't throw an error."); } From 94b67115aa8f7298cec5eba024ce822d771f32a6 Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 22:43:28 -0700 Subject: [PATCH 075/155] Fix builder throwing error on empty URL TODO: clean up dirty flags --- builder/doc.html | 2 +- js/computation_graph.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/doc.html b/builder/doc.html index da5e87a..bfcdf9b 100644 --- a/builder/doc.html +++ b/builder/doc.html @@ -1428,7 +1428,7 @@ -
+
diff --git a/js/computation_graph.js b/js/computation_graph.js index 283f54a..76b1c2d 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -93,9 +93,9 @@ class ComputeNode { this.inputs.push(parent_node) link_name = (link_name !== undefined) ? link_name : parent_node.name; this.input_translation.set(parent_node.name, link_name); - this.inputs_dirty.set(parent_node.name, parent_node.dirty); - if (parent_node.dirty) { + if (parent_node.dirty || (parent_node.value === null && !this.fail_cb)) { this.inputs_dirty_count += 1; + this.inputs_dirty.set(parent_node.name, true); } parent_node.children.push(this); return this; From 56601f1e0dfccb379c57a76570754bb7acb30ee8 Mon Sep 17 00:00:00 2001 From: ferricles Date: Sat, 25 Jun 2022 00:19:55 -0700 Subject: [PATCH 076/155] most of requested changes --- py_script/clean_json.py | 5 +++-- py_script/compress_json.py | 4 ++-- py_script/parse_sets.py | 9 --------- py_script/plot_dps.py | 2 +- py_script/process_ings.py | 1 + 5 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 py_script/parse_sets.py diff --git a/py_script/clean_json.py b/py_script/clean_json.py index 81276c3..910a985 100644 --- a/py_script/clean_json.py +++ b/py_script/clean_json.py @@ -1,6 +1,7 @@ ''' -A generic file used for turning a json into a compressed version of itself (minimal whitespaces). -Compressed files are useful for lowering the amount of data sent. +A generic file used for turning a json into a "clean" version of itself (human-friendly whitespace). +Clean files are useful for human reading and dev debugging. + Usage: python clean_json.py [infile rel path] [outfile rel path] ''' diff --git a/py_script/compress_json.py b/py_script/compress_json.py index a826c36..b021738 100644 --- a/py_script/compress_json.py +++ b/py_script/compress_json.py @@ -1,6 +1,6 @@ ''' -A generic file used for turning a json into a "clean" version of itself (human-friendly whitespace). -Clean files are useful for human reading and dev debugging. +A generic file used for turning a json into a compressed version of itself (minimal whitespaces). +Compressed files are useful for lowering the amount of data sent. Usage: python compress_json.py [infile rel path] [outfile rel path] ''' diff --git a/py_script/parse_sets.py b/py_script/parse_sets.py deleted file mode 100644 index 289578d..0000000 --- a/py_script/parse_sets.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -An old file. - -""" - -with open("sets.txt", "r") as setsFile: - sets_split = (x.split("'", 2)[1][2:] for x in setsFile.read().split("a href=")[1:]) - with open("sets_list.txt", "w") as outFile: - outFile.write("\n".join(sets_split)) diff --git a/py_script/plot_dps.py b/py_script/plot_dps.py index 89bafe5..4c8b33c 100644 --- a/py_script/plot_dps.py +++ b/py_script/plot_dps.py @@ -1,5 +1,5 @@ """ -Plots the dps of all weapons on a neat graph. Used to generate graphics for dps_vis. +Generates data for dps_vis """ import matplotlib.pyplot as plt diff --git a/py_script/process_ings.py b/py_script/process_ings.py index 666281b..d0e9799 100644 --- a/py_script/process_ings.py +++ b/py_script/process_ings.py @@ -11,6 +11,7 @@ import json import sys import os import base64 +import argparse infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] From 2e65e88b6cb49de59e13c6846d548a844c1f257d Mon Sep 17 00:00:00 2001 From: hppeng Date: Sat, 25 Jun 2022 05:23:58 -0700 Subject: [PATCH 077/155] autodoc done --- builder/doc.html | 5 +- dev/builder_colorcode.png | Bin 0 -> 182138 bytes dev/compute_graph.svg | 12 +++ dev/index.html | 65 +++++++++++++- js/d3_export.js | 30 +++++++ js/dev.js | 2 +- js/render_compute_graph.js | 176 +++++++++++++++++++++++++++++++++++++ 7 files changed, 286 insertions(+), 4 deletions(-) create mode 100755 dev/builder_colorcode.png create mode 100755 dev/compute_graph.svg create mode 100644 js/d3_export.js create mode 100644 js/render_compute_graph.js diff --git a/builder/doc.html b/builder/doc.html index bfcdf9b..23a0216 100644 --- a/builder/doc.html +++ b/builder/doc.html @@ -1428,9 +1428,12 @@ -
+
+ + savelink
+ diff --git a/dev/builder_colorcode.png b/dev/builder_colorcode.png new file mode 100755 index 0000000000000000000000000000000000000000..a9501b8e0942b8dd797c0e89fe4508af42501c1e GIT binary patch literal 182138 zcmZ6y1yEc;*Crg?-JRg>!Civ8L$Kfkch|rKcL)&N0t9!5OmMd#gS)#A1OH^-cmLgQ ztM1fH->T_*`<&C~Ir4P0hMGJEDkfO7yQbk!Q9bePa zT%=HXx#kBEpAB*bKzw5-FPWn9By|z7Bn*MvbosYK8DDjYJ?5Ba9hL7I{a?Q!voO)9 zb0x#$By|auFOO^uPHu@Tta%)$t%@}AHu~61CYH5Sc2sr{W5Y}R z-`75gry%)NH*z@j|9uU2g_*WEW?kg&d3t(I%Uo@vQU1@rVVAtyc@qJ<#axD)ivG|4 zBJ-rQmthjUZ!ogVehPtl-2>-86PCOP%?Mtp?2O<2ectH*MZPh`tIzIzUGGf{aFm$yh_TVZlh4B}1DhEscl9&shQ%&cEqKaF9i zMNobGkEoMo1Qn>d-g^Rpzk$KNEZ^t5fYWpwyAgRkt*U7jB!zuhdT!;!w(lgMa^T6i zVqei&;_Id6OGFMIM+uDOm)lRS&vH5F39@}vON;u?dXr>e;|I<5gP(F=d<+10#h5wK zaTNFSprVYr36S1&3q63FxX0zQ7teME`%0WrBM;m%#Nb^5Y|a@#>0mg4g5&(XilPfl z7D%wjvTKBE>cd2_YCwgmVz6j9xuQuNFi8)M!K68p^-u@+zAFr6fAbQ&$( z$CQvbBwEEU0MI0SDzUrt!``{bi0H`wFSJ?T?11OBWS@hhE-G}d3j?$N|wrGKzvnPfiXD3PkmCR4m*Z^l8H>h zcFg3=UcNe8a0J*^Y~+7uY8Gz4c$Onl;g$pzir8OyYj){cosN9@V@H{+82S-6iy$;P zEtoDWxukleiC;{4Sol=VwPgc7%!gX3U#HL6&>jP zbcnE^r(lts!_N0&ifwSpv9XIOCwgIfe@p4FYfbBL{$@_)OFm{n9QC=7RUvudJA2H; z>}OW0b%rQ!l-JPhFjVps+9J%vc@a_d`WMYp`F){#E#r3O`bE`1N2pv;mM|PIN2xmj z?8LLP>@3;xR=5SpvNlBKbTSucVaf1sbmKcd*BkB9y}xg0|6`fMbdRNvV<$3;;INV3 z45^A6BDi*Y!f8OEqn)(88~3aoUCZRXZ}_Q&r4)|lfC638q$qDYnoS-Z->P9-!;~*< zt0p$%qrc{uVy);cE+x4URkIR+A|G>e%__uxejw za811LPYDn6E0z68YS}P65uWFZs{{ECZbU$yrO}AGGd8bgtPxift;tjbHNvz4O(P~( z$}_$2!-W*Bq3}QJ_q)3Ze)r$q&oH3*SJQa88nomT?5joo`Ls@}_TfkV+zES3>34jB zCDyOr-@^3r#!@tKD|U8!Zf5C=$LStuJ1cyBBLXrzFHgl!A*s=p0ioEXmqrEsIsBQorT<#;L3=O(A` zJ=ZGS`%E3G603AdsbrH3;n$P_Q)omVu`@sGpxI1&$ouy-+k67Fo0NcIz&j(2?(Ho;R`6d^k(sw5DIoV>O2dtr7x=id9VXgRg*mWIcc(!K;`H-xa!$c#0-0?A6uRr zTXhMq>T5y{hpFaT<1Qrki)=TdGUera%yLQ5IOV5!jwdD=UKMgSVCHbY_~;*a%;Qp` zJthcn2AB9unBg0h&Ep@+Xbb(=qT8L`!|d z7WNC~!qt9L*T-^?`EZexALA(ty9Gxg4PzJ085E=YXAu26!y;8r^OS@#JAEm738%_7QNQSMgwFU$$4 zKp`YCk?P_Mg9^O;e75Je{6zX`K}-UwWK+$y7H&S`cfi;N2v5zQhF z^}Y@*_@$rKZM(lqFB~0)i{Zn0g+YgiO2aK{gio*0{2(tAK<{#30CkUT-&a?`&4h=o-?3oH`cR<4Bv7o)3e`CWxxr@w15 zy)%`W&+_M*gXy_C>S3I|bcykj6*n_L`_e6K|Fq}qOmR`1T2yUQHDCEO-@HQ6@9;EfH?!Cg3xuN%-b~8UQy!Hj2e<(3+TsEN#b7Ey7g~=i1!N?=-kv-q>)cp;Pm#f{>!xHGO8&m zckuHPllwXw-;N;4T9XVzA;Y*(NVDikPaKy&#b#e8ebm>)yLImTzSjf>|M>y`c9R)Y zNXPQ($@MfPa|^K5qPQo0Kp?79H0!UZs?uhipC5e>SD^H}umC;>UuG1hYxw_4lwfSB(YWxJ!kY1x zx1Q?9lcP@q2>u@*jET>^v`zCBmbDUU9JU%qTzcvo=p*+Mq&!=z}Blz^8#vEo#F zJda=!tuh@k-72yS23v5(eey^{Eak#9k_^;Rai`)!!OVNw0(Jtv$^VUkc<2LFpH~(m z%pTzItW><(4#pjy9ocVr%)sX3iKIk+_3J(#O}}t4A2#ii*XN?0fb$q2H(y0m z0ByfH6K;Iy-mYL>QAFb|oCaW?m*mvns?l%*kOni*ipZ?Kva<5=EHU+CAsSzm11TX1>Z>&rdeFnL zWQDQLS-)$Tr*|J~AD46RaolRhjTOHXUwrEV{%2>LH#RIc&TlFQo>A?{+IsG-aJ%72 zPDv~lSua*96|2d;k16B9KG4uad@XE9@Jvc{OrkNSYq%GdDa&ysF5hvoZ(lBOpWdU- zH9<0^#3XrtLU6c*Pdihn|0law7GfFSpR5bm!a$-+R4dDwGtI`;)crt=x;QTYA1;+) zQP`o8?b96ldQ~ef=F49*BqqUmYGK@BaolZ^yOv9lLf7wg>63=|Bm6>SR<=d9q()|( zp2ye@tegX*-Zli$*)2uh5a9x&hNp2yGgda1mvjcNp9A_8Tx^QUKMo00{|S$`p(Gd-z({wJF)LmLy{!O3>3fUDe=x0@DZ0efNe}o+;Gm#wHM-V38BSHm%G zjiQ?4V>KzDF((Nk)i=+kbN27kuo6>{F8nZGiN?3@kKDoL;c*p|?kqckO?yGz!qdH! zcnXqHklAE*Er`ob`~$;IB2wg9i7jTpW4Fpnv?Ne0I?9Z(CYgeXa+H4B0|$% zKt598eF-rQMt}>}QI?$k{+<3@ZT%Y z;sS9KVkiOMWe!f2CNE1(9Z}3ynP(%_XQ~;zXK#_lYfhpK`>ms!YMr$e*@1wTW30z+ z&_zcY3k0VGewA`8ki%{7#gX>E&{d+o_}O2epRpJ01;e~^_2)Vxealx`lc1sMG)tKf zmkaIVEJ8e;)@u^;nEhv~r4Xzql|otCf|4hi<1_ya0k=Ut056-9px(Uu(kxqB=IEJc?AfP*)ItOweYQ5%_RY+Z!A@KP z^{=JgK1@kIebUK%1K(B7;54tOp~|yDse84pShauB3nV=MOFSKAJr_ZUwq$lJG~hsRYOG zvfa^P_e+Wr?g-bJ~-WQ9#ILk%*;1>tES`wG(IK1XKgpQVB zE*2--Ve=fEv|#!Jcs5R@aHcqx5S^^(=t@FzGQ!e=KFq11OBhYmxHzhWfKO6CV3!fH z4)Am2T^S)h80`dxE%uecTSeBaMxU?X@sXoA$Wa;au4F|iX4yFCN zml7_*0r5|{d0}8gN$IuTJfx4tWy_nt8VqMo-aYfg)}qP-W!O`QBZ)onzTTB}< zJTJ|0A#{N0Twa@d8Chc-@GO*R2Jo?CFmOK=sd>s*4|xs|hnVU29JSF0@2a@2iq!f| zF*gT?R*JnvEOz-ZU`g-;jY1IyWB$eLsN~GrLJ7j-ZV+^W!ugLrO^e@AB}#J`k|I?`rThgXdz$q!M35%QVRz@3-g!*d~caU+5Hys zc9YU-_Hy59cJFjm%ePor99q8~0I-yF0TqRrty`*R_w!|URXirAwLzZiUpNoxq zZ`LkS&i+zCZuPO=JciJ)07yzsV~@GPa!Xh%@^HESpVv1!(WND zZ%b8;9Cj!&COK*ImvLwOaUncK*U#%$)FvJ=>KUa2iTexjte#G!j-Ks%s*0Iw2#Np`Z$BNmv$&uWNKXA%04!-^EJJ zLEC{}){vty%ufEO6CZ?tTh088?;G0sxbrg2qWMUXSsD>3A@H1l;822Y4);2h?_7eos{sKT1ng7d-#1{#yU#sMM_I zZs!C-_b=?`VI{6DCs=2hZ762rpZAX#$K3MDEmJIytf5l1N&4B%Ik-=P>c3z`&7NFR zNE)0i6edtEs~Qwxoo&@dswz*rMMDtrqd}x+HkqJVB3-Gxu|p&Wev^?IL&1ggqnxfP zH8C_t14LM6_5_Psf@J>I!?N)nLy_Y48a?@=opXL47yd+!&8l*!O6`j#VOy*w%`p5s zV5!4%_<-^W87T(P+>k#jE>cE{OYz-zK_D0J@+Y$pmYj*BZ0FA8*OeM)6j=!dGDQh^ z9~W-As4^!|f-DXCP>$Y+#7KeP4>h)ot}gOX{!KG^-9AC{FgiSRmlyw-Zj#G4->!}$B9JWWY?UG7tVMk;ec(Q(d)BVfPUBtpF9a*%= zR2UqKLSQ~N7Mp8>9z5>?6u$!js>kR1*D>OB{k4+e2a#H`%)G1fi~O58SI zfOkWlq<5do5RdyZ@|yz+p-M%x+i=}DSbr`QX=PZyJj$uP+!-9~l#v(^;hUHb-VJiG zI}Lf+AM3e(q=Zg&6Os?U^vR#m?*(@xPL4lX{LAgLa0l^O8&-Ht&7fC!jNHB;Po!Q!y%;ts`_AI>M!q7m-Ef-6qS zrmK!dP}DiJB2_{u3MZmQcAJ^v${>jhBDE7Sa=a9p2OFKLtGgvho%WLiT`4kq+L4;F z)^h=m=9f;C?4xDIZ-Sb~-(2`)F}Rv#!ZZ__1msyXY-6*YmTq$mF_kQ;i}Z>rbe~0s zlBN3jEJ6G;k=pi#nqr!5OB$Gmy|0c8Y(R_%10w-z?j(ws^b&JH+z;_7MAI@ig58e- zqobu5ZL*&{AJIkO_2R-bx9#Lp=YoWmONdQl^yBWC5my5oy2lZE+(1y43jY_ zksmB65Mk>Q_ecQag73*L?XI-gSW;5#-fNMdT%%h%Ecd9Wc@eJ%^|;E3Sm}8TEDyzi z^Scgwf?Y%J^VcDNq}(%HlL|5;KXvV@i^h%5X>OG<-vYy zMlOJ{m6Hqql688|%TUT|kf$nkyE)Z2V5Wx##u zBIWZ-G=B3dg;^J*Uq0VUTO9mgw#Frcr~eN;ZldcENDt2hbrye&h)pV18O( z^tTS#$nsOp&JeF+UkWPU!}_$m=wme5LeBD$7uDph71?PjR|*hi0WV!>AgYL1)^5g) z6amu!i;Iz~&Ev?PnF@Z^&T^dOb8>G647qXfNI=7Pep2soGgp7NyIPB~aW}nx@F~@5Q1y^*;$b^}dR^~Q z%JUo5+tqO?>*cI1yp4=QA(oL_AY?Pe^>NadO!+*+M9k+KZji0{f#9^{*U!so9FZei z8ey8`$gOBAp$rR0P|8iaBY&#WotKYKb1Q|TSY2;-cXeyIaq>;lSZb0OYNVvwjKO$= zqnU*`ogJkuqWlO>>h?6P?dBFx3Z^Erx)hABI_KW)MdR&>3%)U}@Ul)c=g|1K{R%9FC5o5zt5vLL?-3R(Q?#yBMx6gqhKt79Nn=>~k zt?lRD?~vDB-bItQO|_Vnr=gRY&ex8qkUQuO5P021Io<}n`kq)5Os!w9-w@^b3^?WF z)IzQv#P^lr(2h@7)n29#)R)~(nyFsL8bHFP)?Isy{nxxUI`<&TEgP?ev1)>E zF}31L-N)=MB4K~1?kbQ%+<=Nf55d=GjQP*^>Mt{0mt?(&K{gB0$p->!Q= zDDb_`12zKLK=o(L!>?xnJ#O~fPsX)9b_<}I9!t=5RWG<1tFzK2$jM~DO+RQqZhXT9 zdv7`TMew4-4FIcpW(Rp8C_zYQ2 zDSaZ#I+HRpzssj@?dG*cC^J~Dm+L%B(Rpic=oxN+!_2fS;#9=wjZ=0y7w9bYnjND6 z`~)`NVFV`?Nf*8|EFD{L|{4V?imq+z6=HvFrHv78}6}VFO!f%^`P3dDc_+vctDuV^gIT#9- z8{+a4HrBdh`{*&==5up~KT%P%Feh$k@F~#|4RXrj9P>XLWaN#EGFXZPN)|cJ!9r^_ z1(rEcnCivl(eH;9`m~s1v_R7ahKAOM9dt4Eb{5I}jHzR#bA}+3oXJFwuutX!LJ5s3 zjpdyli<1tHy zsRy_S{j8K>Cw9g+iL-tv{gCP-(HpyRcR^(qaAO8e6nT?C{8(_zx$6JQcoCHr z@;HeU46giJ_d@40Rq-VQ-bdoOn@X&?Flh0{XCrX0-Q|&!=kdTS2)?}ZIC{q8q}%z1 za?$hH^xRT@(VJNbdF(VJ3b;%7BK`o%yw?aYeR(Q(_WSkr-D887i)ktFZW8O$O}g33 zQI;wN{@uJPQ9k%weeD$#^0G4$@^S>g>Yk-A?)Xa)_`iDl+Jj5Sb%0KTPkUyxXctQS zuPj-QUy{yB;s^9>;yXJY(mYMqq#M|?dsaVpRQ%b9!01(Bd-Q8KPX0DMG*S9mZ$Y|c z$A%kkn4zc^TPBpzyn0a_0G{(RyuVGT5VK^OXgQN)O8fbfPw7q1AD*A}bf>UYOx&|~ z_#p6&`aJEuOg)7iXFt&AC+q=*Hhstvzx7CW&>F>nu^Q(mx2OX{z-RIptdBc5OEfSU zpY38OxSM8>t_=|l3uamSF*=H86tSJ_z^l@QR^h~C4(g+EgAuSYgWe6FMl4xzzG~sq zXZ-qERki39ao|UTkxH+`3~B&tXLbET2S1_bNiA)v_fOwNRD)Yx)>b=QYJaHCwa`aP zOQwWFtIYW5kus8kKr?~O0K}Ag0urBGMcU;*+B*5{z_Fj3E z!lL%zk)3yo$>VG1GO{7=BJ!qCK1I~$(9)`(6khJ3D^_Vr59gOMmhSzda*nN}Z+SWV8x$+H?zD8gXe^NiRMs)JGpF7!= zypZt`IJux#;22$XM_8PpoM-~7GF*>kPUYt3Tprizp3AMWP`wYM1O9mJGDa`Qzjeh* ze_MPZY=3|etb-8sV4Z7(WLG+SUaY-AcC63m-(DAMdmkcCI3z5qIq7#WNMoFrbsB8y zsYs<&MWwBu*V?--y5EjkMt7!s%Hb*?hX{;HIj`4a^32C~6KKn(V)w8ADjr>Df9MAE zI0d8W;O<@8i-8Zg4*K~%dm&N{oWTc^0ksB1r6n4(8xhpQI+{NHfZA?X)2WPzaUW=( zA8f)F!*5p~p)5{>db&fAaLp+DBPB1>_!~hzc?K#ue#+F>63jeq;)^zq@{m(Z@|ucj zABn*Go!P8UW@buy_=jU^?Hs34HTfceZIxwz>C9%-Uc+q=Dx5@<6DJjBUuP(p26ED7 z>d}C2!%ry*BGu|cLR0QGvahbTUr16GTKEjd%F}>Zh1rzckh0Q~${$lBrRw7`;~t(S zwtj_$Ztt_qzQ2$4PPt&+y!hSptZlyrO{Gbqh?j&xpca?4`L!7}5jCi_z{7@9w|NwC zofDBpw6Bgt!qw4o9&EYRje#3s7jGmHGxmN&$DzvI2x=P`t>lIQUXu3AsL@QF(D&*@ zC0rx@@)&`EBP+Gq0E?wvt}ZU~EJIE6{$oEF_^eXVHtXI&4pXmEDz`zWefl z+AUC=aozJOx)7qiE)m$=k=*57kQ%cBc6okLl>=eD&7WUT=G-%0yc{>{o4T&lvzG_& zJzeIAKh_uj9C~Q^qRD+D>82?Vba#I6LVlA6t&|;I->tSV221Tn2mkIiSl%EqtIFxQ zA4?`0>y$x?Ney}}qe^oHc#B=GshdI)Jk*T3PfZ@YsDhT8srtzHet_OyJ_&no)8`vq z0D}H*xNf=i{|ylrtTS4RGN$y^6Fa`6YTJ$B&GZoudJ}Wr-H%w)`QzOE#A?R85`3Mx z4FiPhy7_gtMe)%6AV$?WqS{ipk@&A#n#62HL&JG%jsW_}Wj}%AzjiTIUZTHC2#X+G zX-TK5&2wOGJ4PY+$;8+NE1gV~`*X*8038r;286Pm5WHgh#bCAB-V5{1;UJ$<$bhN2 z0%3zygBoWm|k1u>+m76{$?44UfQ66B9@enZagDv$_Ofu&o2)P zo(}NxYqNw`mXxyWDV%vnkRx;KY^lR=(k8qgdn%EeTM4J-OC*A3)3AR5-Ory^MI};= z$l`*yhA=KgWw;YN^D=sewCL?5=jfu-I@Ia_GM8Zp^BN+MI9hjs9#86TJ$pMR@2EW5 z`5Be_m$d`)(BGj~VS)-q!<*mR^E~yM$+OW0!(k@5H@^?ay&wLyTSL}!eejy`uy&Ps zVd@2wC0XEnhOqdyTQ9P{G5gj^6}ZD0T;%4vJqa+~SL=R0Pt4nz5TEv3ziWNn&vjTn|0JfRJ+h1@%0|{~qYLjrD=NL^ZBSawoq1?w09& zIn09dIqdBLgIwZyK6QOBt^xl|lsL;&Szxb5*3M4(O~w}<(Ca$3F*mjWi9qV{-t(*a zvW7xk^RzA_;X8GGyCdNt6QuEyyphhwR{P*NVM`a0=C2w$D)AHyNhO=lj%47+>xG<#1RY5gd~eh%=EH9HOzW|0p|ooZMC%7%C*8I4`p$8Tk?b`TAYBdecW(; zSLQiITfndAoKC2h4FQzGjHjqd6ae3ZLrl%nV&)ZoqEYuohim*&)Y&{4R-qd>GgIOs z9`b}2O*U`z{+#c>K~6Til*)C$`swY+bXGzgjv7K%OMZDh5pEA}4t#f-E7!T-mGlIJ zjNiAC2vCS+cdfsXYl#j;R^>gWH#Mu&{sLi=ebbrO(^(v}d@p701gtK}7^%__W*xN9 zH{l_qPDbVlwfN3Awy+=&S|4ssOJy;IcKqGH|uIQbezxafDkNz`xa16#%mPBx17cJTWelIzd)8}u zdd5R7blt^rE6yi>i5v;*4C!c9^WOWZ6^wO>_Mx2_OkG;us6yU74&#c+N?5ryA0XztJ~`^6YUKcn#={~ zx1GUNX@Odk#0(9|t|RfXc8Op0q96KTj+YWQu2#?4$@ra@?{dai&0f}tdQ}p<-vG6c zV~In7_3NeAHIUC~(0!trpQY8*pcCDYXt6+QBg5sS9a~I0BuB#bv4`genJu}3A!kP-amZaI2xDMIHd@tF3J*?in%n*vP(f&FZ zu##cs0p2hRcpA_bkhsl#;4k;-SmY^p$T0CVqwIS6D{)`l{o0?I4_S=G3M#uQwKZY} zU3YkFKqevrN_cxN5BK#iy7P)Cdu|q|b~8+_VpVgfz<&|WgWDfcSgw=Ys?YR29(?UXHn${1)#Q^WMg=YK4M+!D3r^OK}>vj6Wn`-MTnczWoW@ zK!|Becj^+@8d9fl$}CB1ws~>U8kn=7u}%0}PX_o$S4Qv$=b=e<9jLMA;UjY3;0$Z< zZNsj1emt2LNn;yR)^GZnkR*Hx^C2p>$o}|#f($OK@8?3O_GQpqB(2aT(+mrS`rP}U z{tcrK6YlqCYX{(SMzw2~Vm8{XHB8^6E^}z5)jrCaxWWDJ63ylN7&1M|iJJ8uXXTqJ zAnI4&M_wA5tg3?KpJ9PJNG1gtY%&=*ynw}qv}amtnbWImF;gPON&Sdx9|fuJh@?@ z>@X5E6l~g~Z23gZcq<`l*~;NoLK}{JZ51Q*0X!N}aT|t(i=gAMan3%`7OEvUe%VPZ zlEZuj1}Gr|tW6(8VrsmDh5Up0_-|fMr4k6dbaQTGBm+&xzPM2Rh&4BuE@-QG+RO5{6WWnv!|!e` zjiS8JGKeFoxUqM+p7JHmUp8k~YcL*GpHf3wIfE86CAIbdAW1wopO`IrfuAyidUK!iB~CkHo{wf@oxRaMJw$In zj*nL|ogo`8s}C$*^91=%v*44ww)Z942Pi|8O%4V2!j_fy2Dw03^k(`K z7kT{UCyOW5A^zP@!X5^5&9sdf?LRSTWxjtnzI-WWTd=S~h7Nf#Gyz0nkkaDy2M^rJ z&a1aP{}2eC00p?e6DeH(u(jG3%&xPBf%Ur6ef(hdFnRIpaPGI^ca5Y>C3I75=0sJ5 z*$Zho=;?VjTm0b$*;v)lS@Y`kyIt$f7rUaJdYPii>yifd3q>FVf-5dKgC2jOxqS2^ z&caQAzGfJwtr$o|^Wr|E7~H2Pmdr6Cw)FCMGD$ma=jSz0$%Thecd?E1 zL=)>XNf4vspe*r3>w3qwI{N&^=dnc9?EawJj2>aZD0soov*@NG1X^5PTv$JGp@2Y` zFs8KG(Bcr+1E5jz4D)T^SP@#>IMKb6SE)q-h3TBmiJCs?eFWP9=CiNm7)^_OY|y1g zKKNH_E|lJ%9a-LXglOn9K3nM+BUF)eTFIbTTWKbx=|YY0v%c!(aVqG%1U07G#IqqE zN6Lm9PXjQ~<|N0?qK>lWobQLaAlJ%NpIF?GM&TCs6||X$SBpE=0NbxYN64#8Dd?y! z+v=4%ORv=J>I-U+-qPSB6o_REhth>e`r}5ZqAaG#%`^?IJ9j*UG6;O7G6+-!5Ce$J z&&dJS%bvnPu5aRsDRzUp9*nRgqbf7~8pv$CS)t#5^&FCZ^vTA12m!d+Zo*(?ks)Mp zb6ma2B^gRVnX8y;g7MHMF8@z7T5ClO@>hhr$=U$}9a&PJTbV5^I4v7VuxJ1#0nw=8h(7hDhuH*jV4vH_CE52bEQLiq`_=_~Th9+`_13d0#Hg&ywU zY~)imOcvttUTR=7#UGJe!$rgdIztJsx^Co*oMYK#+&h)sb!ek}DzvNzY#>eD^uXk^ z3vxV#cg?7gw1paf*i1`_vq&^vC2df68A3w+JefKgDzoEGD5&#OqnAA)5%qjQ=5N8H zExy67!kZ`-SvFk6Ur(ddCacW3`j25ZVTv6<8Ej)c&vD~icJGs~2OCcR;4O4m2$h}7 z|Ks)3h9VMdB4L-r`EF;vJ)j0$>-v~__~(*23qM$HbR3Xf(67gXMJo2nSADz4-Xg$M zXP?|>^j&)DMlk+Xx9@uRyxij1YR#wmv?IK1XU`#`w%v>LK0g!GP)XSclvXPtwX7A+ z4h@SD?ZM@=J#R3)JXVxJ&Y1V?Xs=wkt{jI8$^B_i-AHPb$}1gIgDkzAa_!NBsl-?` z$mb0f=6|pe$!^&R{`MeG6X#*13yrk|=`m!w;Z1KMmP&GA6MqQe)-X=k(SihlA5Kx> zVS|66L0OipaQmB;o*b$Z>|*6Ub*D`)f5NE&&{{%z9-rf}IVei3PrHXDw^ilS+_en| zLNM{cIWL+F9u=l)VPScuCB%#iKl_I!V?_8(`*UP06j#AoLF?nV=k?!E5CX;;*t$fo z+gB&gjOjIZqh6DLGe7oRo7Yq;>HkR~U=5-QPb0%Lz^uA#G_QPOXL zzR{j~K!AvWbglBaeEZ)i;1`nZO3z`P>sPa`K+A0bRToGl(aT}2fC=NOR7Ro4)1Qmh zpl{FhRKGem2aR0teX!;sAh*1Y5wzG#lWQyt3f{x}cX`g68TuqNYM79dHHbw`(Kk(S1-rXwSSwK}Q`X|YI>`RU2|MWD`51>nx9QhY zeJAPSQ_h4r*@juZ`5#FZUxtlyl<86+iX3q{dW^9|Ss;yrsPo(+a19c^to7E`R`C*} zK7$z&eq`?G>c$dss+MYZNCoTP__P6#0l&kkB~4mgKn`R^^%l4PqV7Ey=;1*3KT^<` z1lvldPc)qV_Fd;eJ2qA|^JN=`j1B;L4DVwhbkRfrpZng{#Y!LPDYOqE@fez{Sb#A1 zj;RqFb-gS~|E*lbk2#maqwd;(!d4Avk4$^RoOJLpJ+z^q1EIMU8%3N+7#|%`76o%_ z08Zn(^Wk#fWT&?&;u3~EY!NYP6!qdG01jZ;-m{(uZI!|H_m9wTLKRFBddwA`1~*rU zd2sVInhoWPpsCJ%g%#`6NBwic`f*Qdvj%~-nRw$pS-jL++Rkn?hx5b3OD)7vN@tQnH#=kwvdEru8An&qJ9DKMKB>5NTN`;fssN zl3(2ie9WhMJo(A7`q5E(|$oe;vNm^tsd;%zBZC ziZ5F>-!#$Gxi06Xkr_Lff z%Kk&`r-=qfmYge^Zc9HKyh^gzKaQZ)4!=gOL1gQHVK-#~^zg0xkC6(Xq>EYY1zDy7 zapt--G{e^@y9%!;CMCRgEw?+`=zxHNaiGZ$=1N>t&a&_(lz%mE~+&2 zz?_uTOB!myq|hj$3TxwCFVw}UAige>fh2{Yu%f5|EFkMN&qScOeit1D53kOjVkQjQ zJ#^9h%QRF`Wsg)HOXWWeQ0^wI-tY%UAO>kzAXdLsBZ-W*U)}>PBXU}Gm64Z|2{raG zYlE#l+7K+(A6IftUA!jPsz+Fn)|c#;X}>Vsn*eV4-`5rD|m*fyjS}4&)r=>+UvnrL*D$+EqFn zk}B(kL(dzNsIlQPs0MkbWS_q0W(O5!+40##_>s5XBq^46nF6RdC>D_&vRNX5A3ik4 zR1%+yN?@O5$xp%UA;Q6!ENVl7y0}aZVxhiWQ*;7B3cKXTd!{lv6FSno`cs_$jxE>(IYG0{`0~wj3trAX;`dipDcIcQlG4?5HpYy1o1Pf*l zhEmMVZU;R}mi#!rRSGN#&RL#P&gOviA1>WRy>IhfIIS;}-GCE;BAX4x_P=A`&W-H3 zY&Ij>Ms$HyJZJ;tH%pC*p@M|Gt3k$u^=ylEJUw=Rtz%~+XFtLBm`q01GB+BT^da_f zw}Q`t)$rleJCbFRSuJ~b>~rXmQ1_IYH81}?|&89;|=AF;rR( zF7B%Bl@50tUdf#vbw!`!{**8kub+$PI|sR#4H?fqS!V-!#1eV4_k&0ErEFV(69!;tOhlSI=s9ir41JFBQo%e(Xs7+0H~ z_94hT;^z0Vn(!cG8`AW>yT=nAtjWO&-JKi9JslDjR^<7v@V? zo#N`V0?VvaO2N2&9OxRM*CpTQJ}j(KYzaMB=x|pdYZN#aC90TO_J^|P*d-ZiYj`18 z*VS-ff&o}D_YpfhsKq%0J24@K>8a^vjqPb@gw1s7pT}tl>ENW~gYuoYvQExU zFb76#Ypsahx#QpE0)ge(aelJcnw$vr3#vKLg4cT5%;WRJO4O?W2+Q{QeDmw3#mI5?Vz25B#rgKoe7-=JPAg!g zZYa-Z@$+hq;-LeFPfTT=}5GQ*bB90$%5Y3V1yev_sSwf#v z%}r(nLbyEmXcQLRbtdssH8ga4I=zcYq7K|7r=Q^{JF7MzP+H@2_gbZSvK31^{w!0- zWag$ZXG*>A1D{ZdmCim0^qAuz@{dpw1OKs0VT9Sog>_sM(P9goPNZU~4(D~;=CsGb zF;<`9)~;QI1Fj;2<2g}0-M{tq!|$)RpOVNQ)#MQS_uHYfPL`aC?D!^JPPd8FL_V_@ ze;};TZ~ckyL;XWr8^0(dnCY&4Q^g8VHd#WgJ@bm3MwSyX%jG!3?|clNmH<^W7!|=) zc5wmQ^Yn;4L&WYkEhEBkRh7KkPUco-AzHDA-S1{^-9H;eQ7M&+jN7yd+-<1L>Kq*H z!aa~SSaYzVktkf6S_v$ehU(~r;G_x)ph@$}I78ed9QG#Ly>KLbI&4H$^?X4r2Lo3> zIkH|F-@JgEHf(+jF6}M{1vw;vWD}NtC{a5aCY6k8d*-g&pSyPR?HXg1PzFb9_t&;H zc83~_RBdH+8)=~=#YVbZQ&(o9w;v^)N#tWb38k58dv5Xx7r;iy*H+_)lhL~vcvv-; zbUW`Haw2_0AYn^Uv_55X3#+RAXu2;8A{dhdeFSmbovluYeQ&HAL5F!aF9BA<6=4mW z+Dbf_Fv%WU6-mAbz}@-_c8gj?wj?DXuBBK3 znugfR&4Sn)Yn-Fc>8OsGnpcNSrVF7Zu5H<+=J%{jxv46@6a@mDIGxDqFUbJns48ylsSW!VUU2Nr;6Pt&4>EBdVHH^Z@~N?j#N`N3pW?$sXs#N)F?X z&KgQSczacr>i~s(Y9|z3>D(X6W-;$qcIeo8&rTho{x+7lR`$H=go@_!Lcj?uf&cAK zREeFI?&r}j=(kX*HG;;hO1v2p0qZh^{wo!bC&Mtg-qlGF_ddC3+7rzUS%zzKMB$=z z+ETy4a6>|%9419bi}rqX}{e3Q}E@7fKD|({k(D zKNXef9re7`XM9$1x0YyTvw`K55D=ihpX%j}Z;RdRBUMwaVP_X0g2xXTypP zQJ8hpSHT3akO)82C@1SOc8|8cSJAVX^)}z>5Uh!qh<1z|7*Neeo z|M6A*!Q$f zV@3I)aHkO=Q&jmeS6EPk#y__l^f7Am)cZ2bfw|;xe@k<~ny%-qd^JC5VF*`t`EF?! zBD{mS`MxNLPJ49UYqv@N`EOF35=(6J-NoupxT6ZMq4Fuut1bkz+B)oAE7-$XV}^1tIzC6N+i?xm^tHX_$;M# zf?H|JGitYfy^`A5!oJdZ+A2XNq5CkocH4Y<_SZi_C2pgc_4vjq5-b$b2gB zAt|zt=O?RO^mz(mq+g=Y&MWxnfOih|?=Bnyv=vCCX+`j>HfWUzuAiZ>fjT8ud8Gk* zv2Y0`-pyg$jhm#@(gMy5mSe~{!!2O58!M2-?i_2I_tL+C8-n^-iEMQO360szJo0cN zal&78h2#8{A}cE%(zvA;LHWBDcoB!Zn#u?ZXBos4LWbKYeeW0>RN3;K=Rv2rL`510 zb48+C)I{?cp4!ugKW6ir1X75D@TRwa1>|&*?GH_3O0xpeOMKNf4V)RCzs)4uV`6B} z*aUZ-DjXjj&3i4wYjMwO!f2$*5w0wh!T^Fc7ia%q4eg4KhSk8Vsai7&>}4jz+a{UW zu>hxMF^qhRj%7YihXcHsO+u0u`z=@IW5Ff+^OX6qSRL2Pqgdf?Z>Lx@NLsMe9XAE) zo>U~?>rN7dr-(X7}QouVddi!_A3xo7|MACeYnDBEA?DU#j!wUtT|z7?;{82^Dh| zCK)izo?uBW$-tE;#>s-0OnP0XOGA`F9^jQtmH(K)>!kdVf&8-;X_>(C@?z5I@#xoB zq8~rZ5fcv2_NliBF(sOL{$P<|LJ&J>gn~|4;kHp5Wu{$@CMRl;?Xpmajx`5y=&dXL zSNRS9oY(ZcJ(XkoB95YAGu!nt|BQ$GpgG2ZG&b+*Z_54QzRw^CA+@5pxjNtYQ$Yb4 z4)Rs75r1<FdYWvhi;Drb+tdnXN#eOIer@q~8fq#jI+7Imv>RWS=`xm@ zRxxgTxc+~Yg27&08^OB0=;HrI-_JCil7VI1W%5HA@)V>~7LtD26>qq);7$Qf>wN51 zWUxp$N)l-fh8R1;*r(2tvhv*A$%Td3mnS~mySv7gz6JMlk)fa$)Y(-Jmigtv(Rzi{ zW>cMTObO@jke|6@?3j*Va3xX22lRWaFXdQ1g|+tnm57FEmO$dvFbmbSvGk3yAk;LY zSv$6OwVaD#7Cwz>nBqIS-b%#???sCL)7G=NP0RP&?QeUycGX2$As%@%$TsI`C^Ons z=tIC)lle=HkEtdgxiqd&`zY@A+dCE6y Eru0mWXnJr&^fH4c20Q-jwS`Ru(fr1n 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⁣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 Cuex9W literal 0 HcmV?d00001 diff --git a/dev/compute_graph.svg b/dev/compute_graph.svg new file mode 100755 index 0000000..5f3413f --- /dev/null +++ b/dev/compute_graph.svg @@ -0,0 +1,12 @@ + + + + + + + +builder-armor-powder-inputbuilder-boost-inputbuilder-powder-special-inputhelmet-inputhelmet-input-displayhelmet-item-displaychestplate-inputchestplate-input-displaychestplate-item-displayleggings-inputleggings-input-displayleggings-item-displayboots-inputboots-input-displayboots-item-displayring1-inputring1-input-displayring1-item-displayring2-inputring2-input-displayring2-item-displaybracelet-inputbracelet-input-displaybracelet-item-displaynecklace-inputnecklace-input-displaynecklace-item-displayweapon-inputweapon-input-displayweapon-item-displayweaponTome1-inputweaponTome1-input-displayweaponTome2-inputweaponTome2-input-displayarmorTome1-inputarmorTome1-input-displayarmorTome2-inputarmorTome2-input-displayarmorTome3-inputarmorTome3-input-displayarmorTome4-inputarmorTome4-input-displayguildTome1-inputguildTome1-input-displayweapon-typelevel-inputbuilder-make-buildbuilder-encodebuilder-url-updatehelmet-powderchestplate-powderleggings-powderboots-powderweapon-powderbuilder-stats-displaybuilder-aggregate-statsbuilder-aggregate-inputsbuilder-sdPct-inputbuilder-sdRaw-inputbuilder-mdPct-inputbuilder-mdRaw-inputbuilder-poison-inputbuilder-fDamPct-inputbuilder-wDamPct-inputbuilder-aDamPct-inputbuilder-tDamPct-inputbuilder-eDamPct-inputbuilder-fDefPct-inputbuilder-wDefPct-inputbuilder-aDefPct-inputbuilder-tDefPct-inputbuilder-eDefPct-inputbuilder-hprRaw-inputbuilder-hprPct-inputbuilder-hpBonus-inputbuilder-atkTier-inputbuilder-spPct1-inputbuilder-spRaw1-inputbuilder-spPct2-inputbuilder-spRaw2-inputbuilder-spPct3-inputbuilder-spRaw3-inputbuilder-spPct4-inputbuilder-spRaw4-inputbuilder-id-setterbuilder-str-inputbuilder-dex-inputbuilder-int-inputbuilder-def-inputbuilder-agi-inputbuilder-powder-special-applybuilder-powder-special-displaybuilder-spell0-selectbuilder-spell0-calcbuilder-spell0-displaybuilder-spell1-selectbuilder-spell1-calcbuilder-spell1-displaybuilder-spell2-selectbuilder-spell2-calcbuilder-spell2-displaybuilder-spell3-selectbuilder-spell3-calcbuilder-spell3-displaybuilder-skillpoint-setterbuilder-show-warnings \ No newline at end of file diff --git a/dev/index.html b/dev/index.html index 438a215..42117a6 100644 --- a/dev/index.html +++ b/dev/index.html @@ -892,12 +892,73 @@ Last updated: 30 May 2022

+
+

+ This section is about how Wynnbuilder's main builder page processes user input and calculates results. + Might be useful if you want to script wynnbuilder or extend it! Or for wynnbuilder developers (internal docs). +

+
+

+ Modeling wynnbuilder's internal computations as a directed graph has a few advantages: +

+
    +
  • Each compute "node" is small(er); easier to debug.
  • +
  • Information flow is specified explicitly (easier to debug).
  • +
  • Easy to build caching for arbitrary computations (only calculate what u need)
  • +
  • Stateless builder! Abstract the entire builder as a chain of function calls
  • +
  • Makes for pretty pictures
  • +
+
+
+ TODO +
+

+ An overview of wynnbuilder's internal structure can be seen here. Arrows indicate flow of information. + Colors correspond roughly as follows: +

+ +

+ The overall logic flow is as follows: +

    +
  • Item and Powder inputs are parsed. Powders are applied to items.
  • +
  • Items and level information are combined to make a build.
  • +
  • Information from input fields for skill points and edit IDs is collected into an ID bonuses table.
  • +
  • Information about active powder specials, strength boosts, etc. are collected into their own ID tables.
  • +
  • All of the above tables are merged with the build's stats table to produce the "Final" ID bonus table.
  • +
  • Which spell variant (think: major id) to use for each of the 4 spells is computed based on the build.
  • +
  • Spell damage is calculated, using the merged stat table, spell info, and weapon info.
  • +
+

+

+ Outputs are computed as follows: +

    +
  • Input box highlights are computed from the items produced by item input box nodes.
  • +
  • Item display is computed from item input boxes.
  • +
  • Build hash/URL is computed from the build, and skillpoint assignment.
  • +
  • Spell damage is displayed based on calculated spell damage results.
  • +
  • Build stats are displayed by builder-stats-display (this same node also displays a bunch of stuff at the bottom of the screen...)
  • +
+

+
+

+ The build sets default skillpoints and edited IDs automatically, whenever a build item/level is updated. + This is done using "soft links" by two nodes shown in red (builder-skillpoint-setter and builder-id-setter). +

+

+ A soft link is where something goes and manually marks nodes dirty and calls their update methods. + This is useful for these cases because the skillpoints and editable ID fields usually take their value from + user input, but in some cases we want to programatically set them. +

+

+ For example another soft link (not shown) is used to implement the reset button. +

+
+
-
- \ No newline at end of file + diff --git a/js/d3_export.js b/js/d3_export.js new file mode 100644 index 0000000..4e92c07 --- /dev/null +++ b/js/d3_export.js @@ -0,0 +1,30 @@ +// http://bl.ocks.org/rokotyan/0556f8facbaf344507cdc45dc3622177 + +// Set-up the export button +function set_export_button(svg, button_id, output_id) { + d3.select('#'+button_id).on('click', function(){ + //get svg source. + var serializer = new XMLSerializer(); + var source = serializer.serializeToString(svg.node()); + console.log(source); + + source = source.replace(/^$/, ''); + //add name spaces. + if(!source.match(/^]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){ + source = source.replace(/^]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){ + source = source.replace(/^ colors[color]) + + node_enter.append('text') + .attr("dx", -20) + .attr("dy", -22) + .style('fill', 'white') + .text(({id, color, data}) => data.name); + + // Let's list the force we wanna apply on the network + var simulation = d3.forceSimulation(data.nodes) // Force algorithm is applied to data.nodes + .force("link", d3.forceLink().strength(0.1) // This force provides links between nodes + .id(function(d) { return d.id; }) // This provide the id of a node + .links(data.links) // and this the list of links + ) + .force("charge", d3.forceManyBody().strength(-400)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength + //.force("center", d3.forceCenter(_bbox.width / 2, _bbox.height / 2).strength(0.1)) // This force attracts nodes to the center of the svg area + .on("tick", ticked); + // This function is run at each iteration of the force algorithm, updating the nodes position. + let scale_transform = {k: 1, x: 0, y: 0} + function ticked() { + link + .attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + + node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' }) + } + + const drag = d3.drag() + .on("start", dragstart) + .on("drag", dragged); + + node_enter.call(drag).on('click', click); + function click(event, d) { + if (event.ctrlKey) { + // Color cycle. + d.color = (d.color + 1) % n_colors; + d3.select(this).selectAll('circle').style("fill", ({id, color, data}) => colors[color]) + } + else { + delete d.fx; + delete d.fy; + d3.select(this).classed("fixed", false); + simulation.alpha(0.5).restart(); + } + } + + function dragstart() { + d3.select(this).classed("fixed", true); + } + function dragged(event, d) { + d.fx = event.x; + d.fy = event.y; + simulation.alpha(0.5).restart(); + } + + const zoom = d3.zoom() + .scaleExtent([0.01, 10]) + .translateExtent([[-10000, -10000], [10000, 10000]]) + .filter(filter) + .on("zoom", zoomed); + view.call(zoom); + + function zoomed({ transform }) { + link.attr('transform', transform); + scale_transform = transform; + node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' }) + redraw_func(); + } + // prevent scrolling then apply the default filter + function filter(event) { + event.preventDefault(); + return (!event.ctrlKey || event.type === 'wheel') && !event.button; + } +} + +set_export_button(svg, 'saveButton', 'saveLink'); + +(async function() { + +// JANKY +while (edit_id_output === undefined) { + await sleep(500); +} + +function redraw() { + _bbox = bbox(); + graph.attr("viewBox", [0, 0, _bbox.width, _bbox.height]); + view.attr("width", _bbox.width - 1) + .attr("height", _bbox.height - 1); +} + +d3.select(window) + .on("resize", function() { + redraw(); + }); +redraw(); + +const data = convert_data(all_nodes); +create_svg(data, redraw); + +console.log("render"); + +})(); From b175bdbcc781b0b5297bd18becfb2217ecbce6bc Mon Sep 17 00:00:00 2001 From: hppeng Date: Sat, 25 Jun 2022 05:44:24 -0700 Subject: [PATCH 078/155] Example argparse --- py_script/get.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/py_script/get.py b/py_script/get.py index 9daa4fc..9cf48a0 100644 --- a/py_script/get.py +++ b/py_script/get.py @@ -7,13 +7,18 @@ Usage: python get.py [url or command] [outfile rel path] Relevant page: https://docs.wynncraft.com/ """ -import requests +import argparse import json -import numpy as np -import sys -#req can either be a link to an API page OR a preset default -req, outfile = sys.argv[1], sys.argv[2] +import numpy as np +import requests + +parser = argparse.ArgumentParser(description="Pull data from wynn API.") +parser.add_argument('target', help='an API page, or preset [items, ings, recipes, terrs, maploc]') +parser.add_argument('outfile', help='output file to dump results into') +args = parser.parse_args() + +req, outfile = args.target, args.outfile CURR_WYNN_VERS = 2.0 @@ -57,4 +62,4 @@ else: response['version'] = CURR_WYNN_VERS -json.dump(response, open(outfile, "w+")) \ No newline at end of file +json.dump(response, open(outfile, "w+")) From e5c2205bd5176bf4e3cfc6ac0bc9adb7ff2fd4e5 Mon Sep 17 00:00:00 2001 From: reschan Date: Sat, 25 Jun 2022 22:03:03 +0700 Subject: [PATCH 079/155] add script to convert string reference to numerical id for atree d ata --- py_script/atree-generateID.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 py_script/atree-generateID.py diff --git a/py_script/atree-generateID.py b/py_script/atree-generateID.py new file mode 100644 index 0000000..4355e72 --- /dev/null +++ b/py_script/atree-generateID.py @@ -0,0 +1,35 @@ +""" +Generate a JSON Ability Tree with: + - All references replaced by numerical IDs + - Extra JSON File with Original name as key and Assigned IDs as value. +given a JSON Ability Tree. +""" +import json + +id = 0 +abilDict = {} +with open("atree-parse.json") as f: + data = json.loads(f.read()) + for classType, info in data.items(): + for abil in info: + abilDict[abil["display_name"]] = id + id += 1 + + with open("atree-ids.json", "w", encoding='utf-8') as id_dest: + json.dump(abilDict, id_dest, ensure_ascii=False, indent=4) + + for classType, info in data.items(): + for abil in range(len(info)): + info[abil]["id"] = abilDict[info[abil]["display_name"]] + for ref in range(len(info[abil]["parents"])): + info[abil]["parents"][ref] = abilDict[info[abil]["parents"][ref]] + + for ref in range(len(info[abil]["dependencies"])): + info[abil]["dependencies"][ref] = abilDict[info[abil]["dependencies"][ref]] + + for ref in range(len(info[abil]["blockers"])): + info[abil]["blockers"][ref] = abilDict[info[abil]["blockers"][ref]] + data[classType] = info + + with open('atree-constants-id.json', 'w', encoding='utf-8') as abil_dest: + json.dump(data, abil_dest, ensure_ascii=False, indent=4) From ab0934b77da63c131686882dbf616b27db69e5fe Mon Sep 17 00:00:00 2001 From: ferricles Date: Sat, 25 Jun 2022 16:35:09 -0700 Subject: [PATCH 080/155] refactor argument parsing from sys to argparse --- py_script/clean_json.py | 11 ++++++++--- py_script/compress_json.py | 11 ++++++++--- py_script/process_ings.py | 6 +++++- py_script/process_items.py | 7 ++++++- py_script/process_recipes.py | 8 ++++++-- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/py_script/clean_json.py b/py_script/clean_json.py index 910a985..56894db 100644 --- a/py_script/clean_json.py +++ b/py_script/clean_json.py @@ -7,8 +7,13 @@ Usage: python clean_json.py [infile rel path] [outfile rel path] ''' if __name__ == "__main__": - import sys import json - infile = sys.argv[1] - outfile = sys.argv[2] + import argparse + + parser = argparse.ArgumentParser(description="Pull data from wynn API.") + parser.add_argument('infile', help='input file to read data from') + parser.add_argument('outfile', help='output file to dump clean data into') + args = parser.parse_args() + + infile, outfile = args.infile, args.outfile json.dump(json.load(open(infile)), open(outfile, "w"), indent = 2) diff --git a/py_script/compress_json.py b/py_script/compress_json.py index b021738..030f739 100644 --- a/py_script/compress_json.py +++ b/py_script/compress_json.py @@ -6,8 +6,13 @@ Usage: python compress_json.py [infile rel path] [outfile rel path] ''' if __name__ == "__main__": - import sys import json - infile = sys.argv[1] - outfile = sys.argv[2] + import argparse + + parser = argparse.ArgumentParser(description="Pull data from wynn API.") + parser.add_argument('infile', help='input file to read data from') + parser.add_argument('outfile', help='output file to dump clean data into') + args = parser.parse_args() + + infile, outfile = args.infile, args.outfile json.dump(json.load(open(infile)), open(outfile, "w")) diff --git a/py_script/process_ings.py b/py_script/process_ings.py index d0e9799..e1ebe8c 100644 --- a/py_script/process_ings.py +++ b/py_script/process_ings.py @@ -13,7 +13,11 @@ import os import base64 import argparse -infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] +parser = argparse.ArgumentParser(description="Process raw pulled ingredient data.") +parser.add_argument('infile', help='input file to read data from') +parser.add_argument('outfile', help='output file to dump clean data into') +args = parser.parse_args() +infile, outfile = args.infile, args.outfile with open(infile, "r") as in_file: ing_data = json.loads(in_file.read()) diff --git a/py_script/process_items.py b/py_script/process_items.py index 27e6ed6..2001271 100644 --- a/py_script/process_items.py +++ b/py_script/process_items.py @@ -14,8 +14,13 @@ import json import sys import os import base64 +import argparse -infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] +parser = argparse.ArgumentParser(description="Process raw pulled item data.") +parser.add_argument('infile', help='input file to read data from') +parser.add_argument('outfile', help='output file to dump clean data into') +args = parser.parse_args() +infile, outfile = args.infile, args.outfile with open(infile, "r") as in_file: data = json.loads(in_file.read()) diff --git a/py_script/process_recipes.py b/py_script/process_recipes.py index 7dad257..f2b13f5 100644 --- a/py_script/process_recipes.py +++ b/py_script/process_recipes.py @@ -11,9 +11,13 @@ import json import sys import os import base64 +import argparse -infile, outfile = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else sys.argv[1] - +parser = argparse.ArgumentParser(description="Process raw pulled recipe data.") +parser.add_argument('infile', help='input file to read data from') +parser.add_argument('outfile', help='output file to dump clean data into') +args = parser.parse_args() +infile, outfile = args.infile, args.outfile with open(infile, "r") as in_file: recipe_data = json.loads(in_file.read()) From 975c0faa1fc6bb6f995a6d2c230d0b3b462d4476 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 00:08:02 -0700 Subject: [PATCH 081/155] Fix crafter page, fix damage calculation for crafted and normal items (no longer rounded powder damage) --- crafter/index.html | 2 -- item/index.html | 2 -- items_adv/index.html | 2 -- js/build_utils.js | 2 +- js/builder_graph.js | 11 ++++-- js/crafter.js | 15 +++++--- js/damage_calc.js | 86 +++++++++++++++++++++++++++++--------------- js/display.js | 15 ++++++++ 8 files changed, 91 insertions(+), 44 deletions(-) diff --git a/crafter/index.html b/crafter/index.html index 76f6cf3..0359611 100644 --- a/crafter/index.html +++ b/crafter/index.html @@ -301,8 +301,6 @@ - - diff --git a/item/index.html b/item/index.html index 758c902..00cfade 100644 --- a/item/index.html +++ b/item/index.html @@ -62,8 +62,6 @@ - - diff --git a/items_adv/index.html b/items_adv/index.html index fb9da0f..fd5d749 100644 --- a/items_adv/index.html +++ b/items_adv/index.html @@ -79,8 +79,6 @@ - - diff --git a/js/build_utils.js b/js/build_utils.js index 80db2f1..430d55c 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -58,7 +58,7 @@ const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ]; const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"]; const wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "Shaman"]]) const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula -const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1)); +const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1)); //weaponTypes.push("sword"); //console.log(types) let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types); diff --git a/js/builder_graph.js b/js/builder_graph.js index 63d08d1..7eae496 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -204,8 +204,13 @@ class ItemInputNode extends InputNode { type_match = item.statMap.get('type') === this.none_item.statMap.get('type'); } if (type_match) { - if (item.statMap.get('category') === 'armor' && powdering !== undefined) { - applyArmorPowders(item.statMap, powdering); + if (powdering !== undefined) { + if (item.statMap.get('category') === 'armor') { + applyArmorPowders(item.statMap, powdering); + } + else if (item.statMap.get('category') === 'weapon') { + apply_weapon_powders(item.statMap, powdering); + } } return item; } @@ -330,7 +335,7 @@ class WeaponInputDisplayNode extends ComputeNode { dps = dps[1]; if (isNaN(dps)) dps = 0; } - this.dps_field.textContent = dps; + this.dps_field.textContent = Math.round(dps); //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")) { diff --git a/js/crafter.js b/js/crafter.js index 66b9087..bb40c7e 100644 --- a/js/crafter.js +++ b/js/crafter.js @@ -172,16 +172,17 @@ function calculateCraft() { document.getElementById("mat-2").textContent = recipe.get("materials")[1].get("item").split(" ").slice(1).join(" ") + " Tier:"; //Display Recipe Stats - displaysq2RecipeStats(player_craft, "recipe-stats"); + displayRecipeStats(player_craft, "recipe-stats"); //Display Craft Stats // displayCraftStats(player_craft, "craft-stats"); let mock_item = player_craft.statMap; - displaysq2ExpandedItem(mock_item, "craft-stats"); + apply_weapon_powders(mock_item); + displayExpandedItem(mock_item, "craft-stats"); //Display Ingredients' Stats for (let i = 1; i < 7; i++) { - displaysq2ExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats"); + displayExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats"); } //Display Warnings - only ingred type warnings for now let warning_elem = document.getElementById("craft-warnings"); @@ -341,7 +342,7 @@ function toggleMaterial(buttonId) { */ function updateCraftedImage() { let input = document.getElementById("recipe-choice"); - if (item_types.includes(input.value)) { + if (all_types.includes(input.value)) { document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png"; } @@ -364,4 +365,8 @@ function resetFields() { calculateCraft(); } -load_ing_init(init_crafter); +(async function() { + let load_promises = [ load_ing_init() ]; + await Promise.all(load_promises); + init_crafter(); +})(); diff --git a/js/damage_calc.js b/js/damage_calc.js index 058ba96..856743d 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -1,34 +1,25 @@ const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]); -// GRR THIS MUTATES THE ITEM +const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; +const damage_present_key = 'damagePresent'; function get_base_dps(item) { const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))]; //SUPER JANK @HPP PLS FIX - let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; if (item.get("tier") !== "Crafted") { - let weapon_result = apply_weapon_powder(item); - let damages = weapon_result[0]; let total_damage = 0; - for (const i in damage_keys) { - total_damage += damages[i][0] + damages[i][1]; - item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]); + for (const damage_k of damage_keys) { + damages = item.get(damage_k); + total_damage += damages[0] + damages[1]; } - total_damage = total_damage / 2; - return total_damage * attack_speed_mult; - } else { - let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; - let results_low = apply_weapon_powder(item, base_low); - let damage_low = results_low[2]; - let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; - let results_high = apply_weapon_powder(item, base_high); - let damage_high = results_high[2]; - + return total_damage * attack_speed_mult / 2; + } + else { let total_damage_min = 0; let total_damage_max = 0; - for (const i in damage_keys) { - total_damage_min += damage_low[i][0] + damage_low[i][1]; - total_damage_max += damage_high[i][0] + damage_high[i][1]; - item.set(damage_keys[i], damage_low[i][0]+"-"+damage_low[i][1]+"\u279c"+damage_high[i][0]+"-"+damage_high[i][1]); + for (const damage_k of damage_keys) { + damages = item.get(damage_k); + total_damage_min += damages[0][0] + damages[0][1]; + total_damage_max += damages[1][0] + damages[1][1]; } total_damage_min = attack_speed_mult * total_damage_min / 2; total_damage_max = attack_speed_mult * total_damage_max / 2; @@ -36,11 +27,38 @@ function get_base_dps(item) { } } +// THIS MUTATES THE ITEM +function apply_weapon_powders(item) { + let present; + if (item.get("tier") !== "Crafted") { + let weapon_result = calc_weapon_powder(item); + let damages = weapon_result[0]; + present = weapon_result[1]; + for (const i in damage_keys) { + item.set(damage_keys[i], damages[i]); + } + } else { + let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; + let results_low = calc_weapon_powder(item, base_low); + let damage_low = results_low[0]; + let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; + let results_high = calc_weapon_powder(item, base_high); + let damage_high = results_high[0]; + present = results_high[1]; + + for (const i in damage_keys) { + item.set(damage_keys[i], [damage_low[i], damage_high[i]]); + } + } + console.log(item); + item.set(damage_present_key, present); +} + /** * weapon: Weapon to apply powder to * damageBases: used by crafted */ -function apply_weapon_powder(weapon, damageBases) { +function calc_weapon_powder(weapon, damageBases) { let powders = weapon.get("powders").slice(); // Array of neutral + ewtfa damages. Each entry is a pair (min, max). @@ -86,10 +104,15 @@ function apply_weapon_powder(weapon, damageBases) { if (neutralRemainingRaw[1] > 0) { let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]); - damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff)); - damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff)); - neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); - neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); + + //damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff)); + //damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff)); + //neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); + //neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); + damages[element+1][0] += min_diff; + damages[element+1][1] += max_diff; + neutralRemainingRaw[0] -= min_diff; + neutralRemainingRaw[1] -= max_diff; } damages[element+1][0] += powder.min; damages[element+1][1] += powder.max; @@ -111,9 +134,14 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno // Array of neutral + ewtfa damages. Each entry is a pair (min, max). // 1. Get weapon damage (with powders). - let weapon_result = apply_weapon_powder(weapon); - let weapon_damages = weapon_result[0]; - let present = weapon_result[1]; + let weapon_damages; + if (weapon.get('tier') === 'Crafted') { + weapon_damages = damage_keys.map(x => weapon.get(x)[1]); + } + else { + weapon_damages = damage_keys.map(x => weapon.get(x)); + } + let present = weapon.get(damage_present_key); // 2. Conversions. // 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here. diff --git a/js/display.js b/js/display.js index 631f0c0..a45eae1 100644 --- a/js/display.js +++ b/js/display.js @@ -173,6 +173,7 @@ function displayExpandedItem(item, parent_id){ // #commands create a new element. // !elemental is some janky hack for elemental damage. // normals just display a thing. + item = new Map(item); // shallow copy if (item.get("category") === "weapon") { item.set('basedps', get_base_dps(item)); } else if (item.get("category") === "armor") { @@ -341,7 +342,21 @@ function displayExpandedItem(item, parent_id){ bckgrd.appendChild(img); } } else { + if (id.endsWith('Dam_')) { + // TODO: kinda jank but replacing lists with txt at this step + let damages = item.get(id); + if (item.get("tier") !== "Crafted") { + damages = damages.map(x => Math.round(x)); + item.set(id, damages[0]+"-"+damages[1]); + } + else { + damages = damages.map(x => x.map(y => Math.round(y))); + item.set(id, damages[0][0]+"-"+damages[0][1]+"\u279c"+damages[1][0]+"-"+damages[1][1]); + } + } + let p_elem; + // TODO: wtf is this if statement if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && parent_div.nodeName === "table") ) { //skp warp p_elem = displayFixedID(parent_div, id, item.get(id), elemental_format); } else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") { From c25d4241942792d084faf7f8ffc560f0bb5b13cb Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 00:43:11 -0700 Subject: [PATCH 082/155] HOTFIX: patch str/dex optimizer --- js/builder_graph.js | 1 - js/optimize.js | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index 899fd6d..a540673 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -178,7 +178,6 @@ class ItemInputNode extends InputNode { } compute_func(input_map) { - console.log("Item update...." + Date.now()); const powdering = input_map.get('powdering'); // built on the assumption of no one will type in CI/CR letter by letter diff --git a/js/optimize.js b/js/optimize.js index 2343d47..f86f2bf 100644 --- a/js/optimize.js +++ b/js/optimize.js @@ -2,19 +2,28 @@ function optimizeStrDex() { if (!player_build) { return; } - const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints; - const base_skillpoints = player_build.base_skillpoints; + const skillpoints = skp_inputs.map(x => x.value); // JANK + let total_assigned = 0; + const min_assigned = player_build.base_skillpoints; + const base_totals = player_build.total_skillpoints; + let base_skillpoints = []; + for (let i in skp_order){ //big bren + const assigned = skillpoints[i] - base_totals[i] + min_assigned[i] + base_skillpoints.push(assigned); + total_assigned += assigned; + } + + const remaining = levelToSkillPoints(player_build.level) - total_assigned; const max_str_boost = 100 - base_skillpoints[0]; const max_dex_boost = 100 - base_skillpoints[1]; if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable - const base_total_skillpoints = player_build.total_skillpoints; let str_bonus = remaining; let dex_bonus = 0; - let best_skillpoints = player_build.total_skillpoints; + let best_skillpoints = skillpoints; let best_damage = 0; for (let i = 0; i <= remaining; ++i) { - let total_skillpoints = base_total_skillpoints.slice(); + let total_skillpoints = skillpoints.slice(); total_skillpoints[0] += Math.min(max_str_boost, str_bonus); total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus); From 6561731a594bb4cc58b576ce98a542a9112ea3dd Mon Sep 17 00:00:00 2001 From: ferricles Date: Sun, 26 Jun 2022 00:48:42 -0700 Subject: [PATCH 083/155] node highlighting + cost display in tooltip --- js/display_atree.js | 75 +++++++++++++++++++++++++++++---------------- js/utils.js | 1 + 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index e21ce61..e419c75 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -10,7 +10,19 @@ function construct_AT(elem, tree) { // add in the "Active" title to atree let active_row = document.createElement("div"); active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center"); - active_row.textContent = "Active:"; + + let active_word = document.createElement("div"); + active_word.classList.add("col"); + active_word.textContent = "Active:"; + + let active_AP_container = document.createElement("div"); + active_AP_container.classList.add("col"); + active_AP_container.textContent = "(0/45 AP)"; + + //I can't believe we can't pass in multiple children at once + active_row.appendChild(active_word); + active_row.appendChild(active_AP_container); + document.getElementById("atree-active").appendChild(active_row); atree_map = new Map(); @@ -104,29 +116,18 @@ function construct_AT(elem, tree) { if (icon === undefined) { icon = "node"; } - node_elem.style = "background-image: url('../media/atree/"+icon+".png'); background-size: cover; width: 100%; height: 100%;"; - - // add tooltip - node_elem.addEventListener('mouseover', function(e) { - if (e.target !== this) {return;} - let tooltip = this.children[0]; - tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px"; - tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px"; - tooltip.style.display = "block"; - }); - - node_elem.addEventListener('mouseout', function(e) { - if (e.target !== this) {return;} - let tooltip = this.children[0]; - tooltip.style.display = "none"; - }); - + let node_img = document.createElement('img'); + node_img.src = '../media/atree/' + icon + '.png'; + node_img.style = 'width: 100%; height:100%'; + // node_elem.style = "background-image: url('../media/atree/"+icon+".png'); background-size: cover; width: 100%; height: 100%;"; + node_elem.style = 'background-size: cover; width: 100%; height:100%; padding: 8%;' + node_elem.appendChild(node_img); node_elem.classList.add("fake-button"); let active_tooltip = document.createElement('div'); active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow"); //was causing active element boxes to be 0 width - // active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px"; + active_tooltip.style.maxWidth = elem.getBoundingClientRect().width * .80 + "px"; active_tooltip.style.display = "none"; // tooltip text formatting @@ -135,12 +136,17 @@ function construct_AT(elem, tree) { active_tooltip_title.classList.add("scaled-font"); active_tooltip_title.innerHTML = node.display_name; - let active_tooltip_text = document.createElement('p'); - active_tooltip_text.classList.add("scaled-font-sm"); - active_tooltip_text.textContent = node.desc; + let active_tooltip_desc = document.createElement('p'); + active_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap"); + active_tooltip_desc.textContent = node.desc; + + let active_tooltip_cost = document.createElement('p'); + active_tooltip_cost.classList.add("scaled-font-sm", "my-0", "mx-1", "text-start"); + active_tooltip_cost.textContent = "Cost: " + node.cost + " AP"; active_tooltip.appendChild(active_tooltip_title); - active_tooltip.appendChild(active_tooltip_text); + active_tooltip.appendChild(active_tooltip_desc); + active_tooltip.appendChild(active_tooltip_cost); node_tooltip = active_tooltip.cloneNode(true); @@ -153,19 +159,36 @@ function construct_AT(elem, tree) { document.getElementById("atree-active").appendChild(active_tooltip); node_elem.addEventListener('click', function(e) { - if (e.target !== this) {return;} + if (e.target !== this && e.target!== this.children[0]) {return;} let tooltip = document.getElementById("atree-ab-" + node.display_name.replaceAll(" ", "")); if (tooltip.style.display == "block") { tooltip.style.display = "none"; this.classList.remove("atree-selected"); - this.style.backgroundImage = 'url("../media/atree/node.png")'; + this.style.backgroundImage = ''; } else { tooltip.style.display = "block"; this.classList.add("atree-selected"); - this.style.backgroundImage = 'url("../media/atree/node-selected.png")'; + this.style.backgroundImage = 'url("../media/atree/node_highlight.png")'; } }); + + // add tooltip + + node_elem.addEventListener('mouseover', function(e) { + if (e.target !== this && e.target!== this.children[0]) {return;} + let tooltip = this.children[this.children.length - 1]; + tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px"; + tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px"; + tooltip.style.display = "block"; + }); + + node_elem.addEventListener('mouseout', function(e) { + if (e.target !== this && e.target!== this.children[0]) {return;} + let tooltip = this.children[this.children.length - 1]; + tooltip.style.display = "none"; + }); + document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; diff --git a/js/utils.js b/js/utils.js index 5691ac9..70ec6dc 100644 --- a/js/utils.js +++ b/js/utils.js @@ -389,3 +389,4 @@ async function hardReload() { function capitalizeFirst(str) { return str[0].toUpperCase() + str.substring(1); } + From 056ff84972e98d815678566f74457cd368672845 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 01:07:43 -0700 Subject: [PATCH 084/155] Misc. changes to powdering workings Items no longer have powders array defined by default (only weapons/armors) --- js/build_utils.js | 1 - js/builder_graph.js | 21 ++++++++++++++------- js/display.js | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/js/build_utils.js b/js/build_utils.js index 430d55c..58e863d 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -218,7 +218,6 @@ function expandItem(item) { } expandedItem.set("minRolls",minRolls); expandedItem.set("maxRolls",maxRolls); - expandedItem.set("powders", []); return expandedItem; } diff --git a/js/builder_graph.js b/js/builder_graph.js index be33da8..e54fd02 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -203,13 +203,11 @@ class ItemInputNode extends InputNode { type_match = item.statMap.get('type') === this.none_item.statMap.get('type'); } if (type_match) { - if (powdering !== undefined) { - if (item.statMap.get('category') === 'armor') { - applyArmorPowders(item.statMap, powdering); - } - else if (item.statMap.get('category') === 'weapon') { - apply_weapon_powders(item.statMap, powdering); - } + if (item.statMap.get('category') === 'armor') { + applyArmorPowders(item.statMap, powdering); + } + else if (item.statMap.get('category') === 'weapon') { + apply_weapon_powders(item.statMap, powdering); } return item; } @@ -247,6 +245,7 @@ class ItemInputDisplayNode extends ComputeNode { this.input_field = document.getElementById(eq+"-choice"); this.health_field = document.getElementById(eq+"-health"); this.level_field = document.getElementById(eq+"-lv"); + this.powder_field = document.getElementById(eq+"-powder"); // possibly None this.image = item_image; this.fail_cb = true; } @@ -271,10 +270,18 @@ class ItemInputDisplayNode extends ComputeNode { this.input_field.classList.add("is-invalid"); return null; } + if (item.statMap.has('powders')) { + this.powder_field.placeholder = "powders"; + } if (item.statMap.has('NONE')) { return null; } + + if (item.statMap.has('powders')) { + this.powder_field.placeholder = item.statMap.get('slots') + ' slots'; + } + const tier = item.statMap.get('tier'); this.input_field.classList.add(tier); if (this.health_field) { diff --git a/js/display.js b/js/display.js index a45eae1..a3edcf9 100644 --- a/js/display.js +++ b/js/display.js @@ -419,7 +419,7 @@ function displayExpandedItem(item, parent_id){ } } //Show powder specials ;-; - let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots", "ring", "bracelet", "necklace"]; + let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots"];//, "ring", "bracelet", "necklace"]; if(nonConsumables.includes(item.get("type"))) { let powder_special = document.createElement("div"); powder_special.classList.add("col"); From 8d357f7531dac775368863690702f63965338150 Mon Sep 17 00:00:00 2001 From: ferricles Date: Sun, 26 Jun 2022 01:15:26 -0700 Subject: [PATCH 085/155] current AP count display --- js/display_atree.js | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index e419c75..ed2b3f3 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -12,14 +12,37 @@ function construct_AT(elem, tree) { active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center"); let active_word = document.createElement("div"); - active_word.classList.add("col"); - active_word.textContent = "Active:"; + active_word.classList.add("col-auto"); + active_word.textContent = "Active Abilities:"; let active_AP_container = document.createElement("div"); - active_AP_container.classList.add("col"); - active_AP_container.textContent = "(0/45 AP)"; - + active_AP_container.classList.add("col-auto"); + + let active_AP_subcontainer = document.createElement("div"); + active_AP_subcontainer.classList.add("row"); + + let active_AP_cost = document.createElement("div"); + active_AP_cost.classList.add("col-auto", "mx-0", "px-0"); + active_AP_cost.id = "active_AP_cost"; + active_AP_cost.textContent = "0"; + let active_AP_slash = document.createElement("div"); + active_AP_slash.classList.add("col-auto", "mx-0", "px-0"); + active_AP_slash.textContent = "/"; + let active_AP_cap = document.createElement("div"); + active_AP_cap.classList.add("col-auto", "mx-0", "px-0"); + active_AP_cap.id = "active_AP_cap"; + active_AP_cap.textContent = "45"; + let active_AP_end = document.createElement("div"); + active_AP_end.classList.add("col-auto", "mx-0", "px-0"); + active_AP_end.textContent = " AP"; + //I can't believe we can't pass in multiple children at once + active_AP_subcontainer.appendChild(active_AP_cost); + active_AP_subcontainer.appendChild(active_AP_slash); + active_AP_subcontainer.appendChild(active_AP_cap); + active_AP_subcontainer.appendChild(active_AP_end); + active_AP_container.appendChild(active_AP_subcontainer); + active_row.appendChild(active_word); active_row.appendChild(active_AP_container); @@ -165,11 +188,13 @@ function construct_AT(elem, tree) { tooltip.style.display = "none"; this.classList.remove("atree-selected"); this.style.backgroundImage = ''; + document.getElementById("active_AP_cost").textContent = parseInt(document.getElementById("active_AP_cost").textContent) - node.cost; } else { tooltip.style.display = "block"; this.classList.add("atree-selected"); this.style.backgroundImage = 'url("../media/atree/node_highlight.png")'; + document.getElementById("active_AP_cost").textContent = parseInt(document.getElementById("active_AP_cost").textContent) + node.cost; } }); From 92f4df365913e7cc1b6666075599010985c1c26d Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 15:59:02 +0700 Subject: [PATCH 086/155] implement atree connector highlighting --- js/display_atree.js | 167 +++++++++++++++--- media/atree/connect_t.png | Bin 692 -> 962 bytes media/atree/highlight_c_2_a.png | Bin 0 -> 1099 bytes media/atree/highlight_c_2_l.png | Bin 0 -> 1107 bytes media/atree/highlight_c_3.png | Bin 0 -> 1090 bytes media/atree/highlight_t_2_a.png | Bin 0 -> 708 bytes media/atree/highlight_t_2_l.png | Bin 0 -> 666 bytes .../{highlight_t.png => highlight_t_3.png} | Bin 632 -> 654 bytes 8 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 media/atree/highlight_c_2_a.png create mode 100644 media/atree/highlight_c_2_l.png create mode 100644 media/atree/highlight_c_3.png create mode 100644 media/atree/highlight_t_2_a.png create mode 100644 media/atree/highlight_t_2_l.png rename media/atree/{highlight_t.png => highlight_t_3.png} (96%) diff --git a/js/display_atree.js b/js/display_atree.js index e21ce61..c5130e2 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -1,5 +1,6 @@ let atree_map; let atree_connectors_map; +let atree_active_connections = []; function construct_AT(elem, tree) { console.log("constructing ability tree UI"); document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later @@ -16,7 +17,7 @@ function construct_AT(elem, tree) { atree_map = new Map(); atree_connectors_map = new Map() for (let i of tree) { - atree_map.set(i.display_name, {display: i.display, parents: i.parents, connectors: []}); + atree_map.set(i.display_name, {display: i.display, parents: i.parents, connectors: new Map(), active: false}); } for (let i = 0; i < tree.length; i++) { @@ -54,9 +55,10 @@ function construct_AT(elem, tree) { } - let connector_list = []; // create connectors based on parent location for (let parent of node.parents) { + atree_map.get(node.display_name).connectors.set(parent, []); + let parent_node = atree_map.get(parent); let connect_elem = document.createElement("div"); @@ -65,8 +67,8 @@ function construct_AT(elem, tree) { for (let i = node.display.row - 1; i > parent_node.display.row; i--) { let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; - atree_map.get(node.display_name).connectors.push(i + "," + node.display.col); - atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line"}); + atree_map.get(node.display_name).connectors.get(parent).push(i + "," + node.display.col); + atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line", owner: [node.display_name, parent]}); resolve_connector(i + "," + node.display.col, node); } // connect horizontally @@ -76,8 +78,8 @@ function construct_AT(elem, tree) { let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; connector.classList.add("rotate-90"); - atree_map.get(node.display_name).connectors.push(parent_node.display.row + "," + i); - atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line"}); + atree_map.get(node.display_name).connectors.get(parent).push(parent_node.display.row + "," + i); + atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line", owner: [node.display_name, parent]}); resolve_connector(parent_node.display.row + "," + i, node); } @@ -86,8 +88,8 @@ function construct_AT(elem, tree) { if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) { let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - atree_map.get(node.display_name).connectors.push(parent_node.display.row + "," + node.display.col); - atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle"}); + atree_map.get(node.display_name).connectors.get(parent).push(parent_node.display.row + "," + node.display.col); + atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle", owner: [node.display_name, parent]}); if (parent_node.display.col > node.display.col) { connector.classList.add("rotate-180"); } @@ -99,12 +101,13 @@ function construct_AT(elem, tree) { } // create node - let node_elem = document.createElement('div') + let node_elem = document.createElement('div'); let icon = node.display.icon; if (icon === undefined) { icon = "node"; } node_elem.style = "background-image: url('../media/atree/"+icon+".png'); background-size: cover; width: 100%; height: 100%;"; + node_elem.classList.add("atree-circle"); // add tooltip node_elem.addEventListener('mouseover', function(e) { @@ -158,13 +161,13 @@ function construct_AT(elem, tree) { if (tooltip.style.display == "block") { tooltip.style.display = "none"; this.classList.remove("atree-selected"); - this.style.backgroundImage = 'url("../media/atree/node.png")'; } else { tooltip.style.display = "block"; this.classList.add("atree-selected"); - this.style.backgroundImage = 'url("../media/atree/node-selected.png")'; } + atree_toggle_state(node); + atree_update_connector(node); }); document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; @@ -179,29 +182,33 @@ function resolve_connector(pos, node) { let line = false; let angle = false; let t = false; + let owners = []; for (let i of atree_connectors_map.get(pos)) { if (i.type == "line") { - line += true; + line = true; } else if (i.type == "angle") { - angle += true; + angle = true; } else if (i.type == "t") { - t += true; + t = true; } + owners = owners.concat(i.owner); } + owners = [...new Set(owners)] + let connect_elem = document.createElement("div"); if ((line && angle)) { - connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;" - connect_elem.classList.add("rotate-180") - atree_connectors_map.set(pos, [{connector: connect_elem, type: "t"}]) + connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;"; + atree_connectors_map.set(pos, [{connector: connect_elem, type: "t", owner: owners, connector_state: {up: 0, left: 0, right: 0, down: 0}}]); } if (node.parents.length == 3 && t && atree_same_row(node)) { - connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;" - atree_connectors_map.set(pos, [{connector: connect_elem, type: "c"}]) + connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;"; + atree_connectors_map.set(pos, [{connector: connect_elem, type: "c", owner: owners, connector_state: {up: 0, left: 0, right: 0, down: 0}}]); } // override the conflict with the first children - atree_connectors_map.set(pos, [atree_connectors_map.get(pos)[0]]) + atree_connectors_map.set(pos, [atree_connectors_map.get(pos)[0]]); + atree_connectors_map.get(pos)[0].owner = owners; } // check if a node doesn't have same row w/ its parents (used to solve conflict) @@ -216,7 +223,125 @@ function atree_same_row(node) { function atree_render_connection() { for (let i of atree_connectors_map.keys()) { if (atree_connectors_map.get(i).length != 0) { - document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector) + document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector); } } } + +function atree_toggle_state(node) { + if (atree_map.get(node.display_name).active) { + atree_map.get(node.display_name).active = false; + } else { + atree_map.get(node.display_name).active = true; + } +} + +function atree_update_connector() { + atree_map.forEach((v) => { + if (v.active) { + atree_compute_highlight(v); + } + }); +} + +function atree_compute_highlight(node) { + node.connectors.forEach((v, k) => { + console.log(node.active); + if (node.active && atree_map.get(k).active) { + for (let i of v) { + connector_data = atree_connectors_map.get(i)[0]; + if (connector_data.type == "c" || connector_data.type == "t") { + connector_data.connector_state = atree_get_state(i); + let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type) + connector_data.connector.className = ""; + connector_data.connector.classList.add("rotate-" + connector_img.rotate); + connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + connector_img.attrib + ".png')"; + } else { + connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + ".png')"; + } + } + } else { + for (let i of v) { + connector_data = atree_connectors_map.get(i)[0]; + if (connector_data.type == "c" || connector_data.type == "t") { + connector_data.connector_state = atree_get_state(i); + let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type) + if (!connector_img) { + connector_data.connector.className = ""; + connector_data.connector.style.backgroundImage = "url('../media/atree/connect_" + connector_data.type + ".png')"; + } else { + connector_data.connector.className = ""; + connector_data.connector.classList.add("rotate-" + connector_img.rotate); + connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + connector_img.attrib + ".png')"; + }; + } else { + connector_data.connector.style.backgroundImage = "url('../media/atree/connect_" + connector_data.type + ".png')"; + } + } + } + }); +} + +function atree_get_state(connector) { + let connector_state = {left: 0, right: 0, up: 0, down: 0} + + for (let abil_name of atree_connectors_map.get(connector)[0].owner) { + state = atree_map.get(abil_name).active; + if (atree_map.get(abil_name).display.col > parseInt(connector.split(",")[1])) { + if (state) { + connector_state.right = 1; + } else { + connector_state.right = 0; + } + } + if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) { + if (state) { + connector_state.left = 1; + } else { + connector_state.left = 0; + } + } + if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) { + if (state) { + connector_state.up = 1; + } else { + connector_state.up = 0; + } + } + if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { + if (state) { + connector_state.down = 1; + } else { + connector_state.down = 0; + } + } + } + return connector_state; +} + +function atree_parse_connector(orient, type) { + // left, right, up, down + // todo + let connector_dict = { + "1100": {attrib: "_2_l", rotate: 0}, + "1010": {attrib: "_2_a", rotate: 0}, + "1001": {attrib: "_2_a", rotate: 270}, + "0110": {attrib: "_2_a", rotate: 90}, + "0101": {attrib: "_2_a", rotate: 180}, + "0011": {attrib: "_2_l", rotate: 90}, + "1110": {attrib: "_3", rotate: 0}, + "1101": {attrib: "_3", rotate: 180}, + "1011": {attrib: "_3", rotate: 270}, + "0111": {attrib: "_3", rotate: 90}, + "1111": {attrib: "", rotate: 0} + } + + console.log(orient); + + let res = "" + for (let i in orient) { + res += orient[i]; + } + + return connector_dict[res]; +} diff --git a/media/atree/connect_t.png b/media/atree/connect_t.png index 8ed976eb9f0d5d3c8d87891a0c058fa5598b8603..9acedd6f7eeff3efc12c7e1987097b695f6c75a2 100644 GIT binary patch literal 962 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBK4*bPWHAE+-(e7DJf6QIg@J)7&(p;*q$2L^4MVQO1_BI@n*aZAUX)O- zcPZ)(4@Z3d#tvzqPWpg9hTkKvb6lx;r!SzwAfN_DX4lKkJM@S%v~bcGovpoQS~P3x zy^pV-{fSlfd&0_)$OJ>%%%4}}{E6GgQnH=z0kQ;K#VCV_5Qt2!VO$l@r@XJq(*=~G hJYD@<);T3K(K>4BZhlJab^A0)(Qk)X*rp+S6aZ>!tVRF; literal 692 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;3=B+po-U3d z6?5L+Fyv!0$Te-*MEOd-G7;z z;W-CG3+E^zJp^pi)8@xD?SB$`w}G4C5DN@lyLUUC*JUSzf)9;R&e=0|dszzFkSqr# d3HpEsi^UU5cvNTD9S{Lo=;`X`vd$@?2>@Frgo6M8 diff --git a/media/atree/highlight_c_2_a.png b/media/atree/highlight_c_2_a.png new file mode 100644 index 0000000000000000000000000000000000000000..c7d3f899961ec602c87d61fb8ac2452c2d1febab GIT binary patch literal 1099 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fn=|)7d$|)7e=epeR2rGbfdS zLF4??iMAex14LT=gSRS)vRZdoq^!`35)dt%q9JzBDN}pPtAtON>@zM+ShB-{YaJ_l zz1V6#y>q*)c5pR4;O|&GdGey}DHZWS8G>3r9(=MpSN*);er-d5w&B@VGZ;+nnh6Jb z`lN>)J)phof&J!tJ5H%WD+h zZah`ly*X^v2DkZtS`V(-H|dGhRf9zlPCSJj0RfJJsy8|=tec#q==eNH`u~L2{gb^F z!v%X}xHX)c-~2lG|H1d1wRf&8nJzM=;hgyKpAw8ryB+qvvbcY|FW8>(&-nvW?|qq` zec+Ay%d5-8?3jLSYwUi#Y}p&e>DC7xOjew|N&Dx@mKeSY`^EXE7?`&^oS$XB{g|bn ze1pi@te6j4F}J|V6^nn6kF|Ns9C z+S&|2l7V6U!aXa26kADEakt z5%>0%VV<&~fU9F>>fQgRvYGXl$<_)u?QJ_w(ZSfBW-uopv)r2_F;C zGzNy;N)9kxk}A+}&JoO9pvw@zM+ShB-{YaJ_l zz1V6#y>q*)c5pR4;O|&GdGey}DHZWS8G>3r9(=MpSN*);er-d5w&B@VGZ;+nnh6Jb z`lN>)J)phof&J!tJ5H%WD+h zZah`ly*X^v2DkZtS`V(-H|dGhRf9zlPCSJj0RfJJsy8|=tec#q==eNH`u~L2{gb^F z!v%X}xHX)c-~2lG|H1d1wRf&8nJzM=;hgyKpAw8ryB+qvvbcY|FW8>(&-nvW?|qq` zec+Ay%d5-8?3jLSYwUi#Y}p&e>DC7xOjew|N&Dx@mKeSY`^EXE7?`&^oS$XB{g|bn ze1pi@te6j4F}J|V6^nn6kF|Ns9C z+S&|2l7V6U!aXa26kAD(9hXXgvC+%!{7Bh9K)xq36hmS!;1AzTik+*Xohfh@$XU7w)J(VTXJDd)HD zf&IK7Js14LZI|yYf4cB{oz<(4g_R7qvY9W$NjH4E#jxc%+k(Aj3~#qGW~}FRsLsVF z^(!dliu@m!wAt&A0Bvok`(^*TnW5I$=Ed*(b=-9KATUJ840{8XioJG>XHrZWSD9Fd zfkM{P)z4*}Q$o`-1rQGyx9(ti0Wj!I;BkuxdP`W`3NkP_H(Y-$3^Hpo_kI(QE|^&W D_;rF+ literal 0 HcmV?d00001 diff --git a/media/atree/highlight_c_3.png b/media/atree/highlight_c_3.png new file mode 100644 index 0000000000000000000000000000000000000000..546bd342fc7fff98613ba6a8ef8a45850c867973 GIT binary patch literal 1090 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fn=|)7d$|)7e=epeR2rGbfdS zLF4??iMAex14LT=gSRS)vRZdoq^!`35)dt%q9JzBDN}pPtAtON>@zM+ShB-{YaJ_l zz1V6#y>q*)c5pR4;O|&GdGey}DHZWS8G>3r9(=MpSN*);er-d5w&B@VGZ;+nnh6Jb z`lN>)J)phof&J!tJ5H%WD+h zZah`ly*X^v2DkZtS`V(-H|dGhRf9zlPCSJj0RfJJsy8|=tec#q==eNH`u~L2{gb^F z!v%X}xHX)c-~2lG|H1d1wRf&8nJzM=;hgyKpAw8ryB+qvvbcY|FW8>(&-nvW?|qq` zec+Ay%d5-8?3jLSYwUi#Y}p&e>DC7xOjew|N&Dx@mKeSY`^EXE7?`&^oS$XB{g|bn ze1pi@te6j4F}J|V6^nn6kF|Ns9C z+S&|2l7V6U!aXa26kADEakt z5%>0%VP3SO2#ez-llS$h-VVpLU74Gs=lUFC{iyY?|NENb;pg|;fBW+<)}M_rgB_GM z7#h~uuz+ZWDkBaDYYs58sAqI@0?(*-^zuvly*MERE#frc^x zu+WQB&JgA5%?w+b!Ays6If!ywNtOk&5W9ALf+|0$!0=W9tn9+nc8GE`ryUSWxSh{v zXT|`u&A~qQ>zr?|PC2iyvwC&$ew=i}w_6NbuCp!JYsT<)D`UobUWe*jhFjUp7w}8% z6OsJ->vQsJ|CDXEyFXoMuUEYWwEfHF^Ka}~Ud(>zc>n*m=mvUw6y$sI;-EqC!0*5O zcP|D0HD_(^1)1aN>gTe~DWR!h8#HpQfRS4a3G38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzEFTqh%1n0P*P&}|DQoy`#+FmV5qH00|~N~1o;I6MSxAr*0NZyEN9IEuI&jGeak|5V18H+ipxclSJBV!U=0 zSGC=~^S^%Fecr$S+t26awM7CA-xPqxF)&=vmS+Mp2D8`$u?O4gS%2g+gIs&TfBi3; zx2wbLzFhV1|MlX=_b;E#7-WCX+jS$9!RniB>BiLzC2y)>DleHU14w+4fIUC3EXPV;kv)GBbv^ISjXMGHlt* sn32wW;T+q7xx5b6gGZv5xu*U38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzEFTqh%1n0P*VE;|38DaHUp4kU|7F!&q^T0RubeF3={{7ZQj3RKTw3T zz$3Dlfr0N32s4Umcr^emxaaBO7*Y}U_LgCujH3XHW6+hq|4%nHY;oUoYo*8XInonk zztz^XZNXe#2W#nuGBbv^ISjXMGHlt*n2|n+BqnY9E%*NQ_X;xH_ABVgiuv*X z-z=XmcXhf`cy0O9#jopMnlZ%wLJSusH#2Nuhe)rhVF8O(6$vzaQvfqBOzj8L z4*q;h7vvz)yFLkm#a^6rWVqD~kq)zopr5}FtqZmynpUjEge!uU(3+iZ{}K?(pEYu?EK literal 0 HcmV?d00001 diff --git a/media/atree/highlight_t.png b/media/atree/highlight_t_3.png similarity index 96% rename from media/atree/highlight_t.png rename to media/atree/highlight_t_3.png index 7629abf158e6fb5e152a4eeed06327a39f7d5be3..a8d7e9235070347ed350265943aca9e72ab81bcb 100644 GIT binary patch delta 30 hcmeyt(#N`?f=Mj9FVdQ&MBb@gaNn83!VS~ delta 7 OcmeBU{lT)Kf(ZZ%@dCO4 From 5748af5f7db12bb06d005a6c64d4b9960e0118af Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 16:01:31 +0700 Subject: [PATCH 087/155] atree node highlighting --- css/sq2bs.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/css/sq2bs.css b/css/sq2bs.css index 0532fc0..6e41131 100644 --- a/css/sq2bs.css +++ b/css/sq2bs.css @@ -483,3 +483,13 @@ a:hover { .hide-scroll::-webkit-scrollbar { display: none; /* Safari and Chrome */ } + +.atree-selected { + outline: 5px solid rgba(95, 214, 223, 0.8); +} + +.atree-circle { + border-radius:50%; + -moz-border-radius:50%; + -webkit-border-radius:50%; +} \ No newline at end of file From 5992d5db7ff05136248cda2fd5368e22cce40aee Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 16:04:02 +0700 Subject: [PATCH 088/155] fix: connector not updating when node is unselected --- js/display_atree.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index c5130e2..dfb905b 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -238,9 +238,7 @@ function atree_toggle_state(node) { function atree_update_connector() { atree_map.forEach((v) => { - if (v.active) { - atree_compute_highlight(v); - } + atree_compute_highlight(v); }); } @@ -336,8 +334,6 @@ function atree_parse_connector(orient, type) { "1111": {attrib: "", rotate: 0} } - console.log(orient); - let res = "" for (let i in orient) { res += orient[i]; From 72f9b2b30d87a9e4345c80f465760b23845c8e52 Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 16:25:54 +0700 Subject: [PATCH 089/155] fix: tri connector rotation --- js/display_atree.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index dfb905b..366944e 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -244,7 +244,6 @@ function atree_update_connector() { function atree_compute_highlight(node) { node.connectors.forEach((v, k) => { - console.log(node.active); if (node.active && atree_map.get(k).active) { for (let i of v) { connector_data = atree_connectors_map.get(i)[0]; @@ -307,20 +306,25 @@ function atree_get_state(connector) { } } if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { + if (state) { connector_state.down = 1; + if (abil_name == "Scorched Earth") { + alert('a') + } } else { connector_state.down = 0; } } } + console.log(connector_state); return connector_state; } function atree_parse_connector(orient, type) { // left, right, up, down // todo - let connector_dict = { + let c_connector_dict = { "1100": {attrib: "_2_l", rotate: 0}, "1010": {attrib: "_2_a", rotate: 0}, "1001": {attrib: "_2_a", rotate: 270}, @@ -334,10 +338,21 @@ function atree_parse_connector(orient, type) { "1111": {attrib: "", rotate: 0} } + let t_connector_dict = { + "1100": {attrib: "_2_l", rotate: 0}, + "1001": {attrib: "_2_a", rotate: "flip"}, + "0101": {attrib: "_2_a", rotate: 0}, + "1101": {attrib: "_3", rotate: 0} + } + let res = "" for (let i in orient) { res += orient[i]; } - return connector_dict[res]; + if (type == "c") { + return c_connector_dict[res]; + } else { + return t_connector_dict[res]; + } } From ab38e5cf5f2509e6e1b4da519e0a1a0004a42cbb Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 16:32:41 +0700 Subject: [PATCH 090/155] remove debug code --- js/display_atree.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 366944e..36f4c06 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -309,15 +309,11 @@ function atree_get_state(connector) { if (state) { connector_state.down = 1; - if (abil_name == "Scorched Earth") { - alert('a') - } } else { connector_state.down = 0; } } } - console.log(connector_state); return connector_state; } From 295e8f3e364bbb820cf00871704b525a0ef1a686 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 03:14:31 -0700 Subject: [PATCH 091/155] Fix damage calc... somewhat unify powder format --- js/builder.js | 1 - js/builder_graph.js | 64 ++++++++-------------- js/damage_calc.js | 119 ++++------------------------------------ js/powders.js | 131 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 148 deletions(-) diff --git a/js/builder.js b/js/builder.js index 3b6cf33..96513d8 100644 --- a/js/builder.js +++ b/js/builder.js @@ -147,7 +147,6 @@ function toggle_tab(tab) { } else { document.querySelector("#"+tab).style.display = "none"; } - console.log(document.querySelector("#"+tab).style.display); } // toggle spell arrow diff --git a/js/builder_graph.js b/js/builder_graph.js index e54fd02..f079f40 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -22,7 +22,6 @@ function update_armor_powder_specials(elem_id) { //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 @@ -86,23 +85,18 @@ let powder_special_input = new (class extends ComputeNode { })(); function updatePowderSpecials(buttonId) { - let name = (buttonId).split("-")[0]; - let power = (buttonId).split("-")[1]; // [1, 5] - + let prefix = (buttonId).split("-")[0].replace(' ', '_') + '-'; let elem = document.getElementById(buttonId); - if (elem.classList.contains("toggleOn")) { //toggle the pressed button off - elem.classList.remove("toggleOn"); - } else { + if (elem.classList.contains("toggleOn")) { 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"); - } + const elem2 = document.getElementById(prefix + i); + if(elem2.classList.contains("toggleOn")) { elem2.classList.remove("toggleOn"); } } //toggle the pressed button on elem.classList.add("toggleOn"); } - powder_special_input.mark_dirty().update(); } @@ -129,6 +123,7 @@ class PowderSpecialCalcNode extends ComputeNode { } class PowderSpecialDisplayNode extends ComputeNode { + // TODO: Refactor this entirely to be adding more spells to the spell list constructor() { super('builder-powder-special-display'); this.fail_cb = true; @@ -137,27 +132,11 @@ class PowderSpecialDisplayNode extends ComputeNode { compute_func(input_map) { const powder_specials = input_map.get('powder-specials'); const stats = input_map.get('stats'); - const weapon = input_map.get('weapon'); + const weapon = input_map.get('build').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"]); - } -} - /** * Node for getting an item's stats from an item input field. * @@ -174,6 +153,11 @@ class ItemInputNode extends InputNode { constructor(name, item_input_field, none_item) { super(name, item_input_field); this.none_item = new Item(none_item); + 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 + } this.none_item.statMap.set('NONE', true); } @@ -197,17 +181,17 @@ class ItemInputNode extends InputNode { item.statMap.set('powders', powdering); } let type_match; - if (this.none_item.statMap.get('category') === 'weapon') { - type_match = item.statMap.get('category') === 'weapon'; + if (this.category == 'weapon') { + type_match = item.statMap.get('category') == 'weapon'; } else { - type_match = item.statMap.get('type') === this.none_item.statMap.get('type'); + type_match = item.statMap.get('type') == this.none_item.statMap.get('type'); } if (type_match) { - if (item.statMap.get('category') === 'armor') { - applyArmorPowders(item.statMap, powdering); + if (item.statMap.get('category') == 'armor') { + applyArmorPowders(item.statMap); } - else if (item.statMap.get('category') === 'weapon') { - apply_weapon_powders(item.statMap, powdering); + else if (item.statMap.get('category') == 'weapon') { + apply_weapon_powders(item.statMap); } return item; } @@ -579,7 +563,7 @@ class SpellDamageCalcNode extends ComputeNode { } compute_func(input_map) { - const weapon = new Map(input_map.get('weapon-input').statMap); + const weapon = input_map.get('build').weapon.statMap; const spell_info = input_map.get('spell-info'); const spell_parts = spell_info[1]; const stats = input_map.get('stats'); @@ -647,6 +631,7 @@ class SpellDisplayNode extends ComputeNode { Returns an array in the order: */ function getMeleeStats(stats, weapon) { + stats = new Map(stats); // Shallow copy const weapon_stats = weapon.statMap; const skillpoints = [ stats.get('str'), @@ -665,9 +650,8 @@ function getMeleeStats(stats, weapon) { adjAtkSpd = 0; } - let damage_mult = stats.get("damageMultiplier"); if (weapon_stats.get("type") === "relik") { - damage_mult = 0.99; // CURSE YOU WYNNCRAFT + stats.set('damageMultiplier', 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. } @@ -1076,7 +1060,7 @@ function builder_graph_init() { // 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'); + .link_to(stat_agg_node, 'stats').link_to(build_node, 'build'); stat_agg_node.link_to(powder_special_calc, 'powder-boost'); stat_agg_node.link_to(armor_powder_node, 'armor-powder'); powder_special_input.update(); @@ -1093,7 +1077,7 @@ function builder_graph_init() { spell_node.link_to(stat_agg_node, 'stats') let calc_node = new SpellDamageCalcNode(i); - calc_node.link_to(item_nodes[8], 'weapon-input').link_to(stat_agg_node, 'stats') + calc_node.link_to(build_node, 'build').link_to(stat_agg_node, 'stats') .link_to(spell_node, 'spell-info'); spelldmg_nodes.push(calc_node); diff --git a/js/damage_calc.js b/js/damage_calc.js index 856743d..e3b2f14 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -1,7 +1,5 @@ const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]); -const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; -const damage_present_key = 'damagePresent'; function get_base_dps(item) { const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))]; //SUPER JANK @HPP PLS FIX @@ -27,107 +25,6 @@ function get_base_dps(item) { } } -// THIS MUTATES THE ITEM -function apply_weapon_powders(item) { - let present; - if (item.get("tier") !== "Crafted") { - let weapon_result = calc_weapon_powder(item); - let damages = weapon_result[0]; - present = weapon_result[1]; - for (const i in damage_keys) { - item.set(damage_keys[i], damages[i]); - } - } else { - let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; - let results_low = calc_weapon_powder(item, base_low); - let damage_low = results_low[0]; - let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; - let results_high = calc_weapon_powder(item, base_high); - let damage_high = results_high[0]; - present = results_high[1]; - - for (const i in damage_keys) { - item.set(damage_keys[i], [damage_low[i], damage_high[i]]); - } - } - console.log(item); - item.set(damage_present_key, present); -} - -/** - * weapon: Weapon to apply powder to - * damageBases: used by crafted - */ -function calc_weapon_powder(weapon, damageBases) { - let powders = weapon.get("powders").slice(); - - // Array of neutral + ewtfa damages. Each entry is a pair (min, max). - let damages = [ - weapon.get('nDam').split('-').map(Number), - weapon.get('eDam').split('-').map(Number), - weapon.get('tDam').split('-').map(Number), - weapon.get('wDam').split('-').map(Number), - weapon.get('fDam').split('-').map(Number), - weapon.get('aDam').split('-').map(Number) - ]; - - // Applying spell conversions - let neutralBase = damages[0].slice(); - let neutralRemainingRaw = damages[0].slice(); - - //powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do. - - //Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA - //1st round - apply each as ingred, 2nd round - apply as normal - if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) { - for (const p of powders.concat(weapon.get("ingredPowders"))) { - let powder = powderStats[p]; //use min, max, and convert - let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error - let diff = Math.floor(damageBases[0] * powder.convert/100); - damageBases[0] -= diff; - damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 ); - } - //update all damages - for (let i = 0; i < damages.length; i++) { - damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)]; - } - neutralRemainingRaw = damages[0].slice(); - neutralBase = damages[0].slice(); - } - - //apply powders to weapon - for (const powderID of powders) { - const powder = powderStats[powderID]; - // Bitwise to force conversion to integer (integer division). - const element = (powderID/6) | 0; - let conversionRatio = powder.convert/100; - if (neutralRemainingRaw[1] > 0) { - let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); - let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]); - - //damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff)); - //damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff)); - //neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); - //neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); - damages[element+1][0] += min_diff; - damages[element+1][1] += max_diff; - neutralRemainingRaw[0] -= min_diff; - neutralRemainingRaw[1] -= max_diff; - } - damages[element+1][0] += powder.min; - damages[element+1][1] += powder.max; - } - - // The ordering of these two blocks decides whether neutral is present when converted away or not. - let present_elements = [] - for (const damage of damages) { - present_elements.push(damage[1] > 0); - } - - // The ordering of these two blocks decides whether neutral is present when converted away or not. - damages[0] = neutralRemainingRaw; - return [damages, present_elements]; -} function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) { // TODO: Roll all the loops together maybe @@ -227,10 +124,18 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw'); } // Next, rainraw and propRaw - let new_min = damages_obj[0] + raw_boost + (damages_obj[0] / total_min) * prop_raw; - let new_max = damages_obj[1] + raw_boost + (damages_obj[1] / total_max) * prop_raw; - if (i != 0) { // rainraw - new_min += (damages_obj[0] / total_elem_min) * rainbow_raw; + let new_min = damages_obj[0] + raw_boost; + let new_max = damages_obj[1] + raw_boost; + if (total_max > 0) { // TODO: what about total negative all raw? + if (total_elem_min > 0) { + new_min += (damages_obj[0] / total_min) * prop_raw; + } + new_max += (damages_obj[1] / total_max) * prop_raw; + } + if (i != 0 && total_elem_max > 0) { // rainraw TODO above + if (total_elem_min > 0) { + new_min += (damages_obj[0] / total_elem_min) * rainbow_raw; + } new_max += (damages_obj[1] / total_elem_max) * rainbow_raw; } damages_obj[0] = new_min; diff --git a/js/powders.js b/js/powders.js index db0d195..cd2ab00 100644 --- a/js/powders.js +++ b/js/powders.js @@ -61,3 +61,134 @@ let powderSpecialStats = [ _ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f _ps("Wind Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a ]; + +/** + * 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) { + const powders = expandedItem.get('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"]); + } +} + +const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ]; +const damage_present_key = 'damagePresent'; +/** + * Apply weapon powders. MUTATES THE ITEM! + * Adds entries for `damage_keys` and `damage_present_key` + * For normal items, `damage_keys` is 6x2 list (elem: [min, max]) + * For crafted items, `damage_keys` is 6x2x2 list (elem: [minroll: [min, max], maxroll: [min, max]]) + */ +function apply_weapon_powders(item) { + let present; + if (item.get("tier") !== "Crafted") { + let weapon_result = calc_weapon_powder(item); + let damages = weapon_result[0]; + present = weapon_result[1]; + for (const i in damage_keys) { + item.set(damage_keys[i], damages[i]); + } + } else { + let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]; + let results_low = calc_weapon_powder(item, base_low); + let damage_low = results_low[0]; + let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]; + let results_high = calc_weapon_powder(item, base_high); + let damage_high = results_high[0]; + present = results_high[1]; + + for (const i in damage_keys) { + item.set(damage_keys[i], [damage_low[i], damage_high[i]]); + } + } + item.set(damage_present_key, present); +} + +/** + * Calculate weapon damage from powder. + * + * Params: + * weapon: Weapon to apply powder to + * damageBases: used by crafted + * + * Return: + * [damages, damage_present] + */ +function calc_weapon_powder(weapon, damageBases) { + let powders = weapon.get("powders").slice(); + + // Array of neutral + ewtfa damages. Each entry is a pair (min, max). + let damages = [ + weapon.get('nDam').split('-').map(Number), + weapon.get('eDam').split('-').map(Number), + weapon.get('tDam').split('-').map(Number), + weapon.get('wDam').split('-').map(Number), + weapon.get('fDam').split('-').map(Number), + weapon.get('aDam').split('-').map(Number) + ]; + + // Applying spell conversions + let neutralBase = damages[0].slice(); + let neutralRemainingRaw = damages[0].slice(); + + //powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do. + + //Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA + //1st round - apply each as ingred, 2nd round - apply as normal + if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) { + for (const p of powders.concat(weapon.get("ingredPowders"))) { + let powder = powderStats[p]; //use min, max, and convert + let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error + let diff = Math.floor(damageBases[0] * powder.convert/100); + damageBases[0] -= diff; + damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 ); + } + //update all damages + for (let i = 0; i < damages.length; i++) { + damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)]; + } + neutralRemainingRaw = damages[0].slice(); + neutralBase = damages[0].slice(); + } + + //apply powders to weapon + for (const powderID of powders) { + const powder = powderStats[powderID]; + // Bitwise to force conversion to integer (integer division). + const element = (powderID/6) | 0; + let conversionRatio = powder.convert/100; + if (neutralRemainingRaw[1] > 0) { + let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]); + let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]); + + //damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff)); + //damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff)); + //neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff)); + //neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff)); + damages[element+1][0] += min_diff; + damages[element+1][1] += max_diff; + neutralRemainingRaw[0] -= min_diff; + neutralRemainingRaw[1] -= max_diff; + } + damages[element+1][0] += powder.min; + damages[element+1][1] += powder.max; + } + + // The ordering of these two blocks decides whether neutral is present when converted away or not. + let present_elements = [] + for (const damage of damages) { + present_elements.push(damage[1] > 0); + } + + // The ordering of these two blocks decides whether neutral is present when converted away or not. + damages[0] = neutralRemainingRaw; + return [damages, present_elements]; +} From d3c11a8a726170c80da875dc12bd2cb8267d8f3b Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 17:48:00 +0700 Subject: [PATCH 092/155] fix: tri failing case --- js/display_atree.js | 42 +++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 36f4c06..9d4292c 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -237,6 +237,11 @@ function atree_toggle_state(node) { } function atree_update_connector() { + atree_connectors_map.forEach((v) => { + if (v.length != 0) { + v[0].connector.style.backgroundImage = "url('../media/atree/connect_" + v[0].type + ".png')"; + } + }); atree_map.forEach((v) => { atree_compute_highlight(v); }); @@ -249,33 +254,15 @@ function atree_compute_highlight(node) { connector_data = atree_connectors_map.get(i)[0]; if (connector_data.type == "c" || connector_data.type == "t") { connector_data.connector_state = atree_get_state(i); - let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type) + let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type); connector_data.connector.className = ""; connector_data.connector.classList.add("rotate-" + connector_img.rotate); connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + connector_img.attrib + ".png')"; } else { connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + ".png')"; - } - } - } else { - for (let i of v) { - connector_data = atree_connectors_map.get(i)[0]; - if (connector_data.type == "c" || connector_data.type == "t") { - connector_data.connector_state = atree_get_state(i); - let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type) - if (!connector_img) { - connector_data.connector.className = ""; - connector_data.connector.style.backgroundImage = "url('../media/atree/connect_" + connector_data.type + ".png')"; - } else { - connector_data.connector.className = ""; - connector_data.connector.classList.add("rotate-" + connector_img.rotate); - connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + connector_img.attrib + ".png')"; - }; - } else { - connector_data.connector.style.backgroundImage = "url('../media/atree/connect_" + connector_data.type + ".png')"; - } - } - } + }; + }; + }; }); } @@ -283,37 +270,38 @@ function atree_get_state(connector) { let connector_state = {left: 0, right: 0, up: 0, down: 0} for (let abil_name of atree_connectors_map.get(connector)[0].owner) { + state = atree_map.get(abil_name).active; if (atree_map.get(abil_name).display.col > parseInt(connector.split(",")[1])) { if (state) { connector_state.right = 1; - } else { + } else if (!connector_state.right) { connector_state.right = 0; } } if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) { if (state) { connector_state.left = 1; - } else { + } else if (!connector_state.left) { connector_state.left = 0; } } if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) { if (state) { connector_state.up = 1; - } else { + } else if (!connector_state.up) { connector_state.up = 0; } } if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { - if (state) { connector_state.down = 1; - } else { + } else if (!connector_state.down) { connector_state.down = 0; } } } + console.log(connector_state) return connector_state; } From b9e04c4c9fab87fd3e129aef7ad61f0978c8feaf Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 17:55:06 +0700 Subject: [PATCH 093/155] add general comments --- js/display_atree.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 9d4292c..d02965f 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -167,7 +167,7 @@ function construct_AT(elem, tree) { this.classList.add("atree-selected"); } atree_toggle_state(node); - atree_update_connector(node); + atree_update_connector(); }); document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; @@ -175,7 +175,7 @@ function construct_AT(elem, tree) { atree_render_connection(); }; -// resolve connector conflict +// resolve connector conflict, when they occupy the same cell. function resolve_connector(pos, node) { if (atree_connectors_map.get(pos).length < 2) {return false;} @@ -228,6 +228,7 @@ function atree_render_connection() { } } +// toggle the state of a node. function atree_toggle_state(node) { if (atree_map.get(node.display_name).active) { atree_map.get(node.display_name).active = false; @@ -236,6 +237,7 @@ function atree_toggle_state(node) { } } +// refresh all connector to default state, then try to calculate the connector for all node function atree_update_connector() { atree_connectors_map.forEach((v) => { if (v.length != 0) { @@ -247,6 +249,7 @@ function atree_update_connector() { }); } +// set the correct connector highlight for an active node, given a node. function atree_compute_highlight(node) { node.connectors.forEach((v, k) => { if (node.active && atree_map.get(k).active) { @@ -266,6 +269,7 @@ function atree_compute_highlight(node) { }); } +// get the current active state of different directions, given a connector coordinate. function atree_get_state(connector) { let connector_state = {left: 0, right: 0, up: 0, down: 0} @@ -305,6 +309,7 @@ function atree_get_state(connector) { return connector_state; } +// parse a sequence of left, right, up, down to appropriate connector image function atree_parse_connector(orient, type) { // left, right, up, down // todo From aa9041bd010a11cad7bc2a3d1e0006a7f0e9b654 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 04:08:54 -0700 Subject: [PATCH 094/155] Fix tome damage calculation... --- js/build.js | 5 +++-- js/build_utils.js | 5 ++++- js/builder_graph.js | 1 + js/damage_calc.js | 3 ++- js/load_tome.js | 2 +- tomes.json | 38 +++++++++++++++++++------------------- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/js/build.js b/js/build.js index fc57316..ff14bee 100644 --- a/js/build.js +++ b/js/build.js @@ -153,7 +153,7 @@ class Build{ */ initBuildStats(){ - let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"]; + let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "damMobs", "defMobs"]; let must_ids = [ "eMdPct","eMdRaw","eSdPct","eSdRaw","eDamPct","eDamRaw","eDamAddMin","eDamAddMax", @@ -188,9 +188,10 @@ class Build{ } statMap.set(id,(statMap.get(id) || 0)+value); } + console.log(item_stats); for (const staticID of staticIDs) { if (item_stats.get(staticID)) { - if (staticID === "dmgMobs") { + if (staticID == "damMobs") { statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID)); } else if (staticID === "defMobs") { diff --git a/js/build_utils.js b/js/build_utils.js index 58e863d..17dfd4e 100644 --- a/js/build_utils.js +++ b/js/build_utils.js @@ -78,12 +78,13 @@ let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slot /*"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 ]; +// Extra fake IDs (reserved for use in spell damage calculation) : damageMultiplier, defMultiplier, poisonPct, activeMajorIDs let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ] //File reading for ID translations for JSON purposes let reversetranslations = new Map(); let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]); -//does not include dmgMobs (wep tomes) and defMobs (armor tomes) +//does not include damMobs (wep tomes) and defMobs (armor tomes) for (const [k, v] of translations) { reversetranslations.set(v, k); } @@ -115,6 +116,8 @@ let nonRolledIDs = [ "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "majorIds", + "damMobs", + "defMobs", // wynn2 damages. "eDamAddMin","eDamAddMax", "tDamAddMin","tDamAddMax", diff --git a/js/builder_graph.js b/js/builder_graph.js index f079f40..b18204b 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -832,6 +832,7 @@ class AggregateStatsNode extends ComputeNode { } } } + console.log(output_stats); return output_stats; } } diff --git a/js/damage_calc.js b/js/damage_calc.js index e3b2f14..a2ad42d 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -101,7 +101,8 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno let total_max = 0; for (let i in damages) { let damage_prefix = damage_elements[i] + specific_boost_str; - let damageBoost = 1 + skill_boost[i] + static_boost + (stats.get(damage_prefix+'Pct')/100); + let damageBoost = 1 + skill_boost[i] + static_boost + + ((stats.get(damage_prefix+'Pct') + stats.get(damage_elements[i]+'DamPct')) /100); damages[i][0] *= Math.max(damageBoost, 0); damages[i][1] *= Math.max(damageBoost, 0); // Collect total damage post %boost diff --git a/js/load_tome.js b/js/load_tome.js index 7b75451..6cde49d 100644 --- a/js/load_tome.js +++ b/js/load_tome.js @@ -1,4 +1,4 @@ -const TOME_DB_VERSION = 2; +const TOME_DB_VERSION = 3; // @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA let tdb; diff --git a/tomes.json b/tomes.json index 0b4c213..dc60657 100644 --- a/tomes.json +++ b/tomes.json @@ -290,7 +290,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 60, - "dmgMobs": 6, + "damMobs": 6, "fixID": false, "id": 20 }, @@ -302,7 +302,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 7, + "damMobs": 7, "str": 3, "fixID": false, "id": 21 @@ -315,7 +315,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 8, + "damMobs": 8, "str": 3, "fixID": false, "id": 22 @@ -328,7 +328,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 7, + "damMobs": 7, "dex": 3, "fixID": false, "id": 23 @@ -341,7 +341,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 8, + "damMobs": 8, "dex": 3, "fixID": false, "id": 24 @@ -354,7 +354,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 7, + "damMobs": 7, "int": 3, "fixID": false, "id": 25 @@ -367,7 +367,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 8, + "damMobs": 8, "int": 3, "fixID": false, "id": 26 @@ -380,7 +380,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 7, + "damMobs": 7, "def": 3, "fixID": false, "id": 27 @@ -393,7 +393,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 8, + "damMobs": 8, "def": 3, "fixID": false, "id": 28 @@ -406,7 +406,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 7, + "damMobs": 7, "agi": 3, "fixID": false, "id": 29 @@ -419,7 +419,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 8, + "damMobs": 8, "agi": 3, "fixID": false, "id": 30 @@ -432,7 +432,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 7, + "damMobs": 7, "str": 1, "dex": 1, "int": 1, @@ -449,7 +449,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 8, + "damMobs": 8, "str": 1, "dex": 1, "int": 1, @@ -466,7 +466,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 12, + "damMobs": 12, "eDamPct": 7, "fixID": false, "id": 33 @@ -479,7 +479,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 12, + "damMobs": 12, "tDamPct": 7, "fixID": false, "id": 34 @@ -492,7 +492,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 12, + "damMobs": 12, "wDamPct": 7, "fixID": false, "id": 35 @@ -505,7 +505,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 12, + "damMobs": 12, "fDamPct": 7, "fixID": false, "id": 36 @@ -518,7 +518,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 12, + "damMobs": 12, "aDamPct": 7, "fixID": false, "id": 37 @@ -531,7 +531,7 @@ "drop": "never", "restrict": "Soulbound Item", "lvl": 80, - "dmgMobs": 12, + "damMobs": 12, "eDamPct": 6, "tDamPct": 6, "wDamPct": 6, From 762120d1895085c37c4a8359d94c7cd8a65d71d6 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 04:12:04 -0700 Subject: [PATCH 095/155] Fix tomes (for real...) --- js/build.js | 11 ++--------- js/builder_graph.js | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/js/build.js b/js/build.js index ff14bee..4ce69b1 100644 --- a/js/build.js +++ b/js/build.js @@ -168,7 +168,6 @@ class Build{ //Create a map of this build's stats let statMap = new Map(); - statMap.set("damageMultiplier", 1); statMap.set("defMultiplier", 1); for (const staticID of staticIDs) { @@ -191,15 +190,7 @@ class Build{ console.log(item_stats); for (const staticID of staticIDs) { if (item_stats.get(staticID)) { - if (staticID == "damMobs") { - statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID)); - } - else if (staticID === "defMobs") { - statMap.set('defMultiplier', statMap.get('defMultiplier') * item_stats.get(staticID)); - } - else { statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID)); - } } } if (item_stats.get("majorIds")) { @@ -208,6 +199,8 @@ class Build{ } } } + statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100)); + statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100)); statMap.set("activeMajorIDs", major_ids); for (const [setName, count] of this.activeSetCounts) { const bonus = sets.get(setName).bonuses[count-1]; diff --git a/js/builder_graph.js b/js/builder_graph.js index b18204b..8bb8c67 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -524,7 +524,7 @@ function getDefenseStats(stats) { defenseStats.push(totalHp); //EHP let ehp = [totalHp, totalHp]; - let defMult = (2 - stats.get("classDef")) * (2 - stats.get("defMultiplier")); + let defMult = (2 - stats.get("classDef")) * stats.get("defMultiplier"); ehp[0] /= (1-def_pct)*(1-agi_pct)*defMult; ehp[1] /= (1-def_pct)*defMult; defenseStats.push(ehp); From 9d79a6de7eeb6b0db163a9f5555bc26681c51bb2 Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 18:18:27 +0700 Subject: [PATCH 096/155] push rotate-flip css --- css/sq2bs.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/css/sq2bs.css b/css/sq2bs.css index 6e41131..9b42afe 100644 --- a/css/sq2bs.css +++ b/css/sq2bs.css @@ -474,6 +474,11 @@ a:hover { transform: rotate(270deg); } +.rotate-flip { + -webkit-transform: scaleX(-1); + transform: scaleX(-1); +} + .hide-scroll { From dcc65516cd0b58999e53a718748717af4904922c Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 18:29:55 +0700 Subject: [PATCH 097/155] fix: unsafe connector rotation --- js/display_atree.js | 64 ++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index d02965f..d83725e 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -156,7 +156,7 @@ function construct_AT(elem, tree) { document.getElementById("atree-active").appendChild(active_tooltip); node_elem.addEventListener('click', function(e) { - if (e.target !== this) {return;} + if (e.target !== this) {return;}; let tooltip = document.getElementById("atree-ab-" + node.display_name.replaceAll(" ", "")); if (tooltip.style.display == "block") { tooltip.style.display = "none"; @@ -165,7 +165,7 @@ function construct_AT(elem, tree) { else { tooltip.style.display = "block"; this.classList.add("atree-selected"); - } + }; atree_toggle_state(node); atree_update_connector(); }); @@ -194,7 +194,7 @@ function resolve_connector(pos, node) { owners = owners.concat(i.owner); } - owners = [...new Set(owners)] + owners = [...new Set(owners)]; let connect_elem = document.createElement("div"); @@ -215,18 +215,18 @@ function resolve_connector(pos, node) { function atree_same_row(node) { for (let i of node.parents) { if (node.display.row == atree_map.get(i).display.row) { return false; } - } + }; return true; -} +}; // draw the connector onto the screen function atree_render_connection() { for (let i of atree_connectors_map.keys()) { if (atree_connectors_map.get(i).length != 0) { document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector); - } - } -} + }; + }; +}; // toggle the state of a node. function atree_toggle_state(node) { @@ -234,8 +234,8 @@ function atree_toggle_state(node) { atree_map.get(node.display_name).active = false; } else { atree_map.get(node.display_name).active = true; - } -} + }; +}; // refresh all connector to default state, then try to calculate the connector for all node function atree_update_connector() { @@ -267,52 +267,50 @@ function atree_compute_highlight(node) { }; }; }); -} +}; // get the current active state of different directions, given a connector coordinate. function atree_get_state(connector) { - let connector_state = {left: 0, right: 0, up: 0, down: 0} + let connector_state = [0, 0, 0, 0]; // left, right, up, down for (let abil_name of atree_connectors_map.get(connector)[0].owner) { state = atree_map.get(abil_name).active; if (atree_map.get(abil_name).display.col > parseInt(connector.split(",")[1])) { if (state) { - connector_state.right = 1; + connector_state[1] = 1; } else if (!connector_state.right) { - connector_state.right = 0; + connector_state[1] = 0; } } if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) { if (state) { - connector_state.left = 1; + connector_state[0] = 1; } else if (!connector_state.left) { - connector_state.left = 0; + connector_state[0] = 0; } } if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) { if (state) { - connector_state.up = 1; + connector_state[2] = 1; } else if (!connector_state.up) { - connector_state.up = 0; + connector_state[2] = 0; } } if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { if (state) { - connector_state.down = 1; + connector_state[3] = 1; } else if (!connector_state.down) { - connector_state.down = 0; - } - } - } - console.log(connector_state) + connector_state[3] = 0; + }; + }; + }; return connector_state; } // parse a sequence of left, right, up, down to appropriate connector image function atree_parse_connector(orient, type) { // left, right, up, down - // todo let c_connector_dict = { "1100": {attrib: "_2_l", rotate: 0}, "1010": {attrib: "_2_a", rotate: 0}, @@ -325,23 +323,23 @@ function atree_parse_connector(orient, type) { "1011": {attrib: "_3", rotate: 270}, "0111": {attrib: "_3", rotate: 90}, "1111": {attrib: "", rotate: 0} - } + }; let t_connector_dict = { "1100": {attrib: "_2_l", rotate: 0}, "1001": {attrib: "_2_a", rotate: "flip"}, "0101": {attrib: "_2_a", rotate: 0}, "1101": {attrib: "_3", rotate: 0} - } + }; - let res = "" - for (let i in orient) { - res += orient[i]; - } + let res = ""; + for (let i of orient) { + res += i; + }; if (type == "c") { return c_connector_dict[res]; } else { return t_connector_dict[res]; - } -} + }; +}; From 01e02d603b19f50ffc2582efc161535da561680f Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 18:34:35 +0700 Subject: [PATCH 098/155] fix: unsafe connector rotation (for real this time) --- js/display_atree.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index d83725e..d97ca36 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -279,28 +279,28 @@ function atree_get_state(connector) { if (atree_map.get(abil_name).display.col > parseInt(connector.split(",")[1])) { if (state) { connector_state[1] = 1; - } else if (!connector_state.right) { + } else if (!connector_state[1]) { connector_state[1] = 0; } } if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) { if (state) { connector_state[0] = 1; - } else if (!connector_state.left) { + } else if (!connector_state[0]) { connector_state[0] = 0; } } if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) { if (state) { connector_state[2] = 1; - } else if (!connector_state.up) { + } else if (!connector_state[2]) { connector_state[2] = 0; } } if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { if (state) { connector_state[3] = 1; - } else if (!connector_state.down) { + } else if (!connector_state[3]) { connector_state[3] = 0; }; }; From 89879e6a02a0d9b1431c8a484987fd73a7bc67c7 Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 18:42:58 +0700 Subject: [PATCH 099/155] fix: correct connector assets --- media/atree/highlight_t_2_a.png | Bin 708 -> 708 bytes media/atree/highlight_t_3.png | Bin 654 -> 654 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/media/atree/highlight_t_2_a.png b/media/atree/highlight_t_2_a.png index 1d808274b041f41fb0fa784c14a04ae6ce1f34a4..e6cd87a670763959d4639d0911ee28777eb60db6 100644 GIT binary patch literal 708 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzEFTqh%1n0P*P&}|DQoy`#+FmV5qH00|~N~1o;I6MSxMiM=PMX)-DKFZnK2`s`NBE21#@{Ftfd>u%oyJ03?2!GOJ)0y|Ni+g0^kv0rzG*Ok;Rz0L6DR6oO)%d3B#|KM2f|9d|}ZIM93 uHw9p9Ffd%uwr2t}2D8`$fd|i@+dtIzy3M1qgA?RjPgg&ebxsLQAPfMu_VwTZ literal 708 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5D>38$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBzEFTqh%1n0P*P&}|DQoy`#+FmV5qH00|~N~1o;I6MSxAr*0NZyEN9IEuI&jGeak|5V18H+ipxclSJBV!U=0 zSGC=~^S^%Fecr$S+t26awM7CA-xPqxF)&=vmS+Mp2D8`$u?O4gS%2g+gIs&TfBi3; zx2wbLzFhV1|MlX=_b;E#7-WCX+jS$9!RniB>BiLzC2y)>DleHU14w+4fIUC3EXPV;kv)GBbv^ISjXMGHlt* sn32wW;T+q7xx5b6gGZv5xu*U3)MtDyDC!36Y6V^O;%vC zo2}qZR delta 336 zcmeBU?PHzLRR7S^#WAEJ?(MCOdD4LbERL5R{jcAwyWsKUMZ7jwPpZ@?Yv5 z>%Y&B-@Cv6^+`vDTg?n0;1F)d0%lYd2{e3D05dO4qVnZWGWp8{a|MLm_xLo@Dx?t$F>tUvOZK@NMdJbbVJ?^mbV_x*Yqxc*<68N=J0 z$tH~AlNcqrYc-9c6@gsq>FVdQ&MBcO OyW{Djj2FlvAW;AW`fVlv From 72999c3014780ba2ce98b7388381ba6c9b184752 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 05:18:23 -0700 Subject: [PATCH 100/155] Fix set initial load --- js/build.js | 1 - js/builder_graph.js | 1 - js/display_atree.js | 6 ++++++ js/load.js | 7 ++++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/js/build.js b/js/build.js index 4ce69b1..fee67cf 100644 --- a/js/build.js +++ b/js/build.js @@ -187,7 +187,6 @@ class Build{ } statMap.set(id,(statMap.get(id) || 0)+value); } - console.log(item_stats); for (const staticID of staticIDs) { if (item_stats.get(staticID)) { statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID)); diff --git a/js/builder_graph.js b/js/builder_graph.js index 8bb8c67..c03feea 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -832,7 +832,6 @@ class AggregateStatsNode extends ComputeNode { } } } - console.log(output_stats); return output_stats; } } diff --git a/js/display_atree.js b/js/display_atree.js index d97ca36..2a65058 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -1,4 +1,5 @@ let atree_map; +let atree_head; let atree_connectors_map; let atree_active_connections = []; function construct_AT(elem, tree) { @@ -26,6 +27,11 @@ function construct_AT(elem, tree) { // create rows if not exist let missing_rows = [node.display.row]; + if (node.parents.length == 0) { + // Assuming there is only one head. + atree_head = node; + } + for (let parent of node.parents) { missing_rows.push(tree.find(object => {return object.display_name === parent;}).display.row); } diff --git a/js/load.js b/js/load.js index 521765e..678e012 100644 --- a/js/load.js +++ b/js/load.js @@ -103,7 +103,7 @@ async function load() { let url = baseUrl + "/compress.json?"+new Date(); let result = await (await fetch(url)).json(); items = result.items; - sets = result.sets; + let sets_ = result.sets; let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite'); add_tx.onabort = function(e) { @@ -121,8 +121,9 @@ async function load() { add_promises.push(req); } let sets_store = add_tx.objectStore('set_db'); - for (const set in sets) { - add_promises.push(sets_store.add(sets[set], set)); + for (const set in sets_) { + add_promises.push(sets_store.add(sets_[set], set)); + sets.set(set, sets_[set]); } add_promises.push(add_tx.complete); From 94e4b52615777be0991f41075f1e2d758cc3d84b Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 05:29:06 -0700 Subject: [PATCH 101/155] Print stack visually on error --- builder/index.html | 2 ++ js/builder.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/builder/index.html b/builder/index.html index 1a14ed3..6385eb9 100644 --- a/builder/index.html +++ b/builder/index.html @@ -406,6 +406,8 @@
+
+
diff --git a/js/builder.js b/js/builder.js index 3b6cf33..78e4a99 100644 --- a/js/builder.js +++ b/js/builder.js @@ -443,6 +443,11 @@ function init() { builder_graph_init(); } +window.onerror = function(message, source, lineno, colno, error) { + document.getElementById('err-box').textContent = message; + document.getElementById('stack-box').textContent = error.stack; +}; + (async function() { let load_promises = [ load_init(), load_ing_init(), load_tome_init() ]; await Promise.all(load_promises); From 1303c959b19d29f4fe13b377f3643cad64bc0439 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 05:35:04 -0700 Subject: [PATCH 102/155] HOTFIX: remove `const` from SumInputNode to allow default value fixing --- js/builder_graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index a540673..928a5c3 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -928,7 +928,7 @@ class SkillPointSetterNode extends ComputeNode { */ class SumNumberInputNode extends InputNode { compute_func(input_map) { - const value = this.input_field.value; + let value = this.input_field.value; if (value === "") { value = 0; } let input_num = 0; From 21773ab52dced6e42a151111e9e1e5382e173409 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 05:18:23 -0700 Subject: [PATCH 103/155] Fix set initial load --- js/display_atree.js | 6 ++++++ js/load.js | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/js/display_atree.js b/js/display_atree.js index 888383e..205a228 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -1,4 +1,5 @@ let atree_map; +let atree_head; let atree_connectors_map; function construct_AT(elem, tree) { console.log("constructing ability tree UI"); @@ -25,6 +26,11 @@ function construct_AT(elem, tree) { // create rows if not exist let missing_rows = [node.display.row]; + if (node.parents.length == 0) { + // Assuming there is only one head. + atree_head = node; + } + for (let parent of node.parents) { missing_rows.push(tree.find(object => {return object.display_name === parent;}).display.row); } diff --git a/js/load.js b/js/load.js index 521765e..678e012 100644 --- a/js/load.js +++ b/js/load.js @@ -103,7 +103,7 @@ async function load() { let url = baseUrl + "/compress.json?"+new Date(); let result = await (await fetch(url)).json(); items = result.items; - sets = result.sets; + let sets_ = result.sets; let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite'); add_tx.onabort = function(e) { @@ -121,8 +121,9 @@ async function load() { add_promises.push(req); } let sets_store = add_tx.objectStore('set_db'); - for (const set in sets) { - add_promises.push(sets_store.add(sets[set], set)); + for (const set in sets_) { + add_promises.push(sets_store.add(sets_[set], set)); + sets.set(set, sets_[set]); } add_promises.push(add_tx.complete); From 83fcfd15f414230a69dc654b858597768bf84610 Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 19:40:17 +0700 Subject: [PATCH 104/155] change to id, from display_name --- js/atree_constants.js | 4141 ++++++++++++++++------------ js/atree_constants_min.js | 2 +- js/atree_constants_str_old.js | 4160 +++++++++++++++++++++++++++++ js/atree_constants_str_old_min.js | 1 + js/display_atree.js | 29 +- 5 files changed, 6651 insertions(+), 1682 deletions(-) create mode 100644 js/atree_constants_str_old.js create mode 100644 js/atree_constants_str_old_min.js diff --git a/js/atree_constants.js b/js/atree_constants.js index 765249f..8e9ae98 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -1,12 +1,14 @@ -const atrees = -{ +const atrees = { "Archer": [ { "display_name": "Arrow Shield", "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", "archetype": "", "archetype_req": 0, - "parents": ["Power Shots", "Cheaper Escape"], + "parents": [ + 60, + 34 + ], "dependencies": [], "blockers": [], "cost": 1, @@ -31,7 +33,14 @@ const atrees = { "name": "Shield Damage", "type": "damage", - "multipliers": [90, 0, 0, 0, 0, 10] + "multipliers": [ + 90, + 0, + 0, + 0, + 0, + 10 + ] }, { "name": "Total Damage", @@ -42,955 +51,1258 @@ const atrees = } ] } - ] + ], + "id": 0 }, - { "display_name": "Escape", "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", - "archetype": "", - "archetype_req": 0, - "parents": ["Heart Shatter"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 3 + ], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 7, - "col": 4 + "row": 7, + "col": 4 }, "properties": { - "aoe": 0, - "range": 0 + "aoe": 0, + "range": 0 }, "effects": [ - { - "type": "replace_spell", - "name": "Escape", - "cost": 25, - "display_text": "Max Damage", - "base_spell": 2, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "None", - "type": "damage", - "multipliers": [0, 0, 0, 0, 0, 0] - }, { - "name": "Total Damage", - "type": "total", - "hits": { - "None": 0 - } + "type": "replace_spell", + "name": "Escape", + "cost": 25, + "display_text": "Max Damage", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] } - ] - } - ] + ], + "id": 1 }, { "display_name": "Arrow Bomb", "desc": "Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)", - "archetype": "", - "archetype_req": 0, - "parents": [], + "archetype": "", + "archetype_req": 0, + "parents": [], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 0, - "col": 4 + "row": 0, + "col": 4 }, "properties": { - "aoe": 4.5, - "range": 26 + "aoe": 4.5, + "range": 26 }, "effects": [ - { - "type": "replace_spell", - "name": "Arrow Bomb", - "cost": 50, - "display_text": "Average Damage", - "base_spell": 3, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Arrow Bomb", - "type": "damage", - "multipliers": [160, 0, 0, 0, 20, 0] - }, { - "name": "Total Damage", - "type": "total", - "hits": { - "Arrow Bomb": 1 - } + "type": "replace_spell", + "name": "Arrow Bomb", + "cost": 50, + "display_text": "Average Damage", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Arrow Bomb", + "type": "damage", + "multipliers": [ + 160, + 0, + 0, + 0, + 20, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Arrow Bomb": 1 + } + } + ] } - ] - } - ] + ], + "id": 2 }, { "display_name": "Heart Shatter", "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.", - "archetype": "", - "archetype_req": 0, - "parents": ["Bow Proficiency I"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 31 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 4, - "col": 4 + "row": 4, + "col": 4 }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [100, 0, 0, 0, 0, 0] - }, - { - - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] + }, + {} + ], + "id": 3 }, { "display_name": "Fire Creep", "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", - "archetype": "", - "archetype_req": 0, - "parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 68, + 86, + 5 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 16, - "col": 6 + "row": 16, + "col": 6 }, - "properties": { - "aoe": 0.8, - "duration": 6 + "properties": { + "aoe": 0.8, + "duration": 6 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fire Creep", - "cost": 0, - "multipliers": [30, 0, 0, 0, 20, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fire Creep": 15 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [ + 30, + 0, + 0, + 0, + 20, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fire Creep": 15 + } } - } - ] + ], + "id": 4 }, { "display_name": "Bryophyte Roots", "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.", - "archetype": "Trapper", - "archetype_req": 1, - "parents": ["Fire Creep", "Earth Mastery"], - "dependencies": ["Arrow Storm"], + "archetype": "Trapper", + "archetype_req": 1, + "parents": [ + 4, + 82 + ], + "dependencies": [ + 7 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 16, - "col": 8 + "row": 16, + "col": 8 }, "properties": { - "aoe": 2, - "duration": 5, - "slowness": 0.4 - }, + "aoe": 2, + "duration": 5, + "slowness": 0.4 + }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Bryophyte Roots", - "cost": 0, - "multipliers": [40, 20, 0, 0, 0, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Bryophyte Roots", + "cost": 0, + "multipliers": [ + 40, + 20, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 5 }, { "display_name": "Nimble String", "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", - "archetype": "", - "archetype_req": 0, - "parents": ["Thunder Mastery", "Arrow Rain"], - "dependencies": ["Arrow Storm"], - "blockers": ["Phantom Ray"], - "cost": 2, + "archetype": "", + "archetype_req": 0, + "parents": [ + 83, + 69 + ], + "dependencies": [ + 7 + ], + "blockers": [ + 68 + ], + "cost": 2, "display": { - "row": 15, - "col": 2 + "row": 15, + "col": 2 }, "properties": { - "shootspeed": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [-15, 0, 0, 0, 0, 0] + "shootspeed": 2 }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Stream", - "cost": 0, - "hits": { - "Single Arrow": 8 + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [ + -15, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Stream", + "cost": 0, + "hits": { + "Single Arrow": 8 + } } - } - ] + ], + "id": 6 }, { "display_name": "Arrow Storm", "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", - "archetype": "", - "archetype_req": 0, - "parents": ["Double Shots", "Cheaper Escape"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 58, + 34 + ], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 9, - "col": 2 + "row": 9, + "col": 2 }, "properties": { - "aoe": 0, - "range": 16 + "aoe": 0, + "range": 16 }, "effects": [ - { - "type": "replace_spell", - "name": "Arrow Storm", - "cost": 40, - "display_text": "Max Damage", - "base_spell": 1, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [30, 0, 10, 0, 0, 0] - }, - { - "name": "Single Stream", - "type": "total", - "hits": { - "Single Arrow": 8 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Stream": 2 - } + { + "type": "replace_spell", + "name": "Arrow Storm", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 30, + 0, + 10, + 0, + 0, + 0 + ] + }, + { + "name": "Single Stream", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Stream": 2 + } + } + ] } - ] - } - ] + ], + "id": 7 }, { "display_name": "Guardian Angels", "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)", "archetype": "Boltslinger", - "archetype_req": 3, - "parents": ["Triple Shots", "Frenzy"], - "dependencies": ["Arrow Shield"], + "archetype_req": 3, + "parents": [ + 59, + 67 + ], + "dependencies": [ + 0 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 19, - "col": 1 + "row": 19, + "col": 1 }, "properties": { - "range": 4, - "duration": 60, - "shots": 8, - "count": 2 - }, + "range": 4, + "duration": 60, + "shots": 8, + "count": 2 + }, "effects": [ - { - "type": "replace_spell", - "name": "Guardian Angels", - "cost": 30, - "display_text": "Total Damage Average", - "base_spell": 4, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [40, 0, 0, 0, 0, 20] - }, - { - "name": "Single Bow", - "type": "total", - "hits": { - "Single Arrow": 8 + { + "type": "replace_spell", + "name": "Guardian Angels", + "cost": 30, + "display_text": "Total Damage Average", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 20 + ] + }, + { + "name": "Single Bow", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Bow": 2 + } } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Bow": 2 - } - } - ] - } - ] + ] + } + ], + "id": 8 }, { "display_name": "Windy Feet", "base_abil": "Escape", "desc": "When casting Escape, give speed to yourself and nearby allies.", "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Arrow Storm"], - "dependencies": [], + "archetype_req": 0, + "parents": [ + 7 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 10, - "col": 1 + "row": 10, + "col": 1 }, "properties": { - "aoe": 8, - "duration": 120 - }, + "aoe": 8, + "duration": 120 + }, "type": "stat_bonus", "bonuses": [ - { - "type": "stat", - "name": "spd", - "value": 20 + { + "type": "stat", + "name": "spd", + "value": 20 } - ] + ], + "id": 9 }, { "display_name": "Basaltic Trap", "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)", "archetype": "Trapper", - "archetype_req": 2, - "parents": ["Bryophyte Roots"], - "dependencies": [], + "archetype_req": 2, + "parents": [ + 5 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 19, - "col": 8 + "row": 19, + "col": 8 }, "properties": { - "aoe": 7, - "traps": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 0, - "multipliers": [140, 30, 0, 0, 30, 0] - } - ] + "aoe": 7, + "traps": 2 }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [ + 140, + 30, + 0, + 0, + 30, + 0 + ] + } + ], + "id": 10 + }, { "display_name": "Windstorm", "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", - "archetype": "", - "archetype_req": 0, - "parents": ["Guardian Angels", "Cheaper Arrow Storm"], - "dependencies": [], - "blockers": ["Phantom Ray"], - "cost": 2, + "archetype": "", + "archetype_req": 0, + "parents": [ + 8, + 33 + ], + "dependencies": [], + "blockers": [ + 68 + ], + "cost": 2, "display": { "row": 21, "col": 1 - }, + }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [-11, 0, -7, 0, 0, 3] - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Stream": 1 + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [ + -11, + 0, + -7, + 0, + 0, + 3 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 1 + } } - } - ] - }, + ], + "id": 11 + }, { "display_name": "Grappling Hook", "base_abil": "Escape", "desc": "When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Focus", "More Shields", "Cheaper Arrow Storm"], - "dependencies": [], - "blockers": ["Escape Artist"], - "cost": 2, + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 61, + 40, + 33 + ], + "dependencies": [], + "blockers": [ + 20 + ], + "cost": 2, "display": { "row": 21, "col": 5 - }, + }, "properties": { "range": 20 }, - "effects": [ - ] - }, + "effects": [], + "id": 12 + }, { "display_name": "Implosion", "desc": "Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Grappling Hook", "More Shields"], - "dependencies": [], + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 12, + 40 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 22, "col": 6 - }, + }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [40, 0, 0, 0, 0, 0] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 0 + ] } - ] - }, + ], + "id": 13 + }, { "display_name": "Twain's Arc", "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", - "archetype": "Sharpshooter", - "archetype_req": 4, - "parents": ["More Focus", "Traveler"], - "dependencies": ["Focus"], + "archetype": "Sharpshooter", + "archetype_req": 4, + "parents": [ + 62, + 64 + ], + "dependencies": [ + 61 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 25, "col": 4 - }, - "properties": { - "range": 64, - "focusReq": 2 - }, - "effects": [ - - { - "type": "replace_spell", - "name": "Twain's Arc", - "cost": 0, - "display_text": "Twain's Arc", - "base_spell": 5, - "spell_type": "damage", - "scaling": "melee", - "display": "Twain's Arc Damage", - "parts": [ - { - "name": "Twain's Arc Damage", - "type": "damage", - "multipliers": [200, 0, 0, 0, 0, 0] - } - ] - } - ] }, + "properties": { + "range": 64, + "focusReq": 2 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Twain's Arc", + "cost": 0, + "display_text": "Twain's Arc", + "base_spell": 5, + "spell_type": "damage", + "scaling": "melee", + "display": "Twain's Arc Damage", + "parts": [ + { + "name": "Twain's Arc Damage", + "type": "damage", + "multipliers": [ + 200, + 0, + 0, + 0, + 0, + 0 + ] + } + ] + } + ], + "id": 14 + }, { "display_name": "Fierce Stomp", "desc": "When using Escape, hold shift to quickly drop down and deal damage.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Refined Gunpowder", "Traveler"], - "dependencies": [], + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 42, + 64 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 26, - "col": 1 + "row": 26, + "col": 1 }, "properties": { - "aoe": 4 + "aoe": 4 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Fierce Stomp", - "cost": 0, - "multipliers": [100, 0, 0, 0, 0, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fierce Stomp": 1 + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fierce Stomp": 1 + } } - } - ] + ], + "id": 15 }, { "display_name": "Scorched Earth", "desc": "Fire Creep become much stronger.", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Twain's Arc"], - "dependencies": ["Fire Creep"], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 14 + ], + "dependencies": [ + 4 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 26 , - "col": 5 + "row": 26, + "col": 5 }, "properties": { - "duration": 2, - "aoe": 0.4 + "duration": 2, + "aoe": 0.4 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fire Creep", - "cost": 0, - "multipliers": [10, 0, 0, 0, 5, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [ + 10, + 0, + 0, + 0, + 5, + 0 + ] + } + ], + "id": 16 }, { "display_name": "Leap", "desc": "When you double tap jump, leap foward. (2s Cooldown)", - "archetype": "Boltslinger", - "archetype_req": 5, - "parents": ["Refined Gunpowder", "Homing Shots"], - "dependencies": [], + "archetype": "Boltslinger", + "archetype_req": 5, + "parents": [ + 42, + 55 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 28, - "col": 0 + "row": 28, + "col": 0 }, "properties": { - "cooldown": 2 - }, - "effects": [ - - ] + "cooldown": 2 }, + "effects": [], + "id": 17 + }, { "display_name": "Shocking Bomb", "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.", - "archetype": "Sharpshooter", - "archetype_req": 5, - "parents": ["Twain's Arc", "Better Arrow Shield", "Homing Shots"], - "dependencies": ["Arrow Bomb"], + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": [ + 14, + 44, + 55 + ], + "dependencies": [ + 2 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 28, - "col": 4 + "row": 28, + "col": 4 }, "properties": { - "gravity": 0 + "gravity": 0 }, "effects": [ - { - "type": "convert_spell_conv", - "target_part": "all", - "conversion": "thunder" - } - ] + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + } + ], + "id": 18 }, { "display_name": "Mana Trap", "desc": "Your Traps will give you 4 Mana per second when you stay close to them.", - "archetype": "Trapper", - "archetype_req": 5, - "parents": ["More Traps", "Better Arrow Shield"], - "dependencies": ["Fire Creep"], + "archetype": "Trapper", + "archetype_req": 5, + "parents": [ + 43, + 44 + ], + "dependencies": [ + 4 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 28, - "col": 8 + "row": 28, + "col": 8 }, "properties": { - "range": 12, - "manaRegen": 4 + "range": 12, + "manaRegen": 4 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 10, - "multipliers": [0, 0, 0, 0, 0, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 10, + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 19 }, { "display_name": "Escape Artist", "desc": "When casting Escape, release 100 arrows towards the ground.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Better Guardian Angels", "Leap"], - "dependencies": [], - "blockers": ["Grappling Hook"], - "cost": 2, + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 46, + 17 + ], + "dependencies": [], + "blockers": [ + 12 + ], + "cost": 2, "display": { - "row": 31, - "col": 0 - }, - "properties": { + "row": 31, + "col": 0 }, + "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Escape Artist", - "cost": 0, - "multipliers": [30, 0, 10, 0, 0, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Escape Artist", + "cost": 0, + "multipliers": [ + 30, + 0, + 10, + 0, + 0, + 0 + ] + } + ], + "id": 20 }, { "display_name": "Initiator", "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", "archetype": "Sharpshooter", - "archetype_req": 5, - "parents": ["Shocking Bomb", "Better Arrow Shield", "Cheaper Arrow Storm (2)"], - "dependencies": ["Focus"], + "archetype_req": 5, + "parents": [ + 18, + 44, + 47 + ], + "dependencies": [ + 61 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 31, - "col": 5 + "row": 31, + "col": 5 }, "properties": { - "focus": 1, - "timer": 5 - }, - "type": "stat_bonus", + "focus": 1, + "timer": 5 + }, + "type": "stat_bonus", "bonuses": [ - { - "type": "stat", - "name": "damPct", - "value": 50 + { + "type": "stat", + "name": "damPct", + "value": 50 } - ] + ], + "id": 21 }, { "display_name": "Call of the Hound", "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Initiator", "Cheaper Arrow Storm (2)"], - "dependencies": ["Arrow Shield"], + "archetype_req": 0, + "parents": [ + 21, + 47 + ], + "dependencies": [ + 0 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 32, - "col": 7 + "row": 32, + "col": 7 }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Call of the Hound", - "cost": 0, - "multipliers": [40, 0, 0, 0, 0, 0] - } - ] + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Call of the Hound", + "cost": 0, + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 22 }, { "display_name": "Arrow Hurricane", "desc": "Arrow Storm will shoot +2 stream of arrows.", - "archetype": "Boltslinger", - "archetype_req": 8, - "parents": ["Precise Shot", "Escape Artist"], - "dependencies": [], - "blockers": ["Phantom Ray"], - "cost": 2, + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": [ + 48, + 20 + ], + "dependencies": [], + "blockers": [ + 68 + ], + "cost": 2, "display": { - "row": 33, - "col": 0 + "row": 33, + "col": 0 }, "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Stream": 2 + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 2 + } } - } - ] + ], + "id": 23 }, { "display_name": "Geyser Stomp", "desc": "Fierce Stomp will create geysers, dealing more damage and vertical knockback.", - "archetype": "", - "archetype_req": 0, - "parents": ["Shrapnel Bomb"], - "dependencies": ["Fierce Stomp"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 56 + ], + "dependencies": [ + 15 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 37, - "col": 1 + "row": 37, + "col": 1 }, "properties": { - "aoe": 1 + "aoe": 1 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Fierce Stomp", - "cost": 0, - "multipliers": [0, 0, 0, 50, 0, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [ + 0, + 0, + 0, + 50, + 0, + 0 + ] + } + ], + "id": 24 }, { "display_name": "Crepuscular Ray", "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.", - "archetype": "Sharpshooter", - "archetype_req": 10, - "parents": ["Cheaper Arrow Shield"], - "dependencies": ["Arrow Storm"], + "archetype": "Sharpshooter", + "archetype_req": 10, + "parents": [ + 49 + ], + "dependencies": [ + 7 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 37, - "col": 4 + "row": 37, + "col": 4 }, "properties": { - "focusReq": 5, - "focusRegen": -1 - }, + "focusReq": 5, + "focusRegen": -1 + }, "effects": [ - { - "type": "replace_spell", - "name": "Crepuscular Ray", - "base_spell": 5, - "spell_type": "damage", - "scaling": "spell", - "display": "One Focus", - "cost": 0, - - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [10, 0, 0, 5, 0, 0] - }, { - "name": "One Focus", - "type": "total", - "hits": { - "Single Arrow": 20 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "One Focus": 7 - } + "type": "replace_spell", + "name": "Crepuscular Ray", + "base_spell": 5, + "spell_type": "damage", + "scaling": "spell", + "display": "One Focus", + "cost": 0, + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 10, + 0, + 0, + 5, + 0, + 0 + ] + }, + { + "name": "One Focus", + "type": "total", + "hits": { + "Single Arrow": 20 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "One Focus": 7 + } + } + ] } - ] - } - ] + ], + "id": 25 }, { "display_name": "Grape Bomb", "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", - "archetype": "", - "archetype_req": 0, - "parents": ["Cheaper Escape (2)"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 51 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 37, - "col": 7 + "row": 37, + "col": 7 }, "properties": { - "miniBombs": 3, - "aoe": 2 + "miniBombs": 3, + "aoe": 2 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Grape Bomb", - "cost": 0, - "multipliers": [30, 0, 0, 0, 10, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Grape Bomb", + "cost": 0, + "multipliers": [ + 30, + 0, + 0, + 0, + 10, + 0 + ] + } + ], + "id": 26 }, { "display_name": "Tangled Traps", "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Grape Bomb"], - "dependencies": ["Basaltic Trap"], + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 26 + ], + "dependencies": [ + 10 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 38, - "col": 6 + "row": 38, + "col": 6 }, "properties": { - "attackSpeed": 0.2 + "attackSpeed": 0.2 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Tangled Traps", - "cost": 0, - "multipliers": [20, 0, 0, 0, 0, 20] - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Tangled Traps", + "cost": 0, + "multipliers": [ + 20, + 0, + 0, + 0, + 0, + 20 + ] + } + ], + "id": 27 }, { "display_name": "Snow Storm", "desc": "Enemies near you will be slowed down.", "archetype": "", - "archetype_req": 0, - "parents": ["Geyser Stomp", "More Focus (2)"], - "dependencies": [], + "archetype_req": 0, + "parents": [ + 24, + 63 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 39, - "col": 2 + "row": 39, + "col": 2 }, "properties": { - "range": 2.5, - "slowness": 0.3 - } + "range": 2.5, + "slowness": 0.3 + }, + "id": 28 }, { "display_name": "All-Seeing Panoptes", "desc": "Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.", "archetype": "Boltslinger", - "archetype_req": 11, - "parents": ["Snow Storm"], - "dependencies": ["Guardian Angels"], + "archetype_req": 11, + "parents": [ + 28 + ], + "dependencies": [ + 8 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 40, - "col": 1 + "row": 40, + "col": 1 }, "properties": { - "range": 10, - "shots": 5 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [0, 0, 0, 0, 20, 0] + "range": 10, + "shots": 5 }, - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Bow", - "cost": 0, - "hits": { - "Single Arrow": 5 + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [ + 0, + 0, + 0, + 0, + 20, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 5 + } } - } - ] + ], + "id": 29 }, { "display_name": "Minefield", "desc": "Allow you to place +6 Traps, but with reduced damage and range.", "archetype": "Trapper", - "archetype_req": 10, - "parents": ["Grape Bomb", "Cheaper Arrow Bomb (2)"], - "dependencies": ["Basaltic Trap"], + "archetype_req": 10, + "parents": [ + 26, + 53 + ], + "dependencies": [ + 10 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 40, "col": 7 - }, + }, "properties": { "aoe": -2, "traps": 6 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 0, - "multipliers": [-80, 0, 0, 0, 0, 0] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [ + -80, + 0, + 0, + 0, + 0, + 0 + ] } - ] - }, + ], + "id": 30 + }, { "display_name": "Bow Proficiency I", "desc": "Improve your Main Attack's damage and range when using a bow.", - "archetype": "", - "archetype_req": 0, - "parents": ["Arrow Bomb"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 2 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 4 - }, + }, "properties": { "mainAtk_range": 6 }, @@ -1005,94 +1317,103 @@ const atrees = } ] } - ] + ], + "id": 31 }, { "display_name": "Cheaper Arrow Bomb", "desc": "Reduce the Mana cost of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": ["Bow Proficiency I"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 31 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 6 - }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "cost": -10 } - ] + ], + "id": 32 }, { "display_name": "Cheaper Arrow Storm", "desc": "Reduce the Mana cost of Arrow Storm.", - "archetype": "", - "archetype_req": 0, - "parents": ["Grappling Hook", "Windstorm", "Focus"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 12, + 11, + 61 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 21, "col": 3 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -5 } - ] + ], + "id": 33 }, { "display_name": "Cheaper Escape", "desc": "Reduce the Mana cost of Escape.", - "archetype": "", - "archetype_req": 0, - "parents": ["Arrow Storm", "Arrow Shield"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 7, + 0 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 9, "col": 4 - }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "cost": -5 } - ] + ], + "id": 34 }, { "display_name": "Earth Mastery", "desc": "Increases your base damage from all Earth attacks", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Arrow Shield"], - "dependencies": [], + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 0 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 8 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -1105,27 +1426,34 @@ const atrees = { "type": "stat", "name": "eDam", - "value": [2, 4] + "value": [ + 2, + 4 + ] } ] } - ] + ], + "id": 82 }, { "display_name": "Thunder Mastery", "desc": "Increases your base damage from all Thunder attacks", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Arrow Storm", "Fire Mastery"], - "dependencies": [], + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 7, + 86, + 34 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 2 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -1138,27 +1466,34 @@ const atrees = { "type": "stat", "name": "tDam", - "value": [1, 8] + "value": [ + 1, + 8 + ] } ] } - ] + ], + "id": 83 }, { "display_name": "Water Mastery", "desc": "Increases your base damage from all Water attacks", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Cheaper Escape", "Thunder Mastery", "Fire Mastery"], - "dependencies": [], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 34, + 83, + 86 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 14, "col": 4 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -1171,27 +1506,32 @@ const atrees = { "type": "stat", "name": "wDam", - "value": [2, 4] + "value": [ + 2, + 4 + ] } ] } - ] + ], + "id": 84 }, { "display_name": "Air Mastery", "desc": "Increases base damage from all Air attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Arrow Storm"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 7 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 0 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -1204,27 +1544,34 @@ const atrees = { "type": "stat", "name": "aDam", - "value": [3, 4] + "value": [ + 3, + 4 + ] } ] } - ] + ], + "id": 85 }, { "display_name": "Fire Mastery", "desc": "Increases base damage from all Earth attacks", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Thunder Mastery", "Arrow Shield"], - "dependencies": [], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 83, + 0, + 34 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 6 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -1237,156 +1584,210 @@ const atrees = { "type": "stat", "name": "fDam", - "value": [3, 5] + "value": [ + 3, + 5 + ] } ] } - ] + ], + "id": 86 }, { "display_name": "More Shields", "desc": "Give +2 charges to Arrow Shield.", - "archetype": "", - "archetype_req": 0, - "parents": ["Grappling Hook", "Basaltic Trap"], - "dependencies": ["Arrow Shield"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 12, + 10 + ], + "dependencies": [ + 0 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 21, "col": 7 - }, + }, "properties": { "shieldCharges": 2 - } + }, + "id": 40 }, { "display_name": "Stormy Feet", "desc": "Windy Feet will last longer and add more speed.", - "archetype": "", - "archetype_req": 0, - "parents": ["Windstorm"], - "dependencies": ["Windy Feet"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 11 + ], + "dependencies": [ + 9 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 23, - "col": 1 + "row": 23, + "col": 1 }, "properties": { - "duration": 60 + "duration": 60 }, "effects": [ - { - "type": "stat_bonus", - "bonuses": [ - { - "type": "stat", - "name": "spdPct", - "value": 20 + { + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spdPct", + "value": 20 + } + ] } - ] - } - ] + ], + "id": 41 }, { "display_name": "Refined Gunpowder", "desc": "Increase the damage of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": ["Windstorm"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 11 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 25, - "col": 0 + "row": 25, + "col": 0 }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [50, 0, 0, 0, 0, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [ + 50, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 42 }, { "display_name": "More Traps", "desc": "Increase the maximum amount of active Traps you can have by +2.", "archetype": "Trapper", - "archetype_req": 10, - "parents": ["Bouncing Bomb"], - "dependencies": ["Basaltic Trap"], + "archetype_req": 10, + "parents": [ + 54 + ], + "dependencies": [ + 10 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 26, "col": 8 - }, + }, "properties": { "traps": 2 - } + }, + "id": 43 }, { "display_name": "Better Arrow Shield", "desc": "Arrow Shield will gain additional area of effect, knockback and damage.", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Mana Trap", "Shocking Bomb", "Twain's Arc"], - "dependencies": ["Arrow Shield"], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 19, + 18, + 14 + ], + "dependencies": [ + 0 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 28, - "col": 6 + "row": 28, + "col": 6 }, "properties": { - "aoe": 1 - }, + "aoe": 1 + }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Shield", - "multipliers": [40, 0, 0, 0, 0, 0] - } - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Shield", + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 44 }, { "display_name": "Better Leap", "desc": "Reduce leap's cooldown by 1s.", "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Leap", "Homing Shots"], - "dependencies": ["Leap"], + "archetype_req": 0, + "parents": [ + 17, + 55 + ], + "dependencies": [ + 17 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 29, "col": 1 - }, + }, "properties": { "cooldown": -1 - } + }, + "id": 45 }, { "display_name": "Better Guardian Angels", "desc": "Your Guardian Angels can shoot +4 arrows before disappearing.", "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Escape Artist", "Homing Shots"], - "dependencies": ["Guardian Angels"], + "archetype_req": 0, + "parents": [ + 20, + 55 + ], + "dependencies": [ + 8 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 31, - "col": 2 - }, - "properties": { + "row": 31, + "col": 2 }, + "properties": {}, "effects": [ { "type": "add_spell_prop", @@ -1397,44 +1798,52 @@ const atrees = "Single Arrow": 4 } } - ] + ], + "id": 46 }, { "display_name": "Cheaper Arrow Storm (2)", "desc": "Reduce the Mana cost of Arrow Storm.", - "archetype": "", - "archetype_req": 0, - "parents": ["Initiator", "Mana Trap"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 21, + 19 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 31, "col": 8 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -5 } - ] + ], + "id": 47 }, { "display_name": "Precise Shot", "desc": "+30% Critical Hit Damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Better Guardian Angels", "Cheaper Arrow Shield", "Arrow Hurricane"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 46, + 49, + 23 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 33, "col": 2 - }, + }, "properties": { "mainAtk_range": 6 }, @@ -1449,118 +1858,138 @@ const atrees = } ] } - ] + ], + "id": 48 }, { "display_name": "Cheaper Arrow Shield", "desc": "Reduce the Mana cost of Arrow Shield.", - "archetype": "", - "archetype_req": 0, - "parents": ["Precise Shot", "Initiator"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 48, + 21 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 33, "col": 4 - }, - "properties": { }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "cost": -5 } - ] + ], + "id": 49 }, { "display_name": "Rocket Jump", "desc": "Arrow Bomb's self-damage will knockback you farther away.", - "archetype": "", - "archetype_req": 0, - "parents": ["Cheaper Arrow Storm (2)", "Initiator"], - "dependencies": ["Arrow Bomb"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 47, + 21 + ], + "dependencies": [ + 2 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 33, "col": 6 - }, - "properties": { - } + }, + "properties": {}, + "id": 50 }, { "display_name": "Cheaper Escape (2)", "desc": "Reduce the Mana cost of Escape.", - "archetype": "", - "archetype_req": 0, - "parents": ["Call of the Hound", "Decimator"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 22, + 70 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 34, "col": 7 - }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "cost": -5 } - ] + ], + "id": 51 }, { "display_name": "Stronger Hook", "desc": "Increase your Grappling Hook's range, speed and strength.", - "archetype": "Trapper", - "archetype_req": 5, - "parents": ["Cheaper Escape (2)"], - "dependencies": ["Grappling Hook"], + "archetype": "Trapper", + "archetype_req": 5, + "parents": [ + 51 + ], + "dependencies": [ + 12 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 35, "col": 8 - }, + }, "properties": { - "range": 8 - } + "range": 8 + }, + "id": 52 }, { "display_name": "Cheaper Arrow Bomb (2)", "desc": "Reduce the Mana cost of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": ["More Focus (2)", "Minefield"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 63, + 30 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 40, "col": 5 - }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "cost": -5 } - ] + ], + "id": 53 }, { "display_name": "Bouncing Bomb", "desc": "Arrow Bomb will bounce once when hitting a block or enemy", "archetype": "", "archetype_req": 0, - "parents": ["More Shields"], + "parents": [ + 40 + ], "dependencies": [], "blockers": [], "cost": 2, @@ -1568,9 +1997,7 @@ const atrees = "row": 25, "col": 7 }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", @@ -1581,14 +2008,18 @@ const atrees = "Arrow Bomb": 2 } } - ] + ], + "id": 54 }, { "display_name": "Homing Shots", "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity", "archetype": "", "archetype_req": 0, - "parents": ["Leap", "Shocking Bomb"], + "parents": [ + 17, + 18 + ], "dependencies": [], "blockers": [], "cost": 2, @@ -1596,45 +2027,53 @@ const atrees = "row": 28, "col": 2 }, - "properties": { - - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 55 }, { "display_name": "Shrapnel Bomb", "desc": "Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area", "archetype": "Boltslinger", "archetype_req": 8, - "parents": ["Arrow Hurricane", "Precise Shot"], + "parents": [ + 23, + 48 + ], "dependencies": [], "blockers": [], "cost": 2, "display": { "row": 34, - "col": 1 - }, - "properties": { - + "col": 1 }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Shrapnel Bomb", "cost": 0, - "multipliers": [40, 0, 0, 0, 20, 0] + "multipliers": [ + 40, + 0, + 0, + 0, + 20, + 0 + ] } - ] + ], + "id": 56 }, { "display_name": "Elusive", "desc": "If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)", "archetype": "Boltslinger", "archetype_req": 0, - "parents": ["Geyser Stomp"], + "parents": [ + 24 + ], "dependencies": [], "blockers": [], "cost": 2, @@ -1642,21 +2081,22 @@ const atrees = "row": 38, "col": 0 }, - "properties": { - - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 57 }, { "display_name": "Double Shots", "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)", "archetype": "Boltslinger", "archetype_req": 0, - "parents": ["Escape"], + "parents": [ + 1 + ], "dependencies": [], - "blockers": ["Power Shots"], + "blockers": [ + 60 + ], "cost": 1, "display": { "row": 7, @@ -1673,15 +2113,21 @@ const atrees = "cost": 0, "multipliers": 0.7 } - ] + ], + "id": 58 }, { "display_name": "Triple Shots", "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow", "archetype": "Boltslinger", "archetype_req": 0, - "parents": ["Arrow Rain", "Frenzy"], - "dependencies": ["Double Shots"], + "parents": [ + 69, + 67 + ], + "dependencies": [ + 58 + ], "blockers": [], "cost": 1, "display": { @@ -1699,34 +2145,38 @@ const atrees = "cost": 0, "multipliers": 0.7 } - ] + ], + "id": 59 }, { "display_name": "Power Shots", "desc": "Main Attack arrows have increased speed and knockback", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": ["Escape"], + "parents": [ + 1 + ], "dependencies": [], - "blockers": ["Double Shots"], + "blockers": [ + 58 + ], "cost": 1, "display": { "row": 7, "col": 6 }, - "properties": { - - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 60 }, { "display_name": "Focus", "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once", "archetype": "Sharpshooter", "archetype_req": 2, - "parents": ["Phantom Ray"], + "parents": [ + 68 + ], "dependencies": [], "blockers": [], "cost": 2, @@ -1734,9 +2184,7 @@ const atrees = "row": 19, "col": 4 }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -1747,17 +2195,23 @@ const atrees = "abil_name": "Focus", "name": "dmgPct" }, - "scaling": [35], + "scaling": [ + 35 + ], "max": 3 } - ] + ], + "id": 61 }, { "display_name": "More Focus", "desc": "Add +2 max Focus", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": ["Cheaper Arrow Storm", "Grappling Hook"], + "parents": [ + 33, + 12 + ], "dependencies": [], "blockers": [], "cost": 1, @@ -1765,9 +2219,7 @@ const atrees = "row": 22, "col": 4 }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -1778,17 +2230,23 @@ const atrees = "abil_name": "Focus", "name": "dmgPct" }, - "scaling": [35], + "scaling": [ + 35 + ], "max": 5 } - ] + ], + "id": 62 }, { "display_name": "More Focus (2)", "desc": "Add +2 max Focus", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": ["Crepuscular Ray", "Snow Storm"], + "parents": [ + 25, + 28 + ], "dependencies": [], "blockers": [], "cost": 1, @@ -1796,9 +2254,7 @@ const atrees = "row": 39, "col": 4 }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -1809,17 +2265,23 @@ const atrees = "abil_name": "Focus", "name": "dmgPct" }, - "scaling": [35], + "scaling": [ + 35 + ], "max": 7 } - ] + ], + "id": 63 }, { "display_name": "Traveler", "desc": "For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)", "archetype": "", "archetype_req": 0, - "parents": ["Refined Gunpowder", "Twain's Arc"], + "parents": [ + 42, + 14 + ], "dependencies": [], "blockers": [], "cost": 1, @@ -1827,9 +2289,7 @@ const atrees = "row": 25, "col": 2 }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -1844,18 +2304,25 @@ const atrees = "type": "stat", "name": "sdRaw" }, - "scaling": [1], + "scaling": [ + 1 + ], "max": 100 } - ] + ], + "id": 64 }, { "display_name": "Patient Hunter", "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%)", "archetype": "Trapper", "archetype_req": 0, - "parents": ["More Shields"], - "dependencies": ["Basaltic Trap"], + "parents": [ + 40 + ], + "dependencies": [ + 10 + ], "blockers": [], "cost": 2, "display": { @@ -1865,17 +2332,20 @@ const atrees = "properties": { "max": 80 }, - "effects": [ - - ] + "effects": [], + "id": 65 }, { "display_name": "Stronger Patient Hunter", "desc": "Add +80% Max Damage to Patient Hunter", "archetype": "Trapper", "archetype_req": 0, - "parents": ["Grape Bomb"], - "dependencies": ["Patient Hunter"], + "parents": [ + 26 + ], + "dependencies": [ + 65 + ], "blockers": [], "cost": 1, "display": { @@ -1885,16 +2355,18 @@ const atrees = "properties": { "max": 80 }, - "effects": [ - - ] + "effects": [], + "id": 66 }, { "display_name": "Frenzy", "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second", "archetype": "Boltslinger", "archetype_req": 0, - "parents": ["Triple Shots", "Nimble String"], + "parents": [ + 59, + 6 + ], "dependencies": [], "blockers": [], "cost": 2, @@ -1902,9 +2374,7 @@ const atrees = "row": 17, "col": 2 }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -1914,93 +2384,127 @@ const atrees = "type": "stat", "name": "spd" }, - "scaling": [6], + "scaling": [ + 6 + ], "max": 200 } - ] + ], + "id": 67 }, { "display_name": "Phantom Ray", "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": ["Water Mastery", "Fire Creep"], - "dependencies": ["Arrow Storm"], - "blockers": ["Windstorm", "Nimble String", "Arrow Hurricane"], + "parents": [ + 84, + 4 + ], + "dependencies": [ + 7 + ], + "blockers": [ + 11, + 6, + 23 + ], "cost": 2, "display": { "row": 16, "col": 4 }, - "properties": { - }, + "properties": {}, "effects": [ - { + { "type": "replace_spell", "name": "Phantom Ray", "cost": 40, - "display_text": "Max Damage", - "base_spell": 1, - "spell_type": "damage", + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", "scaling": "spell", - "display": "Total Damage", + "display": "Total Damage", "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [25, 0, 5, 0, 0, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Arrow": 16 + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 25, + 0, + 5, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Arrow": 16 + } } - } ] } - ] + ], + "id": 68 }, { "display_name": "Arrow Rain", "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies", "archetype": "Trapper", "archetype_req": 0, - "parents": ["Nimble String", "Air Mastery"], - "dependencies": ["Arrow Shield"], + "parents": [ + 6, + 85 + ], + "dependencies": [ + 0 + ], "blockers": [], "cost": 2, "display": { "row": 15, "col": 0 }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "target_part": "Arrow Rain", "cost": 0, - "multipliers": [120, 0, 0, 0, 0, 80] + "multipliers": [ + 120, + 0, + 0, + 0, + 0, + 80 + ] } - ] + ], + "id": 69 }, { "display_name": "Decimator", "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": ["Cheaper Arrow Shield"], - "dependencies": ["Phantom Ray"], + "parents": [ + 49 + ], + "dependencies": [ + 68 + ], "blockers": [], "cost": 1, "display": { "row": 34, "col": 5 }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -2013,19 +2517,20 @@ const atrees = "scaling": 10, "max": 50 } - ] + ], + "id": 70 } ], "Warrior": [ { "display_name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area", - "archetype": "", - "archetype_req": 0, - "parents": [], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 0, "col": 4, @@ -2049,7 +2554,14 @@ const atrees = { "name": "Single Hit", "type": "damage", - "multipliers": [130, 20, 0, 0, 0, 0] + "multipliers": [ + 130, + 20, + 0, + 0, + 0, + 0 + ] }, { "name": "Total Damage", @@ -2060,17 +2572,20 @@ const atrees = } ] } - ] + ], + "id": 71 }, { "display_name": "Spear Proficiency 1", "desc": "Improve your Main Attack's damage and range w/ spear", - "archetype": "", - "archetype_req": 0, - "parents": ["Bash"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 71 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 4, @@ -2090,43 +2605,46 @@ const atrees = } ] } - ] + ], + "id": 72 }, - { "display_name": "Cheaper Bash", "desc": "Reduce the Mana cost of Bash", - "archetype": "", - "archetype_req": 0, - "parents": ["Spear Proficiency 1"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 72 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 2, "icon": "node_0" }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -10 } - ] + ], + "id": 73 }, { "display_name": "Double Bash", "desc": "Bash will hit a second time at a farther range", - "archetype": "", - "archetype_req": 0, - "parents": ["Spear Proficiency 1"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 72 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 4, "col": 4, @@ -2151,27 +2669,35 @@ const atrees = "base_spell": 1, "target_part": "Single Hit", "cost": 0, - "multipliers": [-50, 0, 0, 0, 0, 0] + "multipliers": [ + -50, + 0, + 0, + 0, + 0, + 0 + ] } - ] + ], + "id": 74 }, - { "display_name": "Charge", "desc": "Charge forward at high speed (hold shift to cancel)", - "archetype": "", - "archetype_req": 0, - "parents": ["Double Bash"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 74 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 6, "col": 4, "icon": "node_4" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "replace_spell", @@ -2186,7 +2712,14 @@ const atrees = { "name": "None", "type": "damage", - "multipliers": [0, 0, 0, 0, 0, 0] + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] }, { "name": "Total Damage", @@ -2197,18 +2730,20 @@ const atrees = } ] } - ] + ], + "id": 75 }, - { "display_name": "Heavy Impact", "desc": "After using Charge, violently crash down into the ground and deal damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Uppercut"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 79 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 9, "col": 1, @@ -2223,27 +2758,37 @@ const atrees = "base_spell": 2, "target_part": "Heavy Impact", "cost": 0, - "multipliers": [100, 0, 0, 0, 0, 0] + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] } - ] + ], + "id": 76 }, - { "display_name": "Vehement", "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Charge"], - "dependencies": [], - "blockers": ["Tougher Skin"], - "cost": 1, + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 75 + ], + "dependencies": [], + "blockers": [ + 78 + ], + "cost": 1, "display": { "row": 6, "col": 2, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -2262,28 +2807,34 @@ const atrees = "type": "stat", "name": "spd" }, - "scaling": [1, 1], + "scaling": [ + 1, + 1 + ], "max": 20 } - ] + ], + "id": 77 }, - { "display_name": "Tougher Skin", "desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Charge"], - "dependencies": [], - "blockers": ["Vehement"], - "cost": 1, + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 75 + ], + "dependencies": [], + "blockers": [ + 77 + ], + "cost": 1, "display": { "row": 6, "col": 6, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -2312,21 +2863,26 @@ const atrees = "type": "stat", "name": "hpBonus" }, - "scaling": [10, 10], + "scaling": [ + 10, + 10 + ], "max": 100 } - ] + ], + "id": 78 }, - { "display_name": "Uppercut", "desc": "Rocket enemies in the air and deal massive damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Vehement"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 77 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 8, "col": 2, @@ -2350,7 +2906,14 @@ const atrees = { "name": "Uppercut", "type": "damage", - "multipliers": [150, 50, 50, 0, 0, 0] + "multipliers": [ + 150, + 50, + 50, + 0, + 0, + 0 + ] }, { "name": "Total Damage", @@ -2361,43 +2924,47 @@ const atrees = } ] } - ] + ], + "id": 79 }, - { "display_name": "Cheaper Charge", "desc": "Reduce the Mana cost of Charge", - "archetype": "", - "archetype_req": 0, - "parents": ["Uppercut", "War Scream"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 79, + 81 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 8, "col": 4, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "cost": -5 } - ] + ], + "id": 80 }, - { "display_name": "War Scream", "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies", - "archetype": "", - "archetype_req": 0, - "parents": ["Tougher Skin"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 78 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 8, "col": 6, @@ -2422,29 +2989,37 @@ const atrees = { "name": "War Scream", "type": "damage", - "multipliers": [50, 0, 0, 0, 50, 0] + "multipliers": [ + 50, + 0, + 0, + 0, + 50, + 0 + ] } ] } - ] + ], + "id": 81 }, - { "display_name": "Earth Mastery", "desc": "Increases base damage from all Earth attacks", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Uppercut"], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 79 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 0, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -2457,29 +3032,35 @@ const atrees = { "type": "stat", "name": "eDam", - "value": [2, 4] + "value": [ + 2, + 4 + ] } ] } - ] + ], + "id": 82 }, - { "display_name": "Thunder Mastery", "desc": "Increases base damage from all Thunder attacks", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Uppercut", "Air Mastery"], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 79, + 85, + 80 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 2, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -2492,29 +3073,35 @@ const atrees = { "type": "stat", "name": "tDam", - "value": [1, 8] + "value": [ + 1, + 8 + ] } ] } - ] + ], + "id": 83 }, - { "display_name": "Water Mastery", "desc": "Increases base damage from all Water attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Cheaper Charge", "Thunder Mastery", "Air Mastery"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 80, + 83, + 85 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 11, "col": 4, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -2527,29 +3114,35 @@ const atrees = { "type": "stat", "name": "wDam", - "value": [2, 4] + "value": [ + 2, + 4 + ] } ] } - ] + ], + "id": 84 }, - { "display_name": "Air Mastery", "desc": "Increases base damage from all Air attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["War Scream", "Thunder Mastery"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 81, + 83, + 80 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 6, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -2562,29 +3155,33 @@ const atrees = { "type": "stat", "name": "aDam", - "value": [3, 4] + "value": [ + 3, + 4 + ] } ] } - ] + ], + "id": 85 }, - { "display_name": "Fire Mastery", "desc": "Increases base damage from all Earth attacks", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["War Scream"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 81 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 8, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -2597,22 +3194,28 @@ const atrees = { "type": "stat", "name": "fDam", - "value": [3, 5] + "value": [ + 3, + 5 + ] } ] } - ] + ], + "id": 86 }, - { "display_name": "Quadruple Bash", "desc": "Bash will hit 4 times at an even larger range", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Earth Mastery", "Fireworks"], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 82, + 88 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 0, @@ -2629,41 +3232,57 @@ const atrees = "cost": 0, "hits": { "Single Hit": 2 - } + } }, { "type": "add_spell_prop", "base_spell": 1, "target_part": "Single Hit", "cost": 0, - "multipliers": [-20, 0, 0, 0, 0, 0] + "multipliers": [ + -20, + 0, + 0, + 0, + 0, + 0 + ] } - ] + ], + "id": 87 }, - { "display_name": "Fireworks", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Thunder Mastery", "Quadruple Bash"], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 83, + 87 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 2, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Fireworks", "cost": 0, - "multipliers": [80, 0, 20, 0, 0, 0] + "multipliers": [ + 80, + 0, + 20, + 0, + 0, + 0 + ] }, { "type": "add_spell_prop", @@ -2674,18 +3293,22 @@ const atrees = "Fireworks": 1 } } - ] + ], + "id": 88 }, - { "display_name": "Half-Moon Swipe", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": ["Water Mastery"], - "dependencies": ["Uppercut"], + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": [ + 84 + ], + "dependencies": [ + 79 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 13, "col": 4, @@ -2700,25 +3323,35 @@ const atrees = "base_spell": 3, "target_part": "Uppercut", "cost": -10, - "multipliers": [-70, 0, 0, 0, 0, 0] + "multipliers": [ + -70, + 0, + 0, + 0, + 0, + 0 + ] }, { "type": "convert_spell_conv", "target_part": "all", "conversion": "water" } - ] + ], + "id": 89 }, - { "display_name": "Flyby Jab", "desc": "Damage enemies in your way when using Charge", - "archetype": "", - "archetype_req": 0, - "parents": ["Air Mastery", "Flaming Uppercut"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 85, + 91 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 6, @@ -2733,20 +3366,32 @@ const atrees = "base_spell": 2, "target_part": "Flyby Jab", "cost": 0, - "multipliers": [20, 0, 0, 0, 0, 40] + "multipliers": [ + 20, + 0, + 0, + 0, + 0, + 40 + ] } - ] + ], + "id": 90 }, - { "display_name": "Flaming Uppercut", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Fire Mastery", "Flyby Jab"], - "dependencies": ["Uppercut"], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 86, + 90 + ], + "dependencies": [ + 79 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 8, @@ -2762,7 +3407,14 @@ const atrees = "base_spell": 3, "target_part": "Flaming Uppercut", "cost": 0, - "multipliers": [0, 0, 0, 0, 50, 0] + "multipliers": [ + 0, + 0, + 0, + 0, + 50, + 0 + ] }, { "type": "add_spell_prop", @@ -2782,66 +3434,76 @@ const atrees = "Flaming Uppercut": 5 } } - ] + ], + "id": 91 }, - { "display_name": "Iron Lungs", "desc": "War Scream deals more damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Flyby Jab", "Flaming Uppercut"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 90, + 91 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 7, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "target_part": "War Scream", "cost": 0, - "multipliers": [30, 0, 0, 0, 0, 30] + "multipliers": [ + 30, + 0, + 0, + 0, + 0, + 30 + ] } - ] + ], + "id": 92 }, - { "display_name": "Generalist", "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana", - "archetype": "Battle Monk", - "archetype_req": 3, - "parents": ["Counter"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 3, + "parents": [ + 94 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 15, "col": 2, "icon": "node_3" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 93 }, - { "display_name": "Counter", "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Half-Moon Swipe"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 89 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 15, "col": 4, @@ -2856,20 +3518,31 @@ const atrees = "base_spell": 5, "target_part": "Counter", "cost": 0, - "multipliers": [60, 0, 20, 0, 0, 20] + "multipliers": [ + 60, + 0, + 20, + 0, + 0, + 20 + ] } - ] + ], + "id": 94 }, - { "display_name": "Mantle of the Bovemists", "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)", - "archetype": "Paladin", - "archetype_req": 3, - "parents": ["Iron Lungs"], - "dependencies": ["War Scream"], + "archetype": "Paladin", + "archetype_req": 3, + "parents": [ + 92 + ], + "dependencies": [ + 81 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 15, "col": 7, @@ -2878,20 +3551,23 @@ const atrees = "properties": { "mantle_charge": 3 }, - "effects": [ - - ] + "effects": [], + "id": 95 }, - { "display_name": "Bak'al's Grasp", "desc": "After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)", - "archetype": "Fallen", - "archetype_req": 2, - "parents": ["Quadruple Bash", "Fireworks"], - "dependencies": ["War Scream"], + "archetype": "Fallen", + "archetype_req": 2, + "parents": [ + 87, + 88 + ], + "dependencies": [ + 81 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 16, "col": 1, @@ -2907,24 +3583,29 @@ const atrees = "slider_name": "Corrupted", "output": { "type": "stat", - "name": "raw" + "name": "raw" }, - "scaling": [4], + "scaling": [ + 4 + ], "slider_step": 2, "max": 120 } - ] + ], + "id": 96 }, - { "display_name": "Spear Proficiency 2", "desc": "Improve your Main Attack's damage and range w/ spear", - "archetype": "", - "archetype_req": 0, - "parents": ["Bak'al's Grasp", "Cheaper Uppercut"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 96, + 98 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 17, "col": 0, @@ -2944,96 +3625,103 @@ const atrees = } ] } - ] + ], + "id": 97 }, - { "display_name": "Cheaper Uppercut", "desc": "Reduce the Mana Cost of Uppercut", - "archetype": "", - "archetype_req": 0, - "parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 97, + 99, + 94 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 17, "col": 3, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "cost": -5 } - ] + ], + "id": 98 }, - { "display_name": "Aerodynamics", "desc": "During Charge, you can steer and change direction", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Cheaper Uppercut", "Provoke"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 98, + 100 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 17, "col": 5, "icon": "node_1" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 99 }, - { "display_name": "Provoke", "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Aerodynamics", "Mantle of the Bovemists"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 99, + 95 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 17, "col": 7, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "cost": -5 } - ] + ], + "id": 100 }, - { "display_name": "Precise Strikes", "desc": "+30% Critical Hit Damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Cheaper Uppercut", "Spear Proficiency 2"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 98, + 97 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 18, "col": 2, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -3045,53 +3733,66 @@ const atrees = } ] } - ] + ], + "id": 101 }, - { "display_name": "Air Shout", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", - "archetype": "", - "archetype_req": 0, - "parents": ["Aerodynamics", "Provoke"], - "dependencies": ["War Scream"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 99, + 100 + ], + "dependencies": [ + 81 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 18, "col": 6, "icon": "node_1" }, - "properties": { - - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "target_part": "Air Shout", "cost": 0, - "multipliers": [20, 0, 0, 0, 0, 5] + "multipliers": [ + 20, + 0, + 0, + 0, + 0, + 5 + ] } - ] + ], + "id": 102 }, - { "display_name": "Enraged Blow", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Spear Proficiency 2"], - "dependencies": ["Bak'al's Grasp"], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 97 + ], + "dependencies": [ + 96 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 20, "col": 0, "icon": "node_2" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -3104,50 +3805,66 @@ const atrees = ], "output": { "type": "stat", - "name": "dmgPct" + "name": "dmgPct" }, - "scaling": [2], + "scaling": [ + 2 + ], "max": 200 } - ] + ], + "id": 103 }, - { "display_name": "Flying Kick", "desc": "When using Charge, mobs hit will halt your momentum and get knocked back", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": ["Cheaper Uppercut", "Stronger Mantle"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": [ + 98, + 105 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 20, "col": 3, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "target_part": "Flying Kick", "cost": 0, - "multipliers": [120, 0, 0, 10, 0, 20] + "multipliers": [ + 120, + 0, + 0, + 10, + 0, + 20 + ] } - ] + ], + "id": 104 }, - { "display_name": "Stronger Mantle", "desc": "Add +2 additional charges to Mantle of the Bovemists", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Manachism", "Flying Kick"], - "dependencies": ["Mantle of the Bovemists"], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 106, + 104 + ], + "dependencies": [ + 95 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 20, "col": 6, @@ -3156,20 +3873,21 @@ const atrees = "properties": { "mantle_charge": 2 }, - "effects": [ - - ] + "effects": [], + "id": 105 }, - { "display_name": "Manachism", "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)", - "archetype": "Paladin", - "archetype_req": 3, - "parents": ["Stronger Mantle", "Provoke"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 3, + "parents": [ + 105, + 100 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 20, "col": 8, @@ -3178,47 +3896,59 @@ const atrees = "properties": { "cooldown": 1 }, - "effects": [ - - ] + "effects": [], + "id": 106 }, - { "display_name": "Boiling Blood", "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds", - "archetype": "", - "archetype_req": 0, - "parents": ["Enraged Blow", "Ragnarokkr"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 103, + 108 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 22, "col": 0, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "target_part": "Boiling Blood", "cost": 0, - "multipliers": [25, 0, 0, 0, 5, 0] + "multipliers": [ + 25, + 0, + 0, + 0, + 5, + 0 + ] } - ] + ], + "id": 107 }, - { "display_name": "Ragnarokkr", "desc": "War Scream become deafening, increasing its range and giving damage bonus to players", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Boiling Blood", "Flying Kick"], - "dependencies": ["War Scream"], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 107, + 104 + ], + "dependencies": [ + 81 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 22, "col": 2, @@ -3234,18 +3964,24 @@ const atrees = "base_spell": 4, "cost": 10 } - ] + ], + "id": 108 }, - { "display_name": "Ambidextrous", "desc": "Increase your chance to attack with Counter by +30%", - "archetype": "", - "archetype_req": 0, - "parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"], - "dependencies": ["Counter"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 104, + 105, + 110 + ], + "dependencies": [ + 94 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 22, "col": 4, @@ -3254,27 +3990,27 @@ const atrees = "properties": { "chance": 30 }, - "effects": [ - - ] + "effects": [], + "id": 109 }, - { "display_name": "Burning Heart", "desc": "For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Ambidextrous", "Stronger Bash"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 109, + 111 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 22, "col": 6, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -3289,106 +4025,134 @@ const atrees = "type": "stat", "name": "fDamPct" }, - "scaling": [2], + "scaling": [ + 2 + ], "max": 100, "slider_step": 100 } - ] + ], + "id": 110 }, - { "display_name": "Stronger Bash", "desc": "Increase the damage of Bash", - "archetype": "", - "archetype_req": 0, - "parents": ["Burning Heart", "Manachism"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 110, + 106 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 22, "col": 8, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "target_part": "Single Hit", "cost": 0, - "multipliers": [30, 0, 0, 0, 0, 0] + "multipliers": [ + 30, + 0, + 0, + 0, + 0, + 0 + ] } - ] + ], + "id": 111 }, - { "display_name": "Intoxicating Blood", "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", - "archetype": "Fallen", - "archetype_req": 5, - "parents": ["Ragnarokkr", "Boiling Blood"], - "dependencies": ["Bak'al's Grasp"], + "archetype": "Fallen", + "archetype_req": 5, + "parents": [ + 108, + 107 + ], + "dependencies": [ + 96 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 23, "col": 1, "icon": "node_1" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 112 }, - { "display_name": "Comet", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Ragnarokkr"], - "dependencies": ["Fireworks"], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 108 + ], + "dependencies": [ + 88 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 24, "col": 2, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Comet", "cost": 0, - "multipliers": [80, 20, 0, 0, 0, 0] + "multipliers": [ + 80, + 20, + 0, + 0, + 0, + 0 + ] }, { - "type":"add_spell_prop", + "type": "add_spell_prop", "base_spell": 3, "target_part": "Total Damage", - "cost": 0, + "cost": 0, "hits": { "Comet": 1 } } - ] + ], + "id": 113 }, - { "display_name": "Collide", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", - "archetype": "Battle Monk", - "archetype_req": 4, - "parents": ["Ambidextrous", "Burning Heart"], - "dependencies": ["Flying Kick"], + "archetype": "Battle Monk", + "archetype_req": 4, + "parents": [ + 109, + 110 + ], + "dependencies": [ + 104 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 23, "col": 5, @@ -3403,41 +4167,53 @@ const atrees = "base_spell": 2, "target_part": "Collide", "cost": 0, - "multipliers": [100, 0, 0, 0, 50, 0] + "multipliers": [ + 100, + 0, + 0, + 0, + 50, + 0 + ] } - ] + ], + "id": 114 }, - { "display_name": "Rejuvenating Skin", "desc": "Regain back 30% of the damage you take as healing over 30s", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Burning Heart", "Stronger Bash"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 110, + 111 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 23, "col": 7, "icon": "node_3" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 115 }, - { "display_name": "Uncontainable Corruption", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", - "archetype": "", - "archetype_req": 0, - "parents": ["Boiling Blood", "Radiant Devotee"], - "dependencies": ["Bak'al's Grasp"], + "archetype": "", + "archetype_req": 0, + "parents": [ + 107, + 117 + ], + "dependencies": [ + 96 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 26, "col": 0, @@ -3453,31 +4229,35 @@ const atrees = "slider_name": "Corrupted", "output": { "type": "stat", - "name": "raw" + "name": "raw" }, - "scaling": [1], + "scaling": [ + 1 + ], "slider_step": 2, "max": 50 } - ] + ], + "id": 116 }, - { "display_name": "Radiant Devotee", "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": ["Whirlwind Strike", "Uncontainable Corruption"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": [ + 118, + 116 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 26, "col": 2, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -3491,29 +4271,36 @@ const atrees = "type": "stat", "name": "mr" }, - "scaling": [1], + "scaling": [ + 1 + ], "max": 10, "slider_step": 4 } - ] + ], + "id": 117 }, - { "display_name": "Whirlwind Strike", "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", - "archetype": "Battle Monk", - "archetype_req": 5, - "parents": ["Ambidextrous", "Radiant Devotee"], - "dependencies": ["Uppercut"], + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": [ + 109, + 117 + ], + "dependencies": [ + 79 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 26, "col": 4, "icon": "node_1" }, "properties": { - "range": 2 + "range": 2 }, "effects": [ { @@ -3521,27 +4308,35 @@ const atrees = "base_spell": 3, "target_part": "Uppercut", "cost": 0, - "multipliers": [0, 0, 0, 0, 0, 50] + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 50 + ] } - ] + ], + "id": 118 }, - { "display_name": "Mythril Skin", "desc": "Gain +5% Base Resistance and become immune to knockback", - "archetype": "Paladin", - "archetype_req": 6, - "parents": ["Rejuvenating Skin"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 6, + "parents": [ + 115 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 26, "col": 7, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "raw_stat", @@ -3553,18 +4348,23 @@ const atrees = } ] } - ] + ], + "id": 119 }, - { "display_name": "Armour Breaker", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Uncontainable Corruption", "Radiant Devotee"], - "dependencies": ["Bak'al's Grasp"], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 116, + 117 + ], + "dependencies": [ + 96 + ], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 27, "col": 1, @@ -3573,47 +4373,56 @@ const atrees = "properties": { "duration": 5 }, - "effects": [ - - ] + "effects": [], + "id": 120 }, - { "display_name": "Shield Strike", "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Mythril Skin", "Sparkling Hope"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 119, + 122 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 27, "col": 6, "icon": "node_1" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 5, "target_part": "Shield Strike", "cost": 0, - "multipliers": [60, 0, 20, 0, 0, 0] + "multipliers": [ + 60, + 0, + 20, + 0, + 0, + 0 + ] } - ] + ], + "id": 121 }, - { "display_name": "Sparkling Hope", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Mythril Skin"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 119 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 27, "col": 8, @@ -3628,27 +4437,36 @@ const atrees = "base_spell": 5, "target_part": "Sparkling Hope", "cost": 0, - "multipliers": [10, 0, 5, 0, 0, 0] + "multipliers": [ + 10, + 0, + 5, + 0, + 0, + 0 + ] } - ] + ], + "id": 122 }, - { "display_name": "Massive Bash", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", - "archetype": "Fallen", - "archetype_req": 8, - "parents": ["Tempest", "Uncontainable Corruption"], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 8, + "parents": [ + 124, + 116 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 28, "col": 0, "icon": "node_2" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -3656,24 +4474,29 @@ const atrees = "slider_name": "Corrupted", "output": { "type": "stat", - "name": "bashAoE" + "name": "bashAoE" }, - "scaling": [1], + "scaling": [ + 1 + ], "max": 10, "slider_step": 3 } - ] + ], + "id": 123 }, - { "display_name": "Tempest", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Massive Bash", "Spirit of the Rabbit"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 123, + 125 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 28, "col": 2, @@ -3688,7 +4511,14 @@ const atrees = "base_spell": 4, "target_part": "Tempest", "cost": "0", - "multipliers": [30, 10, 0, 0, 0, 10] + "multipliers": [ + 30, + 10, + 0, + 0, + 0, + 10 + ] }, { "type": "add_spell_prop", @@ -3708,25 +4538,27 @@ const atrees = "Tempest": 3 } } - ] + ], + "id": 124 }, - { "display_name": "Spirit of the Rabbit", "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", - "archetype": "Battle Monk", - "archetype_req": 5, - "parents": ["Tempest", "Whirlwind Strike"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": [ + 124, + 118 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 28, "col": 4, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", @@ -3743,66 +4575,78 @@ const atrees = } ] } - ] + ], + "id": 125 }, - { "display_name": "Massacre", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", - "archetype": "Fallen", - "archetype_req": 5, - "parents": ["Tempest", "Massive Bash"], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 5, + "parents": [ + 124, + 123 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 29, "col": 1, "icon": "node_1" }, - "properties": { - }, - "effects": [ - - ] + "properties": {}, + "effects": [], + "id": 126 }, - { "display_name": "Axe Kick", "desc": "Increase the damage of Uppercut, but also increase its mana cost", - "archetype": "", - "archetype_req": 0, - "parents": ["Tempest", "Spirit of the Rabbit"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 124, + 125 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 29, "col": 3, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Uppercut", "cost": 10, - "multipliers": [100, 0, 0, 0, 0, 0] + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] } - ] + ], + "id": 127 }, - { "display_name": "Radiance", "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", - "archetype": "Paladin", - "archetype_req": 2, - "parents": ["Spirit of the Rabbit", "Cheaper Bash 2"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 2, + "parents": [ + 125, + 129 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 29, "col": 5, @@ -3811,77 +4655,80 @@ const atrees = "properties": { "cooldown": 15 }, - "effects": [ - - ] + "effects": [], + "id": 128 }, - { "display_name": "Cheaper Bash 2", "desc": "Reduce the Mana cost of Bash", - "archetype": "", - "archetype_req": 0, - "parents": ["Radiance", "Shield Strike", "Sparkling Hope"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 128, + 121, + 122 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 29, "col": 7, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -5 } - ] + ], + "id": 129 }, - { "display_name": "Cheaper War Scream", "desc": "Reduce the Mana cost of War Scream", - "archetype": "", - "archetype_req": 0, - "parents": ["Massive Bash"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 123 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 31, "col": 0, "icon": "node_0" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "cost": -5 } - ] + ], + "id": 130 }, - { "display_name": "Discombobulate", "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", - "archetype": "Battle Monk", - "archetype_req": 12, - "parents": ["Cyclone"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 12, + "parents": [ + 133 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 31, "col": 2, "icon": "node_3" }, - "properties": { - }, + "properties": {}, "effects": [ { "type": "stat_scaling", @@ -3889,23 +4736,27 @@ const atrees = "slider_name": "Hits dealt", "output": { "type": "stat", - "name": "rainrawButDifferent" + "name": "rainrawButDifferent" }, - "scaling": [2], + "scaling": [ + 2 + ], "max": 50 } - ] + ], + "id": 131 }, - { "display_name": "Thunderclap", "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", - "archetype": "Battle Monk", - "archetype_req": 8, - "parents": ["Cyclone"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 8, + "parents": [ + 133 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 32, "col": 5, @@ -3920,25 +4771,29 @@ const atrees = }, { "type": "raw_stat", - "bonuses": [{ - "type": "prop", - "abil_name": "Bash", - "name": "aoe", - "value": 3 - }] + "bonuses": [ + { + "type": "prop", + "abil_name": "Bash", + "name": "aoe", + "value": 3 + } + ] } - ] + ], + "id": 132 }, - { "display_name": "Cyclone", "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Spirit of the Rabbit"], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 125 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 31, "col": 4, @@ -3954,7 +4809,14 @@ const atrees = "base_spell": 4, "target_part": "Cyclone", "cost": 0, - "multipliers": [10, 0, 0, 0, 5, 10] + "multipliers": [ + 10, + 0, + 0, + 0, + 5, + 10 + ] }, { "type": "add_spell_prop", @@ -3964,92 +4826,105 @@ const atrees = "hits": { "Cyclone": 40 } - } - ] + ], + "id": 133 }, - { "display_name": "Second Chance", "desc": "When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)", - "archetype": "Paladin", - "archetype_req": 12, - "parents": ["Cheaper Bash 2"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 12, + "parents": [ + 129 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 32, "col": 7, "icon": "node_3" }, "properties": {}, - "effects": [] + "effects": [], + "id": 134 }, - { "display_name": "Blood Pact", "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", - "archetype": "", - "archetype_req": 10, - "parents": ["Cheaper War Scream"], - "dependencies": [], + "archetype": "", + "archetype_req": 10, + "parents": [ + 130 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 34, "col": 1, "icon": "node_3" }, "properties": {}, - "effects": [] + "effects": [], + "id": 135 }, - { "display_name": "Haemorrhage", "desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Blood Pact"], - "dependencies": ["Blood Pact"], + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 135 + ], + "dependencies": [ + 135 + ], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 35, "col": 2, "icon": "node_1" }, "properties": {}, - "effects": [] + "effects": [], + "id": 136 }, - { "display_name": "Brink of Madness", "desc": "If your health is 25% full or less, gain +40% Resistance", - "archetype": "", - "archetype_req": 0, - "parents": ["Blood Pact", "Cheaper Uppercut 2"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 135, + 138 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 35, "col": 4, "icon": "node_2" }, "properties": {}, - "effects": [] + "effects": [], + "id": 137 }, - { "display_name": "Cheaper Uppercut 2", "desc": "Reduce the Mana cost of Uppercut", - "archetype": "", - "archetype_req": 0, - "parents": ["Second Chance", "Brink of Madness"], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [ + 134, + 137 + ], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 35, "col": 6, @@ -4062,18 +4937,20 @@ const atrees = "base_spell": 3, "cost": -5 } - ] + ], + "id": 138 }, - { "display_name": "Martyr", "desc": "When you receive a fatal blow, all nearby allies become invincible", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Second Chance"], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 134 + ], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 35, "col": 8, @@ -4083,78 +4960,8 @@ const atrees = "duration": 3, "aoe": 12 }, - "effects": [] + "effects": [], + "id": 139 } - ], -} - -const atree_example = [ - { - "title": "skill", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 5, - "col": 3, - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 4, - "col": 3, - }, - { - "title": "skill2", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 0, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 1, - "col": 2 - }, - { - "title": "skill3", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 2, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 2, - "col": 3 - }, - { - "title": "skill4", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 2, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 3, - "col": 2 - }, - { - "title": "skill5", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 4, - "col": 2 - }, -]; + ] +} \ No newline at end of file diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js index 73d3e29..f79809c 100644 --- a/js/atree_constants_min.js +++ b/js/atree_constants_min.js @@ -1 +1 @@ -const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4,icon:"node_4"},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4,icon:"node_1"},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4,icon:"node_4"},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2,icon:"node_4"},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6,icon:"node_4"},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0,icon:"node_1"},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4,icon:"node_1"},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6,icon:"node_1"},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8,icon:"node_1"},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2,icon:"node_3"},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4,icon:"node_1"},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7,icon:"node_3"},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1,icon:"node_3"},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5,icon:"node_1"},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:["Mantle of the Bovemists"],blockers:[],cost:1,display:{row:20,col:6,icon:"node_0"},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8,icon:"node_2"},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2,icon:"node_2"},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4,icon:"node_0"},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0,icon:"node_0"},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4,icon:"node_1"},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7,icon:"node_1"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1,icon:"node_2"},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8,icon:"node_2"},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2,icon:"node_1"},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5,icon:"node_2"},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2,icon:"node_3"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:32,col:5,icon:"node_1"},properties:{},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"},{type:"raw_stat",bonuses:[{type:"prop",abil_name:"Bash",name:"aoe",value:3}]}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:31,col:4,icon:"node_1"},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1,icon:"node_3"},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2,icon:"node_1"},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4,icon:"node_2"},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8,icon:"node_1"},properties:{duration:3,aoe:12},effects:[]}]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] \ No newline at end of file +const atrees={"Archer":[{"display_name":"Arrow Shield","desc":"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)","archetype":"","archetype_req":0,"parents":[60,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":6},"properties":{"duration":60},"effects":[{"type":"replace_spell","name":"Arrow Shield","cost":30,"display_text":"Max Damage","base_spell":4,"spell_type":"damage","scaling":"spell","display":"","parts":[{"name":"Shield Damage","type":"damage","multipliers":[90,0,0,0,0,10]},{"name":"Total Damage","type":"total","hits":{"Shield Damage":2}}]}],"id":0},{"display_name":"Escape","desc":"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":7,"col":4},"properties":{"aoe":0,"range":0},"effects":[{"type":"replace_spell","name":"Escape","cost":25,"display_text":"Max Damage","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":1},{"display_name":"Arrow Bomb","desc":"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4},"properties":{"aoe":4.5,"range":26},"effects":[{"type":"replace_spell","name":"Arrow Bomb","cost":50,"display_text":"Average Damage","base_spell":3,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Arrow Bomb","type":"damage","multipliers":[160,0,0,0,20,0]},{"name":"Total Damage","type":"total","hits":{"Arrow Bomb":1}}]}],"id":2},{"display_name":"Heart Shatter","desc":"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[100,0,0,0,0,0]},{}],"id":3},{"display_name":"Fire Creep","desc":"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.","archetype":"","archetype_req":0,"parents":[68,86,5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":16,"col":6},"properties":{"aoe":0.8,"duration":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[30,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fire Creep":15}}],"id":4},{"display_name":"Bryophyte Roots","desc":"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.","archetype":"Trapper","archetype_req":1,"parents":[4,82],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":16,"col":8},"properties":{"aoe":2,"duration":5,"slowness":0.4},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Bryophyte Roots","cost":0,"multipliers":[40,20,0,0,0,0]}],"id":5},{"display_name":"Nimble String","desc":"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.","archetype":"","archetype_req":0,"parents":[83,69],"dependencies":[7],"blockers":[68],"cost":2,"display":{"row":15,"col":2},"properties":{"shootspeed":2},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-15,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","cost":0,"hits":{"Single Arrow":8}}],"id":6},{"display_name":"Arrow Storm","desc":"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.","archetype":"","archetype_req":0,"parents":[58,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":2},"properties":{"aoe":0,"range":16},"effects":[{"type":"replace_spell","name":"Arrow Storm","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,10,0,0,0]},{"name":"Single Stream","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Stream":2}}]}],"id":7},{"display_name":"Guardian Angels","desc":"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)","archetype":"Boltslinger","archetype_req":3,"parents":[59,67],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":19,"col":1},"properties":{"range":4,"duration":60,"shots":8,"count":2},"effects":[{"type":"replace_spell","name":"Guardian Angels","cost":30,"display_text":"Total Damage Average","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[40,0,0,0,0,20]},{"name":"Single Bow","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Bow":2}}]}],"id":8},{"display_name":"Windy Feet","base_abil":"Escape","desc":"When casting Escape, give speed to yourself and nearby allies.","archetype":"Boltslinger","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":1},"properties":{"aoe":8,"duration":120},"type":"stat_bonus","bonuses":[{"type":"stat","name":"spd","value":20}],"id":9},{"display_name":"Basaltic Trap","desc":"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)","archetype":"Trapper","archetype_req":2,"parents":[5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":8},"properties":{"aoe":7,"traps":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[140,30,0,0,30,0]}],"id":10},{"display_name":"Windstorm","desc":"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.","archetype":"","archetype_req":0,"parents":[8,33],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":21,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-11,0,-7,0,0,3]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":1}}],"id":11},{"display_name":"Grappling Hook","base_abil":"Escape","desc":"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)","archetype":"Trapper","archetype_req":0,"parents":[61,40,33],"dependencies":[],"blockers":[20],"cost":2,"display":{"row":21,"col":5},"properties":{"range":20},"effects":[],"id":12},{"display_name":"Implosion","desc":"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.","archetype":"Trapper","archetype_req":0,"parents":[12,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":13},{"display_name":"Twain's Arc","desc":"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)","archetype":"Sharpshooter","archetype_req":4,"parents":[62,64],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":25,"col":4},"properties":{"range":64,"focusReq":2},"effects":[{"type":"replace_spell","name":"Twain's Arc","cost":0,"display_text":"Twain's Arc","base_spell":5,"spell_type":"damage","scaling":"melee","display":"Twain's Arc Damage","parts":[{"name":"Twain's Arc Damage","type":"damage","multipliers":[200,0,0,0,0,0]}]}],"id":14},{"display_name":"Fierce Stomp","desc":"When using Escape, hold shift to quickly drop down and deal damage.","archetype":"Boltslinger","archetype_req":0,"parents":[42,64],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":1},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[100,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":2,"target_part":"Total Damage","cost":0,"hits":{"Fierce Stomp":1}}],"id":15},{"display_name":"Scorched Earth","desc":"Fire Creep become much stronger.","archetype":"Sharpshooter","archetype_req":0,"parents":[14],"dependencies":[4],"blockers":[],"cost":1,"display":{"row":26,"col":5},"properties":{"duration":2,"aoe":0.4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[10,0,0,0,5,0]}],"id":16},{"display_name":"Leap","desc":"When you double tap jump, leap foward. (2s Cooldown)","archetype":"Boltslinger","archetype_req":5,"parents":[42,55],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0},"properties":{"cooldown":2},"effects":[],"id":17},{"display_name":"Shocking Bomb","desc":"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.","archetype":"Sharpshooter","archetype_req":5,"parents":[14,44,55],"dependencies":[2],"blockers":[],"cost":2,"display":{"row":28,"col":4},"properties":{"gravity":0},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"}],"id":18},{"display_name":"Mana Trap","desc":"Your Traps will give you 4 Mana per second when you stay close to them.","archetype":"Trapper","archetype_req":5,"parents":[43,44],"dependencies":[4],"blockers":[],"cost":2,"display":{"row":28,"col":8},"properties":{"range":12,"manaRegen":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":10,"multipliers":[0,0,0,0,0,0]}],"id":19},{"display_name":"Escape Artist","desc":"When casting Escape, release 100 arrows towards the ground.","archetype":"Boltslinger","archetype_req":0,"parents":[46,17],"dependencies":[],"blockers":[12],"cost":2,"display":{"row":31,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Escape Artist","cost":0,"multipliers":[30,0,10,0,0,0]}],"id":20},{"display_name":"Initiator","desc":"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.","archetype":"Sharpshooter","archetype_req":5,"parents":[18,44,47],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":31,"col":5},"properties":{"focus":1,"timer":5},"type":"stat_bonus","bonuses":[{"type":"stat","name":"damPct","value":50}],"id":21},{"display_name":"Call of the Hound","desc":"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.","archetype":"Trapper","archetype_req":0,"parents":[21,47],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":32,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Call of the Hound","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":22},{"display_name":"Arrow Hurricane","desc":"Arrow Storm will shoot +2 stream of arrows.","archetype":"Boltslinger","archetype_req":8,"parents":[48,20],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":33,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":2}}],"id":23},{"display_name":"Geyser Stomp","desc":"Fierce Stomp will create geysers, dealing more damage and vertical knockback.","archetype":"","archetype_req":0,"parents":[56],"dependencies":[15],"blockers":[],"cost":2,"display":{"row":37,"col":1},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[0,0,0,50,0,0]}],"id":24},{"display_name":"Crepuscular Ray","desc":"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.","archetype":"Sharpshooter","archetype_req":10,"parents":[49],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":37,"col":4},"properties":{"focusReq":5,"focusRegen":-1},"effects":[{"type":"replace_spell","name":"Crepuscular Ray","base_spell":5,"spell_type":"damage","scaling":"spell","display":"One Focus","cost":0,"parts":[{"name":"Single Arrow","type":"damage","multipliers":[10,0,0,5,0,0]},{"name":"One Focus","type":"total","hits":{"Single Arrow":20}},{"name":"Total Damage","type":"total","hits":{"One Focus":7}}]}],"id":25},{"display_name":"Grape Bomb","desc":"Arrow bomb will throw 3 additional smaller bombs when exploding.","archetype":"","archetype_req":0,"parents":[51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":37,"col":7},"properties":{"miniBombs":3,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Grape Bomb","cost":0,"multipliers":[30,0,0,0,10,0]}],"id":26},{"display_name":"Tangled Traps","desc":"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":38,"col":6},"properties":{"attackSpeed":0.2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Tangled Traps","cost":0,"multipliers":[20,0,0,0,0,20]}],"id":27},{"display_name":"Snow Storm","desc":"Enemies near you will be slowed down.","archetype":"","archetype_req":0,"parents":[24,63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":39,"col":2},"properties":{"range":2.5,"slowness":0.3},"id":28},{"display_name":"All-Seeing Panoptes","desc":"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.","archetype":"Boltslinger","archetype_req":11,"parents":[28],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":40,"col":1},"properties":{"range":10,"shots":5},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Arrow","cost":0,"multipliers":[0,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":5}}],"id":29},{"display_name":"Minefield","desc":"Allow you to place +6 Traps, but with reduced damage and range.","archetype":"Trapper","archetype_req":10,"parents":[26,53],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":40,"col":7},"properties":{"aoe":-2,"traps":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[-80,0,0,0,0,0]}],"id":30},{"display_name":"Bow Proficiency I","desc":"Improve your Main Attack's damage and range when using a bow.","archetype":"","archetype_req":0,"parents":[2],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":31},{"display_name":"Cheaper Arrow Bomb","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-10}],"id":32},{"display_name":"Cheaper Arrow Storm","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[12,11,61],"dependencies":[],"blockers":[],"cost":1,"display":{"row":21,"col":3},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":33},{"display_name":"Cheaper Escape","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[7,0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":34},{"display_name":"Earth Mastery","desc":"Increases your base damage from all Earth attacks","archetype":"Trapper","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":8},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":82},{"display_name":"Thunder Mastery","desc":"Increases your base damage from all Thunder attacks","archetype":"Boltslinger","archetype_req":0,"parents":[7,86,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":2},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":83},{"display_name":"Water Mastery","desc":"Increases your base damage from all Water attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[34,83,86],"dependencies":[],"blockers":[],"cost":1,"display":{"row":14,"col":4},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":84},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":0},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":85},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[83,0,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":6},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":86},{"display_name":"More Shields","desc":"Give +2 charges to Arrow Shield.","archetype":"","archetype_req":0,"parents":[12,10],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":21,"col":7},"properties":{"shieldCharges":2},"id":40},{"display_name":"Stormy Feet","desc":"Windy Feet will last longer and add more speed.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[9],"blockers":[],"cost":1,"display":{"row":23,"col":1},"properties":{"duration":60},"effects":[{"type":"stat_bonus","bonuses":[{"type":"stat","name":"spdPct","value":20}]}],"id":41},{"display_name":"Refined Gunpowder","desc":"Increase the damage of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[50,0,0,0,0,0]}],"id":42},{"display_name":"More Traps","desc":"Increase the maximum amount of active Traps you can have by +2.","archetype":"Trapper","archetype_req":10,"parents":[54],"dependencies":[10],"blockers":[],"cost":1,"display":{"row":26,"col":8},"properties":{"traps":2},"id":43},{"display_name":"Better Arrow Shield","desc":"Arrow Shield will gain additional area of effect, knockback and damage.","archetype":"Sharpshooter","archetype_req":0,"parents":[19,18,14],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":28,"col":6},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Shield","multipliers":[40,0,0,0,0,0]}],"id":44},{"display_name":"Better Leap","desc":"Reduce leap's cooldown by 1s.","archetype":"Boltslinger","archetype_req":0,"parents":[17,55],"dependencies":[17],"blockers":[],"cost":1,"display":{"row":29,"col":1},"properties":{"cooldown":-1},"id":45},{"display_name":"Better Guardian Angels","desc":"Your Guardian Angels can shoot +4 arrows before disappearing.","archetype":"Boltslinger","archetype_req":0,"parents":[20,55],"dependencies":[8],"blockers":[],"cost":1,"display":{"row":31,"col":2},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":4}}],"id":46},{"display_name":"Cheaper Arrow Storm (2)","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[21,19],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":8},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":47},{"display_name":"Precise Shot","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[46,49,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":2},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdCritPct","value":30}]}],"id":48},{"display_name":"Cheaper Arrow Shield","desc":"Reduce the Mana cost of Arrow Shield.","archetype":"","archetype_req":0,"parents":[48,21],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":49},{"display_name":"Rocket Jump","desc":"Arrow Bomb's self-damage will knockback you farther away.","archetype":"","archetype_req":0,"parents":[47,21],"dependencies":[2],"blockers":[],"cost":1,"display":{"row":33,"col":6},"properties":{},"id":50},{"display_name":"Cheaper Escape (2)","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[22,70],"dependencies":[],"blockers":[],"cost":1,"display":{"row":34,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":51},{"display_name":"Stronger Hook","desc":"Increase your Grappling Hook's range, speed and strength.","archetype":"Trapper","archetype_req":5,"parents":[51],"dependencies":[12],"blockers":[],"cost":1,"display":{"row":35,"col":8},"properties":{"range":8},"id":52},{"display_name":"Cheaper Arrow Bomb (2)","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[63,30],"dependencies":[],"blockers":[],"cost":1,"display":{"row":40,"col":5},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":53},{"display_name":"Bouncing Bomb","desc":"Arrow Bomb will bounce once when hitting a block or enemy","archetype":"","archetype_req":0,"parents":[40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":25,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Arrow Bomb":2}}],"id":54},{"display_name":"Homing Shots","desc":"Your Main Attack arrows will follow nearby enemies and not be affected by gravity","archetype":"","archetype_req":0,"parents":[17,18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2},"properties":{},"effects":[],"id":55},{"display_name":"Shrapnel Bomb","desc":"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area","archetype":"Boltslinger","archetype_req":8,"parents":[23,48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Shrapnel Bomb","cost":0,"multipliers":[40,0,0,0,20,0]}],"id":56},{"display_name":"Elusive","desc":"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)","archetype":"Boltslinger","archetype_req":0,"parents":[24],"dependencies":[],"blockers":[],"cost":2,"display":{"row":38,"col":0},"properties":{},"effects":[],"id":57},{"display_name":"Double Shots","desc":"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)","archetype":"Boltslinger","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[60],"cost":1,"display":{"row":7,"col":2},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":58},{"display_name":"Triple Shots","desc":"Triple Main Attack arrows, but they deal -20% damage per arrow","archetype":"Boltslinger","archetype_req":0,"parents":[69,67],"dependencies":[58],"blockers":[],"cost":1,"display":{"row":17,"col":0},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":59},{"display_name":"Power Shots","desc":"Main Attack arrows have increased speed and knockback","archetype":"Sharpshooter","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[58],"cost":1,"display":{"row":7,"col":6},"properties":{},"effects":[],"id":60},{"display_name":"Focus","desc":"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once","archetype":"Sharpshooter","archetype_req":2,"parents":[68],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"dmgPct"},"scaling":[35],"max":3}],"id":61},{"display_name":"More Focus","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[33,12],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"dmgPct"},"scaling":[35],"max":5}],"id":62},{"display_name":"More Focus (2)","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[25,28],"dependencies":[],"blockers":[],"cost":1,"display":{"row":39,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"dmgPct"},"scaling":[35],"max":7}],"id":63},{"display_name":"Traveler","desc":"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)","archetype":"","archetype_req":0,"parents":[42,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"spd"}],"output":{"type":"stat","name":"sdRaw"},"scaling":[1],"max":100}],"id":64},{"display_name":"Patient Hunter","desc":"Your Traps will deal +20% more damage for every second they are active (Max +80%)","archetype":"Trapper","archetype_req":0,"parents":[40],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":8},"properties":{"max":80},"effects":[],"id":65},{"display_name":"Stronger Patient Hunter","desc":"Add +80% Max Damage to Patient Hunter","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[65],"blockers":[],"cost":1,"display":{"row":38,"col":8},"properties":{"max":80},"effects":[],"id":66},{"display_name":"Frenzy","desc":"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second","archetype":"Boltslinger","archetype_req":0,"parents":[59,6],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"spd"},"scaling":[6],"max":200}],"id":67},{"display_name":"Phantom Ray","desc":"Condense Arrow Storm into a single ray that damages enemies 10 times per second","archetype":"Sharpshooter","archetype_req":0,"parents":[84,4],"dependencies":[7],"blockers":[11,6,23],"cost":2,"display":{"row":16,"col":4},"properties":{},"effects":[{"type":"replace_spell","name":"Phantom Ray","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[25,0,5,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Arrow":16}}]}],"id":68},{"display_name":"Arrow Rain","desc":"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies","archetype":"Trapper","archetype_req":0,"parents":[6,85],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":15,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Arrow Rain","cost":0,"multipliers":[120,0,0,0,0,80]}],"id":69},{"display_name":"Decimator","desc":"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)","archetype":"Sharpshooter","archetype_req":0,"parents":[49],"dependencies":[68],"blockers":[],"cost":1,"display":{"row":34,"col":5},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Phantom Ray hits","output":{"type":"stat","name":"PhRayDmg"},"scaling":10,"max":50}],"id":70}],"Warrior":[{"display_name":"Bash","desc":"Violently bash the ground, dealing high damage in a large area","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4,"icon":"node_4"},"properties":{"aoe":4,"range":3},"effects":[{"type":"replace_spell","name":"Bash","cost":45,"display_text":"Total Damage Average","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Hit","type":"damage","multipliers":[130,20,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Hit":1}}]}],"id":71},{"display_name":"Spear Proficiency 1","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[71],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":72},{"display_name":"Cheaper Bash","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[72],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-10}],"id":73},{"display_name":"Double Bash","desc":"Bash will hit a second time at a farther range","archetype":"","archetype_req":0,"parents":[72],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4,"icon":"node_1"},"properties":{"range":3},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"name":"Single Hit","value":1}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-50,0,0,0,0,0]}],"id":74},{"display_name":"Charge","desc":"Charge forward at high speed (hold shift to cancel)","archetype":"","archetype_req":0,"parents":[74],"dependencies":[],"blockers":[],"cost":1,"display":{"row":6,"col":4,"icon":"node_4"},"properties":{},"effects":[{"type":"replace_spell","name":"Charge","cost":25,"display_text":"Total Damage Average","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":75},{"display_name":"Heavy Impact","desc":"After using Charge, violently crash down into the ground and deal damage","archetype":"","archetype_req":0,"parents":[79],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":1,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Heavy Impact","cost":0,"multipliers":[100,0,0,0,0,0]}],"id":76},{"display_name":"Vehement","desc":"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)","archetype":"Fallen","archetype_req":0,"parents":[75],"dependencies":[],"blockers":[78],"cost":1,"display":{"row":6,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"mdPct"},{"type":"stat","name":"mdRaw"}],"output":{"type":"stat","name":"spd"},"scaling":[1,1],"max":20}],"id":77},{"display_name":"Tougher Skin","desc":"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)","archetype":"Paladin","archetype_req":0,"parents":[75],"dependencies":[],"blockers":[77],"cost":1,"display":{"row":6,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":"5"}]},{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hprRaw"},{"type":"stat","name":"hprPct"}],"output":{"type":"stat","name":"hpBonus"},"scaling":[10,10],"max":100}],"id":78},{"display_name":"Uppercut","desc":"Rocket enemies in the air and deal massive damage","archetype":"","archetype_req":0,"parents":[77],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":2,"icon":"node_4"},"properties":{"aoe":3,"range":5},"effects":[{"type":"replace_spell","name":"Uppercut","cost":45,"display_text":"Total Damage Average","base_spell":3,"spell_type":"damage","scaling":"spell","display":"total","parts":[{"name":"Uppercut","type":"damage","multipliers":[150,50,50,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Uppercut":1}}]}],"id":79},{"display_name":"Cheaper Charge","desc":"Reduce the Mana cost of Charge","archetype":"","archetype_req":0,"parents":[79,81],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":80},{"display_name":"War Scream","desc":"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies","archetype":"","archetype_req":0,"parents":[78],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":6,"icon":"node_4"},"properties":{"duration":30,"aoe":12,"defense_bonus":10},"effects":[{"type":"replace_spell","name":"War Scream","cost":35,"display_text":"War Scream","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage Average","parts":[{"name":"War Scream","type":"damage","multipliers":[50,0,0,0,50,0]}]}],"id":81},{"display_name":"Earth Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Fallen","archetype_req":0,"parents":[79],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":82},{"display_name":"Thunder Mastery","desc":"Increases base damage from all Thunder attacks","archetype":"Fallen","archetype_req":0,"parents":[79,85,80],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":83},{"display_name":"Water Mastery","desc":"Increases base damage from all Water attacks","archetype":"Battle Monk","archetype_req":0,"parents":[80,83,85],"dependencies":[],"blockers":[],"cost":1,"display":{"row":11,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":84},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[81,83,80],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":85},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Paladin","archetype_req":0,"parents":[81],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":86},{"display_name":"Quadruple Bash","desc":"Bash will hit 4 times at an even larger range","archetype":"Fallen","archetype_req":0,"parents":[82,88],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":0,"icon":"node_1"},"properties":{"range":6},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Hit":2}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-20,0,0,0,0,0]}],"id":87},{"display_name":"Fireworks","desc":"Mobs hit by Uppercut will explode mid-air and receive additional damage","archetype":"Fallen","archetype_req":0,"parents":[83,87],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fireworks","cost":0,"multipliers":[80,0,20,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fireworks":1}}],"id":88},{"display_name":"Half-Moon Swipe","desc":"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water","archetype":"Battle Monk","archetype_req":1,"parents":[84],"dependencies":[79],"blockers":[],"cost":2,"display":{"row":13,"col":4,"icon":"node_1"},"properties":{"range":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":-10,"multipliers":[-70,0,0,0,0,0]},{"type":"convert_spell_conv","target_part":"all","conversion":"water"}],"id":89},{"display_name":"Flyby Jab","desc":"Damage enemies in your way when using Charge","archetype":"","archetype_req":0,"parents":[85,91],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":6,"icon":"node_1"},"properties":{"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flyby Jab","cost":0,"multipliers":[20,0,0,0,0,40]}],"id":90},{"display_name":"Flaming Uppercut","desc":"Uppercut will light mobs on fire, dealing damage every 0.6 seconds","archetype":"Paladin","archetype_req":0,"parents":[86,90],"dependencies":[79],"blockers":[],"cost":2,"display":{"row":12,"col":8,"icon":"node_1"},"properties":{"duration":3,"tick":0.6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut","cost":0,"multipliers":[0,0,0,0,50,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut Total Damage","cost":0,"hits":{"Flaming Uppercut":5}},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Flaming Uppercut":5}}],"id":91},{"display_name":"Iron Lungs","desc":"War Scream deals more damage","archetype":"","archetype_req":0,"parents":[90,91],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"War Scream","cost":0,"multipliers":[30,0,0,0,0,30]}],"id":92},{"display_name":"Generalist","desc":"After casting 3 different spells in a row, your next spell will cost 5 mana","archetype":"Battle Monk","archetype_req":3,"parents":[94],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":2,"icon":"node_3"},"properties":{},"effects":[],"id":93},{"display_name":"Counter","desc":"When dodging a nearby enemy attack, get 30% chance to instantly attack back","archetype":"Battle Monk","archetype_req":0,"parents":[89],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":4,"icon":"node_1"},"properties":{"chance":30},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Counter","cost":0,"multipliers":[60,0,20,0,0,20]}],"id":94},{"display_name":"Mantle of the Bovemists","desc":"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)","archetype":"Paladin","archetype_req":3,"parents":[92],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":15,"col":7,"icon":"node_3"},"properties":{"mantle_charge":3},"effects":[],"id":95},{"display_name":"Bak'al's Grasp","desc":"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)","archetype":"Fallen","archetype_req":2,"parents":[87,88],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":16,"col":1,"icon":"node_3"},"properties":{"cooldown":15},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[4],"slider_step":2,"max":120}],"id":96},{"display_name":"Spear Proficiency 2","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[96,98],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":0,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":97},{"display_name":"Cheaper Uppercut","desc":"Reduce the Mana Cost of Uppercut","archetype":"","archetype_req":0,"parents":[97,99,94],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":98},{"display_name":"Aerodynamics","desc":"During Charge, you can steer and change direction","archetype":"Battle Monk","archetype_req":0,"parents":[98,100],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":5,"icon":"node_1"},"properties":{},"effects":[],"id":99},{"display_name":"Provoke","desc":"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream","archetype":"Paladin","archetype_req":0,"parents":[99,95],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":100},{"display_name":"Precise Strikes","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[98,97],"dependencies":[],"blockers":[],"cost":1,"display":{"row":18,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"critDmg","value":30}]}],"id":101},{"display_name":"Air Shout","desc":"War Scream will fire a projectile that can go through walls and deal damage multiple times","archetype":"","archetype_req":0,"parents":[99,100],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":18,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Air Shout","cost":0,"multipliers":[20,0,0,0,0,5]}],"id":102},{"display_name":"Enraged Blow","desc":"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)","archetype":"Fallen","archetype_req":0,"parents":[97],"dependencies":[96],"blockers":[],"cost":2,"display":{"row":20,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"dmgPct"},"scaling":[2],"max":200}],"id":103},{"display_name":"Flying Kick","desc":"When using Charge, mobs hit will halt your momentum and get knocked back","archetype":"Battle Monk","archetype_req":1,"parents":[98,105],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":3,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flying Kick","cost":0,"multipliers":[120,0,0,10,0,20]}],"id":104},{"display_name":"Stronger Mantle","desc":"Add +2 additional charges to Mantle of the Bovemists","archetype":"Paladin","archetype_req":0,"parents":[106,104],"dependencies":[95],"blockers":[],"cost":1,"display":{"row":20,"col":6,"icon":"node_0"},"properties":{"mantle_charge":2},"effects":[],"id":105},{"display_name":"Manachism","desc":"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)","archetype":"Paladin","archetype_req":3,"parents":[105,100],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":8,"icon":"node_2"},"properties":{"cooldown":1},"effects":[],"id":106},{"display_name":"Boiling Blood","desc":"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds","archetype":"","archetype_req":0,"parents":[103,108],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":0,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Boiling Blood","cost":0,"multipliers":[25,0,0,0,5,0]}],"id":107},{"display_name":"Ragnarokkr","desc":"War Scream become deafening, increasing its range and giving damage bonus to players","archetype":"Fallen","archetype_req":0,"parents":[107,104],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":22,"col":2,"icon":"node_2"},"properties":{"damage_bonus":30,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":10}],"id":108},{"display_name":"Ambidextrous","desc":"Increase your chance to attack with Counter by +30%","archetype":"","archetype_req":0,"parents":[104,105,110],"dependencies":[94],"blockers":[],"cost":1,"display":{"row":22,"col":4,"icon":"node_0"},"properties":{"chance":30},"effects":[],"id":109},{"display_name":"Burning Heart","desc":"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)","archetype":"Paladin","archetype_req":0,"parents":[109,111],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"fDamPct"},"scaling":[2],"max":100,"slider_step":100}],"id":110},{"display_name":"Stronger Bash","desc":"Increase the damage of Bash","archetype":"","archetype_req":0,"parents":[110,106],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[30,0,0,0,0,0]}],"id":111},{"display_name":"Intoxicating Blood","desc":"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted","archetype":"Fallen","archetype_req":5,"parents":[108,107],"dependencies":[96],"blockers":[],"cost":2,"display":{"row":23,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":112},{"display_name":"Comet","desc":"After being hit by Fireworks, enemies will crash into the ground and receive more damage","archetype":"Fallen","archetype_req":0,"parents":[108],"dependencies":[88],"blockers":[],"cost":2,"display":{"row":24,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Comet","cost":0,"multipliers":[80,20,0,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Comet":1}}],"id":113},{"display_name":"Collide","desc":"Mobs thrown into walls from Flying Kick will explode and receive additonal damage","archetype":"Battle Monk","archetype_req":4,"parents":[109,110],"dependencies":[104],"blockers":[],"cost":2,"display":{"row":23,"col":5,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Collide","cost":0,"multipliers":[100,0,0,0,50,0]}],"id":114},{"display_name":"Rejuvenating Skin","desc":"Regain back 30% of the damage you take as healing over 30s","archetype":"Paladin","archetype_req":0,"parents":[110,111],"dependencies":[],"blockers":[],"cost":2,"display":{"row":23,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":115},{"display_name":"Uncontainable Corruption","desc":"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1","archetype":"","archetype_req":0,"parents":[107,117],"dependencies":[96],"blockers":[],"cost":1,"display":{"row":26,"col":0,"icon":"node_0"},"properties":{"cooldown":-5},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[1],"slider_step":2,"max":50}],"id":116},{"display_name":"Radiant Devotee","desc":"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)","archetype":"Battle Monk","archetype_req":1,"parents":[118,116],"dependencies":[],"blockers":[],"cost":1,"display":{"row":26,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","inputs":[{"type":"stat","name":"ref"}],"output":{"type":"stat","name":"mr"},"scaling":[1],"max":10,"slider_step":4}],"id":117},{"display_name":"Whirlwind Strike","desc":"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)","archetype":"Battle Monk","archetype_req":5,"parents":[109,117],"dependencies":[79],"blockers":[],"cost":2,"display":{"row":26,"col":4,"icon":"node_1"},"properties":{"range":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":0,"multipliers":[0,0,0,0,0,50]}],"id":118},{"display_name":"Mythril Skin","desc":"Gain +5% Base Resistance and become immune to knockback","archetype":"Paladin","archetype_req":6,"parents":[115],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":5}]}],"id":119},{"display_name":"Armour Breaker","desc":"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage","archetype":"Fallen","archetype_req":0,"parents":[116,117],"dependencies":[96],"blockers":[],"cost":2,"display":{"row":27,"col":1,"icon":"node_2"},"properties":{"duration":5},"effects":[],"id":120},{"display_name":"Shield Strike","desc":"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost","archetype":"Paladin","archetype_req":0,"parents":[119,122],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Shield Strike","cost":0,"multipliers":[60,0,20,0,0,0]}],"id":121},{"display_name":"Sparkling Hope","desc":"Everytime you heal 5% of your max health, deal damage to all nearby enemies","archetype":"Paladin","archetype_req":0,"parents":[119],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":8,"icon":"node_2"},"properties":{"aoe":6},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Sparkling Hope","cost":0,"multipliers":[10,0,5,0,0,0]}],"id":122},{"display_name":"Massive Bash","desc":"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)","archetype":"Fallen","archetype_req":8,"parents":[124,116],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"bashAoE"},"scaling":[1],"max":10,"slider_step":3}],"id":123},{"display_name":"Tempest","desc":"War Scream will ripple the ground and deal damage 3 times in a large area","archetype":"Battle Monk","archetype_req":0,"parents":[123,125],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2,"icon":"node_1"},"properties":{"aoe":16},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest","cost":"0","multipliers":[30,10,0,0,0,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest Total Damage","cost":"0","hits":{"Tempest":3}},{"type":"add_spell_prop","base_spell":4,"target_part":"Total Damage","cost":"0","hits":{"Tempest":3}}],"id":124},{"display_name":"Spirit of the Rabbit","desc":"Reduce the Mana cost of Charge and increase your Walk Speed by +20%","archetype":"Battle Monk","archetype_req":5,"parents":[124,118],"dependencies":[],"blockers":[],"cost":1,"display":{"row":28,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5},{"type":"raw_stat","bonuses":[{"type":"stat","name":"spd","value":20}]}],"id":125},{"display_name":"Massacre","desc":"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar","archetype":"Fallen","archetype_req":5,"parents":[124,123],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":126},{"display_name":"Axe Kick","desc":"Increase the damage of Uppercut, but also increase its mana cost","archetype":"","archetype_req":0,"parents":[124,125],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":10,"multipliers":[100,0,0,0,0,0]}],"id":127},{"display_name":"Radiance","desc":"Bash will buff your allies' positive IDs. (15s Cooldown)","archetype":"Paladin","archetype_req":2,"parents":[125,129],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":5,"icon":"node_2"},"properties":{"cooldown":15},"effects":[],"id":128},{"display_name":"Cheaper Bash 2","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[128,121,122],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":129},{"display_name":"Cheaper War Scream","desc":"Reduce the Mana cost of War Scream","archetype":"","archetype_req":0,"parents":[123],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":130},{"display_name":"Discombobulate","desc":"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second","archetype":"Battle Monk","archetype_req":12,"parents":[133],"dependencies":[],"blockers":[],"cost":2,"display":{"row":31,"col":2,"icon":"node_3"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"rainrawButDifferent"},"scaling":[2],"max":50}],"id":131},{"display_name":"Thunderclap","desc":"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder","archetype":"Battle Monk","archetype_req":8,"parents":[133],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":5,"icon":"node_1"},"properties":{},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"},{"type":"raw_stat","bonuses":[{"type":"prop","abil_name":"Bash","name":"aoe","value":3}]}],"id":132},{"display_name":"Cyclone","desc":"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s","archetype":"Battle Monk","archetype_req":0,"parents":[125],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":4,"icon":"node_1"},"properties":{"aoe":4,"duration":20},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone","cost":0,"multipliers":[10,0,0,0,5,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone Total Damage","cost":0,"hits":{"Cyclone":40}}],"id":133},{"display_name":"Second Chance","desc":"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)","archetype":"Paladin","archetype_req":12,"parents":[129],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":134},{"display_name":"Blood Pact","desc":"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)","archetype":"","archetype_req":10,"parents":[130],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1,"icon":"node_3"},"properties":{},"effects":[],"id":135},{"display_name":"Haemorrhage","desc":"Reduce Blood Pact's health cost. (0.5% health per mana)","archetype":"Fallen","archetype_req":0,"parents":[135],"dependencies":[135],"blockers":[],"cost":1,"display":{"row":35,"col":2,"icon":"node_1"},"properties":{},"effects":[],"id":136},{"display_name":"Brink of Madness","desc":"If your health is 25% full or less, gain +40% Resistance","archetype":"","archetype_req":0,"parents":[135,138],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":4,"icon":"node_2"},"properties":{},"effects":[],"id":137},{"display_name":"Cheaper Uppercut 2","desc":"Reduce the Mana cost of Uppercut","archetype":"","archetype_req":0,"parents":[134,137],"dependencies":[],"blockers":[],"cost":1,"display":{"row":35,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":138},{"display_name":"Martyr","desc":"When you receive a fatal blow, all nearby allies become invincible","archetype":"Paladin","archetype_req":0,"parents":[134],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":8,"icon":"node_1"},"properties":{"duration":3,"aoe":12},"effects":[],"id":139}]} \ No newline at end of file diff --git a/js/atree_constants_str_old.js b/js/atree_constants_str_old.js new file mode 100644 index 0000000..e0c7bc3 --- /dev/null +++ b/js/atree_constants_str_old.js @@ -0,0 +1,4160 @@ +const atrees = +{ + "Archer": [ + { + "display_name": "Arrow Shield", + "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", + "archetype": "", + "archetype_req": 0, + "parents": ["Power Shots", "Cheaper Escape"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 6 + }, + "properties": { + "duration": 60 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Shield", + "cost": 30, + "display_text": "Max Damage", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "", + "parts": [ + { + "name": "Shield Damage", + "type": "damage", + "multipliers": [90, 0, 0, 0, 0, 10] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Shield Damage": 2 + } + } + ] + } + ] + }, + + { + "display_name": "Escape", + "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", + "archetype": "", + "archetype_req": 0, + "parents": ["Heart Shatter"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 7, + "col": 4 + }, + "properties": { + "aoe": 0, + "range": 0 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Escape", + "cost": 25, + "display_text": "Max Damage", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [0, 0, 0, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] + } + ] + }, + { + "display_name": "Arrow Bomb", + "desc": "Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)", + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 0, + "col": 4 + }, + "properties": { + "aoe": 4.5, + "range": 26 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Bomb", + "cost": 50, + "display_text": "Average Damage", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Arrow Bomb", + "type": "damage", + "multipliers": [160, 0, 0, 0, 20, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Arrow Bomb": 1 + } + } + ] + } + ] + }, + { + "display_name": "Heart Shatter", + "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.", + "archetype": "", + "archetype_req": 0, + "parents": ["Bow Proficiency I"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 4, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + }, + { + + } + ] + }, + { + "display_name": "Fire Creep", + "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", + "archetype": "", + "archetype_req": 0, + "parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 6 + }, + "properties": { + "aoe": 0.8, + "duration": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [30, 0, 0, 0, 20, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fire Creep": 15 + } + } + ] + }, + { + "display_name": "Bryophyte Roots", + "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.", + "archetype": "Trapper", + "archetype_req": 1, + "parents": ["Fire Creep", "Earth Mastery"], + "dependencies": ["Arrow Storm"], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 8 + }, + "properties": { + "aoe": 2, + "duration": 5, + "slowness": 0.4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Bryophyte Roots", + "cost": 0, + "multipliers": [40, 20, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Nimble String", + "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", + "archetype": "", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Arrow Rain"], + "dependencies": ["Arrow Storm"], + "blockers": ["Phantom Ray"], + "cost": 2, + "display": { + "row": 15, + "col": 2 + }, + "properties": { + "shootspeed": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [-15, 0, 0, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Stream", + "cost": 0, + "hits": { + "Single Arrow": 8 + } + } + ] + }, + { + "display_name": "Arrow Storm", + "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", + "archetype": "", + "archetype_req": 0, + "parents": ["Double Shots", "Cheaper Escape"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 2 + }, + "properties": { + "aoe": 0, + "range": 16 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Storm", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [30, 0, 10, 0, 0, 0] + }, + { + "name": "Single Stream", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Stream": 2 + } + } + ] + } + ] + }, + { + "display_name": "Guardian Angels", + "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)", + "archetype": "Boltslinger", + "archetype_req": 3, + "parents": ["Triple Shots", "Frenzy"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 1 + }, + "properties": { + "range": 4, + "duration": 60, + "shots": 8, + "count": 2 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Guardian Angels", + "cost": 30, + "display_text": "Total Damage Average", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [40, 0, 0, 0, 0, 20] + }, + { + "name": "Single Bow", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Bow": 2 + } + } + ] + } + ] + }, + { + "display_name": "Windy Feet", + "base_abil": "Escape", + "desc": "When casting Escape, give speed to yourself and nearby allies.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Storm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 1 + }, + "properties": { + "aoe": 8, + "duration": 120 + }, + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spd", + "value": 20 + } + ] + }, + { + "display_name": "Basaltic Trap", + "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)", + "archetype": "Trapper", + "archetype_req": 2, + "parents": ["Bryophyte Roots"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 8 + }, + "properties": { + "aoe": 7, + "traps": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [140, 30, 0, 0, 30, 0] + } + ] + }, + { + "display_name": "Windstorm", + "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", + "archetype": "", + "archetype_req": 0, + "parents": ["Guardian Angels", "Cheaper Arrow Storm"], + "dependencies": [], + "blockers": ["Phantom Ray"], + "cost": 2, + "display": { + "row": 21, + "col": 1 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [-11, 0, -7, 0, 0, 3] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 1 + } + } + ] + }, + { + "display_name": "Grappling Hook", + "base_abil": "Escape", + "desc": "When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Focus", "More Shields", "Cheaper Arrow Storm"], + "dependencies": [], + "blockers": ["Escape Artist"], + "cost": 2, + "display": { + "row": 21, + "col": 5 + }, + "properties": { + "range": 20 + }, + "effects": [ + ] + }, + { + "display_name": "Implosion", + "desc": "Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grappling Hook", "More Shields"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 6 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Twain's Arc", + "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", + "archetype": "Sharpshooter", + "archetype_req": 4, + "parents": ["More Focus", "Traveler"], + "dependencies": ["Focus"], + "blockers": [], + "cost": 2, + "display": { + "row": 25, + "col": 4 + }, + "properties": { + "range": 64, + "focusReq": 2 + }, + "effects": [ + + { + "type": "replace_spell", + "name": "Twain's Arc", + "cost": 0, + "display_text": "Twain's Arc", + "base_spell": 5, + "spell_type": "damage", + "scaling": "melee", + "display": "Twain's Arc Damage", + "parts": [ + { + "name": "Twain's Arc Damage", + "type": "damage", + "multipliers": [200, 0, 0, 0, 0, 0] + } + ] + } + ] + }, + { + "display_name": "Fierce Stomp", + "desc": "When using Escape, hold shift to quickly drop down and deal damage.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Refined Gunpowder", "Traveler"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 1 + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fierce Stomp": 1 + } + } + ] + }, + { + "display_name": "Scorched Earth", + "desc": "Fire Creep become much stronger.", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Twain's Arc"], + "dependencies": ["Fire Creep"], + "blockers": [], + "cost": 1, + "display": { + "row": 26 , + "col": 5 + }, + "properties": { + "duration": 2, + "aoe": 0.4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [10, 0, 0, 0, 5, 0] + } + ] + }, + { + "display_name": "Leap", + "desc": "When you double tap jump, leap foward. (2s Cooldown)", + "archetype": "Boltslinger", + "archetype_req": 5, + "parents": ["Refined Gunpowder", "Homing Shots"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 0 + }, + "properties": { + "cooldown": 2 + }, + "effects": [ + + ] + }, + { + "display_name": "Shocking Bomb", + "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.", + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": ["Twain's Arc", "Better Arrow Shield", "Homing Shots"], + "dependencies": ["Arrow Bomb"], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 4 + }, + "properties": { + "gravity": 0 + }, + "effects": [ + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + } + ] + }, + { + "display_name": "Mana Trap", + "desc": "Your Traps will give you 4 Mana per second when you stay close to them.", + "archetype": "Trapper", + "archetype_req": 5, + "parents": ["More Traps", "Better Arrow Shield"], + "dependencies": ["Fire Creep"], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 8 + }, + "properties": { + "range": 12, + "manaRegen": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 10, + "multipliers": [0, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Escape Artist", + "desc": "When casting Escape, release 100 arrows towards the ground.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Better Guardian Angels", "Leap"], + "dependencies": [], + "blockers": ["Grappling Hook"], + "cost": 2, + "display": { + "row": 31, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Escape Artist", + "cost": 0, + "multipliers": [30, 0, 10, 0, 0, 0] + } + ] + }, + { + "display_name": "Initiator", + "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": ["Shocking Bomb", "Better Arrow Shield", "Cheaper Arrow Storm (2)"], + "dependencies": ["Focus"], + "blockers": [], + "cost": 2, + "display": { + "row": 31, + "col": 5 + }, + "properties": { + "focus": 1, + "timer": 5 + }, + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "damPct", + "value": 50 + } + ] + }, + { + "display_name": "Call of the Hound", + "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Initiator", "Cheaper Arrow Storm (2)"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 7 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Call of the Hound", + "cost": 0, + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Arrow Hurricane", + "desc": "Arrow Storm will shoot +2 stream of arrows.", + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": ["Precise Shot", "Escape Artist"], + "dependencies": [], + "blockers": ["Phantom Ray"], + "cost": 2, + "display": { + "row": 33, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 2 + } + } + ] + }, + { + "display_name": "Geyser Stomp", + "desc": "Fierce Stomp will create geysers, dealing more damage and vertical knockback.", + "archetype": "", + "archetype_req": 0, + "parents": ["Shrapnel Bomb"], + "dependencies": ["Fierce Stomp"], + "blockers": [], + "cost": 2, + "display": { + "row": 37, + "col": 1 + }, + "properties": { + "aoe": 1 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [0, 0, 0, 50, 0, 0] + } + ] + }, + { + "display_name": "Crepuscular Ray", + "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.", + "archetype": "Sharpshooter", + "archetype_req": 10, + "parents": ["Cheaper Arrow Shield"], + "dependencies": ["Arrow Storm"], + "blockers": [], + "cost": 2, + "display": { + "row": 37, + "col": 4 + }, + "properties": { + "focusReq": 5, + "focusRegen": -1 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Crepuscular Ray", + "base_spell": 5, + "spell_type": "damage", + "scaling": "spell", + "display": "One Focus", + "cost": 0, + + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [10, 0, 0, 5, 0, 0] + }, + { + "name": "One Focus", + "type": "total", + "hits": { + "Single Arrow": 20 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "One Focus": 7 + } + } + ] + } + ] + }, + { + "display_name": "Grape Bomb", + "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Escape (2)"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 37, + "col": 7 + }, + "properties": { + "miniBombs": 3, + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Grape Bomb", + "cost": 0, + "multipliers": [30, 0, 0, 0, 10, 0] + } + ] + }, + { + "display_name": "Tangled Traps", + "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grape Bomb"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 6 + }, + "properties": { + "attackSpeed": 0.2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Tangled Traps", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 20] + } + ] + }, + { + "display_name": "Snow Storm", + "desc": "Enemies near you will be slowed down.", + "archetype": "", + "archetype_req": 0, + "parents": ["Geyser Stomp", "More Focus (2)"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 39, + "col": 2 + }, + "properties": { + "range": 2.5, + "slowness": 0.3 + } + }, + { + "display_name": "All-Seeing Panoptes", + "desc": "Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.", + "archetype": "Boltslinger", + "archetype_req": 11, + "parents": ["Snow Storm"], + "dependencies": ["Guardian Angels"], + "blockers": [], + "cost": 2, + "display": { + "row": 40, + "col": 1 + }, + "properties": { + "range": 10, + "shots": 5 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [0, 0, 0, 0, 20, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 5 + } + } + ] + }, + { + "display_name": "Minefield", + "desc": "Allow you to place +6 Traps, but with reduced damage and range.", + "archetype": "Trapper", + "archetype_req": 10, + "parents": ["Grape Bomb", "Cheaper Arrow Bomb (2)"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 2, + "display": { + "row": 40, + "col": 7 + }, + "properties": { + "aoe": -2, + "traps": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [-80, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Bow Proficiency I", + "desc": "Improve your Main Attack's damage and range when using a bow.", + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Bomb"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 4 + }, + "properties": { + "mainAtk_range": 6 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ] + }, + { + "display_name": "Cheaper Arrow Bomb", + "desc": "Reduce the Mana cost of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": ["Bow Proficiency I"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 6 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -10 + } + ] + }, + { + "display_name": "Cheaper Arrow Storm", + "desc": "Reduce the Mana cost of Arrow Storm.", + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "Windstorm", "Focus"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 21, + "col": 3 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ] + }, + { + "display_name": "Cheaper Escape", + "desc": "Reduce the Mana cost of Escape.", + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Storm", "Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ] + }, + { + "display_name": "Earth Mastery", + "desc": "Increases your base damage from all Earth attacks", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 8 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "eDamPct", + "value": 20 + }, + { + "type": "stat", + "name": "eDam", + "value": [2, 4] + } + ] + } + ] + }, + { + "display_name": "Thunder Mastery", + "desc": "Increases your base damage from all Thunder attacks", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Storm", "Fire Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "tDamPct", + "value": 10 + }, + { + "type": "stat", + "name": "tDam", + "value": [1, 8] + } + ] + } + ] + }, + { + "display_name": "Water Mastery", + "desc": "Increases your base damage from all Water attacks", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Escape", "Thunder Mastery", "Fire Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 14, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "wDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "wDam", + "value": [2, 4] + } + ] + } + ] + }, + { + "display_name": "Air Mastery", + "desc": "Increases base damage from all Air attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Arrow Storm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "aDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "aDam", + "value": [3, 4] + } + ] + } + ] + }, + { + "display_name": "Fire Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Arrow Shield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 6 + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "fDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "fDam", + "value": [3, 5] + } + ] + } + ] + }, + { + "display_name": "More Shields", + "desc": "Give +2 charges to Arrow Shield.", + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "Basaltic Trap"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 1, + "display": { + "row": 21, + "col": 7 + }, + "properties": { + "shieldCharges": 2 + } + }, + { + "display_name": "Stormy Feet", + "desc": "Windy Feet will last longer and add more speed.", + "archetype": "", + "archetype_req": 0, + "parents": ["Windstorm"], + "dependencies": ["Windy Feet"], + "blockers": [], + "cost": 1, + "display": { + "row": 23, + "col": 1 + }, + "properties": { + "duration": 60 + }, + "effects": [ + { + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spdPct", + "value": 20 + } + ] + } + ] + }, + { + "display_name": "Refined Gunpowder", + "desc": "Increase the damage of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": ["Windstorm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 25, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [50, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "More Traps", + "desc": "Increase the maximum amount of active Traps you can have by +2.", + "archetype": "Trapper", + "archetype_req": 10, + "parents": ["Bouncing Bomb"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 8 + }, + "properties": { + "traps": 2 + } + }, + { + "display_name": "Better Arrow Shield", + "desc": "Arrow Shield will gain additional area of effect, knockback and damage.", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Mana Trap", "Shocking Bomb", "Twain's Arc"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 1, + "display": { + "row": 28, + "col": 6 + }, + "properties": { + "aoe": 1 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Shield", + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] + }, + { + "display_name": "Better Leap", + "desc": "Reduce leap's cooldown by 1s.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Leap", "Homing Shots"], + "dependencies": ["Leap"], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 1 + }, + "properties": { + "cooldown": -1 + } + }, + { + "display_name": "Better Guardian Angels", + "desc": "Your Guardian Angels can shoot +4 arrows before disappearing.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Escape Artist", "Homing Shots"], + "dependencies": ["Guardian Angels"], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 2 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 4 + } + } + ] + }, + { + "display_name": "Cheaper Arrow Storm (2)", + "desc": "Reduce the Mana cost of Arrow Storm.", + "archetype": "", + "archetype_req": 0, + "parents": ["Initiator", "Mana Trap"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 8 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ] + }, + { + "display_name": "Precise Shot", + "desc": "+30% Critical Hit Damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Better Guardian Angels", "Cheaper Arrow Shield", "Arrow Hurricane"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 33, + "col": 2 + }, + "properties": { + "mainAtk_range": 6 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdCritPct", + "value": 30 + } + ] + } + ] + }, + { + "display_name": "Cheaper Arrow Shield", + "desc": "Reduce the Mana cost of Arrow Shield.", + "archetype": "", + "archetype_req": 0, + "parents": ["Precise Shot", "Initiator"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 33, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ] + }, + { + "display_name": "Rocket Jump", + "desc": "Arrow Bomb's self-damage will knockback you farther away.", + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Arrow Storm (2)", "Initiator"], + "dependencies": ["Arrow Bomb"], + "blockers": [], + "cost": 1, + "display": { + "row": 33, + "col": 6 + }, + "properties": { + } + }, + { + "display_name": "Cheaper Escape (2)", + "desc": "Reduce the Mana cost of Escape.", + "archetype": "", + "archetype_req": 0, + "parents": ["Call of the Hound", "Decimator"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 7 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ] + }, + { + "display_name": "Stronger Hook", + "desc": "Increase your Grappling Hook's range, speed and strength.", + "archetype": "Trapper", + "archetype_req": 5, + "parents": ["Cheaper Escape (2)"], + "dependencies": ["Grappling Hook"], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 8 + }, + "properties": { + "range": 8 + } + }, + { + "display_name": "Cheaper Arrow Bomb (2)", + "desc": "Reduce the Mana cost of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": ["More Focus (2)", "Minefield"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 40, + "col": 5 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ] + }, + { + "display_name": "Bouncing Bomb", + "desc": "Arrow Bomb will bounce once when hitting a block or enemy", + "archetype": "", + "archetype_req": 0, + "parents": ["More Shields"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 25, + "col": 7 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Arrow Bomb": 2 + } + } + ] + }, + { + "display_name": "Homing Shots", + "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity", + "archetype": "", + "archetype_req": 0, + "parents": ["Leap", "Shocking Bomb"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 2 + }, + "properties": { + + }, + "effects": [ + + ] + }, + { + "display_name": "Shrapnel Bomb", + "desc": "Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area", + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": ["Arrow Hurricane", "Precise Shot"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 34, + "col": 1 + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Shrapnel Bomb", + "cost": 0, + "multipliers": [40, 0, 0, 0, 20, 0] + } + ] + }, + { + "display_name": "Elusive", + "desc": "If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Geyser Stomp"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 0 + }, + "properties": { + + }, + "effects": [ + + ] + }, + { + "display_name": "Double Shots", + "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Escape"], + "dependencies": [], + "blockers": ["Power Shots"], + "cost": 1, + "display": { + "row": 7, + "col": 2 + }, + "properties": { + "arrow": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 0, + "target_part": "Melee Damage", + "cost": 0, + "multipliers": 0.7 + } + ] + }, + { + "display_name": "Triple Shots", + "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Rain", "Frenzy"], + "dependencies": ["Double Shots"], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 0 + }, + "properties": { + "arrow": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 0, + "target_part": "Melee Damage", + "cost": 0, + "multipliers": 0.7 + } + ] + }, + { + "display_name": "Power Shots", + "desc": "Main Attack arrows have increased speed and knockback", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Escape"], + "dependencies": [], + "blockers": ["Double Shots"], + "cost": 1, + "display": { + "row": 7, + "col": 6 + }, + "properties": { + + }, + "effects": [ + + ] + }, + { + "display_name": "Focus", + "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once", + "archetype": "Sharpshooter", + "archetype_req": 2, + "parents": ["Phantom Ray"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [35], + "max": 3 + } + ] + }, + { + "display_name": "More Focus", + "desc": "Add +2 max Focus", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Arrow Storm", "Grappling Hook"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [35], + "max": 5 + } + ] + }, + { + "display_name": "More Focus (2)", + "desc": "Add +2 max Focus", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Crepuscular Ray", "Snow Storm"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 39, + "col": 4 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [35], + "max": 7 + } + ] + }, + { + "display_name": "Traveler", + "desc": "For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)", + "archetype": "", + "archetype_req": 0, + "parents": ["Refined Gunpowder", "Twain's Arc"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 25, + "col": 2 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "spd" + } + ], + "output": { + "type": "stat", + "name": "sdRaw" + }, + "scaling": [1], + "max": 100 + } + ] + }, + { + "display_name": "Patient Hunter", + "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%)", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["More Shields"], + "dependencies": ["Basaltic Trap"], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 8 + }, + "properties": { + "max": 80 + }, + "effects": [ + + ] + }, + { + "display_name": "Stronger Patient Hunter", + "desc": "Add +80% Max Damage to Patient Hunter", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grape Bomb"], + "dependencies": ["Patient Hunter"], + "blockers": [], + "cost": 1, + "display": { + "row": 38, + "col": 8 + }, + "properties": { + "max": 80 + }, + "effects": [ + + ] + }, + { + "display_name": "Frenzy", + "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Triple Shots", "Nimble String"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 17, + "col": 2 + }, + "properties": { + + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Hits dealt", + "output": { + "type": "stat", + "name": "spd" + }, + "scaling": [6], + "max": 200 + } + ] + }, + { + "display_name": "Phantom Ray", + "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Water Mastery", "Fire Creep"], + "dependencies": ["Arrow Storm"], + "blockers": ["Windstorm", "Nimble String", "Arrow Hurricane"], + "cost": 2, + "display": { + "row": 16, + "col": 4 + }, + "properties": { + }, + "effects": [ + { + "type": "replace_spell", + "name": "Phantom Ray", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [25, 0, 5, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Arrow": 16 + } + } + ] + } + ] + }, + { + "display_name": "Arrow Rain", + "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies", + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Nimble String", "Air Mastery"], + "dependencies": ["Arrow Shield"], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 0 + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Arrow Rain", + "cost": 0, + "multipliers": [120, 0, 0, 0, 0, 80] + } + ] + }, + { + "display_name": "Decimator", + "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Arrow Shield"], + "dependencies": ["Phantom Ray"], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 5 + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Phantom Ray hits", + "output": { + "type": "stat", + "name": "PhRayDmg" + }, + "scaling": 10, + "max": 50 + } + ] + } + ], + "Warrior": [ + { + "display_name": "Bash", + "desc": "Violently bash the ground, dealing high damage in a large area", + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 0, + "col": 4, + "icon": "node_4" + }, + "properties": { + "aoe": 4, + "range": 3 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Bash", + "cost": 45, + "display_text": "Total Damage Average", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Hit", + "type": "damage", + "multipliers": [130, 20, 0, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Hit": 1 + } + } + ] + } + ] + }, + { + "display_name": "Spear Proficiency 1", + "desc": "Improve your Main Attack's damage and range w/ spear", + "archetype": "", + "archetype_req": 0, + "parents": ["Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 4, + "icon": "node_0" + }, + "properties": { + "melee_range": 1 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ] + }, + + { + "display_name": "Cheaper Bash", + "desc": "Reduce the Mana cost of Bash", + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 1"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 2, + "icon": "node_0" + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -10 + } + ] + }, + { + "display_name": "Double Bash", + "desc": "Bash will hit a second time at a farther range", + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 1"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 4, + "col": 4, + "icon": "node_1" + }, + "properties": { + "range": 3 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "name": "Single Hit", + "value": 1 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [-50, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Charge", + "desc": "Charge forward at high speed (hold shift to cancel)", + "archetype": "", + "archetype_req": 0, + "parents": ["Double Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 6, + "col": 4, + "icon": "node_4" + }, + "properties": { + }, + "effects": [ + { + "type": "replace_spell", + "name": "Charge", + "cost": 25, + "display_text": "Total Damage Average", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [0, 0, 0, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] + } + ] + }, + + { + "display_name": "Heavy Impact", + "desc": "After using Charge, violently crash down into the ground and deal damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 1, + "icon": "node_1" + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Heavy Impact", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Vehement", + "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Charge"], + "dependencies": [], + "blockers": ["Tougher Skin"], + "cost": 1, + "display": { + "row": 6, + "col": 2, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "mdPct" + }, + { + "type": "stat", + "name": "mdRaw" + } + ], + "output": { + "type": "stat", + "name": "spd" + }, + "scaling": [1, 1], + "max": 20 + } + ] + }, + + { + "display_name": "Tougher Skin", + "desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Charge"], + "dependencies": [], + "blockers": ["Vehement"], + "cost": 1, + "display": { + "row": 6, + "col": 6, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "baseResist", + "value": "5" + } + ] + }, + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hprRaw" + }, + { + "type": "stat", + "name": "hprPct" + } + ], + "output": { + "type": "stat", + "name": "hpBonus" + }, + "scaling": [10, 10], + "max": 100 + } + ] + }, + + { + "display_name": "Uppercut", + "desc": "Rocket enemies in the air and deal massive damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Vehement"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 2, + "icon": "node_4" + }, + "properties": { + "aoe": 3, + "range": 5 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Uppercut", + "cost": 45, + "display_text": "Total Damage Average", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "total", + "parts": [ + { + "name": "Uppercut", + "type": "damage", + "multipliers": [150, 50, 50, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Uppercut": 1 + } + } + ] + } + ] + }, + + { + "display_name": "Cheaper Charge", + "desc": "Reduce the Mana cost of Charge", + "archetype": "", + "archetype_req": 0, + "parents": ["Uppercut", "War Scream"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 4, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ] + }, + + { + "display_name": "War Scream", + "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies", + "archetype": "", + "archetype_req": 0, + "parents": ["Tougher Skin"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 6, + "icon": "node_4" + }, + "properties": { + "duration": 30, + "aoe": 12, + "defense_bonus": 10 + }, + "effects": [ + { + "type": "replace_spell", + "name": "War Scream", + "cost": 35, + "display_text": "War Scream", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage Average", + "parts": [ + { + "name": "War Scream", + "type": "damage", + "multipliers": [50, 0, 0, 0, 50, 0] + } + ] + } + ] + }, + + { + "display_name": "Earth Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 0, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "eDamPct", + "value": 20 + }, + { + "type": "stat", + "name": "eDam", + "value": [2, 4] + } + ] + } + ] + }, + + { + "display_name": "Thunder Mastery", + "desc": "Increases base damage from all Thunder attacks", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uppercut", "Air Mastery", "Cheaper Charge"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 2, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "tDamPct", + "value": 10 + }, + { + "type": "stat", + "name": "tDam", + "value": [1, 8] + } + ] + } + ] + }, + + { + "display_name": "Water Mastery", + "desc": "Increases base damage from all Water attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Cheaper Charge", "Thunder Mastery", "Air Mastery"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 11, + "col": 4, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "wDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "wDam", + "value": [2, 4] + } + ] + } + ] + }, + + { + "display_name": "Air Mastery", + "desc": "Increases base damage from all Air attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["War Scream", "Thunder Mastery", "Cheaper Charge"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 6, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "aDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "aDam", + "value": [3, 4] + } + ] + } + ] + }, + + { + "display_name": "Fire Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["War Scream"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 8, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "fDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "fDam", + "value": [3, 5] + } + ] + } + ] + }, + + { + "display_name": "Quadruple Bash", + "desc": "Bash will hit 4 times at an even larger range", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Earth Mastery", "Fireworks"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 0, + "icon": "node_1" + }, + "properties": { + "range": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Hit": 2 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [-20, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Fireworks", + "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Quadruple Bash"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 2, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fireworks", + "cost": 0, + "multipliers": [80, 0, 20, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fireworks": 1 + } + } + ] + }, + + { + "display_name": "Half-Moon Swipe", + "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Water Mastery"], + "dependencies": ["Uppercut"], + "blockers": [], + "cost": 2, + "display": { + "row": 13, + "col": 4, + "icon": "node_1" + }, + "properties": { + "range": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": -10, + "multipliers": [-70, 0, 0, 0, 0, 0] + }, + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "water" + } + ] + }, + + { + "display_name": "Flyby Jab", + "desc": "Damage enemies in your way when using Charge", + "archetype": "", + "archetype_req": 0, + "parents": ["Air Mastery", "Flaming Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 6, + "icon": "node_1" + }, + "properties": { + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Flyby Jab", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 40] + } + ] + }, + + { + "display_name": "Flaming Uppercut", + "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Fire Mastery", "Flyby Jab"], + "dependencies": ["Uppercut"], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 8, + "icon": "node_1" + }, + "properties": { + "duration": 3, + "tick": 0.6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Flaming Uppercut", + "cost": 0, + "multipliers": [0, 0, 0, 0, 50, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Flaming Uppercut Total Damage", + "cost": 0, + "hits": { + "Flaming Uppercut": 5 + } + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Flaming Uppercut": 5 + } + } + ] + }, + + { + "display_name": "Iron Lungs", + "desc": "War Scream deals more damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Flyby Jab", "Flaming Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 7, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "War Scream", + "cost": 0, + "multipliers": [30, 0, 0, 0, 0, 30] + } + ] + }, + + { + "display_name": "Generalist", + "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana", + "archetype": "Battle Monk", + "archetype_req": 3, + "parents": ["Counter"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 2, + "icon": "node_3" + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Counter", + "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Half-Moon Swipe"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 4, + "icon": "node_1" + }, + "properties": { + "chance": 30 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Counter", + "cost": 0, + "multipliers": [60, 0, 20, 0, 0, 20] + } + ] + }, + + { + "display_name": "Mantle of the Bovemists", + "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)", + "archetype": "Paladin", + "archetype_req": 3, + "parents": ["Iron Lungs"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 7, + "icon": "node_3" + }, + "properties": { + "mantle_charge": 3 + }, + "effects": [ + + ] + }, + + { + "display_name": "Bak'al's Grasp", + "desc": "After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)", + "archetype": "Fallen", + "archetype_req": 2, + "parents": ["Quadruple Bash", "Fireworks"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 1, + "icon": "node_3" + }, + "properties": { + "cooldown": 15 + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "raw" + }, + "scaling": [4], + "slider_step": 2, + "max": 120 + } + ] + }, + + { + "display_name": "Spear Proficiency 2", + "desc": "Improve your Main Attack's damage and range w/ spear", + "archetype": "", + "archetype_req": 0, + "parents": ["Bak'al's Grasp", "Cheaper Uppercut"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 0, + "icon": "node_0" + }, + "properties": { + "melee_range": 1 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ] + }, + + { + "display_name": "Cheaper Uppercut", + "desc": "Reduce the Mana Cost of Uppercut", + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 3, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ] + }, + + { + "display_name": "Aerodynamics", + "desc": "During Charge, you can steer and change direction", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Cheaper Uppercut", "Provoke"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 17, + "col": 5, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Provoke", + "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Aerodynamics", "Mantle of the Bovemists"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 7, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ] + }, + + { + "display_name": "Precise Strikes", + "desc": "+30% Critical Hit Damage", + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Uppercut", "Spear Proficiency 2"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 18, + "col": 2, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "critDmg", + "value": 30 + } + ] + } + ] + }, + + { + "display_name": "Air Shout", + "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", + "archetype": "", + "archetype_req": 0, + "parents": ["Aerodynamics", "Provoke"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 18, + "col": 6, + "icon": "node_1" + }, + "properties": { + + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Air Shout", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 5] + } + ] + }, + + { + "display_name": "Enraged Blow", + "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Spear Proficiency 2"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 0, + "icon": "node_2" + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hpBonus" + } + ], + "output": { + "type": "stat", + "name": "dmgPct" + }, + "scaling": [2], + "max": 200 + } + ] + }, + + { + "display_name": "Flying Kick", + "desc": "When using Charge, mobs hit will halt your momentum and get knocked back", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Cheaper Uppercut", "Stronger Mantle"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 3, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Flying Kick", + "cost": 0, + "multipliers": [120, 0, 0, 10, 0, 20] + } + ] + }, + + { + "display_name": "Stronger Mantle", + "desc": "Add +2 additional charges to Mantle of the Bovemists", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Manachism", "Flying Kick"], + "dependencies": ["Mantle of the Bovemists"], + "blockers": [], + "cost": 1, + "display": { + "row": 20, + "col": 6, + "icon": "node_0" + }, + "properties": { + "mantle_charge": 2 + }, + "effects": [ + + ] + }, + + { + "display_name": "Manachism", + "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)", + "archetype": "Paladin", + "archetype_req": 3, + "parents": ["Stronger Mantle", "Provoke"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 8, + "icon": "node_2" + }, + "properties": { + "cooldown": 1 + }, + "effects": [ + + ] + }, + + { + "display_name": "Boiling Blood", + "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds", + "archetype": "", + "archetype_req": 0, + "parents": ["Enraged Blow", "Ragnarokkr"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 0, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Boiling Blood", + "cost": 0, + "multipliers": [25, 0, 0, 0, 5, 0] + } + ] + }, + + { + "display_name": "Ragnarokkr", + "desc": "War Scream become deafening, increasing its range and giving damage bonus to players", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Boiling Blood", "Flying Kick"], + "dependencies": ["War Scream"], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 2, + "icon": "node_2" + }, + "properties": { + "damage_bonus": 30, + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": 10 + } + ] + }, + + { + "display_name": "Ambidextrous", + "desc": "Increase your chance to attack with Counter by +30%", + "archetype": "", + "archetype_req": 0, + "parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"], + "dependencies": ["Counter"], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 4, + "icon": "node_0" + }, + "properties": { + "chance": 30 + }, + "effects": [ + + ] + }, + + { + "display_name": "Burning Heart", + "desc": "For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Ambidextrous", "Stronger Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 6, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hpBonus" + } + ], + "output": { + "type": "stat", + "name": "fDamPct" + }, + "scaling": [2], + "max": 100, + "slider_step": 100 + } + ] + }, + + { + "display_name": "Stronger Bash", + "desc": "Increase the damage of Bash", + "archetype": "", + "archetype_req": 0, + "parents": ["Burning Heart", "Manachism"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 8, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [30, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Intoxicating Blood", + "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", + "archetype": "Fallen", + "archetype_req": 5, + "parents": ["Ragnarokkr", "Boiling Blood"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 1, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Comet", + "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Ragnarokkr"], + "dependencies": ["Fireworks"], + "blockers": [], + "cost": 2, + "display": { + "row": 24, + "col": 2, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Comet", + "cost": 0, + "multipliers": [80, 20, 0, 0, 0, 0] + }, + { + "type":"add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Comet": 1 + } + } + ] + }, + + { + "display_name": "Collide", + "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", + "archetype": "Battle Monk", + "archetype_req": 4, + "parents": ["Ambidextrous", "Burning Heart"], + "dependencies": ["Flying Kick"], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 5, + "icon": "node_1" + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Collide", + "cost": 0, + "multipliers": [100, 0, 0, 0, 50, 0] + } + ] + }, + + { + "display_name": "Rejuvenating Skin", + "desc": "Regain back 30% of the damage you take as healing over 30s", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Burning Heart", "Stronger Bash"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 7, + "icon": "node_3" + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Uncontainable Corruption", + "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", + "archetype": "", + "archetype_req": 0, + "parents": ["Boiling Blood", "Radiant Devotee"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 0, + "icon": "node_0" + }, + "properties": { + "cooldown": -5 + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "raw" + }, + "scaling": [1], + "slider_step": 2, + "max": 50 + } + ] + }, + + { + "display_name": "Radiant Devotee", + "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Whirlwind Strike", "Uncontainable Corruption"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 2, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "inputs": [ + { + "type": "stat", + "name": "ref" + } + ], + "output": { + "type": "stat", + "name": "mr" + }, + "scaling": [1], + "max": 10, + "slider_step": 4 + } + ] + }, + + { + "display_name": "Whirlwind Strike", + "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": ["Ambidextrous", "Radiant Devotee"], + "dependencies": ["Uppercut"], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 4, + "icon": "node_1" + }, + "properties": { + "range": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": 0, + "multipliers": [0, 0, 0, 0, 0, 50] + } + ] + }, + + { + "display_name": "Mythril Skin", + "desc": "Gain +5% Base Resistance and become immune to knockback", + "archetype": "Paladin", + "archetype_req": 6, + "parents": ["Rejuvenating Skin"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 7, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "baseResist", + "value": 5 + } + ] + } + ] + }, + + { + "display_name": "Armour Breaker", + "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uncontainable Corruption", "Radiant Devotee"], + "dependencies": ["Bak'al's Grasp"], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 1, + "icon": "node_2" + }, + "properties": { + "duration": 5 + }, + "effects": [ + + ] + }, + + { + "display_name": "Shield Strike", + "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Mythril Skin", "Sparkling Hope"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 6, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Shield Strike", + "cost": 0, + "multipliers": [60, 0, 20, 0, 0, 0] + } + ] + }, + + { + "display_name": "Sparkling Hope", + "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Mythril Skin"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 8, + "icon": "node_2" + }, + "properties": { + "aoe": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Sparkling Hope", + "cost": 0, + "multipliers": [10, 0, 5, 0, 0, 0] + } + ] + }, + + { + "display_name": "Massive Bash", + "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", + "archetype": "Fallen", + "archetype_req": 8, + "parents": ["Tempest", "Uncontainable Corruption"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 0, + "icon": "node_2" + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "bashAoE" + }, + "scaling": [1], + "max": 10, + "slider_step": 3 + } + ] + }, + + { + "display_name": "Tempest", + "desc": "War Scream will ripple the ground and deal damage 3 times in a large area", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Massive Bash", "Spirit of the Rabbit"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 2, + "icon": "node_1" + }, + "properties": { + "aoe": 16 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Tempest", + "cost": "0", + "multipliers": [30, 10, 0, 0, 0, 10] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Tempest Total Damage", + "cost": "0", + "hits": { + "Tempest": 3 + } + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Total Damage", + "cost": "0", + "hits": { + "Tempest": 3 + } + } + ] + }, + + { + "display_name": "Spirit of the Rabbit", + "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": ["Tempest", "Whirlwind Strike"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 28, + "col": 4, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + }, + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "spd", + "value": 20 + } + ] + } + ] + }, + + { + "display_name": "Massacre", + "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", + "archetype": "Fallen", + "archetype_req": 5, + "parents": ["Tempest", "Massive Bash"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 1, + "icon": "node_1" + }, + "properties": { + }, + "effects": [ + + ] + }, + + { + "display_name": "Axe Kick", + "desc": "Increase the damage of Uppercut, but also increase its mana cost", + "archetype": "", + "archetype_req": 0, + "parents": ["Tempest", "Spirit of the Rabbit"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 3, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": 10, + "multipliers": [100, 0, 0, 0, 0, 0] + } + ] + }, + + { + "display_name": "Radiance", + "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", + "archetype": "Paladin", + "archetype_req": 2, + "parents": ["Spirit of the Rabbit", "Cheaper Bash 2"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 5, + "icon": "node_2" + }, + "properties": { + "cooldown": 15 + }, + "effects": [ + + ] + }, + + { + "display_name": "Cheaper Bash 2", + "desc": "Reduce the Mana cost of Bash", + "archetype": "", + "archetype_req": 0, + "parents": ["Radiance", "Shield Strike", "Sparkling Hope"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 7, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ] + }, + + { + "display_name": "Cheaper War Scream", + "desc": "Reduce the Mana cost of War Scream", + "archetype": "", + "archetype_req": 0, + "parents": ["Massive Bash"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 0, + "icon": "node_0" + }, + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ] + }, + + { + "display_name": "Discombobulate", + "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", + "archetype": "Battle Monk", + "archetype_req": 12, + "parents": ["Cyclone"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 31, + "col": 2, + "icon": "node_3" + }, + "properties": { + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Hits dealt", + "output": { + "type": "stat", + "name": "rainrawButDifferent" + }, + "scaling": [2], + "max": 50 + } + ] + }, + + { + "display_name": "Thunderclap", + "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", + "archetype": "Battle Monk", + "archetype_req": 8, + "parents": ["Cyclone"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 5, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + }, + { + "type": "raw_stat", + "bonuses": [{ + "type": "prop", + "abil_name": "Bash", + "name": "aoe", + "value": 3 + }] + } + ] + }, + + { + "display_name": "Cyclone", + "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Spirit of the Rabbit"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 4, + "icon": "node_1" + }, + "properties": { + "aoe": 4, + "duration": 20 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Cyclone", + "cost": 0, + "multipliers": [10, 0, 0, 0, 5, 10] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Cyclone Total Damage", + "cost": 0, + "hits": { + "Cyclone": 40 + } + + } + ] + }, + + { + "display_name": "Second Chance", + "desc": "When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)", + "archetype": "Paladin", + "archetype_req": 12, + "parents": ["Cheaper Bash 2"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 7, + "icon": "node_3" + }, + "properties": {}, + "effects": [] + }, + + { + "display_name": "Blood Pact", + "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", + "archetype": "", + "archetype_req": 10, + "parents": ["Cheaper War Scream"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 34, + "col": 1, + "icon": "node_3" + }, + "properties": {}, + "effects": [] + }, + + { + "display_name": "Haemorrhage", + "desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Blood Pact"], + "dependencies": ["Blood Pact"], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 2, + "icon": "node_1" + }, + "properties": {}, + "effects": [] + }, + + { + "display_name": "Brink of Madness", + "desc": "If your health is 25% full or less, gain +40% Resistance", + "archetype": "", + "archetype_req": 0, + "parents": ["Blood Pact", "Cheaper Uppercut 2"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 35, + "col": 4, + "icon": "node_2" + }, + "properties": {}, + "effects": [] + }, + + { + "display_name": "Cheaper Uppercut 2", + "desc": "Reduce the Mana cost of Uppercut", + "archetype": "", + "archetype_req": 0, + "parents": ["Second Chance", "Brink of Madness"], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 6, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ] + }, + + { + "display_name": "Martyr", + "desc": "When you receive a fatal blow, all nearby allies become invincible", + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Second Chance"], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 35, + "col": 8, + "icon": "node_1" + }, + "properties": { + "duration": 3, + "aoe": 12 + }, + "effects": [] + } + ], +} + +const atree_example = [ + { + "title": "skill", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 5, + "col": 3, + }, + { + "image": "../media/atree/connect_angle.png", + "connector": true, + "rotate": 270, + "row": 4, + "col": 3, + }, + { + "title": "skill2", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 0, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 1, + "col": 2 + }, + { + "title": "skill3", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 2, + "col": 2 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 90, + "row": 2, + "col": 3 + }, + { + "title": "skill4", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 2, + "col": 4 + }, + { + "image": "../media/atree/connect_line.png", + "connector": true, + "rotate": 0, + "row": 3, + "col": 2 + }, + { + "title": "skill5", + "desc": "desc", + "image": "../media/atree/node.png", + "connector": false, + "row": 4, + "col": 2 + }, +]; diff --git a/js/atree_constants_str_old_min.js b/js/atree_constants_str_old_min.js new file mode 100644 index 0000000..73d3e29 --- /dev/null +++ b/js/atree_constants_str_old_min.js @@ -0,0 +1 @@ +const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4,icon:"node_4"},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4,icon:"node_1"},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4,icon:"node_4"},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2,icon:"node_4"},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6,icon:"node_4"},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0,icon:"node_1"},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4,icon:"node_1"},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6,icon:"node_1"},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8,icon:"node_1"},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2,icon:"node_3"},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4,icon:"node_1"},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7,icon:"node_3"},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1,icon:"node_3"},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5,icon:"node_1"},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:["Mantle of the Bovemists"],blockers:[],cost:1,display:{row:20,col:6,icon:"node_0"},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8,icon:"node_2"},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2,icon:"node_2"},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4,icon:"node_0"},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0,icon:"node_0"},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4,icon:"node_1"},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7,icon:"node_1"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1,icon:"node_2"},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8,icon:"node_2"},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2,icon:"node_1"},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5,icon:"node_2"},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2,icon:"node_3"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:32,col:5,icon:"node_1"},properties:{},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"},{type:"raw_stat",bonuses:[{type:"prop",abil_name:"Bash",name:"aoe",value:3}]}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:31,col:4,icon:"node_1"},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1,icon:"node_3"},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2,icon:"node_1"},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4,icon:"node_2"},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8,icon:"node_1"},properties:{duration:3,aoe:12},effects:[]}]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] \ No newline at end of file diff --git a/js/display_atree.js b/js/display_atree.js index d97ca36..bbdc014 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -17,7 +17,7 @@ function construct_AT(elem, tree) { atree_map = new Map(); atree_connectors_map = new Map() for (let i of tree) { - atree_map.set(i.display_name, {display: i.display, parents: i.parents, connectors: new Map(), active: false}); + atree_map.set(i.id, {display: i.display, parents: i.parents, connectors: new Map(), active: false}); } for (let i = 0; i < tree.length; i++) { @@ -27,7 +27,7 @@ function construct_AT(elem, tree) { let missing_rows = [node.display.row]; for (let parent of node.parents) { - missing_rows.push(tree.find(object => {return object.display_name === parent;}).display.row); + missing_rows.push(tree.find(object => {return object.id === parent;}).display.row); } for (let missing_row of missing_rows) { if (document.getElementById("atree-row-" + missing_row) == null) { @@ -57,7 +57,7 @@ function construct_AT(elem, tree) { // create connectors based on parent location for (let parent of node.parents) { - atree_map.get(node.display_name).connectors.set(parent, []); + atree_map.get(node.id).connectors.set(parent, []); let parent_node = atree_map.get(parent); @@ -67,8 +67,8 @@ function construct_AT(elem, tree) { for (let i = node.display.row - 1; i > parent_node.display.row; i--) { let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; - atree_map.get(node.display_name).connectors.get(parent).push(i + "," + node.display.col); - atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line", owner: [node.display_name, parent]}); + atree_map.get(node.id).connectors.get(parent).push(i + "," + node.display.col); + atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line", owner: [node.id, parent]}); resolve_connector(i + "," + node.display.col, node); } // connect horizontally @@ -78,8 +78,8 @@ function construct_AT(elem, tree) { let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; connector.classList.add("rotate-90"); - atree_map.get(node.display_name).connectors.get(parent).push(parent_node.display.row + "," + i); - atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line", owner: [node.display_name, parent]}); + atree_map.get(node.id).connectors.get(parent).push(parent_node.display.row + "," + i); + atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line", owner: [node.id, parent]}); resolve_connector(parent_node.display.row + "," + i, node); } @@ -88,8 +88,8 @@ function construct_AT(elem, tree) { if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) { let connector = connect_elem.cloneNode(); connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - atree_map.get(node.display_name).connectors.get(parent).push(parent_node.display.row + "," + node.display.col); - atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle", owner: [node.display_name, parent]}); + atree_map.get(node.id).connectors.get(parent).push(parent_node.display.row + "," + node.display.col); + atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle", owner: [node.id, parent]}); if (parent_node.display.col > node.display.col) { connector.classList.add("rotate-180"); } @@ -147,7 +147,7 @@ function construct_AT(elem, tree) { node_tooltip = active_tooltip.cloneNode(true); - active_tooltip.id = "atree-ab-" + node.display_name.replaceAll(" ", ""); + active_tooltip.id = "atree-ab-" + node.id; node_tooltip.style.position = "absolute"; node_tooltip.style.zIndex = "100"; @@ -157,7 +157,7 @@ function construct_AT(elem, tree) { node_elem.addEventListener('click', function(e) { if (e.target !== this) {return;}; - let tooltip = document.getElementById("atree-ab-" + node.display_name.replaceAll(" ", "")); + let tooltip = document.getElementById("atree-ab-" + node.id); if (tooltip.style.display == "block") { tooltip.style.display = "none"; this.classList.remove("atree-selected"); @@ -222,6 +222,7 @@ function atree_same_row(node) { // draw the connector onto the screen function atree_render_connection() { for (let i of atree_connectors_map.keys()) { + if (document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].children.length != 0) {continue;} if (atree_connectors_map.get(i).length != 0) { document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector); }; @@ -230,10 +231,10 @@ function atree_render_connection() { // toggle the state of a node. function atree_toggle_state(node) { - if (atree_map.get(node.display_name).active) { - atree_map.get(node.display_name).active = false; + if (atree_map.get(node.id).active) { + atree_map.get(node.id).active = false; } else { - atree_map.get(node.display_name).active = true; + atree_map.get(node.id).active = true; }; }; From 379ebb8224212d75b7f1c3fa0ef6b7b0db205d07 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 05:44:10 -0700 Subject: [PATCH 105/155] HOTFIX -- update "Original Value" skillpoint display --- js/builder_graph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index 928a5c3..3b390d7 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -721,6 +721,7 @@ class DisplayBuildWarningsNode extends ComputeNode { 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-base", "Original: " + base_totals[i]); setText(skp_order[i] + "-skp-assign", "Assign: " + assigned); setValue(skp_order[i] + "-skp", skillpoints[i]); let linebreak = document.createElement("br"); @@ -908,7 +909,6 @@ class SkillPointSetterNode extends ComputeNode { 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!!! From af3c76681bcace86bd7e28d31f93983e54a66058 Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 19:46:04 +0700 Subject: [PATCH 106/155] fix: cosmetic defect on tri connector --- js/display_atree.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/display_atree.js b/js/display_atree.js index f95d1a0..cb5a723 100644 --- a/js/display_atree.js +++ b/js/display_atree.js @@ -289,6 +289,7 @@ function atree_get_state(connector) { } else if (!connector_state[1]) { connector_state[1] = 0; } + continue; } if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) { if (state) { @@ -296,6 +297,7 @@ function atree_get_state(connector) { } else if (!connector_state[0]) { connector_state[0] = 0; } + continue; } if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) { if (state) { @@ -303,6 +305,7 @@ function atree_get_state(connector) { } else if (!connector_state[2]) { connector_state[2] = 0; } + continue; } if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { if (state) { @@ -310,6 +313,7 @@ function atree_get_state(connector) { } else if (!connector_state[3]) { connector_state[3] = 0; }; + continue; }; }; return connector_state; From 18524b44fa70ec8c62f80f9414cdd89b1a1dc6b4 Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 19:47:12 +0700 Subject: [PATCH 107/155] atree name to id json --- js/atree-ids.json | 137 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 js/atree-ids.json diff --git a/js/atree-ids.json b/js/atree-ids.json new file mode 100644 index 0000000..d04db13 --- /dev/null +++ b/js/atree-ids.json @@ -0,0 +1,137 @@ +{ + "Arrow Shield": 0, + "Escape": 1, + "Arrow Bomb": 2, + "Heart Shatter": 3, + "Fire Creep": 4, + "Bryophyte Roots": 5, + "Nimble String": 6, + "Arrow Storm": 7, + "Guardian Angels": 8, + "Windy Feet": 9, + "Basaltic Trap": 10, + "Windstorm": 11, + "Grappling Hook": 12, + "Implosion": 13, + "Twain's Arc": 14, + "Fierce Stomp": 15, + "Scorched Earth": 16, + "Leap": 17, + "Shocking Bomb": 18, + "Mana Trap": 19, + "Escape Artist": 20, + "Initiator": 21, + "Call of the Hound": 22, + "Arrow Hurricane": 23, + "Geyser Stomp": 24, + "Crepuscular Ray": 25, + "Grape Bomb": 26, + "Tangled Traps": 27, + "Snow Storm": 28, + "All-Seeing Panoptes": 29, + "Minefield": 30, + "Bow Proficiency I": 31, + "Cheaper Arrow Bomb": 32, + "Cheaper Arrow Storm": 33, + "Cheaper Escape": 34, + "Earth Mastery": 82, + "Thunder Mastery": 83, + "Water Mastery": 84, + "Air Mastery": 85, + "Fire Mastery": 86, + "More Shields": 40, + "Stormy Feet": 41, + "Refined Gunpowder": 42, + "More Traps": 43, + "Better Arrow Shield": 44, + "Better Leap": 45, + "Better Guardian Angels": 46, + "Cheaper Arrow Storm (2)": 47, + "Precise Shot": 48, + "Cheaper Arrow Shield": 49, + "Rocket Jump": 50, + "Cheaper Escape (2)": 51, + "Stronger Hook": 52, + "Cheaper Arrow Bomb (2)": 53, + "Bouncing Bomb": 54, + "Homing Shots": 55, + "Shrapnel Bomb": 56, + "Elusive": 57, + "Double Shots": 58, + "Triple Shots": 59, + "Power Shots": 60, + "Focus": 61, + "More Focus": 62, + "More Focus (2)": 63, + "Traveler": 64, + "Patient Hunter": 65, + "Stronger Patient Hunter": 66, + "Frenzy": 67, + "Phantom Ray": 68, + "Arrow Rain": 69, + "Decimator": 70, + "Bash": 71, + "Spear Proficiency 1": 72, + "Cheaper Bash": 73, + "Double Bash": 74, + "Charge": 75, + "Heavy Impact": 76, + "Vehement": 77, + "Tougher Skin": 78, + "Uppercut": 79, + "Cheaper Charge": 80, + "War Scream": 81, + "Quadruple Bash": 87, + "Fireworks": 88, + "Half-Moon Swipe": 89, + "Flyby Jab": 90, + "Flaming Uppercut": 91, + "Iron Lungs": 92, + "Generalist": 93, + "Counter": 94, + "Mantle of the Bovemists": 95, + "Bak'al's Grasp": 96, + "Spear Proficiency 2": 97, + "Cheaper Uppercut": 98, + "Aerodynamics": 99, + "Provoke": 100, + "Precise Strikes": 101, + "Air Shout": 102, + "Enraged Blow": 103, + "Flying Kick": 104, + "Stronger Mantle": 105, + "Manachism": 106, + "Boiling Blood": 107, + "Ragnarokkr": 108, + "Ambidextrous": 109, + "Burning Heart": 110, + "Stronger Bash": 111, + "Intoxicating Blood": 112, + "Comet": 113, + "Collide": 114, + "Rejuvenating Skin": 115, + "Uncontainable Corruption": 116, + "Radiant Devotee": 117, + "Whirlwind Strike": 118, + "Mythril Skin": 119, + "Armour Breaker": 120, + "Shield Strike": 121, + "Sparkling Hope": 122, + "Massive Bash": 123, + "Tempest": 124, + "Spirit of the Rabbit": 125, + "Massacre": 126, + "Axe Kick": 127, + "Radiance": 128, + "Cheaper Bash 2": 129, + "Cheaper War Scream": 130, + "Discombobulate": 131, + "Thunderclap": 132, + "Cyclone": 133, + "Second Chance": 134, + "Blood Pact": 135, + "Haemorrhage": 136, + "Brink of Madness": 137, + "Cheaper Uppercut 2": 138, + "Martyr": 139 +} \ No newline at end of file From a99f164a29ddf7dc9864aee030e99846cf1cf99c Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 06:13:05 -0700 Subject: [PATCH 108/155] Clean up js files rename display_atree -> atree remove d3_export --- builder/doc.html | 3 +-- builder/index.html | 2 +- js/{display_atree.js => atree.js} | 0 js/d3_export.js | 30 ------------------------------ js/damage_calc.js | 2 -- js/render_compute_graph.js | 29 +++++++++++++++++++++++++++++ 6 files changed, 31 insertions(+), 35 deletions(-) rename js/{display_atree.js => atree.js} (100%) delete mode 100644 js/d3_export.js diff --git a/builder/doc.html b/builder/doc.html index 23a0216..579bc38 100644 --- a/builder/doc.html +++ b/builder/doc.html @@ -1420,7 +1420,7 @@ - + @@ -1433,7 +1433,6 @@ savelink
- diff --git a/builder/index.html b/builder/index.html index cd2d180..2c3ceda 100644 --- a/builder/index.html +++ b/builder/index.html @@ -1422,7 +1422,7 @@ - + diff --git a/js/display_atree.js b/js/atree.js similarity index 100% rename from js/display_atree.js rename to js/atree.js diff --git a/js/d3_export.js b/js/d3_export.js deleted file mode 100644 index 4e92c07..0000000 --- a/js/d3_export.js +++ /dev/null @@ -1,30 +0,0 @@ -// http://bl.ocks.org/rokotyan/0556f8facbaf344507cdc45dc3622177 - -// Set-up the export button -function set_export_button(svg, button_id, output_id) { - d3.select('#'+button_id).on('click', function(){ - //get svg source. - var serializer = new XMLSerializer(); - var source = serializer.serializeToString(svg.node()); - console.log(source); - - source = source.replace(/^$/, ''); - //add name spaces. - if(!source.match(/^]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){ - source = source.replace(/^]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){ - source = source.replace(/^$/, ''); + //add name spaces. + if(!source.match(/^]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){ + source = source.replace(/^]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){ + source = source.replace(/^ Date: Sun, 26 Jun 2022 11:18:20 -0700 Subject: [PATCH 109/155] prevent node unrendering --- js/atree.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/atree.js b/js/atree.js index 2daa244..f70242d 100644 --- a/js/atree.js +++ b/js/atree.js @@ -207,7 +207,6 @@ function construct_AT(elem, tree) { if (tooltip.style.display == "block") { tooltip.style.display = "none"; this.classList.remove("atree-selected"); - this.style.backgroundImage = ''; document.getElementById("active_AP_cost").textContent = parseInt(document.getElementById("active_AP_cost").textContent) - node.cost; } else { From 8ff0afa685a85c90d93d0c564e5a58283c45edb2 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 16:49:35 -0700 Subject: [PATCH 110/155] Begin atree integration also don't re-render the entire thing every time something changes -- local updates --- js/atree.js | 455 +++++++++++++++++++++------------- js/atree_constants.js | 16 +- js/atree_constants_min.js | 2 +- js/atree_constants_str_old.js | 18 +- js/builder_graph.js | 17 +- 5 files changed, 299 insertions(+), 209 deletions(-) diff --git a/js/atree.js b/js/atree.js index cb5a723..eb221e7 100644 --- a/js/atree.js +++ b/js/atree.js @@ -1,108 +1,199 @@ -let atree_map; -let atree_head; -let atree_connectors_map; -let atree_active_connections = []; -function construct_AT(elem, tree) { +let abil_points_current; + +/** + * Update ability tree internal representation. (topologically sorted node list) + * + * Signature: AbilityTreeUpdateNode(build: Build) => ATree + */ +const atree_node = new (class extends ComputeNode { + constructor() { super('builder-atree-update'); } + + compute_func(input_map) { + if (input_map.size !== 1) { throw "AbilityTreeUpdateNode accepts exactly one input (build)"; } + const [build] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element + + const atree_raw = atrees[wep_to_class.get(build.weapon.statMap.get('type'))]; + if (!atree_raw) return null; + + let atree_map = new Map(); + let atree_head; + for (const i of atree_raw) { + atree_map.set(i.id, {children: [], node: i}); + if (i.parents.length == 0) { + // Assuming there is only one head. + atree_head = atree_map.get(i.id); + } + } + for (const i of atree_raw) { + let node = atree_map.get(i.id); + let parents = []; + for (const parent_id of node.node.parents) { + let parent_node = atree_map.get(parent_id); + parent_node.children.push(node); + parents.push(parent_node); + } + node.parents = parents; + } + + let atree_topo_sort = []; + topological_sort_tree(atree_head, atree_topo_sort, new Map()); + atree_topo_sort.reverse(); + return atree_topo_sort; + } +})(); + +/** + * Display ability tree from topologically sorted list. + * + * Signature: AbilityTreeRenderNode(atree: ATree) => null + */ +const atree_render = new (class extends ComputeNode { + constructor() { super('builder-atree-render'); this.fail_cb = true; } + + compute_func(input_map) { + if (input_map.size !== 1) { throw "AbilityTreeRenderNode accepts exactly one input (atree)"; } + const [atree] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element + //as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff. + // TODO: FIXME! this is a side effect of `px` based rendering. + if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) { + toggle_tab('atree-dropdown'); + toggleButton('toggle-atree'); + } + + //for some reason we have to cast to string + if (atree) { render_AT(document.getElementById("atree-ui"), atree); } + + if (document.getElementById("toggle-atree").classList.contains("toggleOn")) { + toggle_tab('atree-dropdown'); + toggleButton('toggle-atree'); + } + } +})(); + +atree_render.link_to(atree_node); + +/** + * Create a reverse topological sort of the tree in the result list. + * + * https://en.wikipedia.org/wiki/Topological_sorting + * @param tree: Root of tree to sort + * @param res: Result list (reverse topological order) + * @param mark_state: Bookkeeping. Call with empty Map() + */ +function topological_sort_tree(tree, res, mark_state) { + const state = mark_state.get(tree); + if (state === undefined) { + // unmarked. + mark_state.set(tree, false); // temporary mark + for (const child of tree.children) { + topological_sort_tree(child, res, mark_state); + } + mark_state.set(tree, true); // permanent mark + res.push(tree); + } + // these cases are not needed. Case 1 does nothing, case 2 should never happen. + // else if (state === true) { + // // permanent mark. + // return; + // } + // else if (state === false) { + // // temporary mark. + // } +} + +function render_AT(elem, tree) { console.log("constructing ability tree UI"); document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later elem.innerHTML = ""; //reset the atree in the DOM - - if (tree === undefined) {return false;} // add in the "Active" title to atree let active_row = document.createElement("div"); active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center"); - active_row.textContent = "Active:"; + active_row.textContent = "Active: 0/45 AP"; + abil_points_current = 0; document.getElementById("atree-active").appendChild(active_row); - atree_map = new Map(); - atree_connectors_map = new Map() - for (let i of tree) { - atree_map.set(i.id, {display: i.display, parents: i.parents, connectors: new Map(), active: false}); + let atree_map = new Map(); + let atree_connectors_map = new Map() + let max_row = 0; + for (const i of tree) { + atree_map.set(i.node.id, {node: i.node, connectors: new Map(), active: false}); + if (i.node.display.row > max_row) { + max_row = i.node.display.row; + } + } + // Copy graph structure. + for (const i of tree) { + let node_wrapper = atree_map.get(i.node.id); + node_wrapper.parents = []; + node_wrapper.children = []; + for (const parent of i.parents) { + node_wrapper.parents.push(atree_map.get(parent.node.id)); + } + for (const child of i.children) { + node_wrapper.children.push(atree_map.get(child.node.id)); + } } - for (let i = 0; i < tree.length; i++) { - let node = tree[i]; - - // create rows if not exist - let missing_rows = [node.display.row]; + // Setup grid. + for (let j = 0; j <= max_row; j++) { + let row = document.createElement('div'); + row.classList.add("row"); + row.id = "atree-row-" + j; + //was causing atree rows to be 0 height + // TODO: do this more dynamically + row.style.minHeight = elem.scrollWidth / 9 + "px"; + //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; - if (node.parents.length == 0) { - // Assuming there is only one head. - atree_head = node; - } - - for (let parent of node.parents) { - missing_rows.push(tree.find(object => {return object.id === parent;}).display.row); - } - for (let missing_row of missing_rows) { - if (document.getElementById("atree-row-" + missing_row) == null) { - for (let j = 0; j <= missing_row; j++) { - if (document.getElementById("atree-row-" + j) == null) { - let row = document.createElement('div'); - row.classList.add("row"); - row.id = "atree-row-" + j; - //was causing atree rows to be 0 height - row.style.minHeight = elem.scrollWidth / 9 + "px"; - //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; - - for (let k = 0; k < 9; k++) { - col = document.createElement('div'); - col.classList.add('col', 'px-0'); - col.style.minHeight = elem.scrollWidth / 9 + "px"; - row.appendChild(col); - - atree_connectors_map.set(j + "," + k, []) - }; - elem.appendChild(row); - }; - }; - }; + for (let k = 0; k < 9; k++) { + col = document.createElement('div'); + col.classList.add('col', 'px-0'); + col.style.minHeight = elem.scrollWidth / 9 + "px"; + row.appendChild(col); } + elem.appendChild(row); + } + for (const _node of tree) { + let node_wrap = atree_map.get(_node.node.id); + let node = _node.node; // create connectors based on parent location - for (let parent of node.parents) { - atree_map.get(node.id).connectors.set(parent, []); + for (let parent of node_wrap.parents) { + node_wrap.connectors.set(parent, []); - let parent_node = atree_map.get(parent); + let parent_node = parent.node; + const parent_id = parent_node.id; let connect_elem = document.createElement("div"); connect_elem.style = "background-size: cover; width: 100%; height: 100%;"; // connect up for (let i = node.display.row - 1; i > parent_node.display.row; i--) { let connector = connect_elem.cloneNode(); - connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; - atree_map.get(node.id).connectors.get(parent).push(i + "," + node.display.col); - atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line", owner: [node.id, parent]}); - resolve_connector(i + "," + node.display.col, node); + node_wrap.connectors.get(parent).push(i + "," + node.display.col); + resolve_connector(atree_connectors_map, i + "," + node.display.col, {connector: connector, connections: [0, 0, 1, 1]}); } // connect horizontally let min = Math.min(parent_node.display.col, node.display.col); let max = Math.max(parent_node.display.col, node.display.col); for (let i = min + 1; i < max; i++) { let connector = connect_elem.cloneNode(); - connector.style.backgroundImage = "url('../media/atree/connect_line.png')"; - connector.classList.add("rotate-90"); - atree_map.get(node.id).connectors.get(parent).push(parent_node.display.row + "," + i); - atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line", owner: [node.id, parent]}); - resolve_connector(parent_node.display.row + "," + i, node); + node_wrap.connectors.get(parent).push(parent_node.display.row + "," + i); + resolve_connector(atree_connectors_map, parent_node.display.row + "," + i, {connector: connector, connections: [1, 1, 0, 0]}); } // connect corners - if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) { let connector = connect_elem.cloneNode(); - connector.style.backgroundImage = "url('../media/atree/connect_angle.png')"; - atree_map.get(node.id).connectors.get(parent).push(parent_node.display.row + "," + node.display.col); - atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle", owner: [node.id, parent]}); + node_wrap.connectors.get(parent).push(parent_node.display.row + "," + node.display.col); + let connections = [0, 0, 0, 1]; if (parent_node.display.col > node.display.col) { - connector.classList.add("rotate-180"); + connections[1] = 1; } else {// if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) { - connector.classList.add("rotate-270"); + connections[0] = 1; } - resolve_connector(parent_node.display.row + "," + node.display.col, node); + resolve_connector(atree_connectors_map, parent_node.display.row + "," + node.display.col, {connector: connector, connections: connections}); } } @@ -167,81 +258,96 @@ function construct_AT(elem, tree) { if (tooltip.style.display == "block") { tooltip.style.display = "none"; this.classList.remove("atree-selected"); + abil_points_current -= node.cost; } else { tooltip.style.display = "block"; this.classList.add("atree-selected"); + abil_points_current += node.cost; }; - atree_toggle_state(node); - atree_update_connector(); + active_row.textContent = "Active: "+abil_points_current+"/45 AP"; + atree_toggle_state(atree_connectors_map, node_wrap); }); document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem); }; - - atree_render_connection(); + console.log(atree_connectors_map); + atree_render_connection(atree_connectors_map); }; // resolve connector conflict, when they occupy the same cell. -function resolve_connector(pos, node) { - if (atree_connectors_map.get(pos).length < 2) {return false;} - - let line = false; - let angle = false; - let t = false; - let owners = []; - for (let i of atree_connectors_map.get(pos)) { - if (i.type == "line") { - line = true; - } else if (i.type == "angle") { - angle = true; - } else if (i.type == "t") { - t = true; - } - owners = owners.concat(i.owner); +function resolve_connector(atree_connectors_map, pos, new_connector) { + if (!atree_connectors_map.has(pos)) { + atree_connectors_map.set(pos, new_connector); + return; } - - owners = [...new Set(owners)]; - - let connect_elem = document.createElement("div"); - - if ((line && angle)) { - connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;"; - atree_connectors_map.set(pos, [{connector: connect_elem, type: "t", owner: owners, connector_state: {up: 0, left: 0, right: 0, down: 0}}]); + let existing = atree_connectors_map.get(pos).connections; + for (let i = 0; i < 4; ++i) { + existing[i] += new_connector.connections[i]; } - if (node.parents.length == 3 && t && atree_same_row(node)) { - connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;"; - atree_connectors_map.set(pos, [{connector: connect_elem, type: "c", owner: owners, connector_state: {up: 0, left: 0, right: 0, down: 0}}]); - } - // override the conflict with the first children - atree_connectors_map.set(pos, [atree_connectors_map.get(pos)[0]]); - atree_connectors_map.get(pos)[0].owner = owners; } -// check if a node doesn't have same row w/ its parents (used to solve conflict) -function atree_same_row(node) { - for (let i of node.parents) { - if (node.display.row == atree_map.get(i).display.row) { return false; } - }; - return true; -}; +function set_connector_type(connector_info) { // left right up down + const connections = connector_info.connections; + const connector_elem = connector_info.connector; + if (connections[2]) { + if (connections[0]) { + connector_info.type = 'c'; // cross + return; + } + connector_info.type = 'line'; // vert line + return; + } + if (connections[3]) { // if down: + if (connections[0] && connections[1]) { + connector_info.type = 't'; // all 3 t + return; + } + connector_info.type = 'angle'; // elbow + if (connections[1]) { + connector_elem.classList.add("rotate-180"); + } + else { + connector_elem.classList.add("rotate-270"); + } + return; + } + connector_info.type = 'line'; // horiz line + connector_elem.classList.add("rotate-90"); +} // draw the connector onto the screen -function atree_render_connection() { +function atree_render_connection(atree_connectors_map) { for (let i of atree_connectors_map.keys()) { - if (document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].children.length != 0) {continue;} - if (atree_connectors_map.get(i).length != 0) { - document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector); - }; + let connector_info = atree_connectors_map.get(i); + let connector_elem = connector_info.connector; + set_connector_type(connector_info); + connector_elem.style.backgroundImage = "url('../media/atree/connect_"+connector_info.type+".png')"; + connector_info.highlight = [0, 0, 0, 0]; + console.log(i + ", " + connector_info.type); + let target_elem = document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]]; + if (target_elem.children.length != 0) { + // janky special case... + connector_elem.style.display = 'none'; + } + target_elem.appendChild(connector_elem); }; }; // toggle the state of a node. -function atree_toggle_state(node) { - if (atree_map.get(node.id).active) { - atree_map.get(node.id).active = false; - } else { - atree_map.get(node.id).active = true; - }; +function atree_toggle_state(atree_connectors_map, node_wrapper) { + let node = node_wrapper.node; + const new_state = !node_wrapper.active; + node_wrapper.active = new_state + for (const parent of node_wrapper.parents) { + if (parent.active) { + atree_set_edge(atree_connectors_map, parent, node_wrapper, new_state); // self->parent state only changes if parent is on + } + } + for (const child of node_wrapper.children) { + if (child.active) { + atree_set_edge(atree_connectors_map, node_wrapper, child, new_state); // Same logic as above. + } + } }; // refresh all connector to default state, then try to calculate the connector for all node @@ -256,72 +362,61 @@ function atree_update_connector() { }); } -// set the correct connector highlight for an active node, given a node. -function atree_compute_highlight(node) { - node.connectors.forEach((v, k) => { - if (node.active && atree_map.get(k).active) { - for (let i of v) { - connector_data = atree_connectors_map.get(i)[0]; - if (connector_data.type == "c" || connector_data.type == "t") { - connector_data.connector_state = atree_get_state(i); - let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type); - connector_data.connector.className = ""; - connector_data.connector.classList.add("rotate-" + connector_img.rotate); - connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + connector_img.attrib + ".png')"; - } else { - connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + ".png')"; - }; - }; - }; - }); -}; +function atree_set_edge(atree_connectors_map, parent, child, state) { + const connectors = child.connectors.get(parent); + const parent_row = parent.node.display.row; + const parent_col = parent.node.display.col; + const child_row = child.node.display.row; + const child_col = child.node.display.col; -// get the current active state of different directions, given a connector coordinate. -function atree_get_state(connector) { - let connector_state = [0, 0, 0, 0]; // left, right, up, down + let state_delta = (state ? 1 : -1); + let child_side_idx = (parent_col > child_col ? 0 : 1); + let parent_side_idx = 1 - child_side_idx; + for (const connector_label of connectors) { + let connector_info = atree_connectors_map.get(connector_label); + let connector_elem = connector_info.connector; + let highlight_state = connector_info.highlight; // left right up down + const ctype = connector_info.type; + if (ctype === 't' || ctype === 'c') { + // c, t + const [connector_row, connector_col] = connector_label.split(',').map(x => parseInt(x)); - for (let abil_name of atree_connectors_map.get(connector)[0].owner) { - - state = atree_map.get(abil_name).active; - if (atree_map.get(abil_name).display.col > parseInt(connector.split(",")[1])) { - if (state) { - connector_state[1] = 1; - } else if (!connector_state[1]) { - connector_state[1] = 0; + if (connector_row === parent_row) { + highlight_state[parent_side_idx] += state_delta; } + else { + highlight_state[2] += state_delta; // up connection guaranteed. + } + if (connector_col === child_col) { + highlight_state[3] += state_delta; + } + else { + highlight_state[child_side_idx] += state_delta; + } + + let render_state = highlight_state.map(x => (x > 0 ? 1 : 0)); + + let connector_img = atree_parse_connector(render_state, ctype); + connector_elem.className = ""; + connector_elem.classList.add("rotate-" + connector_img.rotate); + connector_elem.style.backgroundImage = connector_img.img; continue; } - if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) { - if (state) { - connector_state[0] = 1; - } else if (!connector_state[0]) { - connector_state[0] = 0; - } - continue; + // lol bad overloading, [0] is just the whole state + highlight_state[0] += state_delta; + if (highlight_state[0] > 0) { + connector_elem.style.backgroundImage = "url('../media/atree/highlight_"+ctype+".png')"; } - if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) { - if (state) { - connector_state[2] = 1; - } else if (!connector_state[2]) { - connector_state[2] = 0; - } - continue; + else { + connector_elem.style.backgroundImage = "url('../media/atree/connect_"+ctype+".png')"; } - if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) { - if (state) { - connector_state[3] = 1; - } else if (!connector_state[3]) { - connector_state[3] = 0; - }; - continue; - }; - }; - return connector_state; + } } // parse a sequence of left, right, up, down to appropriate connector image function atree_parse_connector(orient, type) { // left, right, up, down + let c_connector_dict = { "1100": {attrib: "_2_l", rotate: 0}, "1010": {attrib: "_2_a", rotate: 0}, @@ -346,11 +441,17 @@ function atree_parse_connector(orient, type) { let res = ""; for (let i of orient) { res += i; - }; + } + if (res === "0000") { + return {img: "url('../media/atree/connect_" + type + ".png')", rotate: 0}; + } + let ret; if (type == "c") { - return c_connector_dict[res]; + ret = c_connector_dict[res]; } else { - return t_connector_dict[res]; + ret = t_connector_dict[res]; }; + ret.img = "url('../media/atree/highlight_" + type + ret.attrib + ".png')"; + return ret; }; diff --git a/js/atree_constants.js b/js/atree_constants.js index 8e9ae98..2f01e7e 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -466,7 +466,7 @@ const atrees = { "display_name": "Windy Feet", "base_abil": "Escape", "desc": "When casting Escape, give speed to yourself and nearby allies.", - "archetype": "Boltslinger", + "archetype": "", "archetype_req": 0, "parents": [ 7 @@ -2193,7 +2193,7 @@ const atrees = { "output": { "type": "stat", "abil_name": "Focus", - "name": "dmgPct" + "name": "damMult" }, "scaling": [ 35 @@ -2228,7 +2228,7 @@ const atrees = { "output": { "type": "stat", "abil_name": "Focus", - "name": "dmgPct" + "name": "damMult" }, "scaling": [ 35 @@ -2263,7 +2263,7 @@ const atrees = { "output": { "type": "stat", "abil_name": "Focus", - "name": "dmgPct" + "name": "damMult" }, "scaling": [ 35 @@ -3805,12 +3805,12 @@ const atrees = { ], "output": { "type": "stat", - "name": "dmgPct" + "name": "damMult" }, "scaling": [ - 2 + 3 ], - "max": 200 + "max": 300 } ], "id": 103 @@ -4964,4 +4964,4 @@ const atrees = { "id": 139 } ] -} \ No newline at end of file +} diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js index f79809c..ac8955c 100644 --- a/js/atree_constants_min.js +++ b/js/atree_constants_min.js @@ -1 +1 @@ -const atrees={"Archer":[{"display_name":"Arrow Shield","desc":"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)","archetype":"","archetype_req":0,"parents":[60,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":6},"properties":{"duration":60},"effects":[{"type":"replace_spell","name":"Arrow Shield","cost":30,"display_text":"Max Damage","base_spell":4,"spell_type":"damage","scaling":"spell","display":"","parts":[{"name":"Shield Damage","type":"damage","multipliers":[90,0,0,0,0,10]},{"name":"Total Damage","type":"total","hits":{"Shield Damage":2}}]}],"id":0},{"display_name":"Escape","desc":"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":7,"col":4},"properties":{"aoe":0,"range":0},"effects":[{"type":"replace_spell","name":"Escape","cost":25,"display_text":"Max Damage","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":1},{"display_name":"Arrow Bomb","desc":"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4},"properties":{"aoe":4.5,"range":26},"effects":[{"type":"replace_spell","name":"Arrow Bomb","cost":50,"display_text":"Average Damage","base_spell":3,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Arrow Bomb","type":"damage","multipliers":[160,0,0,0,20,0]},{"name":"Total Damage","type":"total","hits":{"Arrow Bomb":1}}]}],"id":2},{"display_name":"Heart Shatter","desc":"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[100,0,0,0,0,0]},{}],"id":3},{"display_name":"Fire Creep","desc":"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.","archetype":"","archetype_req":0,"parents":[68,86,5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":16,"col":6},"properties":{"aoe":0.8,"duration":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[30,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fire Creep":15}}],"id":4},{"display_name":"Bryophyte Roots","desc":"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.","archetype":"Trapper","archetype_req":1,"parents":[4,82],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":16,"col":8},"properties":{"aoe":2,"duration":5,"slowness":0.4},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Bryophyte Roots","cost":0,"multipliers":[40,20,0,0,0,0]}],"id":5},{"display_name":"Nimble String","desc":"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.","archetype":"","archetype_req":0,"parents":[83,69],"dependencies":[7],"blockers":[68],"cost":2,"display":{"row":15,"col":2},"properties":{"shootspeed":2},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-15,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","cost":0,"hits":{"Single Arrow":8}}],"id":6},{"display_name":"Arrow Storm","desc":"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.","archetype":"","archetype_req":0,"parents":[58,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":2},"properties":{"aoe":0,"range":16},"effects":[{"type":"replace_spell","name":"Arrow Storm","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,10,0,0,0]},{"name":"Single Stream","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Stream":2}}]}],"id":7},{"display_name":"Guardian Angels","desc":"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)","archetype":"Boltslinger","archetype_req":3,"parents":[59,67],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":19,"col":1},"properties":{"range":4,"duration":60,"shots":8,"count":2},"effects":[{"type":"replace_spell","name":"Guardian Angels","cost":30,"display_text":"Total Damage Average","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[40,0,0,0,0,20]},{"name":"Single Bow","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Bow":2}}]}],"id":8},{"display_name":"Windy Feet","base_abil":"Escape","desc":"When casting Escape, give speed to yourself and nearby allies.","archetype":"Boltslinger","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":1},"properties":{"aoe":8,"duration":120},"type":"stat_bonus","bonuses":[{"type":"stat","name":"spd","value":20}],"id":9},{"display_name":"Basaltic Trap","desc":"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)","archetype":"Trapper","archetype_req":2,"parents":[5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":8},"properties":{"aoe":7,"traps":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[140,30,0,0,30,0]}],"id":10},{"display_name":"Windstorm","desc":"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.","archetype":"","archetype_req":0,"parents":[8,33],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":21,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-11,0,-7,0,0,3]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":1}}],"id":11},{"display_name":"Grappling Hook","base_abil":"Escape","desc":"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)","archetype":"Trapper","archetype_req":0,"parents":[61,40,33],"dependencies":[],"blockers":[20],"cost":2,"display":{"row":21,"col":5},"properties":{"range":20},"effects":[],"id":12},{"display_name":"Implosion","desc":"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.","archetype":"Trapper","archetype_req":0,"parents":[12,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":13},{"display_name":"Twain's Arc","desc":"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)","archetype":"Sharpshooter","archetype_req":4,"parents":[62,64],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":25,"col":4},"properties":{"range":64,"focusReq":2},"effects":[{"type":"replace_spell","name":"Twain's Arc","cost":0,"display_text":"Twain's Arc","base_spell":5,"spell_type":"damage","scaling":"melee","display":"Twain's Arc Damage","parts":[{"name":"Twain's Arc Damage","type":"damage","multipliers":[200,0,0,0,0,0]}]}],"id":14},{"display_name":"Fierce Stomp","desc":"When using Escape, hold shift to quickly drop down and deal damage.","archetype":"Boltslinger","archetype_req":0,"parents":[42,64],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":1},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[100,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":2,"target_part":"Total Damage","cost":0,"hits":{"Fierce Stomp":1}}],"id":15},{"display_name":"Scorched Earth","desc":"Fire Creep become much stronger.","archetype":"Sharpshooter","archetype_req":0,"parents":[14],"dependencies":[4],"blockers":[],"cost":1,"display":{"row":26,"col":5},"properties":{"duration":2,"aoe":0.4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[10,0,0,0,5,0]}],"id":16},{"display_name":"Leap","desc":"When you double tap jump, leap foward. (2s Cooldown)","archetype":"Boltslinger","archetype_req":5,"parents":[42,55],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0},"properties":{"cooldown":2},"effects":[],"id":17},{"display_name":"Shocking Bomb","desc":"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.","archetype":"Sharpshooter","archetype_req":5,"parents":[14,44,55],"dependencies":[2],"blockers":[],"cost":2,"display":{"row":28,"col":4},"properties":{"gravity":0},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"}],"id":18},{"display_name":"Mana Trap","desc":"Your Traps will give you 4 Mana per second when you stay close to them.","archetype":"Trapper","archetype_req":5,"parents":[43,44],"dependencies":[4],"blockers":[],"cost":2,"display":{"row":28,"col":8},"properties":{"range":12,"manaRegen":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":10,"multipliers":[0,0,0,0,0,0]}],"id":19},{"display_name":"Escape Artist","desc":"When casting Escape, release 100 arrows towards the ground.","archetype":"Boltslinger","archetype_req":0,"parents":[46,17],"dependencies":[],"blockers":[12],"cost":2,"display":{"row":31,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Escape Artist","cost":0,"multipliers":[30,0,10,0,0,0]}],"id":20},{"display_name":"Initiator","desc":"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.","archetype":"Sharpshooter","archetype_req":5,"parents":[18,44,47],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":31,"col":5},"properties":{"focus":1,"timer":5},"type":"stat_bonus","bonuses":[{"type":"stat","name":"damPct","value":50}],"id":21},{"display_name":"Call of the Hound","desc":"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.","archetype":"Trapper","archetype_req":0,"parents":[21,47],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":32,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Call of the Hound","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":22},{"display_name":"Arrow Hurricane","desc":"Arrow Storm will shoot +2 stream of arrows.","archetype":"Boltslinger","archetype_req":8,"parents":[48,20],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":33,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":2}}],"id":23},{"display_name":"Geyser Stomp","desc":"Fierce Stomp will create geysers, dealing more damage and vertical knockback.","archetype":"","archetype_req":0,"parents":[56],"dependencies":[15],"blockers":[],"cost":2,"display":{"row":37,"col":1},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[0,0,0,50,0,0]}],"id":24},{"display_name":"Crepuscular Ray","desc":"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.","archetype":"Sharpshooter","archetype_req":10,"parents":[49],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":37,"col":4},"properties":{"focusReq":5,"focusRegen":-1},"effects":[{"type":"replace_spell","name":"Crepuscular Ray","base_spell":5,"spell_type":"damage","scaling":"spell","display":"One Focus","cost":0,"parts":[{"name":"Single Arrow","type":"damage","multipliers":[10,0,0,5,0,0]},{"name":"One Focus","type":"total","hits":{"Single Arrow":20}},{"name":"Total Damage","type":"total","hits":{"One Focus":7}}]}],"id":25},{"display_name":"Grape Bomb","desc":"Arrow bomb will throw 3 additional smaller bombs when exploding.","archetype":"","archetype_req":0,"parents":[51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":37,"col":7},"properties":{"miniBombs":3,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Grape Bomb","cost":0,"multipliers":[30,0,0,0,10,0]}],"id":26},{"display_name":"Tangled Traps","desc":"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":38,"col":6},"properties":{"attackSpeed":0.2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Tangled Traps","cost":0,"multipliers":[20,0,0,0,0,20]}],"id":27},{"display_name":"Snow Storm","desc":"Enemies near you will be slowed down.","archetype":"","archetype_req":0,"parents":[24,63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":39,"col":2},"properties":{"range":2.5,"slowness":0.3},"id":28},{"display_name":"All-Seeing Panoptes","desc":"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.","archetype":"Boltslinger","archetype_req":11,"parents":[28],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":40,"col":1},"properties":{"range":10,"shots":5},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Arrow","cost":0,"multipliers":[0,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":5}}],"id":29},{"display_name":"Minefield","desc":"Allow you to place +6 Traps, but with reduced damage and range.","archetype":"Trapper","archetype_req":10,"parents":[26,53],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":40,"col":7},"properties":{"aoe":-2,"traps":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[-80,0,0,0,0,0]}],"id":30},{"display_name":"Bow Proficiency I","desc":"Improve your Main Attack's damage and range when using a bow.","archetype":"","archetype_req":0,"parents":[2],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":31},{"display_name":"Cheaper Arrow Bomb","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-10}],"id":32},{"display_name":"Cheaper Arrow Storm","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[12,11,61],"dependencies":[],"blockers":[],"cost":1,"display":{"row":21,"col":3},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":33},{"display_name":"Cheaper Escape","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[7,0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":34},{"display_name":"Earth Mastery","desc":"Increases your base damage from all Earth attacks","archetype":"Trapper","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":8},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":82},{"display_name":"Thunder Mastery","desc":"Increases your base damage from all Thunder attacks","archetype":"Boltslinger","archetype_req":0,"parents":[7,86,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":2},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":83},{"display_name":"Water Mastery","desc":"Increases your base damage from all Water attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[34,83,86],"dependencies":[],"blockers":[],"cost":1,"display":{"row":14,"col":4},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":84},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":0},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":85},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[83,0,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":6},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":86},{"display_name":"More Shields","desc":"Give +2 charges to Arrow Shield.","archetype":"","archetype_req":0,"parents":[12,10],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":21,"col":7},"properties":{"shieldCharges":2},"id":40},{"display_name":"Stormy Feet","desc":"Windy Feet will last longer and add more speed.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[9],"blockers":[],"cost":1,"display":{"row":23,"col":1},"properties":{"duration":60},"effects":[{"type":"stat_bonus","bonuses":[{"type":"stat","name":"spdPct","value":20}]}],"id":41},{"display_name":"Refined Gunpowder","desc":"Increase the damage of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[50,0,0,0,0,0]}],"id":42},{"display_name":"More Traps","desc":"Increase the maximum amount of active Traps you can have by +2.","archetype":"Trapper","archetype_req":10,"parents":[54],"dependencies":[10],"blockers":[],"cost":1,"display":{"row":26,"col":8},"properties":{"traps":2},"id":43},{"display_name":"Better Arrow Shield","desc":"Arrow Shield will gain additional area of effect, knockback and damage.","archetype":"Sharpshooter","archetype_req":0,"parents":[19,18,14],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":28,"col":6},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Shield","multipliers":[40,0,0,0,0,0]}],"id":44},{"display_name":"Better Leap","desc":"Reduce leap's cooldown by 1s.","archetype":"Boltslinger","archetype_req":0,"parents":[17,55],"dependencies":[17],"blockers":[],"cost":1,"display":{"row":29,"col":1},"properties":{"cooldown":-1},"id":45},{"display_name":"Better Guardian Angels","desc":"Your Guardian Angels can shoot +4 arrows before disappearing.","archetype":"Boltslinger","archetype_req":0,"parents":[20,55],"dependencies":[8],"blockers":[],"cost":1,"display":{"row":31,"col":2},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":4}}],"id":46},{"display_name":"Cheaper Arrow Storm (2)","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[21,19],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":8},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":47},{"display_name":"Precise Shot","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[46,49,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":2},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdCritPct","value":30}]}],"id":48},{"display_name":"Cheaper Arrow Shield","desc":"Reduce the Mana cost of Arrow Shield.","archetype":"","archetype_req":0,"parents":[48,21],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":49},{"display_name":"Rocket Jump","desc":"Arrow Bomb's self-damage will knockback you farther away.","archetype":"","archetype_req":0,"parents":[47,21],"dependencies":[2],"blockers":[],"cost":1,"display":{"row":33,"col":6},"properties":{},"id":50},{"display_name":"Cheaper Escape (2)","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[22,70],"dependencies":[],"blockers":[],"cost":1,"display":{"row":34,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":51},{"display_name":"Stronger Hook","desc":"Increase your Grappling Hook's range, speed and strength.","archetype":"Trapper","archetype_req":5,"parents":[51],"dependencies":[12],"blockers":[],"cost":1,"display":{"row":35,"col":8},"properties":{"range":8},"id":52},{"display_name":"Cheaper Arrow Bomb (2)","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[63,30],"dependencies":[],"blockers":[],"cost":1,"display":{"row":40,"col":5},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":53},{"display_name":"Bouncing Bomb","desc":"Arrow Bomb will bounce once when hitting a block or enemy","archetype":"","archetype_req":0,"parents":[40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":25,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Arrow Bomb":2}}],"id":54},{"display_name":"Homing Shots","desc":"Your Main Attack arrows will follow nearby enemies and not be affected by gravity","archetype":"","archetype_req":0,"parents":[17,18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2},"properties":{},"effects":[],"id":55},{"display_name":"Shrapnel Bomb","desc":"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area","archetype":"Boltslinger","archetype_req":8,"parents":[23,48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Shrapnel Bomb","cost":0,"multipliers":[40,0,0,0,20,0]}],"id":56},{"display_name":"Elusive","desc":"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)","archetype":"Boltslinger","archetype_req":0,"parents":[24],"dependencies":[],"blockers":[],"cost":2,"display":{"row":38,"col":0},"properties":{},"effects":[],"id":57},{"display_name":"Double Shots","desc":"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)","archetype":"Boltslinger","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[60],"cost":1,"display":{"row":7,"col":2},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":58},{"display_name":"Triple Shots","desc":"Triple Main Attack arrows, but they deal -20% damage per arrow","archetype":"Boltslinger","archetype_req":0,"parents":[69,67],"dependencies":[58],"blockers":[],"cost":1,"display":{"row":17,"col":0},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":59},{"display_name":"Power Shots","desc":"Main Attack arrows have increased speed and knockback","archetype":"Sharpshooter","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[58],"cost":1,"display":{"row":7,"col":6},"properties":{},"effects":[],"id":60},{"display_name":"Focus","desc":"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once","archetype":"Sharpshooter","archetype_req":2,"parents":[68],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"dmgPct"},"scaling":[35],"max":3}],"id":61},{"display_name":"More Focus","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[33,12],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"dmgPct"},"scaling":[35],"max":5}],"id":62},{"display_name":"More Focus (2)","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[25,28],"dependencies":[],"blockers":[],"cost":1,"display":{"row":39,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"dmgPct"},"scaling":[35],"max":7}],"id":63},{"display_name":"Traveler","desc":"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)","archetype":"","archetype_req":0,"parents":[42,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"spd"}],"output":{"type":"stat","name":"sdRaw"},"scaling":[1],"max":100}],"id":64},{"display_name":"Patient Hunter","desc":"Your Traps will deal +20% more damage for every second they are active (Max +80%)","archetype":"Trapper","archetype_req":0,"parents":[40],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":8},"properties":{"max":80},"effects":[],"id":65},{"display_name":"Stronger Patient Hunter","desc":"Add +80% Max Damage to Patient Hunter","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[65],"blockers":[],"cost":1,"display":{"row":38,"col":8},"properties":{"max":80},"effects":[],"id":66},{"display_name":"Frenzy","desc":"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second","archetype":"Boltslinger","archetype_req":0,"parents":[59,6],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"spd"},"scaling":[6],"max":200}],"id":67},{"display_name":"Phantom Ray","desc":"Condense Arrow Storm into a single ray that damages enemies 10 times per second","archetype":"Sharpshooter","archetype_req":0,"parents":[84,4],"dependencies":[7],"blockers":[11,6,23],"cost":2,"display":{"row":16,"col":4},"properties":{},"effects":[{"type":"replace_spell","name":"Phantom Ray","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[25,0,5,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Arrow":16}}]}],"id":68},{"display_name":"Arrow Rain","desc":"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies","archetype":"Trapper","archetype_req":0,"parents":[6,85],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":15,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Arrow Rain","cost":0,"multipliers":[120,0,0,0,0,80]}],"id":69},{"display_name":"Decimator","desc":"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)","archetype":"Sharpshooter","archetype_req":0,"parents":[49],"dependencies":[68],"blockers":[],"cost":1,"display":{"row":34,"col":5},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Phantom Ray hits","output":{"type":"stat","name":"PhRayDmg"},"scaling":10,"max":50}],"id":70}],"Warrior":[{"display_name":"Bash","desc":"Violently bash the ground, dealing high damage in a large area","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4,"icon":"node_4"},"properties":{"aoe":4,"range":3},"effects":[{"type":"replace_spell","name":"Bash","cost":45,"display_text":"Total Damage Average","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Hit","type":"damage","multipliers":[130,20,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Hit":1}}]}],"id":71},{"display_name":"Spear Proficiency 1","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[71],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":72},{"display_name":"Cheaper Bash","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[72],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-10}],"id":73},{"display_name":"Double Bash","desc":"Bash will hit a second time at a farther range","archetype":"","archetype_req":0,"parents":[72],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4,"icon":"node_1"},"properties":{"range":3},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"name":"Single Hit","value":1}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-50,0,0,0,0,0]}],"id":74},{"display_name":"Charge","desc":"Charge forward at high speed (hold shift to cancel)","archetype":"","archetype_req":0,"parents":[74],"dependencies":[],"blockers":[],"cost":1,"display":{"row":6,"col":4,"icon":"node_4"},"properties":{},"effects":[{"type":"replace_spell","name":"Charge","cost":25,"display_text":"Total Damage Average","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":75},{"display_name":"Heavy Impact","desc":"After using Charge, violently crash down into the ground and deal damage","archetype":"","archetype_req":0,"parents":[79],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":1,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Heavy Impact","cost":0,"multipliers":[100,0,0,0,0,0]}],"id":76},{"display_name":"Vehement","desc":"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)","archetype":"Fallen","archetype_req":0,"parents":[75],"dependencies":[],"blockers":[78],"cost":1,"display":{"row":6,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"mdPct"},{"type":"stat","name":"mdRaw"}],"output":{"type":"stat","name":"spd"},"scaling":[1,1],"max":20}],"id":77},{"display_name":"Tougher Skin","desc":"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)","archetype":"Paladin","archetype_req":0,"parents":[75],"dependencies":[],"blockers":[77],"cost":1,"display":{"row":6,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":"5"}]},{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hprRaw"},{"type":"stat","name":"hprPct"}],"output":{"type":"stat","name":"hpBonus"},"scaling":[10,10],"max":100}],"id":78},{"display_name":"Uppercut","desc":"Rocket enemies in the air and deal massive damage","archetype":"","archetype_req":0,"parents":[77],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":2,"icon":"node_4"},"properties":{"aoe":3,"range":5},"effects":[{"type":"replace_spell","name":"Uppercut","cost":45,"display_text":"Total Damage Average","base_spell":3,"spell_type":"damage","scaling":"spell","display":"total","parts":[{"name":"Uppercut","type":"damage","multipliers":[150,50,50,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Uppercut":1}}]}],"id":79},{"display_name":"Cheaper Charge","desc":"Reduce the Mana cost of Charge","archetype":"","archetype_req":0,"parents":[79,81],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":80},{"display_name":"War Scream","desc":"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies","archetype":"","archetype_req":0,"parents":[78],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":6,"icon":"node_4"},"properties":{"duration":30,"aoe":12,"defense_bonus":10},"effects":[{"type":"replace_spell","name":"War Scream","cost":35,"display_text":"War Scream","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage Average","parts":[{"name":"War Scream","type":"damage","multipliers":[50,0,0,0,50,0]}]}],"id":81},{"display_name":"Earth Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Fallen","archetype_req":0,"parents":[79],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":82},{"display_name":"Thunder Mastery","desc":"Increases base damage from all Thunder attacks","archetype":"Fallen","archetype_req":0,"parents":[79,85,80],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":83},{"display_name":"Water Mastery","desc":"Increases base damage from all Water attacks","archetype":"Battle Monk","archetype_req":0,"parents":[80,83,85],"dependencies":[],"blockers":[],"cost":1,"display":{"row":11,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":84},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[81,83,80],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":85},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Paladin","archetype_req":0,"parents":[81],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":86},{"display_name":"Quadruple Bash","desc":"Bash will hit 4 times at an even larger range","archetype":"Fallen","archetype_req":0,"parents":[82,88],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":0,"icon":"node_1"},"properties":{"range":6},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Hit":2}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-20,0,0,0,0,0]}],"id":87},{"display_name":"Fireworks","desc":"Mobs hit by Uppercut will explode mid-air and receive additional damage","archetype":"Fallen","archetype_req":0,"parents":[83,87],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fireworks","cost":0,"multipliers":[80,0,20,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fireworks":1}}],"id":88},{"display_name":"Half-Moon Swipe","desc":"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water","archetype":"Battle Monk","archetype_req":1,"parents":[84],"dependencies":[79],"blockers":[],"cost":2,"display":{"row":13,"col":4,"icon":"node_1"},"properties":{"range":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":-10,"multipliers":[-70,0,0,0,0,0]},{"type":"convert_spell_conv","target_part":"all","conversion":"water"}],"id":89},{"display_name":"Flyby Jab","desc":"Damage enemies in your way when using Charge","archetype":"","archetype_req":0,"parents":[85,91],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":6,"icon":"node_1"},"properties":{"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flyby Jab","cost":0,"multipliers":[20,0,0,0,0,40]}],"id":90},{"display_name":"Flaming Uppercut","desc":"Uppercut will light mobs on fire, dealing damage every 0.6 seconds","archetype":"Paladin","archetype_req":0,"parents":[86,90],"dependencies":[79],"blockers":[],"cost":2,"display":{"row":12,"col":8,"icon":"node_1"},"properties":{"duration":3,"tick":0.6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut","cost":0,"multipliers":[0,0,0,0,50,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut Total Damage","cost":0,"hits":{"Flaming Uppercut":5}},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Flaming Uppercut":5}}],"id":91},{"display_name":"Iron Lungs","desc":"War Scream deals more damage","archetype":"","archetype_req":0,"parents":[90,91],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"War Scream","cost":0,"multipliers":[30,0,0,0,0,30]}],"id":92},{"display_name":"Generalist","desc":"After casting 3 different spells in a row, your next spell will cost 5 mana","archetype":"Battle Monk","archetype_req":3,"parents":[94],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":2,"icon":"node_3"},"properties":{},"effects":[],"id":93},{"display_name":"Counter","desc":"When dodging a nearby enemy attack, get 30% chance to instantly attack back","archetype":"Battle Monk","archetype_req":0,"parents":[89],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":4,"icon":"node_1"},"properties":{"chance":30},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Counter","cost":0,"multipliers":[60,0,20,0,0,20]}],"id":94},{"display_name":"Mantle of the Bovemists","desc":"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)","archetype":"Paladin","archetype_req":3,"parents":[92],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":15,"col":7,"icon":"node_3"},"properties":{"mantle_charge":3},"effects":[],"id":95},{"display_name":"Bak'al's Grasp","desc":"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)","archetype":"Fallen","archetype_req":2,"parents":[87,88],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":16,"col":1,"icon":"node_3"},"properties":{"cooldown":15},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[4],"slider_step":2,"max":120}],"id":96},{"display_name":"Spear Proficiency 2","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[96,98],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":0,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":97},{"display_name":"Cheaper Uppercut","desc":"Reduce the Mana Cost of Uppercut","archetype":"","archetype_req":0,"parents":[97,99,94],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":98},{"display_name":"Aerodynamics","desc":"During Charge, you can steer and change direction","archetype":"Battle Monk","archetype_req":0,"parents":[98,100],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":5,"icon":"node_1"},"properties":{},"effects":[],"id":99},{"display_name":"Provoke","desc":"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream","archetype":"Paladin","archetype_req":0,"parents":[99,95],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":100},{"display_name":"Precise Strikes","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[98,97],"dependencies":[],"blockers":[],"cost":1,"display":{"row":18,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"critDmg","value":30}]}],"id":101},{"display_name":"Air Shout","desc":"War Scream will fire a projectile that can go through walls and deal damage multiple times","archetype":"","archetype_req":0,"parents":[99,100],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":18,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Air Shout","cost":0,"multipliers":[20,0,0,0,0,5]}],"id":102},{"display_name":"Enraged Blow","desc":"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)","archetype":"Fallen","archetype_req":0,"parents":[97],"dependencies":[96],"blockers":[],"cost":2,"display":{"row":20,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"dmgPct"},"scaling":[2],"max":200}],"id":103},{"display_name":"Flying Kick","desc":"When using Charge, mobs hit will halt your momentum and get knocked back","archetype":"Battle Monk","archetype_req":1,"parents":[98,105],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":3,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flying Kick","cost":0,"multipliers":[120,0,0,10,0,20]}],"id":104},{"display_name":"Stronger Mantle","desc":"Add +2 additional charges to Mantle of the Bovemists","archetype":"Paladin","archetype_req":0,"parents":[106,104],"dependencies":[95],"blockers":[],"cost":1,"display":{"row":20,"col":6,"icon":"node_0"},"properties":{"mantle_charge":2},"effects":[],"id":105},{"display_name":"Manachism","desc":"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)","archetype":"Paladin","archetype_req":3,"parents":[105,100],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":8,"icon":"node_2"},"properties":{"cooldown":1},"effects":[],"id":106},{"display_name":"Boiling Blood","desc":"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds","archetype":"","archetype_req":0,"parents":[103,108],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":0,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Boiling Blood","cost":0,"multipliers":[25,0,0,0,5,0]}],"id":107},{"display_name":"Ragnarokkr","desc":"War Scream become deafening, increasing its range and giving damage bonus to players","archetype":"Fallen","archetype_req":0,"parents":[107,104],"dependencies":[81],"blockers":[],"cost":2,"display":{"row":22,"col":2,"icon":"node_2"},"properties":{"damage_bonus":30,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":10}],"id":108},{"display_name":"Ambidextrous","desc":"Increase your chance to attack with Counter by +30%","archetype":"","archetype_req":0,"parents":[104,105,110],"dependencies":[94],"blockers":[],"cost":1,"display":{"row":22,"col":4,"icon":"node_0"},"properties":{"chance":30},"effects":[],"id":109},{"display_name":"Burning Heart","desc":"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)","archetype":"Paladin","archetype_req":0,"parents":[109,111],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"fDamPct"},"scaling":[2],"max":100,"slider_step":100}],"id":110},{"display_name":"Stronger Bash","desc":"Increase the damage of Bash","archetype":"","archetype_req":0,"parents":[110,106],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[30,0,0,0,0,0]}],"id":111},{"display_name":"Intoxicating Blood","desc":"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted","archetype":"Fallen","archetype_req":5,"parents":[108,107],"dependencies":[96],"blockers":[],"cost":2,"display":{"row":23,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":112},{"display_name":"Comet","desc":"After being hit by Fireworks, enemies will crash into the ground and receive more damage","archetype":"Fallen","archetype_req":0,"parents":[108],"dependencies":[88],"blockers":[],"cost":2,"display":{"row":24,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Comet","cost":0,"multipliers":[80,20,0,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Comet":1}}],"id":113},{"display_name":"Collide","desc":"Mobs thrown into walls from Flying Kick will explode and receive additonal damage","archetype":"Battle Monk","archetype_req":4,"parents":[109,110],"dependencies":[104],"blockers":[],"cost":2,"display":{"row":23,"col":5,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Collide","cost":0,"multipliers":[100,0,0,0,50,0]}],"id":114},{"display_name":"Rejuvenating Skin","desc":"Regain back 30% of the damage you take as healing over 30s","archetype":"Paladin","archetype_req":0,"parents":[110,111],"dependencies":[],"blockers":[],"cost":2,"display":{"row":23,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":115},{"display_name":"Uncontainable Corruption","desc":"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1","archetype":"","archetype_req":0,"parents":[107,117],"dependencies":[96],"blockers":[],"cost":1,"display":{"row":26,"col":0,"icon":"node_0"},"properties":{"cooldown":-5},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[1],"slider_step":2,"max":50}],"id":116},{"display_name":"Radiant Devotee","desc":"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)","archetype":"Battle Monk","archetype_req":1,"parents":[118,116],"dependencies":[],"blockers":[],"cost":1,"display":{"row":26,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","inputs":[{"type":"stat","name":"ref"}],"output":{"type":"stat","name":"mr"},"scaling":[1],"max":10,"slider_step":4}],"id":117},{"display_name":"Whirlwind Strike","desc":"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)","archetype":"Battle Monk","archetype_req":5,"parents":[109,117],"dependencies":[79],"blockers":[],"cost":2,"display":{"row":26,"col":4,"icon":"node_1"},"properties":{"range":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":0,"multipliers":[0,0,0,0,0,50]}],"id":118},{"display_name":"Mythril Skin","desc":"Gain +5% Base Resistance and become immune to knockback","archetype":"Paladin","archetype_req":6,"parents":[115],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":5}]}],"id":119},{"display_name":"Armour Breaker","desc":"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage","archetype":"Fallen","archetype_req":0,"parents":[116,117],"dependencies":[96],"blockers":[],"cost":2,"display":{"row":27,"col":1,"icon":"node_2"},"properties":{"duration":5},"effects":[],"id":120},{"display_name":"Shield Strike","desc":"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost","archetype":"Paladin","archetype_req":0,"parents":[119,122],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Shield Strike","cost":0,"multipliers":[60,0,20,0,0,0]}],"id":121},{"display_name":"Sparkling Hope","desc":"Everytime you heal 5% of your max health, deal damage to all nearby enemies","archetype":"Paladin","archetype_req":0,"parents":[119],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":8,"icon":"node_2"},"properties":{"aoe":6},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Sparkling Hope","cost":0,"multipliers":[10,0,5,0,0,0]}],"id":122},{"display_name":"Massive Bash","desc":"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)","archetype":"Fallen","archetype_req":8,"parents":[124,116],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"bashAoE"},"scaling":[1],"max":10,"slider_step":3}],"id":123},{"display_name":"Tempest","desc":"War Scream will ripple the ground and deal damage 3 times in a large area","archetype":"Battle Monk","archetype_req":0,"parents":[123,125],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2,"icon":"node_1"},"properties":{"aoe":16},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest","cost":"0","multipliers":[30,10,0,0,0,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest Total Damage","cost":"0","hits":{"Tempest":3}},{"type":"add_spell_prop","base_spell":4,"target_part":"Total Damage","cost":"0","hits":{"Tempest":3}}],"id":124},{"display_name":"Spirit of the Rabbit","desc":"Reduce the Mana cost of Charge and increase your Walk Speed by +20%","archetype":"Battle Monk","archetype_req":5,"parents":[124,118],"dependencies":[],"blockers":[],"cost":1,"display":{"row":28,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5},{"type":"raw_stat","bonuses":[{"type":"stat","name":"spd","value":20}]}],"id":125},{"display_name":"Massacre","desc":"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar","archetype":"Fallen","archetype_req":5,"parents":[124,123],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":126},{"display_name":"Axe Kick","desc":"Increase the damage of Uppercut, but also increase its mana cost","archetype":"","archetype_req":0,"parents":[124,125],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":10,"multipliers":[100,0,0,0,0,0]}],"id":127},{"display_name":"Radiance","desc":"Bash will buff your allies' positive IDs. (15s Cooldown)","archetype":"Paladin","archetype_req":2,"parents":[125,129],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":5,"icon":"node_2"},"properties":{"cooldown":15},"effects":[],"id":128},{"display_name":"Cheaper Bash 2","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[128,121,122],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":129},{"display_name":"Cheaper War Scream","desc":"Reduce the Mana cost of War Scream","archetype":"","archetype_req":0,"parents":[123],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":130},{"display_name":"Discombobulate","desc":"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second","archetype":"Battle Monk","archetype_req":12,"parents":[133],"dependencies":[],"blockers":[],"cost":2,"display":{"row":31,"col":2,"icon":"node_3"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"rainrawButDifferent"},"scaling":[2],"max":50}],"id":131},{"display_name":"Thunderclap","desc":"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder","archetype":"Battle Monk","archetype_req":8,"parents":[133],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":5,"icon":"node_1"},"properties":{},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"},{"type":"raw_stat","bonuses":[{"type":"prop","abil_name":"Bash","name":"aoe","value":3}]}],"id":132},{"display_name":"Cyclone","desc":"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s","archetype":"Battle Monk","archetype_req":0,"parents":[125],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":4,"icon":"node_1"},"properties":{"aoe":4,"duration":20},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone","cost":0,"multipliers":[10,0,0,0,5,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone Total Damage","cost":0,"hits":{"Cyclone":40}}],"id":133},{"display_name":"Second Chance","desc":"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)","archetype":"Paladin","archetype_req":12,"parents":[129],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":134},{"display_name":"Blood Pact","desc":"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)","archetype":"","archetype_req":10,"parents":[130],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1,"icon":"node_3"},"properties":{},"effects":[],"id":135},{"display_name":"Haemorrhage","desc":"Reduce Blood Pact's health cost. (0.5% health per mana)","archetype":"Fallen","archetype_req":0,"parents":[135],"dependencies":[135],"blockers":[],"cost":1,"display":{"row":35,"col":2,"icon":"node_1"},"properties":{},"effects":[],"id":136},{"display_name":"Brink of Madness","desc":"If your health is 25% full or less, gain +40% Resistance","archetype":"","archetype_req":0,"parents":[135,138],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":4,"icon":"node_2"},"properties":{},"effects":[],"id":137},{"display_name":"Cheaper Uppercut 2","desc":"Reduce the Mana cost of Uppercut","archetype":"","archetype_req":0,"parents":[134,137],"dependencies":[],"blockers":[],"cost":1,"display":{"row":35,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":138},{"display_name":"Martyr","desc":"When you receive a fatal blow, all nearby allies become invincible","archetype":"Paladin","archetype_req":0,"parents":[134],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":8,"icon":"node_1"},"properties":{"duration":3,"aoe":12},"effects":[],"id":139}]} \ No newline at end of file +const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:[60,34],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}],id:0},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:[3],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}],id:1},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}],id:2},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:[31],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}],id:3},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:[68,86,5],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}],id:4},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:[4,82],dependencies:[7],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}],id:5},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:[83,69],dependencies:[7],blockers:[68],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}],id:6},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:[58,34],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}],id:7},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:[59,67],dependencies:[0],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}],id:8},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"",archetype_req:0,parents:[7],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}],id:9},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:[5],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}],id:10},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:[8,33],dependencies:[],blockers:[68],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}],id:11},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:[61,40,33],dependencies:[],blockers:[20],cost:2,display:{row:21,col:5},properties:{range:20},effects:[],id:12},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:[12,40],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}],id:13},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:[62,64],dependencies:[61],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}],id:14},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:[42,64],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}],id:15},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:[14],dependencies:[4],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}],id:16},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:[42,55],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[],id:17},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:[14,44,55],dependencies:[2],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}],id:18},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:[43,44],dependencies:[4],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}],id:19},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:[46,17],dependencies:[],blockers:[12],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}],id:20},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:[18,44,47],dependencies:[61],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}],id:21},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:[21,47],dependencies:[0],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}],id:22},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:[48,20],dependencies:[],blockers:[68],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}],id:23},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:[56],dependencies:[15],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}],id:24},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:[49],dependencies:[7],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}],id:25},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:[51],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}],id:26},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:[26],dependencies:[10],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}],id:27},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:[24,63],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3},id:28},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:[28],dependencies:[8],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}],id:29},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:[26,53],dependencies:[10],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}],id:30},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:[2],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}],id:31},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:[31],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}],id:32},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:[12,11,61],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}],id:33},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:[7,0],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}],id:34},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:[0],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}],id:82},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:[7,86,34],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}],id:83},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:[34,83,86],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}],id:84},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:[7],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}],id:85},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:[83,0,34],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}],id:86},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:[12,10],dependencies:[0],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2},id:40},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:[11],dependencies:[9],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}],id:41},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:[11],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}],id:42},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:[54],dependencies:[10],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2},id:43},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:[19,18,14],dependencies:[0],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}],id:44},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:[17,55],dependencies:[17],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1},id:45},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:[20,55],dependencies:[8],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}],id:46},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:[21,19],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}],id:47},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:[46,49,23],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}],id:48},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:[48,21],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}],id:49},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:[47,21],dependencies:[2],blockers:[],cost:1,display:{row:33,col:6},properties:{},id:50},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:[22,70],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}],id:51},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:[51],dependencies:[12],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8},id:52},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:[63,30],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}],id:53},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:[40],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}],id:54},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:[17,18],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[],id:55},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:[23,48],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}],id:56},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:[24],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[],id:57},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:[1],dependencies:[],blockers:[60],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}],id:58},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:[69,67],dependencies:[58],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}],id:59},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:[1],dependencies:[],blockers:[58],cost:1,display:{row:7,col:6},properties:{},effects:[],id:60},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:[68],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"damMult"},scaling:[35],max:3}],id:61},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:[33,12],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"damMult"},scaling:[35],max:5}],id:62},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:[25,28],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"damMult"},scaling:[35],max:7}],id:63},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:[42,14],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}],id:64},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:[40],dependencies:[10],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[],id:65},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:[26],dependencies:[65],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[],id:66},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:[59,6],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}],id:67},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:[84,4],dependencies:[7],blockers:[11,6,23],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}],id:68},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:[6,85],dependencies:[0],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}],id:69},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:[49],dependencies:[68],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}],id:70}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4,icon:"node_4"},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}],id:71},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:[71],dependencies:[],blockers:[],cost:1,display:{row:2,col:4,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}],id:72},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:[72],dependencies:[],blockers:[],cost:1,display:{row:2,col:2,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}],id:73},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:[72],dependencies:[],blockers:[],cost:1,display:{row:4,col:4,icon:"node_1"},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}],id:74},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:[74],dependencies:[],blockers:[],cost:1,display:{row:6,col:4,icon:"node_4"},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}],id:75},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:[79],dependencies:[],blockers:[],cost:1,display:{row:9,col:1,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}],id:76},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:[75],dependencies:[],blockers:[78],cost:1,display:{row:6,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}],id:77},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:[75],dependencies:[],blockers:[77],cost:1,display:{row:6,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}],id:78},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:[77],dependencies:[],blockers:[],cost:1,display:{row:8,col:2,icon:"node_4"},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}],id:79},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:[79,81],dependencies:[],blockers:[],cost:1,display:{row:8,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}],id:80},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:[78],dependencies:[],blockers:[],cost:1,display:{row:8,col:6,icon:"node_4"},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}],id:81},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:[79],dependencies:[],blockers:[],cost:1,display:{row:10,col:0,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}],id:82},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:[79,85,80],dependencies:[],blockers:[],cost:1,display:{row:10,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}],id:83},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:[80,83,85],dependencies:[],blockers:[],cost:1,display:{row:11,col:4,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}],id:84},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:[81,83,80],dependencies:[],blockers:[],cost:1,display:{row:10,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}],id:85},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:[81],dependencies:[],blockers:[],cost:1,display:{row:10,col:8,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}],id:86},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:[82,88],dependencies:[],blockers:[],cost:2,display:{row:12,col:0,icon:"node_1"},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}],id:87},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:[83,87],dependencies:[],blockers:[],cost:2,display:{row:12,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}],id:88},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:[84],dependencies:[79],blockers:[],cost:2,display:{row:13,col:4,icon:"node_1"},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}],id:89},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:[85,91],dependencies:[],blockers:[],cost:2,display:{row:12,col:6,icon:"node_1"},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}],id:90},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:[86,90],dependencies:[79],blockers:[],cost:2,display:{row:12,col:8,icon:"node_1"},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}],id:91},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:[90,91],dependencies:[],blockers:[],cost:1,display:{row:13,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}],id:92},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:[94],dependencies:[],blockers:[],cost:2,display:{row:15,col:2,icon:"node_3"},properties:{},effects:[],id:93},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:[89],dependencies:[],blockers:[],cost:2,display:{row:15,col:4,icon:"node_1"},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}],id:94},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:[92],dependencies:[81],blockers:[],cost:2,display:{row:15,col:7,icon:"node_3"},properties:{mantle_charge:3},effects:[],id:95},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:[87,88],dependencies:[81],blockers:[],cost:2,display:{row:16,col:1,icon:"node_3"},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}],id:96},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:[96,98],dependencies:[],blockers:[],cost:1,display:{row:17,col:0,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}],id:97},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:[97,99,94],dependencies:[],blockers:[],cost:1,display:{row:17,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}],id:98},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:[98,100],dependencies:[],blockers:[],cost:2,display:{row:17,col:5,icon:"node_1"},properties:{},effects:[],id:99},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:[99,95],dependencies:[],blockers:[],cost:1,display:{row:17,col:7,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}],id:100},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:[98,97],dependencies:[],blockers:[],cost:1,display:{row:18,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}],id:101},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:[99,100],dependencies:[81],blockers:[],cost:2,display:{row:18,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}],id:102},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:[97],dependencies:[96],blockers:[],cost:2,display:{row:20,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"damMult"},scaling:[3],max:300}],id:103},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:[98,105],dependencies:[],blockers:[],cost:2,display:{row:20,col:3,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}],id:104},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:[106,104],dependencies:[95],blockers:[],cost:1,display:{row:20,col:6,icon:"node_0"},properties:{mantle_charge:2},effects:[],id:105},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:[105,100],dependencies:[],blockers:[],cost:2,display:{row:20,col:8,icon:"node_2"},properties:{cooldown:1},effects:[],id:106},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:[103,108],dependencies:[],blockers:[],cost:2,display:{row:22,col:0,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}],id:107},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:[107,104],dependencies:[81],blockers:[],cost:2,display:{row:22,col:2,icon:"node_2"},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}],id:108},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:[104,105,110],dependencies:[94],blockers:[],cost:1,display:{row:22,col:4,icon:"node_0"},properties:{chance:30},effects:[],id:109},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:[109,111],dependencies:[],blockers:[],cost:1,display:{row:22,col:6,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}],id:110},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:[110,106],dependencies:[],blockers:[],cost:1,display:{row:22,col:8,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}],id:111},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:[108,107],dependencies:[96],blockers:[],cost:2,display:{row:23,col:1,icon:"node_1"},properties:{},effects:[],id:112},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:[108],dependencies:[88],blockers:[],cost:2,display:{row:24,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}],id:113},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:[109,110],dependencies:[104],blockers:[],cost:2,display:{row:23,col:5,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}],id:114},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:[110,111],dependencies:[],blockers:[],cost:2,display:{row:23,col:7,icon:"node_3"},properties:{},effects:[],id:115},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:[107,117],dependencies:[96],blockers:[],cost:1,display:{row:26,col:0,icon:"node_0"},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}],id:116},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:[118,116],dependencies:[],blockers:[],cost:1,display:{row:26,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}],id:117},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:[109,117],dependencies:[79],blockers:[],cost:2,display:{row:26,col:4,icon:"node_1"},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}],id:118},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:[115],dependencies:[],blockers:[],cost:2,display:{row:26,col:7,icon:"node_1"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}],id:119},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:[116,117],dependencies:[96],blockers:[],cost:2,display:{row:27,col:1,icon:"node_2"},properties:{duration:5},effects:[],id:120},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:[119,122],dependencies:[],blockers:[],cost:2,display:{row:27,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}],id:121},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:[119],dependencies:[],blockers:[],cost:2,display:{row:27,col:8,icon:"node_2"},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}],id:122},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:[124,116],dependencies:[],blockers:[],cost:2,display:{row:28,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}],id:123},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:[123,125],dependencies:[],blockers:[],cost:2,display:{row:28,col:2,icon:"node_1"},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}],id:124},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:[124,118],dependencies:[],blockers:[],cost:1,display:{row:28,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}],id:125},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:[124,123],dependencies:[],blockers:[],cost:2,display:{row:29,col:1,icon:"node_1"},properties:{},effects:[],id:126},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:[124,125],dependencies:[],blockers:[],cost:1,display:{row:29,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}],id:127},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:[125,129],dependencies:[],blockers:[],cost:2,display:{row:29,col:5,icon:"node_2"},properties:{cooldown:15},effects:[],id:128},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:[128,121,122],dependencies:[],blockers:[],cost:1,display:{row:29,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}],id:129},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:[123],dependencies:[],blockers:[],cost:1,display:{row:31,col:0,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}],id:130},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:[133],dependencies:[],blockers:[],cost:2,display:{row:31,col:2,icon:"node_3"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}],id:131},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:[133],dependencies:[],blockers:[],cost:2,display:{row:32,col:5,icon:"node_1"},properties:{},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"},{type:"raw_stat",bonuses:[{type:"prop",abil_name:"Bash",name:"aoe",value:3}]}],id:132},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:[125],dependencies:[],blockers:[],cost:1,display:{row:31,col:4,icon:"node_1"},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}],id:133},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:[129],dependencies:[],blockers:[],cost:2,display:{row:32,col:7,icon:"node_3"},properties:{},effects:[],id:134},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:[130],dependencies:[],blockers:[],cost:2,display:{row:34,col:1,icon:"node_3"},properties:{},effects:[],id:135},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:[135],dependencies:[135],blockers:[],cost:1,display:{row:35,col:2,icon:"node_1"},properties:{},effects:[],id:136},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:[135,138],dependencies:[],blockers:[],cost:2,display:{row:35,col:4,icon:"node_2"},properties:{},effects:[],id:137},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:[134,137],dependencies:[],blockers:[],cost:1,display:{row:35,col:6,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}],id:138},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:[134],dependencies:[],blockers:[],cost:2,display:{row:35,col:8,icon:"node_1"},properties:{duration:3,aoe:12},effects:[],id:139}]} \ No newline at end of file diff --git a/js/atree_constants_str_old.js b/js/atree_constants_str_old.js index e0c7bc3..b604ecc 100644 --- a/js/atree_constants_str_old.js +++ b/js/atree_constants_str_old.js @@ -367,7 +367,7 @@ const atrees = "display_name": "Windy Feet", "base_abil": "Escape", "desc": "When casting Escape, give speed to yourself and nearby allies.", - "archetype": "Boltslinger", + "archetype": "", "archetype_req": 0, "parents": ["Arrow Storm"], "dependencies": [], @@ -1745,9 +1745,9 @@ const atrees = "output": { "type": "stat", "abil_name": "Focus", - "name": "dmgPct" + "name": "damMult" }, - "scaling": [35], + "scaling": [3], "max": 3 } ] @@ -1776,7 +1776,7 @@ const atrees = "output": { "type": "stat", "abil_name": "Focus", - "name": "dmgPct" + "name": "damMult" }, "scaling": [35], "max": 5 @@ -1807,7 +1807,7 @@ const atrees = "output": { "type": "stat", "abil_name": "Focus", - "name": "dmgPct" + "name": "damMult" }, "scaling": [35], "max": 7 @@ -3104,10 +3104,10 @@ const atrees = ], "output": { "type": "stat", - "name": "dmgPct" + "name": "damMult" }, - "scaling": [2], - "max": 200 + "scaling": [3], + "max": 300 } ] }, @@ -3870,7 +3870,7 @@ const atrees = "display_name": "Discombobulate", "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", "archetype": "Battle Monk", - "archetype_req": 12, + "archetype_req": 11, "parents": ["Cyclone"], "dependencies": [], "blockers": [], diff --git a/js/builder_graph.js b/js/builder_graph.js index 0098c11..4f6ac15 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -326,20 +326,6 @@ class WeaponInputDisplayNode extends ComputeNode { if (isNaN(dps)) dps = 0; } this.dps_field.textContent = Math.round(dps); - - //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'); - } } } @@ -1051,6 +1037,7 @@ function builder_graph_init() { } stat_agg_node.link_to(edit_agg_node); build_disp_node.link_to(stat_agg_node, 'stats'); + atree_node.link_to(build_node, 'build'); for (const input_node of item_nodes.concat(powder_nodes)) { input_node.update(); @@ -1092,6 +1079,8 @@ function builder_graph_init() { let skp_output = new SkillPointSetterNode(edit_input_nodes); skp_output.link_to(build_node); + + edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); let build_warnings_node = new DisplayBuildWarningsNode(); build_warnings_node.link_to(build_node, 'build'); From 8007a808897deb02c5b2a005e68fe46fda0ee58b Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 18:11:17 -0700 Subject: [PATCH 111/155] Spell schema comment --- js/damage_calc.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/js/damage_calc.js b/js/damage_calc.js index 495bb55..74619bb 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -173,6 +173,47 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno return [total_dam_norm, total_dam_crit, damages_results]; } +/* +Spell schema: + +spell: { + name: str internal string name for the spell. Unique identifier + cost: Optional[int] ignored for spells that are not id 1-4 + display_text: str short description of the spell, ex. Bash, Meteor, Arrow Shield + base_spell: int spell index. 0-4 are reserved (0 is melee, 1-4 is common 4 spells) + spell_type: str [TODO: DEPRECATED/REMOVE] "healing" or "damage" + scaling: str "melee" or "spell" + display: str "total" to sum all parts. Or, the name of a spell part + parts: List[part] Parts of this spell (different stuff the spell does basically) +} + +NOTE: when using `replace_spell` on an existing spell, all fields become optional. +Specified fields overwrite existing fields; unspecified fields are left unchanged. + + +There are three possible spell "part" types: damage, heal, and total. + +part: spell_damage | spell_heal | spell_total + +spell_damage: { + name: str != "total" Name of the part. + type: "damage" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields + multipliers: array[num, 6] floating point spellmults (though supposedly wynn only supports integer mults) +} +spell_heal: { + name: str != "total" Name of the part. + type: "heal" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields + power: num floating point healing power (1 is 100% of max hp). +} +spell_total: { + name: str != "total" Name of the part. + type: "total" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields + hits: Map[str, num] Keys are other part names, numbers are the multipliers. Undefined behavior if subparts + are not the same type of spell. Can only pull from spells defined before it. +} + +*/ + const spell_table = { "wand": [ { title: "Heal", cost: 6, parts: [ From 913b5a7c85d2a668303cbfc8e64bc2727e7d1fe9 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 18:23:07 -0700 Subject: [PATCH 112/155] Fix guardian angels spell mult --- js/atree_constants.js | 4 ++-- js/atree_constants_str_old.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 2f01e7e..b52508c 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -435,12 +435,12 @@ const atrees = { "name": "Single Arrow", "type": "damage", "multipliers": [ - 40, + 30, 0, 0, 0, 0, - 20 + 10 ] }, { diff --git a/js/atree_constants_str_old.js b/js/atree_constants_str_old.js index b604ecc..8c4d906 100644 --- a/js/atree_constants_str_old.js +++ b/js/atree_constants_str_old.js @@ -343,7 +343,7 @@ const atrees = { "name": "Single Arrow", "type": "damage", - "multipliers": [40, 0, 0, 0, 0, 20] + "multipliers": [30, 0, 0, 0, 0, 10] }, { "name": "Single Bow", From 581891116d502209f4af039508587627172b2310 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 20:24:38 -0700 Subject: [PATCH 113/155] Fix a compute graph state update bug (double link), fix powder special display --- js/builder_graph.js | 16 ++++++++-------- js/damage_calc.js | 30 ++++++++++++++++++++++++++++++ js/display.js | 5 +++-- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index 4f6ac15..d1825e2 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -833,14 +833,13 @@ class AggregateEditableIDNode extends ComputeNode { compute_func(input_map) { const build = input_map.get('build'); input_map.delete('build'); - const weapon = input_map.get('weapon'); input_map.delete('weapon'); const output_stats = new Map(build.statMap); for (const [k, v] of input_map.entries()) { output_stats.set(k, v); } - output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type"))); + output_stats.set('classDef', classDefenseMultipliers.get(build.weapon.statMap.get("type"))); return output_stats; } } @@ -946,6 +945,9 @@ let powder_nodes = []; let spelldmg_nodes = []; let edit_input_nodes = []; let skp_inputs = []; +let build_node; +let stat_agg_node; +let edit_agg_node; function builder_graph_init() { // Phase 1/2: Set up item input, propagate updates, etc. @@ -980,7 +982,7 @@ function builder_graph_init() { 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. - let build_node = new BuildAssembleNode(); + build_node = new BuildAssembleNode(); for (const input of item_nodes) { build_node.link_to(input); } @@ -1011,9 +1013,9 @@ function builder_graph_init() { build_disp_node.link_to(build_node, 'build'); // 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) - let stat_agg_node = new AggregateStatsNode(); - let edit_agg_node = new AggregateEditableIDNode(); - edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); + stat_agg_node = new AggregateStatsNode(); + edit_agg_node = new AggregateEditableIDNode(); + edit_agg_node.link_to(build_node, 'build'); for (const field of editable_item_fields) { // Create nodes that listens to each editable id input, the node name should match the "id" const elem = document.getElementById(field); @@ -1079,8 +1081,6 @@ function builder_graph_init() { let skp_output = new SkillPointSetterNode(edit_input_nodes); skp_output.link_to(build_node); - - edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon'); let build_warnings_node = new DisplayBuildWarningsNode(); build_warnings_node.link_to(build_node, 'build'); diff --git a/js/damage_calc.js b/js/damage_calc.js index 74619bb..2195926 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -214,6 +214,36 @@ spell_total: { */ +const default_spells = { + wand: [{ + name: "Melee", // TODO: name for melee attacks? + display_text: "Mage basic attack", + base_spell: 0, // Spell 0 is special cased to be handled with melee logic. + spell_type: "damage", + scaling: "melee", + display: "total", + parts: { name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] } + }], + spear: [{ + name: "Melee", // TODO: name for melee attacks? + display_text: "Warrior basic attack", + base_spell: 0, // Spell 0 is special cased to be handled with melee logic. + spell_type: "damage", + scaling: "melee", + display: "total", + parts: { name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] } + }], + bow: [ + + ], + dagger: [ + + ], + relik: [ + + ], +} + const spell_table = { "wand": [ { title: "Heal", cost: 6, parts: [ diff --git a/js/display.js b/js/display.js index a3edcf9..a47667d 100644 --- a/js/display.js +++ b/js/display.js @@ -1476,9 +1476,10 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa let tmp_conv = []; for (let i in part.conversion) { - tmp_conv.push(part.conversion[i] * part.multiplier[power-1]); + tmp_conv.push(part.conversion[i] * part.multiplier[power-1] / 100); } - let _results = calculateSpellDamage(stats, weapon, tmp_conv, false); + console.log(tmp_conv); + let _results = calculateSpellDamage(stats, weapon, tmp_conv, false, true); let critChance = skillPointsToPercentage(skillpoints[1]); let save_damages = []; From c7fd1c53f8ed1d9e33ac70c7fb0894b6b5321156 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 23:42:00 -0700 Subject: [PATCH 114/155] Internal spell representation overhaul Using new (more expressive) schema Somehow made display code cleaner !!! --- js/builder_graph.js | 87 ++++++++++++++++++++++++------ js/computation_graph.js | 23 ++++++++ js/damage_calc.js | 107 ++++++++++++++++++++++++------------ js/display.js | 116 ++++++++++++++++++---------------------- 4 files changed, 218 insertions(+), 115 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index d1825e2..b8b4833 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -468,6 +468,7 @@ class PowderInputNode extends InputNode { * Signature: SpellSelectNode(build: Build) => [Spell, SpellParts] */ class SpellSelectNode extends ComputeNode { + // TODO: rewrite me entirely... constructor(spell_num) { super("builder-spell"+spell_num+"-select"); this.spell_idx = spell_num; @@ -475,10 +476,18 @@ class SpellSelectNode extends ComputeNode { compute_func(input_map) { const build = input_map.get('build'); + let stats = build.statMap; const i = this.spell_idx; - let spell = spell_table[build.weapon.statMap.get("type")][i]; - let stats = build.statMap; + const spells = default_spells[build.weapon.statMap.get("type")]; + let spell; + for (const _spell of spells) { + if (_spell.base_spell === i) { + spell = _spell; + break; + } + } + if (spell === undefined) { return null; } let spell_parts; if (spell.parts) { @@ -551,6 +560,7 @@ class SpellDamageCalcNode extends ComputeNode { compute_func(input_map) { const weapon = input_map.get('build').weapon.statMap; const spell_info = input_map.get('spell-info'); + const spell = spell_info[0]; const spell_parts = spell_info[1]; const stats = input_map.get('stats'); const damage_mult = stats.get('damageMultiplier'); @@ -562,23 +572,66 @@ class SpellDamageCalcNode extends ComputeNode { stats.get('agi') ]; let spell_results = [] + 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); + // TODO: move preprocessing to separate node/node chain for (const part of spell_parts) { - if (part.type === "damage") { - let tmp_conv = []; - for (let i in part.conversion) { - tmp_conv.push(part.conversion[i] * part.multiplier/100); + let spell_result; + if ('multipliers' in part) { // damage type spell + let results = calculateSpellDamage(stats, weapon, part.multipliers, use_spell, !use_speed); + spell_result = { + type: "damage", + normal_min: results[2].map(x => x[0]), + normal_max: results[2].map(x => x[1]), + normal_total: results[0], + crit_min: results[2].map(x => x[2]), + crit_max: results[2].map(x => x[3]), + crit_total: results[1], } - let results = calculateSpellDamage(stats, weapon, tmp_conv, true); - spell_results.push(results); - } else if (part.type === "heal") { + } else if ('power' in part) { // TODO: wynn2 formula - 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); - spell_results.push(heal_amount); - } else if (part.type === "total") { - // TODO: remove "total" type - spell_results.push(null); + 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); + spell_result = { + type: "heal", + heal_amount: _heal_amount + } + } else if ('hits' in part) { + 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] + } + 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 (spell_result.type) { + if (subpart.type !== spell_result.type) { + throw "SpellCalc total subpart type mismatch"; + } + } + else { + spell_result.type = subpart.type; + } + if (spell_result.type === 'damage') { + for (const key of dam_res_keys) { + for (let i in spell_result.normal_min) { + spell_result[key][i] += subpart[key][i] * hits; + } + } + } + else { + spell_result.heal_amount += subpart.heal_amount; + } + } } + spell_result.name = part.name; + spell_results.push(spell_result); + spell_result_map.set(part.name, spell_result); } return spell_results; } @@ -604,12 +657,11 @@ class SpellDisplayNode extends ComputeNode { 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]; const i = this.spell_idx; let parent_elem = document.getElementById("spell"+i+"-info"); let overallparent_elem = document.getElementById("spell"+i+"-infoAvg"); - displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, spell_parts, damages); + displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, damages); } } @@ -1059,7 +1111,8 @@ function builder_graph_init() { // Also do something similar for skill points - for (let i = 0; i < 4; ++i) { + //for (let i = 0; i < 4; ++i) { + for (let i = 0; i < 1; ++i) { let spell_node = new SpellSelectNode(i); spell_node.link_to(build_node, 'build'); // TODO: link and rewrite spell_node to the stat agg node diff --git a/js/computation_graph.js b/js/computation_graph.js index 76b1c2d..a3903eb 100644 --- a/js/computation_graph.js +++ b/js/computation_graph.js @@ -89,6 +89,9 @@ class ComputeNode { throw "no compute func specified"; } + /** + * Add link to a parent compute node, optionally with an alias. + */ link_to(parent_node, link_name) { this.inputs.push(parent_node) link_name = (link_name !== undefined) ? link_name : parent_node.name; @@ -100,6 +103,26 @@ class ComputeNode { parent_node.children.push(this); return this; } + + /** + * Delete a link to a parent node. + * TODO: time complexity of list deletion (not super relevant but it hurts my soul) + */ + remove_link(parent_node) { + const idx = this.inputs.indexOf(parent_node); // Get idx + this.inputs.splice(idx, 1); // remove element + + this.input_translations.delete(parent_node.name); + const was_dirty = this.inputs_dirty.get(parent_node.name); + this.inputs_dirty.delete(parent_node.name); + if (was_dirty) { + this.inputs_dirty_count -= 1; + } + + const idx2 = parent_node.children.indexOf(this); + parent_node.children.splice(idx2, 1); + return this; + } } /** diff --git a/js/damage_calc.js b/js/damage_calc.js index 2195926..2710ec4 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -56,11 +56,14 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno // 2.2. Next, apply elemental conversions using damage computed in step 1.1. // Also, track which elements are present. (Add onto those present in the weapon itself.) + let total_convert = 0; //TODO get confirmation that this is how raw works. for (let i = 1; i <= 5; ++i) { if (conversions[i] > 0) { - damages[i][0] += conversions[i]/100 * weapon_min; - damages[i][1] += conversions[i]/100 * weapon_max; + const conv_frac = conversions[i]/100; + damages[i][0] += conv_frac * weapon_min; + damages[i][1] += conf_frac * weapon_max; present[i] = true; + total_convert += conv_frac } } @@ -125,22 +128,22 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw'); } // Next, rainraw and propRaw - let new_min = damages_obj[0] + raw_boost; - let new_max = damages_obj[1] + raw_boost; + let min_boost = raw_boost; + let max_boost = raw_boost; if (total_max > 0) { // TODO: what about total negative all raw? if (total_elem_min > 0) { - new_min += (damages_obj[0] / total_min) * prop_raw; + min_boost += (damages_obj[0] / total_min) * prop_raw; } - new_max += (damages_obj[1] / total_max) * prop_raw; + max_boost += (damages_obj[1] / total_max) * prop_raw; } if (i != 0 && total_elem_max > 0) { // rainraw TODO above if (total_elem_min > 0) { - new_min += (damages_obj[0] / total_elem_min) * rainbow_raw; + min_boost += (damages_obj[0] / total_elem_min) * rainbow_raw; } - new_max += (damages_obj[1] / total_elem_max) * rainbow_raw; + max_boost += (damages_obj[1] / total_elem_max) * rainbow_raw; } - damages_obj[0] = new_min; - damages_obj[1] = new_max; + damages_obj[0] += min_boost * total_convert; + damages_obj[1] += max_boost * total_convert; } // 6. Strength boosters @@ -182,8 +185,9 @@ spell: { display_text: str short description of the spell, ex. Bash, Meteor, Arrow Shield base_spell: int spell index. 0-4 are reserved (0 is melee, 1-4 is common 4 spells) spell_type: str [TODO: DEPRECATED/REMOVE] "healing" or "damage" - scaling: str "melee" or "spell" - display: str "total" to sum all parts. Or, the name of a spell part + scaling: Optional[str] [DEFAULT: "spell"] "melee" or "spell" + use_atkspd: Optional[bool] [DEFAULT: true] true to factor attack speed, false otherwise. + display: Optional[str] [DEFAULT: "total"] "total" to sum all parts. Or, the name of a spell part parts: List[part] Parts of this spell (different stuff the spell does basically) } @@ -212,37 +216,74 @@ spell_total: { are not the same type of spell. Can only pull from spells defined before it. } + +Before passing to display, use the following structs. +NOTE: total is collapsed into damage or healing. + +spell_damage: { + type: "damage" Internal use + name: str Display name of part. Should be human readable + normal_min: array[num, 6] floating point damages (no crit, min), can be less than zero. Order: NETWFA + normal_max: array[num, 6] floating point damages (no crit, max) + normal_total: array[num, 2] (min, max) noncrit total damage (not negative) + crit_min: array[num, 6] floating point damages (crit, min), can be less than zero. Order: NETWFA + crit_max: array[num, 6] floating point damages (crit, max) + crit_total: array[num, 2] (min, max) crit total damage (not negative) +} +spell_heal: { + type: "heal" Internal use + name: str Display name of part. Should be human readable + heal_amount: num floating point HP healed (self) +} + */ const default_spells = { wand: [{ - name: "Melee", // TODO: name for melee attacks? + name: "Magic Strike", // TODO: name for melee attacks? display_text: "Mage basic attack", - base_spell: 0, // Spell 0 is special cased to be handled with melee logic. - spell_type: "damage", - scaling: "melee", - display: "total", - parts: { name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] } + base_spell: 0, + scaling: "melee", use_atkspd: false, + display: "Melee", + parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }] }], spear: [{ name: "Melee", // TODO: name for melee attacks? display_text: "Warrior basic attack", - base_spell: 0, // Spell 0 is special cased to be handled with melee logic. - spell_type: "damage", - scaling: "melee", - display: "total", - parts: { name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] } + base_spell: 0, + scaling: "melee", use_atkspd: false, + display: "Melee", + parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }] }], - bow: [ - - ], - dagger: [ - - ], - relik: [ - - ], -} + bow: [{ + name: "Bow Shot", // TODO: name for melee attacks? + display_text: "Archer basic attack", + base_spell: 0, + scaling: "melee", use_atkspd: false, + display: "Melee", + parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }] + }], + dagger: [{ + name: "Melee", // TODO: name for melee attacks? + display_text: "Assassin basic attack", + base_spell: 0, + scaling: "melee", use_atkspd: false, + display: "Melee", + parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }] + }], + relik: [{ + name: "Spread Beam", // TODO: name for melee attacks? + display_text: "Shaman basic attack", + base_spell: 0, + spell_type: "damage", + scaling: "melee", use_atkspd: false, + display: "Total", + parts: [ + { name: "Single Beam", multipliers: [33, 0, 0, 0, 0, 0] }, + { name: "Total", hits: { "Single Beam": 3 } } + ] + }] +}; const spell_table = { "wand": [ diff --git a/js/display.js b/js/display.js index a47667d..e4114ed 100644 --- a/js/display.js +++ b/js/display.js @@ -1579,7 +1579,7 @@ function getBaseSpellCost(stats, spellIdx, cost) { } -function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_parts, damages) { +function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_results) { // TODO: remove spellIdx (just used to flag melee and cost) // TODO: move cost calc out parent_elem.textContent = ""; @@ -1589,7 +1589,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell overallparent_elem.textContent = ""; let title_elemavg = document.createElement("b"); - if (spellIdx != 0) { + if ('cost' in spell) { let first = document.createElement("span"); first.textContent = spell.title + " ("; title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here. @@ -1611,44 +1611,36 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell title_elemavg.appendChild(third_summary); } else { - title_elem.textContent = spell.title; - title_elemavg.textContent = spell.title; + title_elem.textContent = spell.name; + title_elemavg.textContent = spell.name; } parent_elem.append(title_elem); overallparent_elem.append(title_elemavg); - overallparent_elem.append(displayNextCosts(stats, spell, spellIdx)); + if ('cost' in spell) { + overallparent_elem.append(displayNextCosts(stats, spell, spellIdx)); + } let critChance = skillPointsToPercentage(stats.get('dex')); - let save_damages = []; - let part_divavg = document.createElement("p"); overallparent_elem.append(part_divavg); - for (let i = 0; i < spell_parts.length; ++i) { - const part = spell_parts[i]; - const damage = damages[i]; + for (let i = 0; i < spell_results.length; ++i) { + const damage_info = spell_results[i]; let part_div = document.createElement("p"); parent_elem.append(part_div); let subtitle_elem = document.createElement("p"); - subtitle_elem.textContent = part.subtitle; + subtitle_elem.textContent = damage_info.name part_div.append(subtitle_elem); - if (part.type === "damage") { - let _results = damage; - let totalDamNormal = _results[0]; - let totalDamCrit = _results[1]; - let results = _results[2]; - - for (let i = 0; i < 6; ++i) { - for (let j in results[i]) { - results[i][j] = results[i][j].toFixed(2); - } - } + if (damage_info.type === "damage") { + let totalDamNormal = damage_info.normal_total; + let totalDamCrit = damage_info.crit_total; + let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0; @@ -1659,11 +1651,11 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell part_div.append(averageLabel); - if (part.summary == true) { + if (damage_info.name === spell.display) { let overallaverageLabel = document.createElement("p"); let first = document.createElement("span"); let second = document.createElement("span"); - first.textContent = part.subtitle + " Average: "; + first.textContent = damage_info.name+ " Average: "; second.textContent = averageDamage.toFixed(2); overallaverageLabel.appendChild(first); overallaverageLabel.appendChild(second); @@ -1671,71 +1663,65 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell part_divavg.append(overallaverageLabel); } - function _damage_display(label_text, average, result_idx) { + function _damage_display(label_text, average, dmg_min, dmg_max) { let label = document.createElement("p"); label.textContent = label_text+average.toFixed(2); part_div.append(label); - let arrmin = []; - let arrmax = []; for (let i = 0; i < 6; i++){ - if (results[i][1] != 0){ + if (dmg_max[i] != 0){ let p = document.createElement("p"); p.classList.add(damageClasses[i]); - p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1]; - arrmin.push(results[i][result_idx]); - arrmax.push(results[i][result_idx + 1]); + p.textContent = dmg_min[i].toFixed(2)+" \u2013 "+dmg_max[i].toFixed(2); part_div.append(p); } } } - _damage_display("Non-Crit Average: ", nonCritAverage, 0); - _damage_display("Crit Average: ", critAverage, 2); - - save_damages.push(averageDamage); - } else if (part.type === "heal") { - let heal_amount = damage; + _damage_display("Non-Crit Average: ", nonCritAverage, damage_info.normal_min, damage_info.normal_max); + _damage_display("Crit Average: ", critAverage, damage_info.crit_min, damage_info.crit_max); + } else if (damage_info.type === "heal") { + let heal_amount = damage_info.heal_amount; let healLabel = document.createElement("p"); healLabel.textContent = heal_amount; // healLabel.classList.add("damagep"); part_div.append(healLabel); - if (part.summary == true) { + if (damage_info.name === spell.display) { let overallhealLabel = document.createElement("p"); let first = document.createElement("span"); let second = document.createElement("span"); - first.textContent = part.subtitle + ": "; + first.textContent = damage_info.name+ ": "; second.textContent = heal_amount; overallhealLabel.appendChild(first); second.classList.add("Set"); overallhealLabel.appendChild(second); part_divavg.append(overallhealLabel); } - } else if (part.type === "total") { - let total_damage = 0; - for (let i in part.factors) { - total_damage += save_damages[i] * part.factors[i]; - } - - let dmgarr = part.factors.slice(); - dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")"); - - - let averageLabel = document.createElement("p"); - averageLabel.textContent = "Average: "+total_damage.toFixed(2); - averageLabel.classList.add("damageSubtitle"); - part_div.append(averageLabel); - - let overallaverageLabel = document.createElement("p"); - let overallaverageLabelFirst = document.createElement("span"); - let overallaverageLabelSecond = document.createElement("span"); - overallaverageLabelFirst.textContent = "Average: "; - overallaverageLabelSecond.textContent = total_damage.toFixed(2); - overallaverageLabelSecond.classList.add("Damage"); - - - overallaverageLabel.appendChild(overallaverageLabelFirst); - overallaverageLabel.appendChild(overallaverageLabelSecond); - part_divavg.append(overallaverageLabel); +// } else if (part.type === "total") { +// let total_damage = 0; +// for (let i in part.factors) { +// total_damage += save_damages[i] * part.factors[i]; +// } +// +// let dmgarr = part.factors.slice(); +// dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")"); +// +// +// let averageLabel = document.createElement("p"); +// averageLabel.textContent = "Average: "+total_damage.toFixed(2); +// averageLabel.classList.add("damageSubtitle"); +// part_div.append(averageLabel); +// +// let overallaverageLabel = document.createElement("p"); +// let overallaverageLabelFirst = document.createElement("span"); +// let overallaverageLabelSecond = document.createElement("span"); +// overallaverageLabelFirst.textContent = "Average: "; +// overallaverageLabelSecond.textContent = total_damage.toFixed(2); +// overallaverageLabelSecond.classList.add("Damage"); +// +// +// overallaverageLabel.appendChild(overallaverageLabelFirst); +// overallaverageLabel.appendChild(overallaverageLabelSecond); +// part_divavg.append(overallaverageLabel); } } From 3fb59494d9d4d5af6eea7aa38436eec47f232c43 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 23:54:05 -0700 Subject: [PATCH 115/155] Testing healing --- js/builder_graph.js | 7 +++--- js/damage_calc.js | 10 ++++++++ js/display.js | 58 +++++++++++---------------------------------- 3 files changed, 28 insertions(+), 47 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index b8b4833..b371052 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -592,7 +592,7 @@ class SpellDamageCalcNode extends ComputeNode { } } else if ('power' in part) { // TODO: wynn2 formula - 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); + let _heal_amount = (part.power * getDefenseStats(stats)[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))); spell_result = { type: "heal", heal_amount: _heal_amount @@ -604,7 +604,8 @@ class SpellDamageCalcNode extends ComputeNode { normal_total: [0, 0], crit_min: [0, 0, 0, 0, 0, 0], crit_max: [0, 0, 0, 0, 0, 0], - crit_total: [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)) { @@ -1111,7 +1112,7 @@ function builder_graph_init() { // Also do something similar for skill points - //for (let i = 0; i < 4; ++i) { + //for (let i = 0; i < 4; ++i) { TODO: testing code for (let i = 0; i < 1; ++i) { let spell_node = new SpellSelectNode(i); spell_node.link_to(build_node, 'build'); diff --git a/js/damage_calc.js b/js/damage_calc.js index 2710ec4..9a18fbd 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -246,6 +246,16 @@ const default_spells = { scaling: "melee", use_atkspd: false, display: "Melee", parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }] + }, { + name: "Heal", // TODO: name for melee attacks? + display_text: "Heal spell!", + base_spell: 1, + display: "Total Heal", + parts: [ + { name: "First Pulse", power: 0.12 }, + { name: "Second and Third Pulses", power: 0.06 }, + { name: "Total Heal", hits: { "First Pulse": 1, "Second and Third Pulses": 2 } } + ] }], spear: [{ name: "Melee", // TODO: name for melee attacks? diff --git a/js/display.js b/js/display.js index e4114ed..6f01d41 100644 --- a/js/display.js +++ b/js/display.js @@ -1627,6 +1627,18 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell let part_divavg = document.createElement("p"); overallparent_elem.append(part_divavg); + function _summary(text, val, fmt) { + let overallaverageLabel = document.createElement("p"); + let first = document.createElement("span"); + let second = document.createElement("span"); + first.textContent = text; + second.textContent = val.toFixed(2); + overallaverageLabel.appendChild(first); + overallaverageLabel.appendChild(second); + second.classList.add(fmt); + part_divavg.append(overallaverageLabel); + } + for (let i = 0; i < spell_results.length; ++i) { const damage_info = spell_results[i]; @@ -1652,15 +1664,7 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell if (damage_info.name === spell.display) { - let overallaverageLabel = document.createElement("p"); - let first = document.createElement("span"); - let second = document.createElement("span"); - first.textContent = damage_info.name+ " Average: "; - second.textContent = averageDamage.toFixed(2); - overallaverageLabel.appendChild(first); - overallaverageLabel.appendChild(second); - second.classList.add("Damage"); - part_divavg.append(overallaverageLabel); + _summary(damage_info.name+ " Average: ", averageDamage, "Damage"); } function _damage_display(label_text, average, dmg_min, dmg_max) { @@ -1686,42 +1690,8 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell // healLabel.classList.add("damagep"); part_div.append(healLabel); if (damage_info.name === spell.display) { - let overallhealLabel = document.createElement("p"); - let first = document.createElement("span"); - let second = document.createElement("span"); - first.textContent = damage_info.name+ ": "; - second.textContent = heal_amount; - overallhealLabel.appendChild(first); - second.classList.add("Set"); - overallhealLabel.appendChild(second); - part_divavg.append(overallhealLabel); + _summary(damage_info.name+ ": ", heal_amount, "Set"); } -// } else if (part.type === "total") { -// let total_damage = 0; -// for (let i in part.factors) { -// total_damage += save_damages[i] * part.factors[i]; -// } -// -// let dmgarr = part.factors.slice(); -// dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")"); -// -// -// let averageLabel = document.createElement("p"); -// averageLabel.textContent = "Average: "+total_damage.toFixed(2); -// averageLabel.classList.add("damageSubtitle"); -// part_div.append(averageLabel); -// -// let overallaverageLabel = document.createElement("p"); -// let overallaverageLabelFirst = document.createElement("span"); -// let overallaverageLabelSecond = document.createElement("span"); -// overallaverageLabelFirst.textContent = "Average: "; -// overallaverageLabelSecond.textContent = total_damage.toFixed(2); -// overallaverageLabelSecond.classList.add("Damage"); -// -// -// overallaverageLabel.appendChild(overallaverageLabelFirst); -// overallaverageLabel.appendChild(overallaverageLabelSecond); -// part_divavg.append(overallaverageLabel); } } From 65c2d397549858ff5cf32c068bda8257dff5c1ad Mon Sep 17 00:00:00 2001 From: ferricles Date: Mon, 27 Jun 2022 00:09:49 -0700 Subject: [PATCH 116/155] atree ID rework started --- js/atree-ids.json | 137 - py_script/atree-generateID.py | 4 +- py_script/atree-ids.json | 146 + py_script/atree-parse.json | 4967 +++++++++++++++++ .../textures}/items/boots/boots--chain.png | Bin .../textures}/items/boots/boots--diamond.png | Bin .../textures}/items/boots/boots--golden.png | Bin .../textures}/items/boots/boots--iron.png | Bin .../textures}/items/boots/boots--leather.png | Bin .../textures}/items/bow/bow--air1.png | Bin .../textures}/items/bow/bow--air2.png | Bin .../textures}/items/bow/bow--air3.png | Bin .../textures}/items/bow/bow--default1.png | Bin .../textures}/items/bow/bow--default2.png | Bin .../textures}/items/bow/bow--earth1.png | Bin .../textures}/items/bow/bow--earth2.png | Bin .../textures}/items/bow/bow--earth3.png | Bin .../textures}/items/bow/bow--fire1.png | Bin .../textures}/items/bow/bow--fire2.png | Bin .../textures}/items/bow/bow--fire3.png | Bin .../textures}/items/bow/bow--generic1.png | Bin .../textures}/items/bow/bow--generic2.png | Bin .../textures}/items/bow/bow--generic3.png | Bin .../textures}/items/bow/bow--thunder1.png | Bin .../textures}/items/bow/bow--thunder2.png | Bin .../textures}/items/bow/bow--thunder3.png | Bin .../textures}/items/bow/bow--water1.png | Bin .../textures}/items/bow/bow--water2.png | Bin .../textures}/items/bow/bow--water3.png | Bin .../items/chestplate/chestplate--chain.png | Bin .../items/chestplate/chestplate--diamond.png | Bin .../items/chestplate/chestplate--golden.png | Bin .../items/chestplate/chestplate--iron.png | Bin .../items/chestplate/chestplate--leather.png | Bin .../textures}/items/dagger/dagger--air1.png | Bin .../textures}/items/dagger/dagger--air2.png | Bin .../textures}/items/dagger/dagger--air3.png | Bin .../items/dagger/dagger--default1.png | Bin .../items/dagger/dagger--default2.png | Bin .../textures}/items/dagger/dagger--earth1.png | Bin .../textures}/items/dagger/dagger--earth2.png | Bin .../textures}/items/dagger/dagger--earth3.png | Bin .../textures}/items/dagger/dagger--fire1.png | Bin .../textures}/items/dagger/dagger--fire2.png | Bin .../textures}/items/dagger/dagger--fire3.png | Bin .../items/dagger/dagger--generic1.png | Bin .../items/dagger/dagger--generic2.png | Bin .../items/dagger/dagger--generic3.png | Bin .../items/dagger/dagger--thunder1.png | Bin .../items/dagger/dagger--thunder2.png | Bin .../items/dagger/dagger--thunder3.png | Bin .../textures}/items/dagger/dagger--water1.png | Bin .../textures}/items/dagger/dagger--water2.png | Bin .../textures}/items/dagger/dagger--water3.png | Bin .../textures}/items/helmet/helmet--chain.png | Bin .../items/helmet/helmet--diamond.png | Bin .../textures}/items/helmet/helmet--golden.png | Bin .../textures}/items/helmet/helmet--iron.png | Bin .../items/helmet/helmet--leather.png | Bin .../items/leggings/leggings--chain.png | Bin .../items/leggings/leggings--diamond.png | Bin .../items/leggings/leggings--golden.png | Bin .../items/leggings/leggings--iron.png | Bin .../items/leggings/leggings--leather.png | Bin .../textures}/items/relik/relik--air1.png | Bin .../textures}/items/relik/relik--air2.png | Bin .../textures}/items/relik/relik--air3.png | Bin .../textures}/items/relik/relik--default1.png | Bin .../textures}/items/relik/relik--default2.png | Bin .../textures}/items/relik/relik--earth1.png | Bin .../textures}/items/relik/relik--earth2.png | Bin .../textures}/items/relik/relik--earth3.png | Bin .../textures}/items/relik/relik--fire1.png | Bin .../textures}/items/relik/relik--fire2.png | Bin .../textures}/items/relik/relik--fire3.png | Bin .../textures}/items/relik/relik--generic1.png | Bin .../textures}/items/relik/relik--generic2.png | Bin .../textures}/items/relik/relik--generic3.png | 2 +- .../textures}/items/relik/relik--thunder1.png | Bin .../textures}/items/relik/relik--thunder2.png | Bin .../textures}/items/relik/relik--thunder3.png | Bin .../textures}/items/relik/relik--water1.png | 2 +- .../textures}/items/relik/relik--water2.png | 2 +- .../textures}/items/relik/relik--water3.png | 2 +- .../textures}/items/spear/spear--air1.png | Bin .../textures}/items/spear/spear--air2.png | Bin .../textures}/items/spear/spear--air3.png | Bin .../textures}/items/spear/spear--default1.png | Bin .../textures}/items/spear/spear--default2.png | Bin .../textures}/items/spear/spear--earth1.png | Bin .../textures}/items/spear/spear--earth2.png | Bin .../textures}/items/spear/spear--earth3.png | Bin .../textures}/items/spear/spear--fire1.png | Bin .../textures}/items/spear/spear--fire2.png | Bin .../textures}/items/spear/spear--fire3.png | Bin .../textures}/items/spear/spear--generic1.png | Bin .../textures}/items/spear/spear--generic2.png | Bin .../textures}/items/spear/spear--generic3.png | Bin .../textures}/items/spear/spear--thunder1.png | Bin .../textures}/items/spear/spear--thunder2.png | Bin .../textures}/items/spear/spear--thunder3.png | Bin .../textures}/items/spear/spear--water1.png | Bin .../textures}/items/spear/spear--water2.png | Bin .../textures}/items/spear/spear--water3.png | Bin .../textures}/items/wand/wand--air1.png | Bin .../textures}/items/wand/wand--air2.png | Bin .../textures}/items/wand/wand--air3.png | Bin .../textures}/items/wand/wand--default1.png | Bin .../textures}/items/wand/wand--default2.png | Bin .../textures}/items/wand/wand--earth1.png | Bin .../textures}/items/wand/wand--earth2.png | Bin .../textures}/items/wand/wand--earth3.png | Bin .../textures}/items/wand/wand--fire1.png | Bin .../textures}/items/wand/wand--fire2.png | Bin .../textures}/items/wand/wand--fire3.png | Bin .../textures}/items/wand/wand--generic1.png | Bin .../textures}/items/wand/wand--generic2.png | Bin .../textures}/items/wand/wand--generic3.png | 2 +- .../textures}/items/wand/wand--thunder1.png | Bin .../textures}/items/wand/wand--thunder2.png | Bin .../textures}/items/wand/wand--thunder3.png | Bin .../textures}/items/wand/wand--water1.png | Bin .../textures}/items/wand/wand--water2.png | Bin .../textures}/items/wand/wand--water3.png | Bin .../textures}/powder/dye_powder_cyan.png | Bin .../textures}/powder/dye_powder_gray.png | Bin .../textures}/powder/dye_powder_green.png | Bin .../powder/dye_powder_light_blue.png | Bin .../textures}/powder/dye_powder_lime.png | Bin .../textures}/powder/dye_powder_orange.png | Bin .../textures}/powder/dye_powder_pink.png | Bin .../textures}/powder/dye_powder_red.png | Bin .../textures}/powder/dye_powder_silver.png | Bin .../textures}/powder/dye_powder_yellow.png | Bin 134 files changed, 5121 insertions(+), 143 deletions(-) delete mode 100644 js/atree-ids.json create mode 100644 py_script/atree-ids.json create mode 100644 py_script/atree-parse.json rename {textures => py_script/textures}/items/boots/boots--chain.png (100%) rename {textures => py_script/textures}/items/boots/boots--diamond.png (100%) rename {textures => py_script/textures}/items/boots/boots--golden.png (100%) rename {textures => py_script/textures}/items/boots/boots--iron.png (100%) rename {textures => py_script/textures}/items/boots/boots--leather.png (100%) rename {textures => py_script/textures}/items/bow/bow--air1.png (100%) rename {textures => py_script/textures}/items/bow/bow--air2.png (100%) rename {textures => py_script/textures}/items/bow/bow--air3.png (100%) rename {textures => py_script/textures}/items/bow/bow--default1.png (100%) rename {textures => py_script/textures}/items/bow/bow--default2.png (100%) rename {textures => py_script/textures}/items/bow/bow--earth1.png (100%) rename {textures => py_script/textures}/items/bow/bow--earth2.png (100%) rename {textures => py_script/textures}/items/bow/bow--earth3.png (100%) rename {textures => py_script/textures}/items/bow/bow--fire1.png (100%) rename {textures => py_script/textures}/items/bow/bow--fire2.png (100%) rename {textures => py_script/textures}/items/bow/bow--fire3.png (100%) rename {textures => py_script/textures}/items/bow/bow--generic1.png (100%) rename {textures => py_script/textures}/items/bow/bow--generic2.png (100%) rename {textures => py_script/textures}/items/bow/bow--generic3.png (100%) rename {textures => py_script/textures}/items/bow/bow--thunder1.png (100%) rename {textures => py_script/textures}/items/bow/bow--thunder2.png (100%) rename {textures => py_script/textures}/items/bow/bow--thunder3.png (100%) rename {textures => py_script/textures}/items/bow/bow--water1.png (100%) rename {textures => py_script/textures}/items/bow/bow--water2.png (100%) rename {textures => py_script/textures}/items/bow/bow--water3.png (100%) rename {textures => py_script/textures}/items/chestplate/chestplate--chain.png (100%) rename {textures => py_script/textures}/items/chestplate/chestplate--diamond.png (100%) rename {textures => py_script/textures}/items/chestplate/chestplate--golden.png (100%) rename {textures => py_script/textures}/items/chestplate/chestplate--iron.png (100%) rename {textures => py_script/textures}/items/chestplate/chestplate--leather.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--air1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--air2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--air3.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--default1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--default2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--earth1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--earth2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--earth3.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--fire1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--fire2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--fire3.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--generic1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--generic2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--generic3.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--thunder1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--thunder2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--thunder3.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--water1.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--water2.png (100%) rename {textures => py_script/textures}/items/dagger/dagger--water3.png (100%) rename {textures => py_script/textures}/items/helmet/helmet--chain.png (100%) rename {textures => py_script/textures}/items/helmet/helmet--diamond.png (100%) rename {textures => py_script/textures}/items/helmet/helmet--golden.png (100%) rename {textures => py_script/textures}/items/helmet/helmet--iron.png (100%) rename {textures => py_script/textures}/items/helmet/helmet--leather.png (100%) rename {textures => py_script/textures}/items/leggings/leggings--chain.png (100%) rename {textures => py_script/textures}/items/leggings/leggings--diamond.png (100%) rename {textures => py_script/textures}/items/leggings/leggings--golden.png (100%) rename {textures => py_script/textures}/items/leggings/leggings--iron.png (100%) rename {textures => py_script/textures}/items/leggings/leggings--leather.png (100%) rename {textures => py_script/textures}/items/relik/relik--air1.png (100%) rename {textures => py_script/textures}/items/relik/relik--air2.png (100%) rename {textures => py_script/textures}/items/relik/relik--air3.png (100%) rename {textures => py_script/textures}/items/relik/relik--default1.png (100%) rename {textures => py_script/textures}/items/relik/relik--default2.png (100%) rename {textures => py_script/textures}/items/relik/relik--earth1.png (100%) rename {textures => py_script/textures}/items/relik/relik--earth2.png (100%) rename {textures => py_script/textures}/items/relik/relik--earth3.png (100%) rename {textures => py_script/textures}/items/relik/relik--fire1.png (100%) rename {textures => py_script/textures}/items/relik/relik--fire2.png (100%) rename {textures => py_script/textures}/items/relik/relik--fire3.png (100%) rename {textures => py_script/textures}/items/relik/relik--generic1.png (100%) rename {textures => py_script/textures}/items/relik/relik--generic2.png (100%) rename {textures => py_script/textures}/items/relik/relik--generic3.png (99%) rename {textures => py_script/textures}/items/relik/relik--thunder1.png (100%) rename {textures => py_script/textures}/items/relik/relik--thunder2.png (100%) rename {textures => py_script/textures}/items/relik/relik--thunder3.png (100%) rename {textures => py_script/textures}/items/relik/relik--water1.png (99%) rename {textures => py_script/textures}/items/relik/relik--water2.png (99%) rename {textures => py_script/textures}/items/relik/relik--water3.png (99%) rename {textures => py_script/textures}/items/spear/spear--air1.png (100%) rename {textures => py_script/textures}/items/spear/spear--air2.png (100%) rename {textures => py_script/textures}/items/spear/spear--air3.png (100%) rename {textures => py_script/textures}/items/spear/spear--default1.png (100%) rename {textures => py_script/textures}/items/spear/spear--default2.png (100%) rename {textures => py_script/textures}/items/spear/spear--earth1.png (100%) rename {textures => py_script/textures}/items/spear/spear--earth2.png (100%) rename {textures => py_script/textures}/items/spear/spear--earth3.png (100%) rename {textures => py_script/textures}/items/spear/spear--fire1.png (100%) rename {textures => py_script/textures}/items/spear/spear--fire2.png (100%) rename {textures => py_script/textures}/items/spear/spear--fire3.png (100%) rename {textures => py_script/textures}/items/spear/spear--generic1.png (100%) rename {textures => py_script/textures}/items/spear/spear--generic2.png (100%) rename {textures => py_script/textures}/items/spear/spear--generic3.png (100%) rename {textures => py_script/textures}/items/spear/spear--thunder1.png (100%) rename {textures => py_script/textures}/items/spear/spear--thunder2.png (100%) rename {textures => py_script/textures}/items/spear/spear--thunder3.png (100%) rename {textures => py_script/textures}/items/spear/spear--water1.png (100%) rename {textures => py_script/textures}/items/spear/spear--water2.png (100%) rename {textures => py_script/textures}/items/spear/spear--water3.png (100%) rename {textures => py_script/textures}/items/wand/wand--air1.png (100%) rename {textures => py_script/textures}/items/wand/wand--air2.png (100%) rename {textures => py_script/textures}/items/wand/wand--air3.png (100%) rename {textures => py_script/textures}/items/wand/wand--default1.png (100%) rename {textures => py_script/textures}/items/wand/wand--default2.png (100%) rename {textures => py_script/textures}/items/wand/wand--earth1.png (100%) rename {textures => py_script/textures}/items/wand/wand--earth2.png (100%) rename {textures => py_script/textures}/items/wand/wand--earth3.png (100%) rename {textures => py_script/textures}/items/wand/wand--fire1.png (100%) rename {textures => py_script/textures}/items/wand/wand--fire2.png (100%) rename {textures => py_script/textures}/items/wand/wand--fire3.png (100%) rename {textures => py_script/textures}/items/wand/wand--generic1.png (100%) rename {textures => py_script/textures}/items/wand/wand--generic2.png (100%) rename {textures => py_script/textures}/items/wand/wand--generic3.png (99%) rename {textures => py_script/textures}/items/wand/wand--thunder1.png (100%) rename {textures => py_script/textures}/items/wand/wand--thunder2.png (100%) rename {textures => py_script/textures}/items/wand/wand--thunder3.png (100%) rename {textures => py_script/textures}/items/wand/wand--water1.png (100%) rename {textures => py_script/textures}/items/wand/wand--water2.png (100%) rename {textures => py_script/textures}/items/wand/wand--water3.png (100%) rename {textures => py_script/textures}/powder/dye_powder_cyan.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_gray.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_green.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_light_blue.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_lime.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_orange.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_pink.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_red.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_silver.png (100%) mode change 100755 => 100644 rename {textures => py_script/textures}/powder/dye_powder_yellow.png (100%) mode change 100755 => 100644 diff --git a/js/atree-ids.json b/js/atree-ids.json deleted file mode 100644 index d04db13..0000000 --- a/js/atree-ids.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "Arrow Shield": 0, - "Escape": 1, - "Arrow Bomb": 2, - "Heart Shatter": 3, - "Fire Creep": 4, - "Bryophyte Roots": 5, - "Nimble String": 6, - "Arrow Storm": 7, - "Guardian Angels": 8, - "Windy Feet": 9, - "Basaltic Trap": 10, - "Windstorm": 11, - "Grappling Hook": 12, - "Implosion": 13, - "Twain's Arc": 14, - "Fierce Stomp": 15, - "Scorched Earth": 16, - "Leap": 17, - "Shocking Bomb": 18, - "Mana Trap": 19, - "Escape Artist": 20, - "Initiator": 21, - "Call of the Hound": 22, - "Arrow Hurricane": 23, - "Geyser Stomp": 24, - "Crepuscular Ray": 25, - "Grape Bomb": 26, - "Tangled Traps": 27, - "Snow Storm": 28, - "All-Seeing Panoptes": 29, - "Minefield": 30, - "Bow Proficiency I": 31, - "Cheaper Arrow Bomb": 32, - "Cheaper Arrow Storm": 33, - "Cheaper Escape": 34, - "Earth Mastery": 82, - "Thunder Mastery": 83, - "Water Mastery": 84, - "Air Mastery": 85, - "Fire Mastery": 86, - "More Shields": 40, - "Stormy Feet": 41, - "Refined Gunpowder": 42, - "More Traps": 43, - "Better Arrow Shield": 44, - "Better Leap": 45, - "Better Guardian Angels": 46, - "Cheaper Arrow Storm (2)": 47, - "Precise Shot": 48, - "Cheaper Arrow Shield": 49, - "Rocket Jump": 50, - "Cheaper Escape (2)": 51, - "Stronger Hook": 52, - "Cheaper Arrow Bomb (2)": 53, - "Bouncing Bomb": 54, - "Homing Shots": 55, - "Shrapnel Bomb": 56, - "Elusive": 57, - "Double Shots": 58, - "Triple Shots": 59, - "Power Shots": 60, - "Focus": 61, - "More Focus": 62, - "More Focus (2)": 63, - "Traveler": 64, - "Patient Hunter": 65, - "Stronger Patient Hunter": 66, - "Frenzy": 67, - "Phantom Ray": 68, - "Arrow Rain": 69, - "Decimator": 70, - "Bash": 71, - "Spear Proficiency 1": 72, - "Cheaper Bash": 73, - "Double Bash": 74, - "Charge": 75, - "Heavy Impact": 76, - "Vehement": 77, - "Tougher Skin": 78, - "Uppercut": 79, - "Cheaper Charge": 80, - "War Scream": 81, - "Quadruple Bash": 87, - "Fireworks": 88, - "Half-Moon Swipe": 89, - "Flyby Jab": 90, - "Flaming Uppercut": 91, - "Iron Lungs": 92, - "Generalist": 93, - "Counter": 94, - "Mantle of the Bovemists": 95, - "Bak'al's Grasp": 96, - "Spear Proficiency 2": 97, - "Cheaper Uppercut": 98, - "Aerodynamics": 99, - "Provoke": 100, - "Precise Strikes": 101, - "Air Shout": 102, - "Enraged Blow": 103, - "Flying Kick": 104, - "Stronger Mantle": 105, - "Manachism": 106, - "Boiling Blood": 107, - "Ragnarokkr": 108, - "Ambidextrous": 109, - "Burning Heart": 110, - "Stronger Bash": 111, - "Intoxicating Blood": 112, - "Comet": 113, - "Collide": 114, - "Rejuvenating Skin": 115, - "Uncontainable Corruption": 116, - "Radiant Devotee": 117, - "Whirlwind Strike": 118, - "Mythril Skin": 119, - "Armour Breaker": 120, - "Shield Strike": 121, - "Sparkling Hope": 122, - "Massive Bash": 123, - "Tempest": 124, - "Spirit of the Rabbit": 125, - "Massacre": 126, - "Axe Kick": 127, - "Radiance": 128, - "Cheaper Bash 2": 129, - "Cheaper War Scream": 130, - "Discombobulate": 131, - "Thunderclap": 132, - "Cyclone": 133, - "Second Chance": 134, - "Blood Pact": 135, - "Haemorrhage": 136, - "Brink of Madness": 137, - "Cheaper Uppercut 2": 138, - "Martyr": 139 -} \ No newline at end of file diff --git a/py_script/atree-generateID.py b/py_script/atree-generateID.py index 4355e72..4657cf1 100644 --- a/py_script/atree-generateID.py +++ b/py_script/atree-generateID.py @@ -11,6 +11,8 @@ abilDict = {} with open("atree-parse.json") as f: data = json.loads(f.read()) for classType, info in data.items(): + #reset IDs for every class and start at 1 + id = 1 for abil in info: abilDict[abil["display_name"]] = id id += 1 @@ -32,4 +34,4 @@ with open("atree-parse.json") as f: data[classType] = info with open('atree-constants-id.json', 'w', encoding='utf-8') as abil_dest: - json.dump(data, abil_dest, ensure_ascii=False, indent=4) + json.dump(data, abil_dest, ensure_ascii=False, indent=4) \ No newline at end of file diff --git a/py_script/atree-ids.json b/py_script/atree-ids.json new file mode 100644 index 0000000..16db827 --- /dev/null +++ b/py_script/atree-ids.json @@ -0,0 +1,146 @@ +{ + "Archer": { + "Arrow Shield": 1, + "Escape": 2, + "Arrow Bomb": 3, + "Heart Shatter": 4, + "Fire Creep": 5, + "Bryophyte Roots": 6, + "Nimble String": 7, + "Arrow Storm": 8, + "Guardian Angels": 9, + "Windy Feet": 10, + "Basaltic Trap": 11, + "Windstorm": 12, + "Grappling Hook": 13, + "Implosion": 14, + "Twain's Arc": 15, + "Fierce Stomp": 16, + "Scorched Earth": 17, + "Leap": 18, + "Shocking Bomb": 19, + "Mana Trap": 20, + "Escape Artist": 21, + "Initiator": 22, + "Call of the Hound": 23, + "Arrow Hurricane": 24, + "Geyser Stomp": 25, + "Crepuscular Ray": 26, + "Grape Bomb": 27, + "Tangled Traps": 28, + "Snow Storm": 29, + "All-Seeing Panoptes": 30, + "Minefield": 31, + "Bow Proficiency I": 32, + "Cheaper Arrow Bomb": 33, + "Cheaper Arrow Storm": 34, + "Cheaper Escape": 35, + "Earth Mastery": 36, + "Thunder Mastery": 37, + "Water Mastery": 38, + "Air Mastery": 39, + "Fire Mastery": 40, + "More Shields": 41, + "Stormy Feet": 42, + "Refined Gunpowder": 43, + "More Traps": 44, + "Better Arrow Shield": 45, + "Better Leap": 46, + "Better Guardian Angels": 47, + "Cheaper Arrow Storm (2)": 48, + "Precise Shot": 49, + "Cheaper Arrow Shield": 50, + "Rocket Jump": 51, + "Cheaper Escape (2)": 52, + "Stronger Hook": 53, + "Cheaper Arrow Bomb (2)": 54, + "Bouncing Bomb": 55, + "Homing Shots": 56, + "Shrapnel Bomb": 57, + "Elusive": 58, + "Double Shots": 59, + "Triple Shots": 60, + "Power Shots": 61, + "Focus": 62, + "More Focus": 63, + "More Focus (2)": 64, + "Traveler": 65, + "Patient Hunter": 66, + "Stronger Patient Hunter": 67, + "Frenzy": 68, + "Phantom Ray": 69, + "Arrow Rain": 70, + "Decimator": 71 + }, + "Warrior": { + "Bash": 1, + "Spear Proficiency 1": 2, + "Cheaper Bash": 3, + "Double Bash": 4, + "Charge": 5, + "Heavy Impact": 6, + "Vehement": 7, + "Tougher Skin": 8, + "Uppercut": 9, + "Cheaper Charge": 10, + "War Scream": 11, + "Earth Mastery": 12, + "Thunder Mastery": 13, + "Water Mastery": 14, + "Air Mastery": 15, + "Fire Mastery": 16, + "Quadruple Bash": 17, + "Fireworks": 18, + "Half-Moon Swipe": 19, + "Flyby Jab": 20, + "Flaming Uppercut": 21, + "Iron Lungs": 22, + "Generalist": 23, + "Counter": 24, + "Mantle of the Bovemists": 25, + "Bak'al's Grasp": 26, + "Spear Proficiency 2": 27, + "Cheaper Uppercut": 28, + "Aerodynamics": 29, + "Provoke": 30, + "Precise Strikes": 31, + "Air Shout": 32, + "Enraged Blow": 33, + "Flying Kick": 34, + "Stronger Mantle": 35, + "Manachism": 36, + "Boiling Blood": 37, + "Ragnarokkr": 38, + "Ambidextrous": 39, + "Burning Heart": 40, + "Stronger Bash": 41, + "Intoxicating Blood": 42, + "Comet": 43, + "Collide": 44, + "Rejuvenating Skin": 45, + "Uncontainable Corruption": 46, + "Radiant Devotee": 47, + "Whirlwind Strike": 48, + "Mythril Skin": 49, + "Armour Breaker": 50, + "Shield Strike": 51, + "Sparkling Hope": 52, + "Massive Bash": 53, + "Tempest": 54, + "Spirit of the Rabbit": 55, + "Massacre": 56, + "Axe Kick": 57, + "Radiance": 58, + "Cheaper Bash 2": 59, + "Cheaper War Scream": 60, + "Discombobulate": 61, + "Thunderclap": 62, + "Cyclone": 63, + "Second Chance": 64, + "Blood Pact": 65, + "Haemorrhage": 66, + "Brink of Madness": 67, + "Cheaper Uppercut 2": 68, + "Martyr": 69 + } +} \ No newline at end of file diff --git a/py_script/atree-parse.json b/py_script/atree-parse.json new file mode 100644 index 0000000..7eabec7 --- /dev/null +++ b/py_script/atree-parse.json @@ -0,0 +1,4967 @@ +{ + "Archer": [ + { + "display_name": "Arrow Shield", + "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", + "archetype": "", + "archetype_req": 0, + "parents": [ + 60, + 34 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 6 + }, + "properties": { + "duration": 60 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Shield", + "cost": 30, + "display_text": "Max Damage", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "", + "parts": [ + { + "name": "Shield Damage", + "type": "damage", + "multipliers": [ + 90, + 0, + 0, + 0, + 0, + 10 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Shield Damage": 2 + } + } + ] + } + ], + "id": 0 + }, + { + "display_name": "Escape", + "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", + "archetype": "", + "archetype_req": 0, + "parents": [ + 3 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 7, + "col": 4 + }, + "properties": { + "aoe": 0, + "range": 0 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Escape", + "cost": 25, + "display_text": "Max Damage", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] + } + ], + "id": 1 + }, + { + "display_name": "Arrow Bomb", + "desc": "Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)", + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 0, + "col": 4 + }, + "properties": { + "aoe": 4.5, + "range": 26 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Bomb", + "cost": 50, + "display_text": "Average Damage", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Arrow Bomb", + "type": "damage", + "multipliers": [ + 160, + 0, + 0, + 0, + 20, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Arrow Bomb": 1 + } + } + ] + } + ], + "id": 2 + }, + { + "display_name": "Heart Shatter", + "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 31 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 4, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] + }, + {} + ], + "id": 3 + }, + { + "display_name": "Fire Creep", + "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 68, + 86, + 5 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 6 + }, + "properties": { + "aoe": 0.8, + "duration": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [ + 30, + 0, + 0, + 0, + 20, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fire Creep": 15 + } + } + ], + "id": 4 + }, + { + "display_name": "Bryophyte Roots", + "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.", + "archetype": "Trapper", + "archetype_req": 1, + "parents": [ + 4, + 82 + ], + "dependencies": [ + 7 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 8 + }, + "properties": { + "aoe": 2, + "duration": 5, + "slowness": 0.4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Bryophyte Roots", + "cost": 0, + "multipliers": [ + 40, + 20, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 5 + }, + { + "display_name": "Nimble String", + "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 83, + 69 + ], + "dependencies": [ + 7 + ], + "blockers": [ + 68 + ], + "cost": 2, + "display": { + "row": 15, + "col": 2 + }, + "properties": { + "shootspeed": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [ + -15, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Stream", + "cost": 0, + "hits": { + "Single Arrow": 8 + } + } + ], + "id": 6 + }, + { + "display_name": "Arrow Storm", + "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 58, + 34 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 2 + }, + "properties": { + "aoe": 0, + "range": 16 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Arrow Storm", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 30, + 0, + 10, + 0, + 0, + 0 + ] + }, + { + "name": "Single Stream", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Stream": 2 + } + } + ] + } + ], + "id": 7 + }, + { + "display_name": "Guardian Angels", + "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)", + "archetype": "Boltslinger", + "archetype_req": 3, + "parents": [ + 59, + 67 + ], + "dependencies": [ + 0 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 1 + }, + "properties": { + "range": 4, + "duration": 60, + "shots": 8, + "count": 2 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Guardian Angels", + "cost": 30, + "display_text": "Total Damage Average", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 20 + ] + }, + { + "name": "Single Bow", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Bow": 2 + } + } + ] + } + ], + "id": 8 + }, + { + "display_name": "Windy Feet", + "base_abil": "Escape", + "desc": "When casting Escape, give speed to yourself and nearby allies.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 7 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 1 + }, + "properties": { + "aoe": 8, + "duration": 120 + }, + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spd", + "value": 20 + } + ], + "id": 9 + }, + { + "display_name": "Basaltic Trap", + "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)", + "archetype": "Trapper", + "archetype_req": 2, + "parents": [ + 5 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 8 + }, + "properties": { + "aoe": 7, + "traps": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [ + 140, + 30, + 0, + 0, + 30, + 0 + ] + } + ], + "id": 10 + }, + { + "display_name": "Windstorm", + "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 8, + 33 + ], + "dependencies": [], + "blockers": [ + 68 + ], + "cost": 2, + "display": { + "row": 21, + "col": 1 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [ + -11, + 0, + -7, + 0, + 0, + 3 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 1 + } + } + ], + "id": 11 + }, + { + "display_name": "Grappling Hook", + "base_abil": "Escape", + "desc": "When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 61, + 40, + 33 + ], + "dependencies": [], + "blockers": [ + 20 + ], + "cost": 2, + "display": { + "row": 21, + "col": 5 + }, + "properties": { + "range": 20 + }, + "effects": [], + "id": 12 + }, + { + "display_name": "Implosion", + "desc": "Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 12, + 40 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 6 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 13 + }, + { + "display_name": "Twain's Arc", + "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", + "archetype": "Sharpshooter", + "archetype_req": 4, + "parents": [ + 62, + 64 + ], + "dependencies": [ + 61 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 25, + "col": 4 + }, + "properties": { + "range": 64, + "focusReq": 2 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Twain's Arc", + "cost": 0, + "display_text": "Twain's Arc", + "base_spell": 5, + "spell_type": "damage", + "scaling": "melee", + "display": "Twain's Arc Damage", + "parts": [ + { + "name": "Twain's Arc Damage", + "type": "damage", + "multipliers": [ + 200, + 0, + 0, + 0, + 0, + 0 + ] + } + ] + } + ], + "id": 14 + }, + { + "display_name": "Fierce Stomp", + "desc": "When using Escape, hold shift to quickly drop down and deal damage.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 42, + 64 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 1 + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fierce Stomp": 1 + } + } + ], + "id": 15 + }, + { + "display_name": "Scorched Earth", + "desc": "Fire Creep become much stronger.", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 14 + ], + "dependencies": [ + 4 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 5 + }, + "properties": { + "duration": 2, + "aoe": 0.4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [ + 10, + 0, + 0, + 0, + 5, + 0 + ] + } + ], + "id": 16 + }, + { + "display_name": "Leap", + "desc": "When you double tap jump, leap foward. (2s Cooldown)", + "archetype": "Boltslinger", + "archetype_req": 5, + "parents": [ + 42, + 55 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 0 + }, + "properties": { + "cooldown": 2 + }, + "effects": [], + "id": 17 + }, + { + "display_name": "Shocking Bomb", + "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.", + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": [ + 14, + 44, + 55 + ], + "dependencies": [ + 2 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 4 + }, + "properties": { + "gravity": 0 + }, + "effects": [ + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + } + ], + "id": 18 + }, + { + "display_name": "Mana Trap", + "desc": "Your Traps will give you 4 Mana per second when you stay close to them.", + "archetype": "Trapper", + "archetype_req": 5, + "parents": [ + 43, + 44 + ], + "dependencies": [ + 4 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 8 + }, + "properties": { + "range": 12, + "manaRegen": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 10, + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 19 + }, + { + "display_name": "Escape Artist", + "desc": "When casting Escape, release 100 arrows towards the ground.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 46, + 17 + ], + "dependencies": [], + "blockers": [ + 12 + ], + "cost": 2, + "display": { + "row": 31, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Escape Artist", + "cost": 0, + "multipliers": [ + 30, + 0, + 10, + 0, + 0, + 0 + ] + } + ], + "id": 20 + }, + { + "display_name": "Initiator", + "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": [ + 18, + 44, + 47 + ], + "dependencies": [ + 61 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 31, + "col": 5 + }, + "properties": { + "focus": 1, + "timer": 5 + }, + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "damPct", + "value": 50 + } + ], + "id": 21 + }, + { + "display_name": "Call of the Hound", + "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 21, + 47 + ], + "dependencies": [ + 0 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 7 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Call of the Hound", + "cost": 0, + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 22 + }, + { + "display_name": "Arrow Hurricane", + "desc": "Arrow Storm will shoot +2 stream of arrows.", + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": [ + 48, + 20 + ], + "dependencies": [], + "blockers": [ + 68 + ], + "cost": 2, + "display": { + "row": 33, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 2 + } + } + ], + "id": 23 + }, + { + "display_name": "Geyser Stomp", + "desc": "Fierce Stomp will create geysers, dealing more damage and vertical knockback.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 56 + ], + "dependencies": [ + 15 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 37, + "col": 1 + }, + "properties": { + "aoe": 1 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [ + 0, + 0, + 0, + 50, + 0, + 0 + ] + } + ], + "id": 24 + }, + { + "display_name": "Crepuscular Ray", + "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.", + "archetype": "Sharpshooter", + "archetype_req": 10, + "parents": [ + 49 + ], + "dependencies": [ + 7 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 37, + "col": 4 + }, + "properties": { + "focusReq": 5, + "focusRegen": -1 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Crepuscular Ray", + "base_spell": 5, + "spell_type": "damage", + "scaling": "spell", + "display": "One Focus", + "cost": 0, + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 10, + 0, + 0, + 5, + 0, + 0 + ] + }, + { + "name": "One Focus", + "type": "total", + "hits": { + "Single Arrow": 20 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "One Focus": 7 + } + } + ] + } + ], + "id": 25 + }, + { + "display_name": "Grape Bomb", + "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 51 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 37, + "col": 7 + }, + "properties": { + "miniBombs": 3, + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Grape Bomb", + "cost": 0, + "multipliers": [ + 30, + 0, + 0, + 0, + 10, + 0 + ] + } + ], + "id": 26 + }, + { + "display_name": "Tangled Traps", + "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s.", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 26 + ], + "dependencies": [ + 10 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 6 + }, + "properties": { + "attackSpeed": 0.2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Tangled Traps", + "cost": 0, + "multipliers": [ + 20, + 0, + 0, + 0, + 0, + 20 + ] + } + ], + "id": 27 + }, + { + "display_name": "Snow Storm", + "desc": "Enemies near you will be slowed down.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 24, + 63 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 39, + "col": 2 + }, + "properties": { + "range": 2.5, + "slowness": 0.3 + }, + "id": 28 + }, + { + "display_name": "All-Seeing Panoptes", + "desc": "Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.", + "archetype": "Boltslinger", + "archetype_req": 11, + "parents": [ + 28 + ], + "dependencies": [ + 8 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 40, + "col": 1 + }, + "properties": { + "range": 10, + "shots": 5 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [ + 0, + 0, + 0, + 0, + 20, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 5 + } + } + ], + "id": 29 + }, + { + "display_name": "Minefield", + "desc": "Allow you to place +6 Traps, but with reduced damage and range.", + "archetype": "Trapper", + "archetype_req": 10, + "parents": [ + 26, + 53 + ], + "dependencies": [ + 10 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 40, + "col": 7 + }, + "properties": { + "aoe": -2, + "traps": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [ + -80, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 30 + }, + { + "display_name": "Bow Proficiency I", + "desc": "Improve your Main Attack's damage and range when using a bow.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 2 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 4 + }, + "properties": { + "mainAtk_range": 6 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ], + "id": 31 + }, + { + "display_name": "Cheaper Arrow Bomb", + "desc": "Reduce the Mana cost of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 31 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 6 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -10 + } + ], + "id": 32 + }, + { + "display_name": "Cheaper Arrow Storm", + "desc": "Reduce the Mana cost of Arrow Storm.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 12, + 11, + 61 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 21, + "col": 3 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ], + "id": 33 + }, + { + "display_name": "Cheaper Escape", + "desc": "Reduce the Mana cost of Escape.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 7, + 0 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ], + "id": 34 + }, + { + "display_name": "Earth Mastery", + "desc": "Increases your base damage from all Earth attacks", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 0 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 8 + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "eDamPct", + "value": 20 + }, + { + "type": "stat", + "name": "eDam", + "value": [ + 2, + 4 + ] + } + ] + } + ], + "id": 82 + }, + { + "display_name": "Thunder Mastery", + "desc": "Increases your base damage from all Thunder attacks", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 7, + 86, + 34 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 2 + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "tDamPct", + "value": 10 + }, + { + "type": "stat", + "name": "tDam", + "value": [ + 1, + 8 + ] + } + ] + } + ], + "id": 83 + }, + { + "display_name": "Water Mastery", + "desc": "Increases your base damage from all Water attacks", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 34, + 83, + 86 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 14, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "wDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "wDam", + "value": [ + 2, + 4 + ] + } + ] + } + ], + "id": 84 + }, + { + "display_name": "Air Mastery", + "desc": "Increases base damage from all Air attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 7 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "aDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "aDam", + "value": [ + 3, + 4 + ] + } + ] + } + ], + "id": 85 + }, + { + "display_name": "Fire Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 83, + 0, + 34 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 6 + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "fDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "fDam", + "value": [ + 3, + 5 + ] + } + ] + } + ], + "id": 86 + }, + { + "display_name": "More Shields", + "desc": "Give +2 charges to Arrow Shield.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 12, + 10 + ], + "dependencies": [ + 0 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 21, + "col": 7 + }, + "properties": { + "shieldCharges": 2 + }, + "id": 40 + }, + { + "display_name": "Stormy Feet", + "desc": "Windy Feet will last longer and add more speed.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 11 + ], + "dependencies": [ + 9 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 23, + "col": 1 + }, + "properties": { + "duration": 60 + }, + "effects": [ + { + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spdPct", + "value": 20 + } + ] + } + ], + "id": 41 + }, + { + "display_name": "Refined Gunpowder", + "desc": "Increase the damage of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 11 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 25, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [ + 50, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 42 + }, + { + "display_name": "More Traps", + "desc": "Increase the maximum amount of active Traps you can have by +2.", + "archetype": "Trapper", + "archetype_req": 10, + "parents": [ + 54 + ], + "dependencies": [ + 10 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 8 + }, + "properties": { + "traps": 2 + }, + "id": 43 + }, + { + "display_name": "Better Arrow Shield", + "desc": "Arrow Shield will gain additional area of effect, knockback and damage.", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 19, + 18, + 14 + ], + "dependencies": [ + 0 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 28, + "col": 6 + }, + "properties": { + "aoe": 1 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Shield", + "multipliers": [ + 40, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 44 + }, + { + "display_name": "Better Leap", + "desc": "Reduce leap's cooldown by 1s.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 17, + 55 + ], + "dependencies": [ + 17 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 1 + }, + "properties": { + "cooldown": -1 + }, + "id": 45 + }, + { + "display_name": "Better Guardian Angels", + "desc": "Your Guardian Angels can shoot +4 arrows before disappearing.", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 20, + 55 + ], + "dependencies": [ + 8 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 2 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 4 + } + } + ], + "id": 46 + }, + { + "display_name": "Cheaper Arrow Storm (2)", + "desc": "Reduce the Mana cost of Arrow Storm.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 21, + 19 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 8 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ], + "id": 47 + }, + { + "display_name": "Precise Shot", + "desc": "+30% Critical Hit Damage", + "archetype": "", + "archetype_req": 0, + "parents": [ + 46, + 49, + 23 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 33, + "col": 2 + }, + "properties": { + "mainAtk_range": 6 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdCritPct", + "value": 30 + } + ] + } + ], + "id": 48 + }, + { + "display_name": "Cheaper Arrow Shield", + "desc": "Reduce the Mana cost of Arrow Shield.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 48, + 21 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 33, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ], + "id": 49 + }, + { + "display_name": "Rocket Jump", + "desc": "Arrow Bomb's self-damage will knockback you farther away.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 47, + 21 + ], + "dependencies": [ + 2 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 33, + "col": 6 + }, + "properties": {}, + "id": 50 + }, + { + "display_name": "Cheaper Escape (2)", + "desc": "Reduce the Mana cost of Escape.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 22, + 70 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 7 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ], + "id": 51 + }, + { + "display_name": "Stronger Hook", + "desc": "Increase your Grappling Hook's range, speed and strength.", + "archetype": "Trapper", + "archetype_req": 5, + "parents": [ + 51 + ], + "dependencies": [ + 12 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 8 + }, + "properties": { + "range": 8 + }, + "id": 52 + }, + { + "display_name": "Cheaper Arrow Bomb (2)", + "desc": "Reduce the Mana cost of Arrow Bomb.", + "archetype": "", + "archetype_req": 0, + "parents": [ + 63, + 30 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 40, + "col": 5 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ], + "id": 53 + }, + { + "display_name": "Bouncing Bomb", + "desc": "Arrow Bomb will bounce once when hitting a block or enemy", + "archetype": "", + "archetype_req": 0, + "parents": [ + 40 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 25, + "col": 7 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Arrow Bomb": 2 + } + } + ], + "id": 54 + }, + { + "display_name": "Homing Shots", + "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity", + "archetype": "", + "archetype_req": 0, + "parents": [ + 17, + 18 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 2 + }, + "properties": {}, + "effects": [], + "id": 55 + }, + { + "display_name": "Shrapnel Bomb", + "desc": "Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area", + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": [ + 23, + 48 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 34, + "col": 1 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Shrapnel Bomb", + "cost": 0, + "multipliers": [ + 40, + 0, + 0, + 0, + 20, + 0 + ] + } + ], + "id": 56 + }, + { + "display_name": "Elusive", + "desc": "If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 24 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 38, + "col": 0 + }, + "properties": {}, + "effects": [], + "id": 57 + }, + { + "display_name": "Double Shots", + "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 1 + ], + "dependencies": [], + "blockers": [ + 60 + ], + "cost": 1, + "display": { + "row": 7, + "col": 2 + }, + "properties": { + "arrow": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 0, + "target_part": "Melee Damage", + "cost": 0, + "multipliers": 0.7 + } + ], + "id": 58 + }, + { + "display_name": "Triple Shots", + "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 69, + 67 + ], + "dependencies": [ + 58 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 0 + }, + "properties": { + "arrow": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 0, + "target_part": "Melee Damage", + "cost": 0, + "multipliers": 0.7 + } + ], + "id": 59 + }, + { + "display_name": "Power Shots", + "desc": "Main Attack arrows have increased speed and knockback", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 1 + ], + "dependencies": [], + "blockers": [ + 58 + ], + "cost": 1, + "display": { + "row": 7, + "col": 6 + }, + "properties": {}, + "effects": [], + "id": 60 + }, + { + "display_name": "Focus", + "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once", + "archetype": "Sharpshooter", + "archetype_req": 2, + "parents": [ + 68 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 19, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [ + 35 + ], + "max": 3 + } + ], + "id": 61 + }, + { + "display_name": "More Focus", + "desc": "Add +2 max Focus", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 33, + 12 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [ + 35 + ], + "max": 5 + } + ], + "id": 62 + }, + { + "display_name": "More Focus (2)", + "desc": "Add +2 max Focus", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 25, + 28 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 39, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Focus", + "output": { + "type": "stat", + "abil_name": "Focus", + "name": "dmgPct" + }, + "scaling": [ + 35 + ], + "max": 7 + } + ], + "id": 63 + }, + { + "display_name": "Traveler", + "desc": "For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)", + "archetype": "", + "archetype_req": 0, + "parents": [ + 42, + 14 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 25, + "col": 2 + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "spd" + } + ], + "output": { + "type": "stat", + "name": "sdRaw" + }, + "scaling": [ + 1 + ], + "max": 100 + } + ], + "id": 64 + }, + { + "display_name": "Patient Hunter", + "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%)", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 40 + ], + "dependencies": [ + 10 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 8 + }, + "properties": { + "max": 80 + }, + "effects": [], + "id": 65 + }, + { + "display_name": "Stronger Patient Hunter", + "desc": "Add +80% Max Damage to Patient Hunter", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 26 + ], + "dependencies": [ + 65 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 38, + "col": 8 + }, + "properties": { + "max": 80 + }, + "effects": [], + "id": 66 + }, + { + "display_name": "Frenzy", + "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second", + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": [ + 59, + 6 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 17, + "col": 2 + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Hits dealt", + "output": { + "type": "stat", + "name": "spd" + }, + "scaling": [ + 6 + ], + "max": 200 + } + ], + "id": 67 + }, + { + "display_name": "Phantom Ray", + "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 84, + 4 + ], + "dependencies": [ + 7 + ], + "blockers": [ + 11, + 6, + 23 + ], + "cost": 2, + "display": { + "row": 16, + "col": 4 + }, + "properties": {}, + "effects": [ + { + "type": "replace_spell", + "name": "Phantom Ray", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [ + 25, + 0, + 5, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Arrow": 16 + } + } + ] + } + ], + "id": 68 + }, + { + "display_name": "Arrow Rain", + "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies", + "archetype": "Trapper", + "archetype_req": 0, + "parents": [ + 6, + 85 + ], + "dependencies": [ + 0 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 0 + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Arrow Rain", + "cost": 0, + "multipliers": [ + 120, + 0, + 0, + 0, + 0, + 80 + ] + } + ], + "id": 69 + }, + { + "display_name": "Decimator", + "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)", + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": [ + 49 + ], + "dependencies": [ + 68 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 34, + "col": 5 + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Phantom Ray hits", + "output": { + "type": "stat", + "name": "PhRayDmg" + }, + "scaling": 10, + "max": 50 + } + ], + "id": 70 + } + ], + "Warrior": [ + { + "display_name": "Bash", + "desc": "Violently bash the ground, dealing high damage in a large area", + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 0, + "col": 4, + "icon": "node_4" + }, + "properties": { + "aoe": 4, + "range": 3 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Bash", + "cost": 45, + "display_text": "Total Damage Average", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Hit", + "type": "damage", + "multipliers": [ + 130, + 20, + 0, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Hit": 1 + } + } + ] + } + ], + "id": 71 + }, + { + "display_name": "Spear Proficiency 1", + "desc": "Improve your Main Attack's damage and range w/ spear", + "archetype": "", + "archetype_req": 0, + "parents": [ + 71 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 4, + "icon": "node_0" + }, + "properties": { + "melee_range": 1 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ], + "id": 72 + }, + { + "display_name": "Cheaper Bash", + "desc": "Reduce the Mana cost of Bash", + "archetype": "", + "archetype_req": 0, + "parents": [ + 72 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 2, + "col": 2, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -10 + } + ], + "id": 73 + }, + { + "display_name": "Double Bash", + "desc": "Bash will hit a second time at a farther range", + "archetype": "", + "archetype_req": 0, + "parents": [ + 72 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 4, + "col": 4, + "icon": "node_1" + }, + "properties": { + "range": 3 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "name": "Single Hit", + "value": 1 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [ + -50, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 74 + }, + { + "display_name": "Charge", + "desc": "Charge forward at high speed (hold shift to cancel)", + "archetype": "", + "archetype_req": 0, + "parents": [ + 74 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 6, + "col": 4, + "icon": "node_4" + }, + "properties": {}, + "effects": [ + { + "type": "replace_spell", + "name": "Charge", + "cost": 25, + "display_text": "Total Damage Average", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } + } + ] + } + ], + "id": 75 + }, + { + "display_name": "Heavy Impact", + "desc": "After using Charge, violently crash down into the ground and deal damage", + "archetype": "", + "archetype_req": 0, + "parents": [ + 79 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 9, + "col": 1, + "icon": "node_1" + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Heavy Impact", + "cost": 0, + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 76 + }, + { + "display_name": "Vehement", + "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 75 + ], + "dependencies": [], + "blockers": [ + 78 + ], + "cost": 1, + "display": { + "row": 6, + "col": 2, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "mdPct" + }, + { + "type": "stat", + "name": "mdRaw" + } + ], + "output": { + "type": "stat", + "name": "spd" + }, + "scaling": [ + 1, + 1 + ], + "max": 20 + } + ], + "id": 77 + }, + { + "display_name": "Tougher Skin", + "desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 75 + ], + "dependencies": [], + "blockers": [ + 77 + ], + "cost": 1, + "display": { + "row": 6, + "col": 6, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "baseResist", + "value": "5" + } + ] + }, + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hprRaw" + }, + { + "type": "stat", + "name": "hprPct" + } + ], + "output": { + "type": "stat", + "name": "hpBonus" + }, + "scaling": [ + 10, + 10 + ], + "max": 100 + } + ], + "id": 78 + }, + { + "display_name": "Uppercut", + "desc": "Rocket enemies in the air and deal massive damage", + "archetype": "", + "archetype_req": 0, + "parents": [ + 77 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 2, + "icon": "node_4" + }, + "properties": { + "aoe": 3, + "range": 5 + }, + "effects": [ + { + "type": "replace_spell", + "name": "Uppercut", + "cost": 45, + "display_text": "Total Damage Average", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "total", + "parts": [ + { + "name": "Uppercut", + "type": "damage", + "multipliers": [ + 150, + 50, + 50, + 0, + 0, + 0 + ] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Uppercut": 1 + } + } + ] + } + ], + "id": 79 + }, + { + "display_name": "Cheaper Charge", + "desc": "Reduce the Mana cost of Charge", + "archetype": "", + "archetype_req": 0, + "parents": [ + 79, + 81 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 4, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + } + ], + "id": 80 + }, + { + "display_name": "War Scream", + "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies", + "archetype": "", + "archetype_req": 0, + "parents": [ + 78 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 8, + "col": 6, + "icon": "node_4" + }, + "properties": { + "duration": 30, + "aoe": 12, + "defense_bonus": 10 + }, + "effects": [ + { + "type": "replace_spell", + "name": "War Scream", + "cost": 35, + "display_text": "War Scream", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage Average", + "parts": [ + { + "name": "War Scream", + "type": "damage", + "multipliers": [ + 50, + 0, + 0, + 0, + 50, + 0 + ] + } + ] + } + ], + "id": 81 + }, + { + "display_name": "Earth Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 79 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 0, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "eDamPct", + "value": 20 + }, + { + "type": "stat", + "name": "eDam", + "value": [ + 2, + 4 + ] + } + ] + } + ], + "id": 82 + }, + { + "display_name": "Thunder Mastery", + "desc": "Increases base damage from all Thunder attacks", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 79, + 85, + 80 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 2, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "tDamPct", + "value": 10 + }, + { + "type": "stat", + "name": "tDam", + "value": [ + 1, + 8 + ] + } + ] + } + ], + "id": 83 + }, + { + "display_name": "Water Mastery", + "desc": "Increases base damage from all Water attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 80, + 83, + 85 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 11, + "col": 4, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "wDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "wDam", + "value": [ + 2, + 4 + ] + } + ] + } + ], + "id": 84 + }, + { + "display_name": "Air Mastery", + "desc": "Increases base damage from all Air attacks", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 81, + 83, + 80 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 6, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "aDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "aDam", + "value": [ + 3, + 4 + ] + } + ] + } + ], + "id": 85 + }, + { + "display_name": "Fire Mastery", + "desc": "Increases base damage from all Earth attacks", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 81 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 10, + "col": 8, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "fDamPct", + "value": 15 + }, + { + "type": "stat", + "name": "fDam", + "value": [ + 3, + 5 + ] + } + ] + } + ], + "id": 86 + }, + { + "display_name": "Quadruple Bash", + "desc": "Bash will hit 4 times at an even larger range", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 82, + 88 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 0, + "icon": "node_1" + }, + "properties": { + "range": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Hit": 2 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [ + -20, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 87 + }, + { + "display_name": "Fireworks", + "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 83, + 87 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 2, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fireworks", + "cost": 0, + "multipliers": [ + 80, + 0, + 20, + 0, + 0, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fireworks": 1 + } + } + ], + "id": 88 + }, + { + "display_name": "Half-Moon Swipe", + "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": [ + 84 + ], + "dependencies": [ + 79 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 13, + "col": 4, + "icon": "node_1" + }, + "properties": { + "range": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": -10, + "multipliers": [ + -70, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "water" + } + ], + "id": 89 + }, + { + "display_name": "Flyby Jab", + "desc": "Damage enemies in your way when using Charge", + "archetype": "", + "archetype_req": 0, + "parents": [ + 85, + 91 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 6, + "icon": "node_1" + }, + "properties": { + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Flyby Jab", + "cost": 0, + "multipliers": [ + 20, + 0, + 0, + 0, + 0, + 40 + ] + } + ], + "id": 90 + }, + { + "display_name": "Flaming Uppercut", + "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 86, + 90 + ], + "dependencies": [ + 79 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 12, + "col": 8, + "icon": "node_1" + }, + "properties": { + "duration": 3, + "tick": 0.6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Flaming Uppercut", + "cost": 0, + "multipliers": [ + 0, + 0, + 0, + 0, + 50, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Flaming Uppercut Total Damage", + "cost": 0, + "hits": { + "Flaming Uppercut": 5 + } + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Flaming Uppercut": 5 + } + } + ], + "id": 91 + }, + { + "display_name": "Iron Lungs", + "desc": "War Scream deals more damage", + "archetype": "", + "archetype_req": 0, + "parents": [ + 90, + 91 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 13, + "col": 7, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "War Scream", + "cost": 0, + "multipliers": [ + 30, + 0, + 0, + 0, + 0, + 30 + ] + } + ], + "id": 92 + }, + { + "display_name": "Generalist", + "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana", + "archetype": "Battle Monk", + "archetype_req": 3, + "parents": [ + 94 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 2, + "icon": "node_3" + }, + "properties": {}, + "effects": [], + "id": 93 + }, + { + "display_name": "Counter", + "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 89 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 4, + "icon": "node_1" + }, + "properties": { + "chance": 30 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Counter", + "cost": 0, + "multipliers": [ + 60, + 0, + 20, + 0, + 0, + 20 + ] + } + ], + "id": 94 + }, + { + "display_name": "Mantle of the Bovemists", + "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)", + "archetype": "Paladin", + "archetype_req": 3, + "parents": [ + 92 + ], + "dependencies": [ + 81 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 15, + "col": 7, + "icon": "node_3" + }, + "properties": { + "mantle_charge": 3 + }, + "effects": [], + "id": 95 + }, + { + "display_name": "Bak'al's Grasp", + "desc": "After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)", + "archetype": "Fallen", + "archetype_req": 2, + "parents": [ + 87, + 88 + ], + "dependencies": [ + 81 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 16, + "col": 1, + "icon": "node_3" + }, + "properties": { + "cooldown": 15 + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "raw" + }, + "scaling": [ + 4 + ], + "slider_step": 2, + "max": 120 + } + ], + "id": 96 + }, + { + "display_name": "Spear Proficiency 2", + "desc": "Improve your Main Attack's damage and range w/ spear", + "archetype": "", + "archetype_req": 0, + "parents": [ + 96, + 98 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 0, + "icon": "node_0" + }, + "properties": { + "melee_range": 1 + }, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "mdPct", + "value": 5 + } + ] + } + ], + "id": 97 + }, + { + "display_name": "Cheaper Uppercut", + "desc": "Reduce the Mana Cost of Uppercut", + "archetype": "", + "archetype_req": 0, + "parents": [ + 97, + 99, + 94 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 3, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ], + "id": 98 + }, + { + "display_name": "Aerodynamics", + "desc": "During Charge, you can steer and change direction", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 98, + 100 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 17, + "col": 5, + "icon": "node_1" + }, + "properties": {}, + "effects": [], + "id": 99 + }, + { + "display_name": "Provoke", + "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 99, + 95 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 17, + "col": 7, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ], + "id": 100 + }, + { + "display_name": "Precise Strikes", + "desc": "+30% Critical Hit Damage", + "archetype": "", + "archetype_req": 0, + "parents": [ + 98, + 97 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 18, + "col": 2, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "critDmg", + "value": 30 + } + ] + } + ], + "id": 101 + }, + { + "display_name": "Air Shout", + "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", + "archetype": "", + "archetype_req": 0, + "parents": [ + 99, + 100 + ], + "dependencies": [ + 81 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 18, + "col": 6, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Air Shout", + "cost": 0, + "multipliers": [ + 20, + 0, + 0, + 0, + 0, + 5 + ] + } + ], + "id": 102 + }, + { + "display_name": "Enraged Blow", + "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 97 + ], + "dependencies": [ + 96 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 0, + "icon": "node_2" + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hpBonus" + } + ], + "output": { + "type": "stat", + "name": "dmgPct" + }, + "scaling": [ + 2 + ], + "max": 200 + } + ], + "id": 103 + }, + { + "display_name": "Flying Kick", + "desc": "When using Charge, mobs hit will halt your momentum and get knocked back", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": [ + 98, + 105 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 3, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Flying Kick", + "cost": 0, + "multipliers": [ + 120, + 0, + 0, + 10, + 0, + 20 + ] + } + ], + "id": 104 + }, + { + "display_name": "Stronger Mantle", + "desc": "Add +2 additional charges to Mantle of the Bovemists", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 106, + 104 + ], + "dependencies": [ + 95 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 20, + "col": 6, + "icon": "node_0" + }, + "properties": { + "mantle_charge": 2 + }, + "effects": [], + "id": 105 + }, + { + "display_name": "Manachism", + "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)", + "archetype": "Paladin", + "archetype_req": 3, + "parents": [ + 105, + 100 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 20, + "col": 8, + "icon": "node_2" + }, + "properties": { + "cooldown": 1 + }, + "effects": [], + "id": 106 + }, + { + "display_name": "Boiling Blood", + "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds", + "archetype": "", + "archetype_req": 0, + "parents": [ + 103, + 108 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 0, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Boiling Blood", + "cost": 0, + "multipliers": [ + 25, + 0, + 0, + 0, + 5, + 0 + ] + } + ], + "id": 107 + }, + { + "display_name": "Ragnarokkr", + "desc": "War Scream become deafening, increasing its range and giving damage bonus to players", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 107, + 104 + ], + "dependencies": [ + 81 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 22, + "col": 2, + "icon": "node_2" + }, + "properties": { + "damage_bonus": 30, + "aoe": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": 10 + } + ], + "id": 108 + }, + { + "display_name": "Ambidextrous", + "desc": "Increase your chance to attack with Counter by +30%", + "archetype": "", + "archetype_req": 0, + "parents": [ + 104, + 105, + 110 + ], + "dependencies": [ + 94 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 4, + "icon": "node_0" + }, + "properties": { + "chance": 30 + }, + "effects": [], + "id": 109 + }, + { + "display_name": "Burning Heart", + "desc": "For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 109, + 111 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 6, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": false, + "inputs": [ + { + "type": "stat", + "name": "hpBonus" + } + ], + "output": { + "type": "stat", + "name": "fDamPct" + }, + "scaling": [ + 2 + ], + "max": 100, + "slider_step": 100 + } + ], + "id": 110 + }, + { + "display_name": "Stronger Bash", + "desc": "Increase the damage of Bash", + "archetype": "", + "archetype_req": 0, + "parents": [ + 110, + 106 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 22, + "col": 8, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Hit", + "cost": 0, + "multipliers": [ + 30, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 111 + }, + { + "display_name": "Intoxicating Blood", + "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", + "archetype": "Fallen", + "archetype_req": 5, + "parents": [ + 108, + 107 + ], + "dependencies": [ + 96 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 1, + "icon": "node_1" + }, + "properties": {}, + "effects": [], + "id": 112 + }, + { + "display_name": "Comet", + "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 108 + ], + "dependencies": [ + 88 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 24, + "col": 2, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Comet", + "cost": 0, + "multipliers": [ + 80, + 20, + 0, + 0, + 0, + 0 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Comet": 1 + } + } + ], + "id": 113 + }, + { + "display_name": "Collide", + "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", + "archetype": "Battle Monk", + "archetype_req": 4, + "parents": [ + 109, + 110 + ], + "dependencies": [ + 104 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 5, + "icon": "node_1" + }, + "properties": { + "aoe": 4 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Collide", + "cost": 0, + "multipliers": [ + 100, + 0, + 0, + 0, + 50, + 0 + ] + } + ], + "id": 114 + }, + { + "display_name": "Rejuvenating Skin", + "desc": "Regain back 30% of the damage you take as healing over 30s", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 110, + 111 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 23, + "col": 7, + "icon": "node_3" + }, + "properties": {}, + "effects": [], + "id": 115 + }, + { + "display_name": "Uncontainable Corruption", + "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", + "archetype": "", + "archetype_req": 0, + "parents": [ + 107, + 117 + ], + "dependencies": [ + 96 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 0, + "icon": "node_0" + }, + "properties": { + "cooldown": -5 + }, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "raw" + }, + "scaling": [ + 1 + ], + "slider_step": 2, + "max": 50 + } + ], + "id": 116 + }, + { + "display_name": "Radiant Devotee", + "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)", + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": [ + 118, + 116 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 26, + "col": 2, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "inputs": [ + { + "type": "stat", + "name": "ref" + } + ], + "output": { + "type": "stat", + "name": "mr" + }, + "scaling": [ + 1 + ], + "max": 10, + "slider_step": 4 + } + ], + "id": 117 + }, + { + "display_name": "Whirlwind Strike", + "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": [ + 109, + 117 + ], + "dependencies": [ + 79 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 4, + "icon": "node_1" + }, + "properties": { + "range": 2 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": 0, + "multipliers": [ + 0, + 0, + 0, + 0, + 0, + 50 + ] + } + ], + "id": 118 + }, + { + "display_name": "Mythril Skin", + "desc": "Gain +5% Base Resistance and become immune to knockback", + "archetype": "Paladin", + "archetype_req": 6, + "parents": [ + 115 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 26, + "col": 7, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "baseResist", + "value": 5 + } + ] + } + ], + "id": 119 + }, + { + "display_name": "Armour Breaker", + "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 116, + 117 + ], + "dependencies": [ + 96 + ], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 1, + "icon": "node_2" + }, + "properties": { + "duration": 5 + }, + "effects": [], + "id": 120 + }, + { + "display_name": "Shield Strike", + "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 119, + 122 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 6, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Shield Strike", + "cost": 0, + "multipliers": [ + 60, + 0, + 20, + 0, + 0, + 0 + ] + } + ], + "id": 121 + }, + { + "display_name": "Sparkling Hope", + "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 119 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 27, + "col": 8, + "icon": "node_2" + }, + "properties": { + "aoe": 6 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 5, + "target_part": "Sparkling Hope", + "cost": 0, + "multipliers": [ + 10, + 0, + 5, + 0, + 0, + 0 + ] + } + ], + "id": 122 + }, + { + "display_name": "Massive Bash", + "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", + "archetype": "Fallen", + "archetype_req": 8, + "parents": [ + 124, + 116 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 0, + "icon": "node_2" + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Corrupted", + "output": { + "type": "stat", + "name": "bashAoE" + }, + "scaling": [ + 1 + ], + "max": 10, + "slider_step": 3 + } + ], + "id": 123 + }, + { + "display_name": "Tempest", + "desc": "War Scream will ripple the ground and deal damage 3 times in a large area", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 123, + 125 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 28, + "col": 2, + "icon": "node_1" + }, + "properties": { + "aoe": 16 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Tempest", + "cost": "0", + "multipliers": [ + 30, + 10, + 0, + 0, + 0, + 10 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Tempest Total Damage", + "cost": "0", + "hits": { + "Tempest": 3 + } + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Total Damage", + "cost": "0", + "hits": { + "Tempest": 3 + } + } + ], + "id": 124 + }, + { + "display_name": "Spirit of the Rabbit", + "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": [ + 124, + 118 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 28, + "col": 4, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 2, + "cost": -5 + }, + { + "type": "raw_stat", + "bonuses": [ + { + "type": "stat", + "name": "spd", + "value": 20 + } + ] + } + ], + "id": 125 + }, + { + "display_name": "Massacre", + "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", + "archetype": "Fallen", + "archetype_req": 5, + "parents": [ + 124, + 123 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 1, + "icon": "node_1" + }, + "properties": {}, + "effects": [], + "id": 126 + }, + { + "display_name": "Axe Kick", + "desc": "Increase the damage of Uppercut, but also increase its mana cost", + "archetype": "", + "archetype_req": 0, + "parents": [ + 124, + 125 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 3, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Uppercut", + "cost": 10, + "multipliers": [ + 100, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "id": 127 + }, + { + "display_name": "Radiance", + "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", + "archetype": "Paladin", + "archetype_req": 2, + "parents": [ + 125, + 129 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 29, + "col": 5, + "icon": "node_2" + }, + "properties": { + "cooldown": 15 + }, + "effects": [], + "id": 128 + }, + { + "display_name": "Cheaper Bash 2", + "desc": "Reduce the Mana cost of Bash", + "archetype": "", + "archetype_req": 0, + "parents": [ + 128, + 121, + 122 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 29, + "col": 7, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "cost": -5 + } + ], + "id": 129 + }, + { + "display_name": "Cheaper War Scream", + "desc": "Reduce the Mana cost of War Scream", + "archetype": "", + "archetype_req": 0, + "parents": [ + 123 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 0, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "cost": -5 + } + ], + "id": 130 + }, + { + "display_name": "Discombobulate", + "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", + "archetype": "Battle Monk", + "archetype_req": 12, + "parents": [ + 133 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 31, + "col": 2, + "icon": "node_3" + }, + "properties": {}, + "effects": [ + { + "type": "stat_scaling", + "slider": true, + "slider_name": "Hits dealt", + "output": { + "type": "stat", + "name": "rainrawButDifferent" + }, + "scaling": [ + 2 + ], + "max": 50 + } + ], + "id": 131 + }, + { + "display_name": "Thunderclap", + "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", + "archetype": "Battle Monk", + "archetype_req": 8, + "parents": [ + 133 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 5, + "icon": "node_1" + }, + "properties": {}, + "effects": [ + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + }, + { + "type": "raw_stat", + "bonuses": [ + { + "type": "prop", + "abil_name": "Bash", + "name": "aoe", + "value": 3 + } + ] + } + ], + "id": 132 + }, + { + "display_name": "Cyclone", + "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": [ + 125 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 31, + "col": 4, + "icon": "node_1" + }, + "properties": { + "aoe": 4, + "duration": 20 + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Cyclone", + "cost": 0, + "multipliers": [ + 10, + 0, + 0, + 0, + 5, + 10 + ] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Cyclone Total Damage", + "cost": 0, + "hits": { + "Cyclone": 40 + } + } + ], + "id": 133 + }, + { + "display_name": "Second Chance", + "desc": "When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)", + "archetype": "Paladin", + "archetype_req": 12, + "parents": [ + 129 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 32, + "col": 7, + "icon": "node_3" + }, + "properties": {}, + "effects": [], + "id": 134 + }, + { + "display_name": "Blood Pact", + "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", + "archetype": "", + "archetype_req": 10, + "parents": [ + 130 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 34, + "col": 1, + "icon": "node_3" + }, + "properties": {}, + "effects": [], + "id": 135 + }, + { + "display_name": "Haemorrhage", + "desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", + "archetype": "Fallen", + "archetype_req": 0, + "parents": [ + 135 + ], + "dependencies": [ + 135 + ], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 2, + "icon": "node_1" + }, + "properties": {}, + "effects": [], + "id": 136 + }, + { + "display_name": "Brink of Madness", + "desc": "If your health is 25% full or less, gain +40% Resistance", + "archetype": "", + "archetype_req": 0, + "parents": [ + 135, + 138 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 35, + "col": 4, + "icon": "node_2" + }, + "properties": {}, + "effects": [], + "id": 137 + }, + { + "display_name": "Cheaper Uppercut 2", + "desc": "Reduce the Mana cost of Uppercut", + "archetype": "", + "archetype_req": 0, + "parents": [ + 134, + 137 + ], + "dependencies": [], + "blockers": [], + "cost": 1, + "display": { + "row": 35, + "col": 6, + "icon": "node_0" + }, + "properties": {}, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "cost": -5 + } + ], + "id": 138 + }, + { + "display_name": "Martyr", + "desc": "When you receive a fatal blow, all nearby allies become invincible", + "archetype": "Paladin", + "archetype_req": 0, + "parents": [ + 134 + ], + "dependencies": [], + "blockers": [], + "cost": 2, + "display": { + "row": 35, + "col": 8, + "icon": "node_1" + }, + "properties": { + "duration": 3, + "aoe": 12 + }, + "effects": [], + "id": 139 + } + ] +} \ No newline at end of file diff --git a/textures/items/boots/boots--chain.png b/py_script/textures/items/boots/boots--chain.png similarity index 100% rename from textures/items/boots/boots--chain.png rename to py_script/textures/items/boots/boots--chain.png diff --git a/textures/items/boots/boots--diamond.png b/py_script/textures/items/boots/boots--diamond.png similarity index 100% rename from textures/items/boots/boots--diamond.png rename to py_script/textures/items/boots/boots--diamond.png diff --git a/textures/items/boots/boots--golden.png b/py_script/textures/items/boots/boots--golden.png similarity index 100% rename from textures/items/boots/boots--golden.png rename to py_script/textures/items/boots/boots--golden.png diff --git a/textures/items/boots/boots--iron.png b/py_script/textures/items/boots/boots--iron.png similarity index 100% rename from textures/items/boots/boots--iron.png rename to py_script/textures/items/boots/boots--iron.png diff --git a/textures/items/boots/boots--leather.png b/py_script/textures/items/boots/boots--leather.png similarity index 100% rename from textures/items/boots/boots--leather.png rename to py_script/textures/items/boots/boots--leather.png diff --git a/textures/items/bow/bow--air1.png b/py_script/textures/items/bow/bow--air1.png similarity index 100% rename from textures/items/bow/bow--air1.png rename to py_script/textures/items/bow/bow--air1.png diff --git a/textures/items/bow/bow--air2.png b/py_script/textures/items/bow/bow--air2.png similarity index 100% rename from textures/items/bow/bow--air2.png rename to py_script/textures/items/bow/bow--air2.png diff --git a/textures/items/bow/bow--air3.png b/py_script/textures/items/bow/bow--air3.png similarity index 100% rename from textures/items/bow/bow--air3.png rename to py_script/textures/items/bow/bow--air3.png diff --git a/textures/items/bow/bow--default1.png b/py_script/textures/items/bow/bow--default1.png similarity index 100% rename from textures/items/bow/bow--default1.png rename to py_script/textures/items/bow/bow--default1.png diff --git a/textures/items/bow/bow--default2.png b/py_script/textures/items/bow/bow--default2.png similarity index 100% rename from textures/items/bow/bow--default2.png rename to py_script/textures/items/bow/bow--default2.png diff --git a/textures/items/bow/bow--earth1.png b/py_script/textures/items/bow/bow--earth1.png similarity index 100% rename from textures/items/bow/bow--earth1.png rename to py_script/textures/items/bow/bow--earth1.png diff --git a/textures/items/bow/bow--earth2.png b/py_script/textures/items/bow/bow--earth2.png similarity index 100% rename from textures/items/bow/bow--earth2.png rename to py_script/textures/items/bow/bow--earth2.png diff --git a/textures/items/bow/bow--earth3.png b/py_script/textures/items/bow/bow--earth3.png similarity index 100% rename from textures/items/bow/bow--earth3.png rename to py_script/textures/items/bow/bow--earth3.png diff --git a/textures/items/bow/bow--fire1.png b/py_script/textures/items/bow/bow--fire1.png similarity index 100% rename from textures/items/bow/bow--fire1.png rename to py_script/textures/items/bow/bow--fire1.png diff --git a/textures/items/bow/bow--fire2.png b/py_script/textures/items/bow/bow--fire2.png similarity index 100% rename from textures/items/bow/bow--fire2.png rename to py_script/textures/items/bow/bow--fire2.png diff --git a/textures/items/bow/bow--fire3.png b/py_script/textures/items/bow/bow--fire3.png similarity index 100% rename from textures/items/bow/bow--fire3.png rename to py_script/textures/items/bow/bow--fire3.png diff --git a/textures/items/bow/bow--generic1.png b/py_script/textures/items/bow/bow--generic1.png similarity index 100% rename from textures/items/bow/bow--generic1.png rename to py_script/textures/items/bow/bow--generic1.png diff --git a/textures/items/bow/bow--generic2.png b/py_script/textures/items/bow/bow--generic2.png similarity index 100% rename from textures/items/bow/bow--generic2.png rename to py_script/textures/items/bow/bow--generic2.png diff --git a/textures/items/bow/bow--generic3.png b/py_script/textures/items/bow/bow--generic3.png similarity index 100% rename from textures/items/bow/bow--generic3.png rename to py_script/textures/items/bow/bow--generic3.png diff --git a/textures/items/bow/bow--thunder1.png b/py_script/textures/items/bow/bow--thunder1.png similarity index 100% rename from textures/items/bow/bow--thunder1.png rename to py_script/textures/items/bow/bow--thunder1.png diff --git a/textures/items/bow/bow--thunder2.png b/py_script/textures/items/bow/bow--thunder2.png similarity index 100% rename from textures/items/bow/bow--thunder2.png rename to py_script/textures/items/bow/bow--thunder2.png diff --git a/textures/items/bow/bow--thunder3.png b/py_script/textures/items/bow/bow--thunder3.png similarity index 100% rename from textures/items/bow/bow--thunder3.png rename to py_script/textures/items/bow/bow--thunder3.png diff --git a/textures/items/bow/bow--water1.png b/py_script/textures/items/bow/bow--water1.png similarity index 100% rename from textures/items/bow/bow--water1.png rename to py_script/textures/items/bow/bow--water1.png diff --git a/textures/items/bow/bow--water2.png b/py_script/textures/items/bow/bow--water2.png similarity index 100% rename from textures/items/bow/bow--water2.png rename to py_script/textures/items/bow/bow--water2.png diff --git a/textures/items/bow/bow--water3.png b/py_script/textures/items/bow/bow--water3.png similarity index 100% rename from textures/items/bow/bow--water3.png rename to py_script/textures/items/bow/bow--water3.png diff --git a/textures/items/chestplate/chestplate--chain.png b/py_script/textures/items/chestplate/chestplate--chain.png similarity index 100% rename from textures/items/chestplate/chestplate--chain.png rename to py_script/textures/items/chestplate/chestplate--chain.png diff --git a/textures/items/chestplate/chestplate--diamond.png b/py_script/textures/items/chestplate/chestplate--diamond.png similarity index 100% rename from textures/items/chestplate/chestplate--diamond.png rename to py_script/textures/items/chestplate/chestplate--diamond.png diff --git a/textures/items/chestplate/chestplate--golden.png b/py_script/textures/items/chestplate/chestplate--golden.png similarity index 100% rename from textures/items/chestplate/chestplate--golden.png rename to py_script/textures/items/chestplate/chestplate--golden.png diff --git a/textures/items/chestplate/chestplate--iron.png b/py_script/textures/items/chestplate/chestplate--iron.png similarity index 100% rename from textures/items/chestplate/chestplate--iron.png rename to py_script/textures/items/chestplate/chestplate--iron.png diff --git a/textures/items/chestplate/chestplate--leather.png b/py_script/textures/items/chestplate/chestplate--leather.png similarity index 100% rename from textures/items/chestplate/chestplate--leather.png rename to py_script/textures/items/chestplate/chestplate--leather.png diff --git a/textures/items/dagger/dagger--air1.png b/py_script/textures/items/dagger/dagger--air1.png similarity index 100% rename from textures/items/dagger/dagger--air1.png rename to py_script/textures/items/dagger/dagger--air1.png diff --git a/textures/items/dagger/dagger--air2.png b/py_script/textures/items/dagger/dagger--air2.png similarity index 100% rename from textures/items/dagger/dagger--air2.png rename to py_script/textures/items/dagger/dagger--air2.png diff --git a/textures/items/dagger/dagger--air3.png b/py_script/textures/items/dagger/dagger--air3.png similarity index 100% rename from textures/items/dagger/dagger--air3.png rename to py_script/textures/items/dagger/dagger--air3.png diff --git a/textures/items/dagger/dagger--default1.png b/py_script/textures/items/dagger/dagger--default1.png similarity index 100% rename from textures/items/dagger/dagger--default1.png rename to py_script/textures/items/dagger/dagger--default1.png diff --git a/textures/items/dagger/dagger--default2.png b/py_script/textures/items/dagger/dagger--default2.png similarity index 100% rename from textures/items/dagger/dagger--default2.png rename to py_script/textures/items/dagger/dagger--default2.png diff --git a/textures/items/dagger/dagger--earth1.png b/py_script/textures/items/dagger/dagger--earth1.png similarity index 100% rename from textures/items/dagger/dagger--earth1.png rename to py_script/textures/items/dagger/dagger--earth1.png diff --git a/textures/items/dagger/dagger--earth2.png b/py_script/textures/items/dagger/dagger--earth2.png similarity index 100% rename from textures/items/dagger/dagger--earth2.png rename to py_script/textures/items/dagger/dagger--earth2.png diff --git a/textures/items/dagger/dagger--earth3.png b/py_script/textures/items/dagger/dagger--earth3.png similarity index 100% rename from textures/items/dagger/dagger--earth3.png rename to py_script/textures/items/dagger/dagger--earth3.png diff --git a/textures/items/dagger/dagger--fire1.png b/py_script/textures/items/dagger/dagger--fire1.png similarity index 100% rename from textures/items/dagger/dagger--fire1.png rename to py_script/textures/items/dagger/dagger--fire1.png diff --git a/textures/items/dagger/dagger--fire2.png b/py_script/textures/items/dagger/dagger--fire2.png similarity index 100% rename from textures/items/dagger/dagger--fire2.png rename to py_script/textures/items/dagger/dagger--fire2.png diff --git a/textures/items/dagger/dagger--fire3.png b/py_script/textures/items/dagger/dagger--fire3.png similarity index 100% rename from textures/items/dagger/dagger--fire3.png rename to py_script/textures/items/dagger/dagger--fire3.png diff --git a/textures/items/dagger/dagger--generic1.png b/py_script/textures/items/dagger/dagger--generic1.png similarity index 100% rename from textures/items/dagger/dagger--generic1.png rename to py_script/textures/items/dagger/dagger--generic1.png diff --git a/textures/items/dagger/dagger--generic2.png b/py_script/textures/items/dagger/dagger--generic2.png similarity index 100% rename from textures/items/dagger/dagger--generic2.png rename to py_script/textures/items/dagger/dagger--generic2.png diff --git a/textures/items/dagger/dagger--generic3.png b/py_script/textures/items/dagger/dagger--generic3.png similarity index 100% rename from textures/items/dagger/dagger--generic3.png rename to py_script/textures/items/dagger/dagger--generic3.png diff --git a/textures/items/dagger/dagger--thunder1.png b/py_script/textures/items/dagger/dagger--thunder1.png similarity index 100% rename from textures/items/dagger/dagger--thunder1.png rename to py_script/textures/items/dagger/dagger--thunder1.png diff --git a/textures/items/dagger/dagger--thunder2.png b/py_script/textures/items/dagger/dagger--thunder2.png similarity index 100% rename from textures/items/dagger/dagger--thunder2.png rename to py_script/textures/items/dagger/dagger--thunder2.png diff --git a/textures/items/dagger/dagger--thunder3.png b/py_script/textures/items/dagger/dagger--thunder3.png similarity index 100% rename from textures/items/dagger/dagger--thunder3.png rename to py_script/textures/items/dagger/dagger--thunder3.png diff --git a/textures/items/dagger/dagger--water1.png b/py_script/textures/items/dagger/dagger--water1.png similarity index 100% rename from textures/items/dagger/dagger--water1.png rename to py_script/textures/items/dagger/dagger--water1.png diff --git a/textures/items/dagger/dagger--water2.png b/py_script/textures/items/dagger/dagger--water2.png similarity index 100% rename from textures/items/dagger/dagger--water2.png rename to py_script/textures/items/dagger/dagger--water2.png diff --git a/textures/items/dagger/dagger--water3.png b/py_script/textures/items/dagger/dagger--water3.png similarity index 100% rename from textures/items/dagger/dagger--water3.png rename to py_script/textures/items/dagger/dagger--water3.png diff --git a/textures/items/helmet/helmet--chain.png b/py_script/textures/items/helmet/helmet--chain.png similarity index 100% rename from textures/items/helmet/helmet--chain.png rename to py_script/textures/items/helmet/helmet--chain.png diff --git a/textures/items/helmet/helmet--diamond.png b/py_script/textures/items/helmet/helmet--diamond.png similarity index 100% rename from textures/items/helmet/helmet--diamond.png rename to py_script/textures/items/helmet/helmet--diamond.png diff --git a/textures/items/helmet/helmet--golden.png b/py_script/textures/items/helmet/helmet--golden.png similarity index 100% rename from textures/items/helmet/helmet--golden.png rename to py_script/textures/items/helmet/helmet--golden.png diff --git a/textures/items/helmet/helmet--iron.png b/py_script/textures/items/helmet/helmet--iron.png similarity index 100% rename from textures/items/helmet/helmet--iron.png rename to py_script/textures/items/helmet/helmet--iron.png diff --git a/textures/items/helmet/helmet--leather.png b/py_script/textures/items/helmet/helmet--leather.png similarity index 100% rename from textures/items/helmet/helmet--leather.png rename to py_script/textures/items/helmet/helmet--leather.png diff --git a/textures/items/leggings/leggings--chain.png b/py_script/textures/items/leggings/leggings--chain.png similarity index 100% rename from textures/items/leggings/leggings--chain.png rename to py_script/textures/items/leggings/leggings--chain.png diff --git a/textures/items/leggings/leggings--diamond.png b/py_script/textures/items/leggings/leggings--diamond.png similarity index 100% rename from textures/items/leggings/leggings--diamond.png rename to py_script/textures/items/leggings/leggings--diamond.png diff --git a/textures/items/leggings/leggings--golden.png b/py_script/textures/items/leggings/leggings--golden.png similarity index 100% rename from textures/items/leggings/leggings--golden.png rename to py_script/textures/items/leggings/leggings--golden.png diff --git a/textures/items/leggings/leggings--iron.png b/py_script/textures/items/leggings/leggings--iron.png similarity index 100% rename from textures/items/leggings/leggings--iron.png rename to py_script/textures/items/leggings/leggings--iron.png diff --git a/textures/items/leggings/leggings--leather.png b/py_script/textures/items/leggings/leggings--leather.png similarity index 100% rename from textures/items/leggings/leggings--leather.png rename to py_script/textures/items/leggings/leggings--leather.png diff --git a/textures/items/relik/relik--air1.png b/py_script/textures/items/relik/relik--air1.png similarity index 100% rename from textures/items/relik/relik--air1.png rename to py_script/textures/items/relik/relik--air1.png diff --git a/textures/items/relik/relik--air2.png b/py_script/textures/items/relik/relik--air2.png similarity index 100% rename from textures/items/relik/relik--air2.png rename to py_script/textures/items/relik/relik--air2.png diff --git a/textures/items/relik/relik--air3.png b/py_script/textures/items/relik/relik--air3.png similarity index 100% rename from textures/items/relik/relik--air3.png rename to py_script/textures/items/relik/relik--air3.png diff --git a/textures/items/relik/relik--default1.png b/py_script/textures/items/relik/relik--default1.png similarity index 100% rename from textures/items/relik/relik--default1.png rename to py_script/textures/items/relik/relik--default1.png diff --git a/textures/items/relik/relik--default2.png b/py_script/textures/items/relik/relik--default2.png similarity index 100% rename from textures/items/relik/relik--default2.png rename to py_script/textures/items/relik/relik--default2.png diff --git a/textures/items/relik/relik--earth1.png b/py_script/textures/items/relik/relik--earth1.png similarity index 100% rename from textures/items/relik/relik--earth1.png rename to py_script/textures/items/relik/relik--earth1.png diff --git a/textures/items/relik/relik--earth2.png b/py_script/textures/items/relik/relik--earth2.png similarity index 100% rename from textures/items/relik/relik--earth2.png rename to py_script/textures/items/relik/relik--earth2.png diff --git a/textures/items/relik/relik--earth3.png b/py_script/textures/items/relik/relik--earth3.png similarity index 100% rename from textures/items/relik/relik--earth3.png rename to py_script/textures/items/relik/relik--earth3.png diff --git a/textures/items/relik/relik--fire1.png b/py_script/textures/items/relik/relik--fire1.png similarity index 100% rename from textures/items/relik/relik--fire1.png rename to py_script/textures/items/relik/relik--fire1.png diff --git a/textures/items/relik/relik--fire2.png b/py_script/textures/items/relik/relik--fire2.png similarity index 100% rename from textures/items/relik/relik--fire2.png rename to py_script/textures/items/relik/relik--fire2.png diff --git a/textures/items/relik/relik--fire3.png b/py_script/textures/items/relik/relik--fire3.png similarity index 100% rename from textures/items/relik/relik--fire3.png rename to py_script/textures/items/relik/relik--fire3.png diff --git a/textures/items/relik/relik--generic1.png b/py_script/textures/items/relik/relik--generic1.png similarity index 100% rename from textures/items/relik/relik--generic1.png rename to py_script/textures/items/relik/relik--generic1.png diff --git a/textures/items/relik/relik--generic2.png b/py_script/textures/items/relik/relik--generic2.png similarity index 100% rename from textures/items/relik/relik--generic2.png rename to py_script/textures/items/relik/relik--generic2.png diff --git a/textures/items/relik/relik--generic3.png b/py_script/textures/items/relik/relik--generic3.png similarity index 99% rename from textures/items/relik/relik--generic3.png rename to py_script/textures/items/relik/relik--generic3.png index fe68d65..a4530e4 100644 --- a/textures/items/relik/relik--generic3.png +++ b/py_script/textures/items/relik/relik--generic3.png @@ -2,7 +2,7 @@ - 404 Not Found +<title> 404 Not Found
diff --git a/textures/items/relik/relik--thunder1.png b/py_script/textures/items/relik/relik--thunder1.png similarity index 100% rename from textures/items/relik/relik--thunder1.png rename to py_script/textures/items/relik/relik--thunder1.png diff --git a/textures/items/relik/relik--thunder2.png b/py_script/textures/items/relik/relik--thunder2.png similarity index 100% rename from textures/items/relik/relik--thunder2.png rename to py_script/textures/items/relik/relik--thunder2.png diff --git a/textures/items/relik/relik--thunder3.png b/py_script/textures/items/relik/relik--thunder3.png similarity index 100% rename from textures/items/relik/relik--thunder3.png rename to py_script/textures/items/relik/relik--thunder3.png diff --git a/textures/items/relik/relik--water1.png b/py_script/textures/items/relik/relik--water1.png similarity index 99% rename from textures/items/relik/relik--water1.png rename to py_script/textures/items/relik/relik--water1.png index fe68d65..a4530e4 100644 --- a/textures/items/relik/relik--water1.png +++ b/py_script/textures/items/relik/relik--water1.png @@ -2,7 +2,7 @@ - 404 Not Found +<title> 404 Not Found
diff --git a/textures/items/relik/relik--water2.png b/py_script/textures/items/relik/relik--water2.png similarity index 99% rename from textures/items/relik/relik--water2.png rename to py_script/textures/items/relik/relik--water2.png index fe68d65..a4530e4 100644 --- a/textures/items/relik/relik--water2.png +++ b/py_script/textures/items/relik/relik--water2.png @@ -2,7 +2,7 @@ - 404 Not Found +<title> 404 Not Found
diff --git a/textures/items/relik/relik--water3.png b/py_script/textures/items/relik/relik--water3.png similarity index 99% rename from textures/items/relik/relik--water3.png rename to py_script/textures/items/relik/relik--water3.png index fe68d65..a4530e4 100644 --- a/textures/items/relik/relik--water3.png +++ b/py_script/textures/items/relik/relik--water3.png @@ -2,7 +2,7 @@ - 404 Not Found +<title> 404 Not Found
diff --git a/textures/items/spear/spear--air1.png b/py_script/textures/items/spear/spear--air1.png similarity index 100% rename from textures/items/spear/spear--air1.png rename to py_script/textures/items/spear/spear--air1.png diff --git a/textures/items/spear/spear--air2.png b/py_script/textures/items/spear/spear--air2.png similarity index 100% rename from textures/items/spear/spear--air2.png rename to py_script/textures/items/spear/spear--air2.png diff --git a/textures/items/spear/spear--air3.png b/py_script/textures/items/spear/spear--air3.png similarity index 100% rename from textures/items/spear/spear--air3.png rename to py_script/textures/items/spear/spear--air3.png diff --git a/textures/items/spear/spear--default1.png b/py_script/textures/items/spear/spear--default1.png similarity index 100% rename from textures/items/spear/spear--default1.png rename to py_script/textures/items/spear/spear--default1.png diff --git a/textures/items/spear/spear--default2.png b/py_script/textures/items/spear/spear--default2.png similarity index 100% rename from textures/items/spear/spear--default2.png rename to py_script/textures/items/spear/spear--default2.png diff --git a/textures/items/spear/spear--earth1.png b/py_script/textures/items/spear/spear--earth1.png similarity index 100% rename from textures/items/spear/spear--earth1.png rename to py_script/textures/items/spear/spear--earth1.png diff --git a/textures/items/spear/spear--earth2.png b/py_script/textures/items/spear/spear--earth2.png similarity index 100% rename from textures/items/spear/spear--earth2.png rename to py_script/textures/items/spear/spear--earth2.png diff --git a/textures/items/spear/spear--earth3.png b/py_script/textures/items/spear/spear--earth3.png similarity index 100% rename from textures/items/spear/spear--earth3.png rename to py_script/textures/items/spear/spear--earth3.png diff --git a/textures/items/spear/spear--fire1.png b/py_script/textures/items/spear/spear--fire1.png similarity index 100% rename from textures/items/spear/spear--fire1.png rename to py_script/textures/items/spear/spear--fire1.png diff --git a/textures/items/spear/spear--fire2.png b/py_script/textures/items/spear/spear--fire2.png similarity index 100% rename from textures/items/spear/spear--fire2.png rename to py_script/textures/items/spear/spear--fire2.png diff --git a/textures/items/spear/spear--fire3.png b/py_script/textures/items/spear/spear--fire3.png similarity index 100% rename from textures/items/spear/spear--fire3.png rename to py_script/textures/items/spear/spear--fire3.png diff --git a/textures/items/spear/spear--generic1.png b/py_script/textures/items/spear/spear--generic1.png similarity index 100% rename from textures/items/spear/spear--generic1.png rename to py_script/textures/items/spear/spear--generic1.png diff --git a/textures/items/spear/spear--generic2.png b/py_script/textures/items/spear/spear--generic2.png similarity index 100% rename from textures/items/spear/spear--generic2.png rename to py_script/textures/items/spear/spear--generic2.png diff --git a/textures/items/spear/spear--generic3.png b/py_script/textures/items/spear/spear--generic3.png similarity index 100% rename from textures/items/spear/spear--generic3.png rename to py_script/textures/items/spear/spear--generic3.png diff --git a/textures/items/spear/spear--thunder1.png b/py_script/textures/items/spear/spear--thunder1.png similarity index 100% rename from textures/items/spear/spear--thunder1.png rename to py_script/textures/items/spear/spear--thunder1.png diff --git a/textures/items/spear/spear--thunder2.png b/py_script/textures/items/spear/spear--thunder2.png similarity index 100% rename from textures/items/spear/spear--thunder2.png rename to py_script/textures/items/spear/spear--thunder2.png diff --git a/textures/items/spear/spear--thunder3.png b/py_script/textures/items/spear/spear--thunder3.png similarity index 100% rename from textures/items/spear/spear--thunder3.png rename to py_script/textures/items/spear/spear--thunder3.png diff --git a/textures/items/spear/spear--water1.png b/py_script/textures/items/spear/spear--water1.png similarity index 100% rename from textures/items/spear/spear--water1.png rename to py_script/textures/items/spear/spear--water1.png diff --git a/textures/items/spear/spear--water2.png b/py_script/textures/items/spear/spear--water2.png similarity index 100% rename from textures/items/spear/spear--water2.png rename to py_script/textures/items/spear/spear--water2.png diff --git a/textures/items/spear/spear--water3.png b/py_script/textures/items/spear/spear--water3.png similarity index 100% rename from textures/items/spear/spear--water3.png rename to py_script/textures/items/spear/spear--water3.png diff --git a/textures/items/wand/wand--air1.png b/py_script/textures/items/wand/wand--air1.png similarity index 100% rename from textures/items/wand/wand--air1.png rename to py_script/textures/items/wand/wand--air1.png diff --git a/textures/items/wand/wand--air2.png b/py_script/textures/items/wand/wand--air2.png similarity index 100% rename from textures/items/wand/wand--air2.png rename to py_script/textures/items/wand/wand--air2.png diff --git a/textures/items/wand/wand--air3.png b/py_script/textures/items/wand/wand--air3.png similarity index 100% rename from textures/items/wand/wand--air3.png rename to py_script/textures/items/wand/wand--air3.png diff --git a/textures/items/wand/wand--default1.png b/py_script/textures/items/wand/wand--default1.png similarity index 100% rename from textures/items/wand/wand--default1.png rename to py_script/textures/items/wand/wand--default1.png diff --git a/textures/items/wand/wand--default2.png b/py_script/textures/items/wand/wand--default2.png similarity index 100% rename from textures/items/wand/wand--default2.png rename to py_script/textures/items/wand/wand--default2.png diff --git a/textures/items/wand/wand--earth1.png b/py_script/textures/items/wand/wand--earth1.png similarity index 100% rename from textures/items/wand/wand--earth1.png rename to py_script/textures/items/wand/wand--earth1.png diff --git a/textures/items/wand/wand--earth2.png b/py_script/textures/items/wand/wand--earth2.png similarity index 100% rename from textures/items/wand/wand--earth2.png rename to py_script/textures/items/wand/wand--earth2.png diff --git a/textures/items/wand/wand--earth3.png b/py_script/textures/items/wand/wand--earth3.png similarity index 100% rename from textures/items/wand/wand--earth3.png rename to py_script/textures/items/wand/wand--earth3.png diff --git a/textures/items/wand/wand--fire1.png b/py_script/textures/items/wand/wand--fire1.png similarity index 100% rename from textures/items/wand/wand--fire1.png rename to py_script/textures/items/wand/wand--fire1.png diff --git a/textures/items/wand/wand--fire2.png b/py_script/textures/items/wand/wand--fire2.png similarity index 100% rename from textures/items/wand/wand--fire2.png rename to py_script/textures/items/wand/wand--fire2.png diff --git a/textures/items/wand/wand--fire3.png b/py_script/textures/items/wand/wand--fire3.png similarity index 100% rename from textures/items/wand/wand--fire3.png rename to py_script/textures/items/wand/wand--fire3.png diff --git a/textures/items/wand/wand--generic1.png b/py_script/textures/items/wand/wand--generic1.png similarity index 100% rename from textures/items/wand/wand--generic1.png rename to py_script/textures/items/wand/wand--generic1.png diff --git a/textures/items/wand/wand--generic2.png b/py_script/textures/items/wand/wand--generic2.png similarity index 100% rename from textures/items/wand/wand--generic2.png rename to py_script/textures/items/wand/wand--generic2.png diff --git a/textures/items/wand/wand--generic3.png b/py_script/textures/items/wand/wand--generic3.png similarity index 99% rename from textures/items/wand/wand--generic3.png rename to py_script/textures/items/wand/wand--generic3.png index fe68d65..a4530e4 100644 --- a/textures/items/wand/wand--generic3.png +++ b/py_script/textures/items/wand/wand--generic3.png @@ -2,7 +2,7 @@ - 404 Not Found +<title> 404 Not Found
diff --git a/textures/items/wand/wand--thunder1.png b/py_script/textures/items/wand/wand--thunder1.png similarity index 100% rename from textures/items/wand/wand--thunder1.png rename to py_script/textures/items/wand/wand--thunder1.png diff --git a/textures/items/wand/wand--thunder2.png b/py_script/textures/items/wand/wand--thunder2.png similarity index 100% rename from textures/items/wand/wand--thunder2.png rename to py_script/textures/items/wand/wand--thunder2.png diff --git a/textures/items/wand/wand--thunder3.png b/py_script/textures/items/wand/wand--thunder3.png similarity index 100% rename from textures/items/wand/wand--thunder3.png rename to py_script/textures/items/wand/wand--thunder3.png diff --git a/textures/items/wand/wand--water1.png b/py_script/textures/items/wand/wand--water1.png similarity index 100% rename from textures/items/wand/wand--water1.png rename to py_script/textures/items/wand/wand--water1.png diff --git a/textures/items/wand/wand--water2.png b/py_script/textures/items/wand/wand--water2.png similarity index 100% rename from textures/items/wand/wand--water2.png rename to py_script/textures/items/wand/wand--water2.png diff --git a/textures/items/wand/wand--water3.png b/py_script/textures/items/wand/wand--water3.png similarity index 100% rename from textures/items/wand/wand--water3.png rename to py_script/textures/items/wand/wand--water3.png diff --git a/textures/powder/dye_powder_cyan.png b/py_script/textures/powder/dye_powder_cyan.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_cyan.png rename to py_script/textures/powder/dye_powder_cyan.png diff --git a/textures/powder/dye_powder_gray.png b/py_script/textures/powder/dye_powder_gray.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_gray.png rename to py_script/textures/powder/dye_powder_gray.png diff --git a/textures/powder/dye_powder_green.png b/py_script/textures/powder/dye_powder_green.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_green.png rename to py_script/textures/powder/dye_powder_green.png diff --git a/textures/powder/dye_powder_light_blue.png b/py_script/textures/powder/dye_powder_light_blue.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_light_blue.png rename to py_script/textures/powder/dye_powder_light_blue.png diff --git a/textures/powder/dye_powder_lime.png b/py_script/textures/powder/dye_powder_lime.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_lime.png rename to py_script/textures/powder/dye_powder_lime.png diff --git a/textures/powder/dye_powder_orange.png b/py_script/textures/powder/dye_powder_orange.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_orange.png rename to py_script/textures/powder/dye_powder_orange.png diff --git a/textures/powder/dye_powder_pink.png b/py_script/textures/powder/dye_powder_pink.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_pink.png rename to py_script/textures/powder/dye_powder_pink.png diff --git a/textures/powder/dye_powder_red.png b/py_script/textures/powder/dye_powder_red.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_red.png rename to py_script/textures/powder/dye_powder_red.png diff --git a/textures/powder/dye_powder_silver.png b/py_script/textures/powder/dye_powder_silver.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_silver.png rename to py_script/textures/powder/dye_powder_silver.png diff --git a/textures/powder/dye_powder_yellow.png b/py_script/textures/powder/dye_powder_yellow.png old mode 100755 new mode 100644 similarity index 100% rename from textures/powder/dye_powder_yellow.png rename to py_script/textures/powder/dye_powder_yellow.png From 5fde5faacbfd653375ab79827a53ef659ae3559c Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 27 Jun 2022 01:32:26 -0700 Subject: [PATCH 117/155] really minor code cleanup --- js/atree.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/atree.js b/js/atree.js index 47e80b3..099715a 100644 --- a/js/atree.js +++ b/js/atree.js @@ -56,17 +56,14 @@ const atree_render = new (class extends ComputeNode { //as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff. // TODO: FIXME! this is a side effect of `px` based rendering. if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) { - toggle_tab('atree-dropdown'); - toggleButton('toggle-atree'); + toggle_tab('atree-dropdown'); toggleButton('toggle-atree'); } //for some reason we have to cast to string if (atree) { render_AT(document.getElementById("atree-ui"), atree); } - if (document.getElementById("toggle-atree").classList.contains("toggleOn")) { - toggle_tab('atree-dropdown'); - toggleButton('toggle-atree'); - } + //Toggle on, previously was toggled off + toggle_tab('atree-dropdown'); toggleButton('toggle-atree'); } })(); From 5106b7b6816bc967c31a84ecc1752361eadf8469 Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 27 Jun 2022 01:41:13 -0700 Subject: [PATCH 118/155] HOTFIX: damage multipliers from potion effect not being registered --- js/builder_graph.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/builder_graph.js b/js/builder_graph.js index 3b390d7..0b0dae0 100644 --- a/js/builder_graph.js +++ b/js/builder_graph.js @@ -50,7 +50,10 @@ let boosts_node = new (class extends ComputeNode { if (key === "vanish") { def_boost += .15 } } } - return [damage_boost, def_boost]; + let res = new Map(); + res.set('damageMultiplier', 1+damage_boost); + res.set('defMultiplier', 1+def_boost); + return res; } })().update(); @@ -646,7 +649,7 @@ function getMeleeStats(stats, weapon) { let damage_mult = stats.get("damageMultiplier"); if (weapon_stats.get("type") === "relik") { - damage_mult = 0.99; // CURSE YOU WYNNCRAFT + 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. } From b36b16c49d750d69c18817a68e22e719b773f6b2 Mon Sep 17 00:00:00 2001 From: reschan Date: Mon, 27 Jun 2022 16:23:22 +0700 Subject: [PATCH 119/155] fix: separate atree ids for each class --- js/atree_constants.js | 442 +++++++++++++++++----------------- js/atree_constants_min.js | 2 +- js/atree_ids.json | 146 +++++++++++ py_script/atree-convertID.py | 29 +++ py_script/atree-generateID.py | 32 ++- 5 files changed, 412 insertions(+), 239 deletions(-) create mode 100644 js/atree_ids.json create mode 100644 py_script/atree-convertID.py diff --git a/js/atree_constants.js b/js/atree_constants.js index 2f01e7e..48fd997 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -202,7 +202,7 @@ const atrees = { "archetype_req": 0, "parents": [ 68, - 86, + 39, 5 ], "dependencies": [], @@ -250,7 +250,7 @@ const atrees = { "archetype_req": 1, "parents": [ 4, - 82 + 35 ], "dependencies": [ 7 @@ -290,7 +290,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 83, + 36, 69 ], "dependencies": [ @@ -435,12 +435,12 @@ const atrees = { "name": "Single Arrow", "type": "damage", "multipliers": [ - 40, + 30, 0, 0, 0, 0, - 20 + 10 ] }, { @@ -1434,7 +1434,7 @@ const atrees = { ] } ], - "id": 82 + "id": 35 }, { "display_name": "Thunder Mastery", @@ -1443,7 +1443,7 @@ const atrees = { "archetype_req": 0, "parents": [ 7, - 86, + 39, 34 ], "dependencies": [], @@ -1474,7 +1474,7 @@ const atrees = { ] } ], - "id": 83 + "id": 36 }, { "display_name": "Water Mastery", @@ -1483,8 +1483,8 @@ const atrees = { "archetype_req": 0, "parents": [ 34, - 83, - 86 + 36, + 39 ], "dependencies": [], "blockers": [], @@ -1514,7 +1514,7 @@ const atrees = { ] } ], - "id": 84 + "id": 37 }, { "display_name": "Air Mastery", @@ -1552,7 +1552,7 @@ const atrees = { ] } ], - "id": 85 + "id": 38 }, { "display_name": "Fire Mastery", @@ -1560,7 +1560,7 @@ const atrees = { "archetype": "Sharpshooter", "archetype_req": 0, "parents": [ - 83, + 36, 0, 34 ], @@ -1592,7 +1592,7 @@ const atrees = { ] } ], - "id": 86 + "id": 39 }, { "display_name": "More Shields", @@ -2196,7 +2196,7 @@ const atrees = { "name": "damMult" }, "scaling": [ - 35 + 3 ], "max": 3 } @@ -2398,7 +2398,7 @@ const atrees = { "archetype": "Sharpshooter", "archetype_req": 0, "parents": [ - 84, + 37, 4 ], "dependencies": [ @@ -2457,7 +2457,7 @@ const atrees = { "archetype_req": 0, "parents": [ 6, - 85 + 38 ], "dependencies": [ 0 @@ -2573,7 +2573,7 @@ const atrees = { ] } ], - "id": 71 + "id": 0 }, { "display_name": "Spear Proficiency 1", @@ -2581,7 +2581,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 71 + 0 ], "dependencies": [], "blockers": [], @@ -2606,7 +2606,7 @@ const atrees = { ] } ], - "id": 72 + "id": 1 }, { "display_name": "Cheaper Bash", @@ -2614,7 +2614,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 72 + 1 ], "dependencies": [], "blockers": [], @@ -2632,7 +2632,7 @@ const atrees = { "cost": -10 } ], - "id": 73 + "id": 2 }, { "display_name": "Double Bash", @@ -2640,7 +2640,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 72 + 1 ], "dependencies": [], "blockers": [], @@ -2679,7 +2679,7 @@ const atrees = { ] } ], - "id": 74 + "id": 3 }, { "display_name": "Charge", @@ -2687,7 +2687,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 74 + 3 ], "dependencies": [], "blockers": [], @@ -2731,7 +2731,7 @@ const atrees = { ] } ], - "id": 75 + "id": 4 }, { "display_name": "Heavy Impact", @@ -2739,7 +2739,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 79 + 8 ], "dependencies": [], "blockers": [], @@ -2768,7 +2768,7 @@ const atrees = { ] } ], - "id": 76 + "id": 5 }, { "display_name": "Vehement", @@ -2776,11 +2776,11 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 75 + 4 ], "dependencies": [], "blockers": [ - 78 + 7 ], "cost": 1, "display": { @@ -2814,7 +2814,7 @@ const atrees = { "max": 20 } ], - "id": 77 + "id": 6 }, { "display_name": "Tougher Skin", @@ -2822,11 +2822,11 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 75 + 4 ], "dependencies": [], "blockers": [ - 77 + 6 ], "cost": 1, "display": { @@ -2870,7 +2870,7 @@ const atrees = { "max": 100 } ], - "id": 78 + "id": 7 }, { "display_name": "Uppercut", @@ -2878,7 +2878,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 77 + 6 ], "dependencies": [], "blockers": [], @@ -2925,7 +2925,7 @@ const atrees = { ] } ], - "id": 79 + "id": 8 }, { "display_name": "Cheaper Charge", @@ -2933,8 +2933,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 79, - 81 + 8, + 10 ], "dependencies": [], "blockers": [], @@ -2952,7 +2952,7 @@ const atrees = { "cost": -5 } ], - "id": 80 + "id": 9 }, { "display_name": "War Scream", @@ -2960,7 +2960,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 78 + 7 ], "dependencies": [], "blockers": [], @@ -3001,7 +3001,7 @@ const atrees = { ] } ], - "id": 81 + "id": 10 }, { "display_name": "Earth Mastery", @@ -3009,7 +3009,7 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 79 + 8 ], "dependencies": [], "blockers": [], @@ -3040,7 +3040,7 @@ const atrees = { ] } ], - "id": 82 + "id": 11 }, { "display_name": "Thunder Mastery", @@ -3048,9 +3048,9 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 79, - 85, - 80 + 8, + 14, + 9 ], "dependencies": [], "blockers": [], @@ -3081,7 +3081,7 @@ const atrees = { ] } ], - "id": 83 + "id": 12 }, { "display_name": "Water Mastery", @@ -3089,9 +3089,9 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 0, "parents": [ - 80, - 83, - 85 + 9, + 12, + 14 ], "dependencies": [], "blockers": [], @@ -3122,7 +3122,7 @@ const atrees = { ] } ], - "id": 84 + "id": 13 }, { "display_name": "Air Mastery", @@ -3130,9 +3130,9 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 0, "parents": [ - 81, - 83, - 80 + 10, + 12, + 9 ], "dependencies": [], "blockers": [], @@ -3163,7 +3163,7 @@ const atrees = { ] } ], - "id": 85 + "id": 14 }, { "display_name": "Fire Mastery", @@ -3171,7 +3171,7 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 81 + 10 ], "dependencies": [], "blockers": [], @@ -3202,7 +3202,7 @@ const atrees = { ] } ], - "id": 86 + "id": 15 }, { "display_name": "Quadruple Bash", @@ -3210,8 +3210,8 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 82, - 88 + 11, + 17 ], "dependencies": [], "blockers": [], @@ -3249,7 +3249,7 @@ const atrees = { ] } ], - "id": 87 + "id": 16 }, { "display_name": "Fireworks", @@ -3257,8 +3257,8 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 83, - 87 + 12, + 16 ], "dependencies": [], "blockers": [], @@ -3294,7 +3294,7 @@ const atrees = { } } ], - "id": 88 + "id": 17 }, { "display_name": "Half-Moon Swipe", @@ -3302,10 +3302,10 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 1, "parents": [ - 84 + 13 ], "dependencies": [ - 79 + 8 ], "blockers": [], "cost": 2, @@ -3338,7 +3338,7 @@ const atrees = { "conversion": "water" } ], - "id": 89 + "id": 18 }, { "display_name": "Flyby Jab", @@ -3346,8 +3346,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 85, - 91 + 14, + 20 ], "dependencies": [], "blockers": [], @@ -3376,7 +3376,7 @@ const atrees = { ] } ], - "id": 90 + "id": 19 }, { "display_name": "Flaming Uppercut", @@ -3384,11 +3384,11 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 86, - 90 + 15, + 19 ], "dependencies": [ - 79 + 8 ], "blockers": [], "cost": 2, @@ -3435,7 +3435,7 @@ const atrees = { } } ], - "id": 91 + "id": 20 }, { "display_name": "Iron Lungs", @@ -3443,8 +3443,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 90, - 91 + 19, + 20 ], "dependencies": [], "blockers": [], @@ -3471,7 +3471,7 @@ const atrees = { ] } ], - "id": 92 + "id": 21 }, { "display_name": "Generalist", @@ -3479,7 +3479,7 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 3, "parents": [ - 94 + 23 ], "dependencies": [], "blockers": [], @@ -3491,7 +3491,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 93 + "id": 22 }, { "display_name": "Counter", @@ -3499,7 +3499,7 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 0, "parents": [ - 89 + 18 ], "dependencies": [], "blockers": [], @@ -3528,7 +3528,7 @@ const atrees = { ] } ], - "id": 94 + "id": 23 }, { "display_name": "Mantle of the Bovemists", @@ -3536,10 +3536,10 @@ const atrees = { "archetype": "Paladin", "archetype_req": 3, "parents": [ - 92 + 21 ], "dependencies": [ - 81 + 10 ], "blockers": [], "cost": 2, @@ -3552,7 +3552,7 @@ const atrees = { "mantle_charge": 3 }, "effects": [], - "id": 95 + "id": 24 }, { "display_name": "Bak'al's Grasp", @@ -3560,11 +3560,11 @@ const atrees = { "archetype": "Fallen", "archetype_req": 2, "parents": [ - 87, - 88 + 16, + 17 ], "dependencies": [ - 81 + 10 ], "blockers": [], "cost": 2, @@ -3592,7 +3592,7 @@ const atrees = { "max": 120 } ], - "id": 96 + "id": 25 }, { "display_name": "Spear Proficiency 2", @@ -3600,8 +3600,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 96, - 98 + 25, + 27 ], "dependencies": [], "blockers": [], @@ -3626,7 +3626,7 @@ const atrees = { ] } ], - "id": 97 + "id": 26 }, { "display_name": "Cheaper Uppercut", @@ -3634,9 +3634,9 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 97, - 99, - 94 + 26, + 28, + 23 ], "dependencies": [], "blockers": [], @@ -3654,7 +3654,7 @@ const atrees = { "cost": -5 } ], - "id": 98 + "id": 27 }, { "display_name": "Aerodynamics", @@ -3662,8 +3662,8 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 0, "parents": [ - 98, - 100 + 27, + 29 ], "dependencies": [], "blockers": [], @@ -3675,7 +3675,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 99 + "id": 28 }, { "display_name": "Provoke", @@ -3683,8 +3683,8 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 99, - 95 + 28, + 24 ], "dependencies": [], "blockers": [], @@ -3702,7 +3702,7 @@ const atrees = { "cost": -5 } ], - "id": 100 + "id": 29 }, { "display_name": "Precise Strikes", @@ -3710,8 +3710,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 98, - 97 + 27, + 26 ], "dependencies": [], "blockers": [], @@ -3734,7 +3734,7 @@ const atrees = { ] } ], - "id": 101 + "id": 30 }, { "display_name": "Air Shout", @@ -3742,11 +3742,11 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 99, - 100 + 28, + 29 ], "dependencies": [ - 81 + 10 ], "blockers": [], "cost": 2, @@ -3772,7 +3772,7 @@ const atrees = { ] } ], - "id": 102 + "id": 31 }, { "display_name": "Enraged Blow", @@ -3780,10 +3780,10 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 97 + 26 ], "dependencies": [ - 96 + 25 ], "blockers": [], "cost": 2, @@ -3813,7 +3813,7 @@ const atrees = { "max": 300 } ], - "id": 103 + "id": 32 }, { "display_name": "Flying Kick", @@ -3821,8 +3821,8 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 1, "parents": [ - 98, - 105 + 27, + 34 ], "dependencies": [], "blockers": [], @@ -3849,7 +3849,7 @@ const atrees = { ] } ], - "id": 104 + "id": 33 }, { "display_name": "Stronger Mantle", @@ -3857,11 +3857,11 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 106, - 104 + 35, + 33 ], "dependencies": [ - 95 + 24 ], "blockers": [], "cost": 1, @@ -3874,7 +3874,7 @@ const atrees = { "mantle_charge": 2 }, "effects": [], - "id": 105 + "id": 34 }, { "display_name": "Manachism", @@ -3882,8 +3882,8 @@ const atrees = { "archetype": "Paladin", "archetype_req": 3, "parents": [ - 105, - 100 + 34, + 29 ], "dependencies": [], "blockers": [], @@ -3897,7 +3897,7 @@ const atrees = { "cooldown": 1 }, "effects": [], - "id": 106 + "id": 35 }, { "display_name": "Boiling Blood", @@ -3905,8 +3905,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 103, - 108 + 32, + 37 ], "dependencies": [], "blockers": [], @@ -3933,7 +3933,7 @@ const atrees = { ] } ], - "id": 107 + "id": 36 }, { "display_name": "Ragnarokkr", @@ -3941,11 +3941,11 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 107, - 104 + 36, + 33 ], "dependencies": [ - 81 + 10 ], "blockers": [], "cost": 2, @@ -3965,7 +3965,7 @@ const atrees = { "cost": 10 } ], - "id": 108 + "id": 37 }, { "display_name": "Ambidextrous", @@ -3973,12 +3973,12 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 104, - 105, - 110 + 33, + 34, + 39 ], "dependencies": [ - 94 + 23 ], "blockers": [], "cost": 1, @@ -3991,7 +3991,7 @@ const atrees = { "chance": 30 }, "effects": [], - "id": 109 + "id": 38 }, { "display_name": "Burning Heart", @@ -3999,8 +3999,8 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 109, - 111 + 38, + 40 ], "dependencies": [], "blockers": [], @@ -4032,7 +4032,7 @@ const atrees = { "slider_step": 100 } ], - "id": 110 + "id": 39 }, { "display_name": "Stronger Bash", @@ -4040,8 +4040,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 110, - 106 + 39, + 35 ], "dependencies": [], "blockers": [], @@ -4068,7 +4068,7 @@ const atrees = { ] } ], - "id": 111 + "id": 40 }, { "display_name": "Intoxicating Blood", @@ -4076,11 +4076,11 @@ const atrees = { "archetype": "Fallen", "archetype_req": 5, "parents": [ - 108, - 107 + 37, + 36 ], "dependencies": [ - 96 + 25 ], "blockers": [], "cost": 2, @@ -4091,7 +4091,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 112 + "id": 41 }, { "display_name": "Comet", @@ -4099,10 +4099,10 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 108 + 37 ], "dependencies": [ - 88 + 17 ], "blockers": [], "cost": 2, @@ -4137,7 +4137,7 @@ const atrees = { } } ], - "id": 113 + "id": 42 }, { "display_name": "Collide", @@ -4145,11 +4145,11 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 4, "parents": [ - 109, - 110 + 38, + 39 ], "dependencies": [ - 104 + 33 ], "blockers": [], "cost": 2, @@ -4177,7 +4177,7 @@ const atrees = { ] } ], - "id": 114 + "id": 43 }, { "display_name": "Rejuvenating Skin", @@ -4185,8 +4185,8 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 110, - 111 + 39, + 40 ], "dependencies": [], "blockers": [], @@ -4198,7 +4198,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 115 + "id": 44 }, { "display_name": "Uncontainable Corruption", @@ -4206,11 +4206,11 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 107, - 117 + 36, + 46 ], "dependencies": [ - 96 + 25 ], "blockers": [], "cost": 1, @@ -4238,7 +4238,7 @@ const atrees = { "max": 50 } ], - "id": 116 + "id": 45 }, { "display_name": "Radiant Devotee", @@ -4246,8 +4246,8 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 1, "parents": [ - 118, - 116 + 47, + 45 ], "dependencies": [], "blockers": [], @@ -4278,7 +4278,7 @@ const atrees = { "slider_step": 4 } ], - "id": 117 + "id": 46 }, { "display_name": "Whirlwind Strike", @@ -4286,11 +4286,11 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 5, "parents": [ - 109, - 117 + 38, + 46 ], "dependencies": [ - 79 + 8 ], "blockers": [], "cost": 2, @@ -4318,7 +4318,7 @@ const atrees = { ] } ], - "id": 118 + "id": 47 }, { "display_name": "Mythril Skin", @@ -4326,7 +4326,7 @@ const atrees = { "archetype": "Paladin", "archetype_req": 6, "parents": [ - 115 + 44 ], "dependencies": [], "blockers": [], @@ -4349,7 +4349,7 @@ const atrees = { ] } ], - "id": 119 + "id": 48 }, { "display_name": "Armour Breaker", @@ -4357,11 +4357,11 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 116, - 117 + 45, + 46 ], "dependencies": [ - 96 + 25 ], "blockers": [], "cost": 2, @@ -4374,7 +4374,7 @@ const atrees = { "duration": 5 }, "effects": [], - "id": 120 + "id": 49 }, { "display_name": "Shield Strike", @@ -4382,8 +4382,8 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 119, - 122 + 48, + 51 ], "dependencies": [], "blockers": [], @@ -4410,7 +4410,7 @@ const atrees = { ] } ], - "id": 121 + "id": 50 }, { "display_name": "Sparkling Hope", @@ -4418,7 +4418,7 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 119 + 48 ], "dependencies": [], "blockers": [], @@ -4447,7 +4447,7 @@ const atrees = { ] } ], - "id": 122 + "id": 51 }, { "display_name": "Massive Bash", @@ -4455,8 +4455,8 @@ const atrees = { "archetype": "Fallen", "archetype_req": 8, "parents": [ - 124, - 116 + 53, + 45 ], "dependencies": [], "blockers": [], @@ -4483,7 +4483,7 @@ const atrees = { "slider_step": 3 } ], - "id": 123 + "id": 52 }, { "display_name": "Tempest", @@ -4491,8 +4491,8 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 0, "parents": [ - 123, - 125 + 52, + 54 ], "dependencies": [], "blockers": [], @@ -4539,7 +4539,7 @@ const atrees = { } } ], - "id": 124 + "id": 53 }, { "display_name": "Spirit of the Rabbit", @@ -4547,8 +4547,8 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 5, "parents": [ - 124, - 118 + 53, + 47 ], "dependencies": [], "blockers": [], @@ -4576,7 +4576,7 @@ const atrees = { ] } ], - "id": 125 + "id": 54 }, { "display_name": "Massacre", @@ -4584,8 +4584,8 @@ const atrees = { "archetype": "Fallen", "archetype_req": 5, "parents": [ - 124, - 123 + 53, + 52 ], "dependencies": [], "blockers": [], @@ -4597,7 +4597,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 126 + "id": 55 }, { "display_name": "Axe Kick", @@ -4605,8 +4605,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 124, - 125 + 53, + 54 ], "dependencies": [], "blockers": [], @@ -4633,7 +4633,7 @@ const atrees = { ] } ], - "id": 127 + "id": 56 }, { "display_name": "Radiance", @@ -4641,8 +4641,8 @@ const atrees = { "archetype": "Paladin", "archetype_req": 2, "parents": [ - 125, - 129 + 54, + 58 ], "dependencies": [], "blockers": [], @@ -4656,7 +4656,7 @@ const atrees = { "cooldown": 15 }, "effects": [], - "id": 128 + "id": 57 }, { "display_name": "Cheaper Bash 2", @@ -4664,9 +4664,9 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 128, - 121, - 122 + 57, + 50, + 51 ], "dependencies": [], "blockers": [], @@ -4684,7 +4684,7 @@ const atrees = { "cost": -5 } ], - "id": 129 + "id": 58 }, { "display_name": "Cheaper War Scream", @@ -4692,7 +4692,7 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 123 + 52 ], "dependencies": [], "blockers": [], @@ -4710,15 +4710,15 @@ const atrees = { "cost": -5 } ], - "id": 130 + "id": 59 }, { "display_name": "Discombobulate", "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", "archetype": "Battle Monk", - "archetype_req": 12, + "archetype_req": 11, "parents": [ - 133 + 62 ], "dependencies": [], "blockers": [], @@ -4744,7 +4744,7 @@ const atrees = { "max": 50 } ], - "id": 131 + "id": 60 }, { "display_name": "Thunderclap", @@ -4752,7 +4752,7 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 8, "parents": [ - 133 + 62 ], "dependencies": [], "blockers": [], @@ -4781,7 +4781,7 @@ const atrees = { ] } ], - "id": 132 + "id": 61 }, { "display_name": "Cyclone", @@ -4789,7 +4789,7 @@ const atrees = { "archetype": "Battle Monk", "archetype_req": 0, "parents": [ - 125 + 54 ], "dependencies": [], "blockers": [], @@ -4828,7 +4828,7 @@ const atrees = { } } ], - "id": 133 + "id": 62 }, { "display_name": "Second Chance", @@ -4836,7 +4836,7 @@ const atrees = { "archetype": "Paladin", "archetype_req": 12, "parents": [ - 129 + 58 ], "dependencies": [], "blockers": [], @@ -4848,7 +4848,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 134 + "id": 63 }, { "display_name": "Blood Pact", @@ -4856,7 +4856,7 @@ const atrees = { "archetype": "", "archetype_req": 10, "parents": [ - 130 + 59 ], "dependencies": [], "blockers": [], @@ -4868,7 +4868,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 135 + "id": 64 }, { "display_name": "Haemorrhage", @@ -4876,10 +4876,10 @@ const atrees = { "archetype": "Fallen", "archetype_req": 0, "parents": [ - 135 + 64 ], "dependencies": [ - 135 + 64 ], "blockers": [], "cost": 1, @@ -4890,7 +4890,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 136 + "id": 65 }, { "display_name": "Brink of Madness", @@ -4898,8 +4898,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 135, - 138 + 64, + 67 ], "dependencies": [], "blockers": [], @@ -4911,7 +4911,7 @@ const atrees = { }, "properties": {}, "effects": [], - "id": 137 + "id": 66 }, { "display_name": "Cheaper Uppercut 2", @@ -4919,8 +4919,8 @@ const atrees = { "archetype": "", "archetype_req": 0, "parents": [ - 134, - 137 + 63, + 66 ], "dependencies": [], "blockers": [], @@ -4938,7 +4938,7 @@ const atrees = { "cost": -5 } ], - "id": 138 + "id": 67 }, { "display_name": "Martyr", @@ -4946,7 +4946,7 @@ const atrees = { "archetype": "Paladin", "archetype_req": 0, "parents": [ - 134 + 63 ], "dependencies": [], "blockers": [], @@ -4961,7 +4961,7 @@ const atrees = { "aoe": 12 }, "effects": [], - "id": 139 + "id": 68 } ] -} +} \ No newline at end of file diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js index ac8955c..52d1082 100644 --- a/js/atree_constants_min.js +++ b/js/atree_constants_min.js @@ -1 +1 @@ -const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:[60,34],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}],id:0},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:[3],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}],id:1},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}],id:2},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:[31],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}],id:3},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:[68,86,5],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}],id:4},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:[4,82],dependencies:[7],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}],id:5},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:[83,69],dependencies:[7],blockers:[68],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}],id:6},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:[58,34],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}],id:7},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:[59,67],dependencies:[0],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}],id:8},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"",archetype_req:0,parents:[7],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}],id:9},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:[5],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}],id:10},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:[8,33],dependencies:[],blockers:[68],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}],id:11},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:[61,40,33],dependencies:[],blockers:[20],cost:2,display:{row:21,col:5},properties:{range:20},effects:[],id:12},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:[12,40],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}],id:13},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:[62,64],dependencies:[61],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}],id:14},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:[42,64],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}],id:15},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:[14],dependencies:[4],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}],id:16},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:[42,55],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[],id:17},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:[14,44,55],dependencies:[2],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}],id:18},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:[43,44],dependencies:[4],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}],id:19},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:[46,17],dependencies:[],blockers:[12],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}],id:20},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:[18,44,47],dependencies:[61],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}],id:21},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:[21,47],dependencies:[0],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}],id:22},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:[48,20],dependencies:[],blockers:[68],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}],id:23},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:[56],dependencies:[15],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}],id:24},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:[49],dependencies:[7],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}],id:25},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:[51],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}],id:26},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:[26],dependencies:[10],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}],id:27},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:[24,63],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3},id:28},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:[28],dependencies:[8],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}],id:29},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:[26,53],dependencies:[10],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}],id:30},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:[2],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}],id:31},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:[31],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}],id:32},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:[12,11,61],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}],id:33},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:[7,0],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}],id:34},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:[0],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}],id:82},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:[7,86,34],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}],id:83},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:[34,83,86],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}],id:84},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:[7],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}],id:85},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:[83,0,34],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}],id:86},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:[12,10],dependencies:[0],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2},id:40},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:[11],dependencies:[9],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}],id:41},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:[11],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}],id:42},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:[54],dependencies:[10],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2},id:43},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:[19,18,14],dependencies:[0],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}],id:44},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:[17,55],dependencies:[17],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1},id:45},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:[20,55],dependencies:[8],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}],id:46},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:[21,19],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}],id:47},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:[46,49,23],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}],id:48},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:[48,21],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}],id:49},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:[47,21],dependencies:[2],blockers:[],cost:1,display:{row:33,col:6},properties:{},id:50},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:[22,70],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}],id:51},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:[51],dependencies:[12],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8},id:52},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:[63,30],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}],id:53},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:[40],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}],id:54},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:[17,18],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[],id:55},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:[23,48],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}],id:56},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:[24],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[],id:57},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:[1],dependencies:[],blockers:[60],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}],id:58},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:[69,67],dependencies:[58],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}],id:59},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:[1],dependencies:[],blockers:[58],cost:1,display:{row:7,col:6},properties:{},effects:[],id:60},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:[68],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"damMult"},scaling:[35],max:3}],id:61},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:[33,12],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"damMult"},scaling:[35],max:5}],id:62},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:[25,28],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"damMult"},scaling:[35],max:7}],id:63},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:[42,14],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}],id:64},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:[40],dependencies:[10],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[],id:65},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:[26],dependencies:[65],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[],id:66},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:[59,6],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}],id:67},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:[84,4],dependencies:[7],blockers:[11,6,23],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}],id:68},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:[6,85],dependencies:[0],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}],id:69},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:[49],dependencies:[68],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}],id:70}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4,icon:"node_4"},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}],id:71},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:[71],dependencies:[],blockers:[],cost:1,display:{row:2,col:4,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}],id:72},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:[72],dependencies:[],blockers:[],cost:1,display:{row:2,col:2,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}],id:73},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:[72],dependencies:[],blockers:[],cost:1,display:{row:4,col:4,icon:"node_1"},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}],id:74},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:[74],dependencies:[],blockers:[],cost:1,display:{row:6,col:4,icon:"node_4"},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}],id:75},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:[79],dependencies:[],blockers:[],cost:1,display:{row:9,col:1,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}],id:76},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:[75],dependencies:[],blockers:[78],cost:1,display:{row:6,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}],id:77},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:[75],dependencies:[],blockers:[77],cost:1,display:{row:6,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}],id:78},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:[77],dependencies:[],blockers:[],cost:1,display:{row:8,col:2,icon:"node_4"},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}],id:79},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:[79,81],dependencies:[],blockers:[],cost:1,display:{row:8,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}],id:80},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:[78],dependencies:[],blockers:[],cost:1,display:{row:8,col:6,icon:"node_4"},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}],id:81},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:[79],dependencies:[],blockers:[],cost:1,display:{row:10,col:0,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}],id:82},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:[79,85,80],dependencies:[],blockers:[],cost:1,display:{row:10,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}],id:83},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:[80,83,85],dependencies:[],blockers:[],cost:1,display:{row:11,col:4,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}],id:84},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:[81,83,80],dependencies:[],blockers:[],cost:1,display:{row:10,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}],id:85},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:[81],dependencies:[],blockers:[],cost:1,display:{row:10,col:8,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}],id:86},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:[82,88],dependencies:[],blockers:[],cost:2,display:{row:12,col:0,icon:"node_1"},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}],id:87},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:[83,87],dependencies:[],blockers:[],cost:2,display:{row:12,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}],id:88},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:[84],dependencies:[79],blockers:[],cost:2,display:{row:13,col:4,icon:"node_1"},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}],id:89},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:[85,91],dependencies:[],blockers:[],cost:2,display:{row:12,col:6,icon:"node_1"},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}],id:90},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:[86,90],dependencies:[79],blockers:[],cost:2,display:{row:12,col:8,icon:"node_1"},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}],id:91},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:[90,91],dependencies:[],blockers:[],cost:1,display:{row:13,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}],id:92},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:[94],dependencies:[],blockers:[],cost:2,display:{row:15,col:2,icon:"node_3"},properties:{},effects:[],id:93},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:[89],dependencies:[],blockers:[],cost:2,display:{row:15,col:4,icon:"node_1"},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}],id:94},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:[92],dependencies:[81],blockers:[],cost:2,display:{row:15,col:7,icon:"node_3"},properties:{mantle_charge:3},effects:[],id:95},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:[87,88],dependencies:[81],blockers:[],cost:2,display:{row:16,col:1,icon:"node_3"},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}],id:96},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:[96,98],dependencies:[],blockers:[],cost:1,display:{row:17,col:0,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}],id:97},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:[97,99,94],dependencies:[],blockers:[],cost:1,display:{row:17,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}],id:98},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:[98,100],dependencies:[],blockers:[],cost:2,display:{row:17,col:5,icon:"node_1"},properties:{},effects:[],id:99},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:[99,95],dependencies:[],blockers:[],cost:1,display:{row:17,col:7,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}],id:100},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:[98,97],dependencies:[],blockers:[],cost:1,display:{row:18,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}],id:101},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:[99,100],dependencies:[81],blockers:[],cost:2,display:{row:18,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}],id:102},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:[97],dependencies:[96],blockers:[],cost:2,display:{row:20,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"damMult"},scaling:[3],max:300}],id:103},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:[98,105],dependencies:[],blockers:[],cost:2,display:{row:20,col:3,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}],id:104},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:[106,104],dependencies:[95],blockers:[],cost:1,display:{row:20,col:6,icon:"node_0"},properties:{mantle_charge:2},effects:[],id:105},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:[105,100],dependencies:[],blockers:[],cost:2,display:{row:20,col:8,icon:"node_2"},properties:{cooldown:1},effects:[],id:106},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:[103,108],dependencies:[],blockers:[],cost:2,display:{row:22,col:0,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}],id:107},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:[107,104],dependencies:[81],blockers:[],cost:2,display:{row:22,col:2,icon:"node_2"},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}],id:108},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:[104,105,110],dependencies:[94],blockers:[],cost:1,display:{row:22,col:4,icon:"node_0"},properties:{chance:30},effects:[],id:109},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:[109,111],dependencies:[],blockers:[],cost:1,display:{row:22,col:6,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}],id:110},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:[110,106],dependencies:[],blockers:[],cost:1,display:{row:22,col:8,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}],id:111},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:[108,107],dependencies:[96],blockers:[],cost:2,display:{row:23,col:1,icon:"node_1"},properties:{},effects:[],id:112},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:[108],dependencies:[88],blockers:[],cost:2,display:{row:24,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}],id:113},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:[109,110],dependencies:[104],blockers:[],cost:2,display:{row:23,col:5,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}],id:114},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:[110,111],dependencies:[],blockers:[],cost:2,display:{row:23,col:7,icon:"node_3"},properties:{},effects:[],id:115},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:[107,117],dependencies:[96],blockers:[],cost:1,display:{row:26,col:0,icon:"node_0"},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}],id:116},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:[118,116],dependencies:[],blockers:[],cost:1,display:{row:26,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}],id:117},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:[109,117],dependencies:[79],blockers:[],cost:2,display:{row:26,col:4,icon:"node_1"},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}],id:118},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:[115],dependencies:[],blockers:[],cost:2,display:{row:26,col:7,icon:"node_1"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}],id:119},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:[116,117],dependencies:[96],blockers:[],cost:2,display:{row:27,col:1,icon:"node_2"},properties:{duration:5},effects:[],id:120},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:[119,122],dependencies:[],blockers:[],cost:2,display:{row:27,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}],id:121},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:[119],dependencies:[],blockers:[],cost:2,display:{row:27,col:8,icon:"node_2"},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}],id:122},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:[124,116],dependencies:[],blockers:[],cost:2,display:{row:28,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}],id:123},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:[123,125],dependencies:[],blockers:[],cost:2,display:{row:28,col:2,icon:"node_1"},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}],id:124},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:[124,118],dependencies:[],blockers:[],cost:1,display:{row:28,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}],id:125},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:[124,123],dependencies:[],blockers:[],cost:2,display:{row:29,col:1,icon:"node_1"},properties:{},effects:[],id:126},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:[124,125],dependencies:[],blockers:[],cost:1,display:{row:29,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}],id:127},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:[125,129],dependencies:[],blockers:[],cost:2,display:{row:29,col:5,icon:"node_2"},properties:{cooldown:15},effects:[],id:128},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:[128,121,122],dependencies:[],blockers:[],cost:1,display:{row:29,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}],id:129},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:[123],dependencies:[],blockers:[],cost:1,display:{row:31,col:0,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}],id:130},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:[133],dependencies:[],blockers:[],cost:2,display:{row:31,col:2,icon:"node_3"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}],id:131},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:[133],dependencies:[],blockers:[],cost:2,display:{row:32,col:5,icon:"node_1"},properties:{},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"},{type:"raw_stat",bonuses:[{type:"prop",abil_name:"Bash",name:"aoe",value:3}]}],id:132},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:[125],dependencies:[],blockers:[],cost:1,display:{row:31,col:4,icon:"node_1"},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}],id:133},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:[129],dependencies:[],blockers:[],cost:2,display:{row:32,col:7,icon:"node_3"},properties:{},effects:[],id:134},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:[130],dependencies:[],blockers:[],cost:2,display:{row:34,col:1,icon:"node_3"},properties:{},effects:[],id:135},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:[135],dependencies:[135],blockers:[],cost:1,display:{row:35,col:2,icon:"node_1"},properties:{},effects:[],id:136},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:[135,138],dependencies:[],blockers:[],cost:2,display:{row:35,col:4,icon:"node_2"},properties:{},effects:[],id:137},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:[134,137],dependencies:[],blockers:[],cost:1,display:{row:35,col:6,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}],id:138},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:[134],dependencies:[],blockers:[],cost:2,display:{row:35,col:8,icon:"node_1"},properties:{duration:3,aoe:12},effects:[],id:139}]} \ No newline at end of file +const atrees={"Archer":[{"display_name":"Arrow Shield","desc":"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)","archetype":"","archetype_req":0,"parents":[60,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":6},"properties":{"duration":60},"effects":[{"type":"replace_spell","name":"Arrow Shield","cost":30,"display_text":"Max Damage","base_spell":4,"spell_type":"damage","scaling":"spell","display":"","parts":[{"name":"Shield Damage","type":"damage","multipliers":[90,0,0,0,0,10]},{"name":"Total Damage","type":"total","hits":{"Shield Damage":2}}]}],"id":0},{"display_name":"Escape","desc":"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":7,"col":4},"properties":{"aoe":0,"range":0},"effects":[{"type":"replace_spell","name":"Escape","cost":25,"display_text":"Max Damage","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":1},{"display_name":"Arrow Bomb","desc":"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4},"properties":{"aoe":4.5,"range":26},"effects":[{"type":"replace_spell","name":"Arrow Bomb","cost":50,"display_text":"Average Damage","base_spell":3,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Arrow Bomb","type":"damage","multipliers":[160,0,0,0,20,0]},{"name":"Total Damage","type":"total","hits":{"Arrow Bomb":1}}]}],"id":2},{"display_name":"Heart Shatter","desc":"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[100,0,0,0,0,0]},{}],"id":3},{"display_name":"Fire Creep","desc":"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.","archetype":"","archetype_req":0,"parents":[68,39,5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":16,"col":6},"properties":{"aoe":0.8,"duration":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[30,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fire Creep":15}}],"id":4},{"display_name":"Bryophyte Roots","desc":"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.","archetype":"Trapper","archetype_req":1,"parents":[4,35],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":16,"col":8},"properties":{"aoe":2,"duration":5,"slowness":0.4},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Bryophyte Roots","cost":0,"multipliers":[40,20,0,0,0,0]}],"id":5},{"display_name":"Nimble String","desc":"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.","archetype":"","archetype_req":0,"parents":[36,69],"dependencies":[7],"blockers":[68],"cost":2,"display":{"row":15,"col":2},"properties":{"shootspeed":2},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-15,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","cost":0,"hits":{"Single Arrow":8}}],"id":6},{"display_name":"Arrow Storm","desc":"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.","archetype":"","archetype_req":0,"parents":[58,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":2},"properties":{"aoe":0,"range":16},"effects":[{"type":"replace_spell","name":"Arrow Storm","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,10,0,0,0]},{"name":"Single Stream","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Stream":2}}]}],"id":7},{"display_name":"Guardian Angels","desc":"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)","archetype":"Boltslinger","archetype_req":3,"parents":[59,67],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":19,"col":1},"properties":{"range":4,"duration":60,"shots":8,"count":2},"effects":[{"type":"replace_spell","name":"Guardian Angels","cost":30,"display_text":"Total Damage Average","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,0,0,0,10]},{"name":"Single Bow","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Bow":2}}]}],"id":8},{"display_name":"Windy Feet","base_abil":"Escape","desc":"When casting Escape, give speed to yourself and nearby allies.","archetype":"","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":1},"properties":{"aoe":8,"duration":120},"type":"stat_bonus","bonuses":[{"type":"stat","name":"spd","value":20}],"id":9},{"display_name":"Basaltic Trap","desc":"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)","archetype":"Trapper","archetype_req":2,"parents":[5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":8},"properties":{"aoe":7,"traps":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[140,30,0,0,30,0]}],"id":10},{"display_name":"Windstorm","desc":"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.","archetype":"","archetype_req":0,"parents":[8,33],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":21,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-11,0,-7,0,0,3]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":1}}],"id":11},{"display_name":"Grappling Hook","base_abil":"Escape","desc":"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)","archetype":"Trapper","archetype_req":0,"parents":[61,40,33],"dependencies":[],"blockers":[20],"cost":2,"display":{"row":21,"col":5},"properties":{"range":20},"effects":[],"id":12},{"display_name":"Implosion","desc":"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.","archetype":"Trapper","archetype_req":0,"parents":[12,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":13},{"display_name":"Twain's Arc","desc":"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)","archetype":"Sharpshooter","archetype_req":4,"parents":[62,64],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":25,"col":4},"properties":{"range":64,"focusReq":2},"effects":[{"type":"replace_spell","name":"Twain's Arc","cost":0,"display_text":"Twain's Arc","base_spell":5,"spell_type":"damage","scaling":"melee","display":"Twain's Arc Damage","parts":[{"name":"Twain's Arc Damage","type":"damage","multipliers":[200,0,0,0,0,0]}]}],"id":14},{"display_name":"Fierce Stomp","desc":"When using Escape, hold shift to quickly drop down and deal damage.","archetype":"Boltslinger","archetype_req":0,"parents":[42,64],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":1},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[100,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":2,"target_part":"Total Damage","cost":0,"hits":{"Fierce Stomp":1}}],"id":15},{"display_name":"Scorched Earth","desc":"Fire Creep become much stronger.","archetype":"Sharpshooter","archetype_req":0,"parents":[14],"dependencies":[4],"blockers":[],"cost":1,"display":{"row":26,"col":5},"properties":{"duration":2,"aoe":0.4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[10,0,0,0,5,0]}],"id":16},{"display_name":"Leap","desc":"When you double tap jump, leap foward. (2s Cooldown)","archetype":"Boltslinger","archetype_req":5,"parents":[42,55],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0},"properties":{"cooldown":2},"effects":[],"id":17},{"display_name":"Shocking Bomb","desc":"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.","archetype":"Sharpshooter","archetype_req":5,"parents":[14,44,55],"dependencies":[2],"blockers":[],"cost":2,"display":{"row":28,"col":4},"properties":{"gravity":0},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"}],"id":18},{"display_name":"Mana Trap","desc":"Your Traps will give you 4 Mana per second when you stay close to them.","archetype":"Trapper","archetype_req":5,"parents":[43,44],"dependencies":[4],"blockers":[],"cost":2,"display":{"row":28,"col":8},"properties":{"range":12,"manaRegen":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":10,"multipliers":[0,0,0,0,0,0]}],"id":19},{"display_name":"Escape Artist","desc":"When casting Escape, release 100 arrows towards the ground.","archetype":"Boltslinger","archetype_req":0,"parents":[46,17],"dependencies":[],"blockers":[12],"cost":2,"display":{"row":31,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Escape Artist","cost":0,"multipliers":[30,0,10,0,0,0]}],"id":20},{"display_name":"Initiator","desc":"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.","archetype":"Sharpshooter","archetype_req":5,"parents":[18,44,47],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":31,"col":5},"properties":{"focus":1,"timer":5},"type":"stat_bonus","bonuses":[{"type":"stat","name":"damPct","value":50}],"id":21},{"display_name":"Call of the Hound","desc":"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.","archetype":"Trapper","archetype_req":0,"parents":[21,47],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":32,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Call of the Hound","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":22},{"display_name":"Arrow Hurricane","desc":"Arrow Storm will shoot +2 stream of arrows.","archetype":"Boltslinger","archetype_req":8,"parents":[48,20],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":33,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":2}}],"id":23},{"display_name":"Geyser Stomp","desc":"Fierce Stomp will create geysers, dealing more damage and vertical knockback.","archetype":"","archetype_req":0,"parents":[56],"dependencies":[15],"blockers":[],"cost":2,"display":{"row":37,"col":1},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[0,0,0,50,0,0]}],"id":24},{"display_name":"Crepuscular Ray","desc":"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.","archetype":"Sharpshooter","archetype_req":10,"parents":[49],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":37,"col":4},"properties":{"focusReq":5,"focusRegen":-1},"effects":[{"type":"replace_spell","name":"Crepuscular Ray","base_spell":5,"spell_type":"damage","scaling":"spell","display":"One Focus","cost":0,"parts":[{"name":"Single Arrow","type":"damage","multipliers":[10,0,0,5,0,0]},{"name":"One Focus","type":"total","hits":{"Single Arrow":20}},{"name":"Total Damage","type":"total","hits":{"One Focus":7}}]}],"id":25},{"display_name":"Grape Bomb","desc":"Arrow bomb will throw 3 additional smaller bombs when exploding.","archetype":"","archetype_req":0,"parents":[51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":37,"col":7},"properties":{"miniBombs":3,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Grape Bomb","cost":0,"multipliers":[30,0,0,0,10,0]}],"id":26},{"display_name":"Tangled Traps","desc":"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":38,"col":6},"properties":{"attackSpeed":0.2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Tangled Traps","cost":0,"multipliers":[20,0,0,0,0,20]}],"id":27},{"display_name":"Snow Storm","desc":"Enemies near you will be slowed down.","archetype":"","archetype_req":0,"parents":[24,63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":39,"col":2},"properties":{"range":2.5,"slowness":0.3},"id":28},{"display_name":"All-Seeing Panoptes","desc":"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.","archetype":"Boltslinger","archetype_req":11,"parents":[28],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":40,"col":1},"properties":{"range":10,"shots":5},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Arrow","cost":0,"multipliers":[0,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":5}}],"id":29},{"display_name":"Minefield","desc":"Allow you to place +6 Traps, but with reduced damage and range.","archetype":"Trapper","archetype_req":10,"parents":[26,53],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":40,"col":7},"properties":{"aoe":-2,"traps":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[-80,0,0,0,0,0]}],"id":30},{"display_name":"Bow Proficiency I","desc":"Improve your Main Attack's damage and range when using a bow.","archetype":"","archetype_req":0,"parents":[2],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":31},{"display_name":"Cheaper Arrow Bomb","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-10}],"id":32},{"display_name":"Cheaper Arrow Storm","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[12,11,61],"dependencies":[],"blockers":[],"cost":1,"display":{"row":21,"col":3},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":33},{"display_name":"Cheaper Escape","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[7,0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":34},{"display_name":"Earth Mastery","desc":"Increases your base damage from all Earth attacks","archetype":"Trapper","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":8},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":35},{"display_name":"Thunder Mastery","desc":"Increases your base damage from all Thunder attacks","archetype":"Boltslinger","archetype_req":0,"parents":[7,39,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":2},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":36},{"display_name":"Water Mastery","desc":"Increases your base damage from all Water attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[34,36,39],"dependencies":[],"blockers":[],"cost":1,"display":{"row":14,"col":4},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":37},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":0},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":38},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[36,0,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":6},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":39},{"display_name":"More Shields","desc":"Give +2 charges to Arrow Shield.","archetype":"","archetype_req":0,"parents":[12,10],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":21,"col":7},"properties":{"shieldCharges":2},"id":40},{"display_name":"Stormy Feet","desc":"Windy Feet will last longer and add more speed.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[9],"blockers":[],"cost":1,"display":{"row":23,"col":1},"properties":{"duration":60},"effects":[{"type":"stat_bonus","bonuses":[{"type":"stat","name":"spdPct","value":20}]}],"id":41},{"display_name":"Refined Gunpowder","desc":"Increase the damage of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[50,0,0,0,0,0]}],"id":42},{"display_name":"More Traps","desc":"Increase the maximum amount of active Traps you can have by +2.","archetype":"Trapper","archetype_req":10,"parents":[54],"dependencies":[10],"blockers":[],"cost":1,"display":{"row":26,"col":8},"properties":{"traps":2},"id":43},{"display_name":"Better Arrow Shield","desc":"Arrow Shield will gain additional area of effect, knockback and damage.","archetype":"Sharpshooter","archetype_req":0,"parents":[19,18,14],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":28,"col":6},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Shield","multipliers":[40,0,0,0,0,0]}],"id":44},{"display_name":"Better Leap","desc":"Reduce leap's cooldown by 1s.","archetype":"Boltslinger","archetype_req":0,"parents":[17,55],"dependencies":[17],"blockers":[],"cost":1,"display":{"row":29,"col":1},"properties":{"cooldown":-1},"id":45},{"display_name":"Better Guardian Angels","desc":"Your Guardian Angels can shoot +4 arrows before disappearing.","archetype":"Boltslinger","archetype_req":0,"parents":[20,55],"dependencies":[8],"blockers":[],"cost":1,"display":{"row":31,"col":2},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":4}}],"id":46},{"display_name":"Cheaper Arrow Storm (2)","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[21,19],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":8},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":47},{"display_name":"Precise Shot","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[46,49,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":2},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdCritPct","value":30}]}],"id":48},{"display_name":"Cheaper Arrow Shield","desc":"Reduce the Mana cost of Arrow Shield.","archetype":"","archetype_req":0,"parents":[48,21],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":49},{"display_name":"Rocket Jump","desc":"Arrow Bomb's self-damage will knockback you farther away.","archetype":"","archetype_req":0,"parents":[47,21],"dependencies":[2],"blockers":[],"cost":1,"display":{"row":33,"col":6},"properties":{},"id":50},{"display_name":"Cheaper Escape (2)","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[22,70],"dependencies":[],"blockers":[],"cost":1,"display":{"row":34,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":51},{"display_name":"Stronger Hook","desc":"Increase your Grappling Hook's range, speed and strength.","archetype":"Trapper","archetype_req":5,"parents":[51],"dependencies":[12],"blockers":[],"cost":1,"display":{"row":35,"col":8},"properties":{"range":8},"id":52},{"display_name":"Cheaper Arrow Bomb (2)","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[63,30],"dependencies":[],"blockers":[],"cost":1,"display":{"row":40,"col":5},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":53},{"display_name":"Bouncing Bomb","desc":"Arrow Bomb will bounce once when hitting a block or enemy","archetype":"","archetype_req":0,"parents":[40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":25,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Arrow Bomb":2}}],"id":54},{"display_name":"Homing Shots","desc":"Your Main Attack arrows will follow nearby enemies and not be affected by gravity","archetype":"","archetype_req":0,"parents":[17,18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2},"properties":{},"effects":[],"id":55},{"display_name":"Shrapnel Bomb","desc":"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area","archetype":"Boltslinger","archetype_req":8,"parents":[23,48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Shrapnel Bomb","cost":0,"multipliers":[40,0,0,0,20,0]}],"id":56},{"display_name":"Elusive","desc":"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)","archetype":"Boltslinger","archetype_req":0,"parents":[24],"dependencies":[],"blockers":[],"cost":2,"display":{"row":38,"col":0},"properties":{},"effects":[],"id":57},{"display_name":"Double Shots","desc":"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)","archetype":"Boltslinger","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[60],"cost":1,"display":{"row":7,"col":2},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":58},{"display_name":"Triple Shots","desc":"Triple Main Attack arrows, but they deal -20% damage per arrow","archetype":"Boltslinger","archetype_req":0,"parents":[69,67],"dependencies":[58],"blockers":[],"cost":1,"display":{"row":17,"col":0},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":59},{"display_name":"Power Shots","desc":"Main Attack arrows have increased speed and knockback","archetype":"Sharpshooter","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[58],"cost":1,"display":{"row":7,"col":6},"properties":{},"effects":[],"id":60},{"display_name":"Focus","desc":"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once","archetype":"Sharpshooter","archetype_req":2,"parents":[68],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[3],"max":3}],"id":61},{"display_name":"More Focus","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[33,12],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[35],"max":5}],"id":62},{"display_name":"More Focus (2)","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[25,28],"dependencies":[],"blockers":[],"cost":1,"display":{"row":39,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[35],"max":7}],"id":63},{"display_name":"Traveler","desc":"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)","archetype":"","archetype_req":0,"parents":[42,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"spd"}],"output":{"type":"stat","name":"sdRaw"},"scaling":[1],"max":100}],"id":64},{"display_name":"Patient Hunter","desc":"Your Traps will deal +20% more damage for every second they are active (Max +80%)","archetype":"Trapper","archetype_req":0,"parents":[40],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":8},"properties":{"max":80},"effects":[],"id":65},{"display_name":"Stronger Patient Hunter","desc":"Add +80% Max Damage to Patient Hunter","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[65],"blockers":[],"cost":1,"display":{"row":38,"col":8},"properties":{"max":80},"effects":[],"id":66},{"display_name":"Frenzy","desc":"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second","archetype":"Boltslinger","archetype_req":0,"parents":[59,6],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"spd"},"scaling":[6],"max":200}],"id":67},{"display_name":"Phantom Ray","desc":"Condense Arrow Storm into a single ray that damages enemies 10 times per second","archetype":"Sharpshooter","archetype_req":0,"parents":[37,4],"dependencies":[7],"blockers":[11,6,23],"cost":2,"display":{"row":16,"col":4},"properties":{},"effects":[{"type":"replace_spell","name":"Phantom Ray","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[25,0,5,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Arrow":16}}]}],"id":68},{"display_name":"Arrow Rain","desc":"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies","archetype":"Trapper","archetype_req":0,"parents":[6,38],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":15,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Arrow Rain","cost":0,"multipliers":[120,0,0,0,0,80]}],"id":69},{"display_name":"Decimator","desc":"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)","archetype":"Sharpshooter","archetype_req":0,"parents":[49],"dependencies":[68],"blockers":[],"cost":1,"display":{"row":34,"col":5},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Phantom Ray hits","output":{"type":"stat","name":"PhRayDmg"},"scaling":10,"max":50}],"id":70}],"Warrior":[{"display_name":"Bash","desc":"Violently bash the ground, dealing high damage in a large area","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4,"icon":"node_4"},"properties":{"aoe":4,"range":3},"effects":[{"type":"replace_spell","name":"Bash","cost":45,"display_text":"Total Damage Average","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Hit","type":"damage","multipliers":[130,20,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Hit":1}}]}],"id":0},{"display_name":"Spear Proficiency 1","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":1},{"display_name":"Cheaper Bash","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-10}],"id":2},{"display_name":"Double Bash","desc":"Bash will hit a second time at a farther range","archetype":"","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4,"icon":"node_1"},"properties":{"range":3},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"name":"Single Hit","value":1}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-50,0,0,0,0,0]}],"id":3},{"display_name":"Charge","desc":"Charge forward at high speed (hold shift to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":6,"col":4,"icon":"node_4"},"properties":{},"effects":[{"type":"replace_spell","name":"Charge","cost":25,"display_text":"Total Damage Average","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":4},{"display_name":"Heavy Impact","desc":"After using Charge, violently crash down into the ground and deal damage","archetype":"","archetype_req":0,"parents":[8],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":1,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Heavy Impact","cost":0,"multipliers":[100,0,0,0,0,0]}],"id":5},{"display_name":"Vehement","desc":"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)","archetype":"Fallen","archetype_req":0,"parents":[4],"dependencies":[],"blockers":[7],"cost":1,"display":{"row":6,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"mdPct"},{"type":"stat","name":"mdRaw"}],"output":{"type":"stat","name":"spd"},"scaling":[1,1],"max":20}],"id":6},{"display_name":"Tougher Skin","desc":"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)","archetype":"Paladin","archetype_req":0,"parents":[4],"dependencies":[],"blockers":[6],"cost":1,"display":{"row":6,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":"5"}]},{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hprRaw"},{"type":"stat","name":"hprPct"}],"output":{"type":"stat","name":"hpBonus"},"scaling":[10,10],"max":100}],"id":7},{"display_name":"Uppercut","desc":"Rocket enemies in the air and deal massive damage","archetype":"","archetype_req":0,"parents":[6],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":2,"icon":"node_4"},"properties":{"aoe":3,"range":5},"effects":[{"type":"replace_spell","name":"Uppercut","cost":45,"display_text":"Total Damage Average","base_spell":3,"spell_type":"damage","scaling":"spell","display":"total","parts":[{"name":"Uppercut","type":"damage","multipliers":[150,50,50,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Uppercut":1}}]}],"id":8},{"display_name":"Cheaper Charge","desc":"Reduce the Mana cost of Charge","archetype":"","archetype_req":0,"parents":[8,10],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":9},{"display_name":"War Scream","desc":"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies","archetype":"","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":6,"icon":"node_4"},"properties":{"duration":30,"aoe":12,"defense_bonus":10},"effects":[{"type":"replace_spell","name":"War Scream","cost":35,"display_text":"War Scream","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage Average","parts":[{"name":"War Scream","type":"damage","multipliers":[50,0,0,0,50,0]}]}],"id":10},{"display_name":"Earth Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Fallen","archetype_req":0,"parents":[8],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":11},{"display_name":"Thunder Mastery","desc":"Increases base damage from all Thunder attacks","archetype":"Fallen","archetype_req":0,"parents":[8,14,9],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":12},{"display_name":"Water Mastery","desc":"Increases base damage from all Water attacks","archetype":"Battle Monk","archetype_req":0,"parents":[9,12,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":11,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":13},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[10,12,9],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":14},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Paladin","archetype_req":0,"parents":[10],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":15},{"display_name":"Quadruple Bash","desc":"Bash will hit 4 times at an even larger range","archetype":"Fallen","archetype_req":0,"parents":[11,17],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":0,"icon":"node_1"},"properties":{"range":6},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Hit":2}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-20,0,0,0,0,0]}],"id":16},{"display_name":"Fireworks","desc":"Mobs hit by Uppercut will explode mid-air and receive additional damage","archetype":"Fallen","archetype_req":0,"parents":[12,16],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fireworks","cost":0,"multipliers":[80,0,20,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fireworks":1}}],"id":17},{"display_name":"Half-Moon Swipe","desc":"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water","archetype":"Battle Monk","archetype_req":1,"parents":[13],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":13,"col":4,"icon":"node_1"},"properties":{"range":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":-10,"multipliers":[-70,0,0,0,0,0]},{"type":"convert_spell_conv","target_part":"all","conversion":"water"}],"id":18},{"display_name":"Flyby Jab","desc":"Damage enemies in your way when using Charge","archetype":"","archetype_req":0,"parents":[14,20],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":6,"icon":"node_1"},"properties":{"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flyby Jab","cost":0,"multipliers":[20,0,0,0,0,40]}],"id":19},{"display_name":"Flaming Uppercut","desc":"Uppercut will light mobs on fire, dealing damage every 0.6 seconds","archetype":"Paladin","archetype_req":0,"parents":[15,19],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":12,"col":8,"icon":"node_1"},"properties":{"duration":3,"tick":0.6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut","cost":0,"multipliers":[0,0,0,0,50,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut Total Damage","cost":0,"hits":{"Flaming Uppercut":5}},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Flaming Uppercut":5}}],"id":20},{"display_name":"Iron Lungs","desc":"War Scream deals more damage","archetype":"","archetype_req":0,"parents":[19,20],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"War Scream","cost":0,"multipliers":[30,0,0,0,0,30]}],"id":21},{"display_name":"Generalist","desc":"After casting 3 different spells in a row, your next spell will cost 5 mana","archetype":"Battle Monk","archetype_req":3,"parents":[23],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":2,"icon":"node_3"},"properties":{},"effects":[],"id":22},{"display_name":"Counter","desc":"When dodging a nearby enemy attack, get 30% chance to instantly attack back","archetype":"Battle Monk","archetype_req":0,"parents":[18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":4,"icon":"node_1"},"properties":{"chance":30},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Counter","cost":0,"multipliers":[60,0,20,0,0,20]}],"id":23},{"display_name":"Mantle of the Bovemists","desc":"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)","archetype":"Paladin","archetype_req":3,"parents":[21],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":15,"col":7,"icon":"node_3"},"properties":{"mantle_charge":3},"effects":[],"id":24},{"display_name":"Bak'al's Grasp","desc":"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)","archetype":"Fallen","archetype_req":2,"parents":[16,17],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":16,"col":1,"icon":"node_3"},"properties":{"cooldown":15},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[4],"slider_step":2,"max":120}],"id":25},{"display_name":"Spear Proficiency 2","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[25,27],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":0,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":26},{"display_name":"Cheaper Uppercut","desc":"Reduce the Mana Cost of Uppercut","archetype":"","archetype_req":0,"parents":[26,28,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":27},{"display_name":"Aerodynamics","desc":"During Charge, you can steer and change direction","archetype":"Battle Monk","archetype_req":0,"parents":[27,29],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":5,"icon":"node_1"},"properties":{},"effects":[],"id":28},{"display_name":"Provoke","desc":"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream","archetype":"Paladin","archetype_req":0,"parents":[28,24],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":29},{"display_name":"Precise Strikes","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[27,26],"dependencies":[],"blockers":[],"cost":1,"display":{"row":18,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"critDmg","value":30}]}],"id":30},{"display_name":"Air Shout","desc":"War Scream will fire a projectile that can go through walls and deal damage multiple times","archetype":"","archetype_req":0,"parents":[28,29],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":18,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Air Shout","cost":0,"multipliers":[20,0,0,0,0,5]}],"id":31},{"display_name":"Enraged Blow","desc":"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)","archetype":"Fallen","archetype_req":0,"parents":[26],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":20,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"damMult"},"scaling":[3],"max":300}],"id":32},{"display_name":"Flying Kick","desc":"When using Charge, mobs hit will halt your momentum and get knocked back","archetype":"Battle Monk","archetype_req":1,"parents":[27,34],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":3,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flying Kick","cost":0,"multipliers":[120,0,0,10,0,20]}],"id":33},{"display_name":"Stronger Mantle","desc":"Add +2 additional charges to Mantle of the Bovemists","archetype":"Paladin","archetype_req":0,"parents":[35,33],"dependencies":[24],"blockers":[],"cost":1,"display":{"row":20,"col":6,"icon":"node_0"},"properties":{"mantle_charge":2},"effects":[],"id":34},{"display_name":"Manachism","desc":"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)","archetype":"Paladin","archetype_req":3,"parents":[34,29],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":8,"icon":"node_2"},"properties":{"cooldown":1},"effects":[],"id":35},{"display_name":"Boiling Blood","desc":"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds","archetype":"","archetype_req":0,"parents":[32,37],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":0,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Boiling Blood","cost":0,"multipliers":[25,0,0,0,5,0]}],"id":36},{"display_name":"Ragnarokkr","desc":"War Scream become deafening, increasing its range and giving damage bonus to players","archetype":"Fallen","archetype_req":0,"parents":[36,33],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":2,"icon":"node_2"},"properties":{"damage_bonus":30,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":10}],"id":37},{"display_name":"Ambidextrous","desc":"Increase your chance to attack with Counter by +30%","archetype":"","archetype_req":0,"parents":[33,34,39],"dependencies":[23],"blockers":[],"cost":1,"display":{"row":22,"col":4,"icon":"node_0"},"properties":{"chance":30},"effects":[],"id":38},{"display_name":"Burning Heart","desc":"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)","archetype":"Paladin","archetype_req":0,"parents":[38,40],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"fDamPct"},"scaling":[2],"max":100,"slider_step":100}],"id":39},{"display_name":"Stronger Bash","desc":"Increase the damage of Bash","archetype":"","archetype_req":0,"parents":[39,35],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[30,0,0,0,0,0]}],"id":40},{"display_name":"Intoxicating Blood","desc":"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted","archetype":"Fallen","archetype_req":5,"parents":[37,36],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":23,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":41},{"display_name":"Comet","desc":"After being hit by Fireworks, enemies will crash into the ground and receive more damage","archetype":"Fallen","archetype_req":0,"parents":[37],"dependencies":[17],"blockers":[],"cost":2,"display":{"row":24,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Comet","cost":0,"multipliers":[80,20,0,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Comet":1}}],"id":42},{"display_name":"Collide","desc":"Mobs thrown into walls from Flying Kick will explode and receive additonal damage","archetype":"Battle Monk","archetype_req":4,"parents":[38,39],"dependencies":[33],"blockers":[],"cost":2,"display":{"row":23,"col":5,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Collide","cost":0,"multipliers":[100,0,0,0,50,0]}],"id":43},{"display_name":"Rejuvenating Skin","desc":"Regain back 30% of the damage you take as healing over 30s","archetype":"Paladin","archetype_req":0,"parents":[39,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":23,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":44},{"display_name":"Uncontainable Corruption","desc":"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1","archetype":"","archetype_req":0,"parents":[36,46],"dependencies":[25],"blockers":[],"cost":1,"display":{"row":26,"col":0,"icon":"node_0"},"properties":{"cooldown":-5},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[1],"slider_step":2,"max":50}],"id":45},{"display_name":"Radiant Devotee","desc":"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)","archetype":"Battle Monk","archetype_req":1,"parents":[47,45],"dependencies":[],"blockers":[],"cost":1,"display":{"row":26,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","inputs":[{"type":"stat","name":"ref"}],"output":{"type":"stat","name":"mr"},"scaling":[1],"max":10,"slider_step":4}],"id":46},{"display_name":"Whirlwind Strike","desc":"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)","archetype":"Battle Monk","archetype_req":5,"parents":[38,46],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":26,"col":4,"icon":"node_1"},"properties":{"range":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":0,"multipliers":[0,0,0,0,0,50]}],"id":47},{"display_name":"Mythril Skin","desc":"Gain +5% Base Resistance and become immune to knockback","archetype":"Paladin","archetype_req":6,"parents":[44],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":5}]}],"id":48},{"display_name":"Armour Breaker","desc":"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage","archetype":"Fallen","archetype_req":0,"parents":[45,46],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":27,"col":1,"icon":"node_2"},"properties":{"duration":5},"effects":[],"id":49},{"display_name":"Shield Strike","desc":"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost","archetype":"Paladin","archetype_req":0,"parents":[48,51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Shield Strike","cost":0,"multipliers":[60,0,20,0,0,0]}],"id":50},{"display_name":"Sparkling Hope","desc":"Everytime you heal 5% of your max health, deal damage to all nearby enemies","archetype":"Paladin","archetype_req":0,"parents":[48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":8,"icon":"node_2"},"properties":{"aoe":6},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Sparkling Hope","cost":0,"multipliers":[10,0,5,0,0,0]}],"id":51},{"display_name":"Massive Bash","desc":"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)","archetype":"Fallen","archetype_req":8,"parents":[53,45],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"bashAoE"},"scaling":[1],"max":10,"slider_step":3}],"id":52},{"display_name":"Tempest","desc":"War Scream will ripple the ground and deal damage 3 times in a large area","archetype":"Battle Monk","archetype_req":0,"parents":[52,54],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2,"icon":"node_1"},"properties":{"aoe":16},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest","cost":"0","multipliers":[30,10,0,0,0,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest Total Damage","cost":"0","hits":{"Tempest":3}},{"type":"add_spell_prop","base_spell":4,"target_part":"Total Damage","cost":"0","hits":{"Tempest":3}}],"id":53},{"display_name":"Spirit of the Rabbit","desc":"Reduce the Mana cost of Charge and increase your Walk Speed by +20%","archetype":"Battle Monk","archetype_req":5,"parents":[53,47],"dependencies":[],"blockers":[],"cost":1,"display":{"row":28,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5},{"type":"raw_stat","bonuses":[{"type":"stat","name":"spd","value":20}]}],"id":54},{"display_name":"Massacre","desc":"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar","archetype":"Fallen","archetype_req":5,"parents":[53,52],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":55},{"display_name":"Axe Kick","desc":"Increase the damage of Uppercut, but also increase its mana cost","archetype":"","archetype_req":0,"parents":[53,54],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":10,"multipliers":[100,0,0,0,0,0]}],"id":56},{"display_name":"Radiance","desc":"Bash will buff your allies' positive IDs. (15s Cooldown)","archetype":"Paladin","archetype_req":2,"parents":[54,58],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":5,"icon":"node_2"},"properties":{"cooldown":15},"effects":[],"id":57},{"display_name":"Cheaper Bash 2","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[57,50,51],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":58},{"display_name":"Cheaper War Scream","desc":"Reduce the Mana cost of War Scream","archetype":"","archetype_req":0,"parents":[52],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":59},{"display_name":"Discombobulate","desc":"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second","archetype":"Battle Monk","archetype_req":11,"parents":[62],"dependencies":[],"blockers":[],"cost":2,"display":{"row":31,"col":2,"icon":"node_3"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"rainrawButDifferent"},"scaling":[2],"max":50}],"id":60},{"display_name":"Thunderclap","desc":"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder","archetype":"Battle Monk","archetype_req":8,"parents":[62],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":5,"icon":"node_1"},"properties":{},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"},{"type":"raw_stat","bonuses":[{"type":"prop","abil_name":"Bash","name":"aoe","value":3}]}],"id":61},{"display_name":"Cyclone","desc":"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s","archetype":"Battle Monk","archetype_req":0,"parents":[54],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":4,"icon":"node_1"},"properties":{"aoe":4,"duration":20},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone","cost":0,"multipliers":[10,0,0,0,5,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone Total Damage","cost":0,"hits":{"Cyclone":40}}],"id":62},{"display_name":"Second Chance","desc":"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)","archetype":"Paladin","archetype_req":12,"parents":[58],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":63},{"display_name":"Blood Pact","desc":"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)","archetype":"","archetype_req":10,"parents":[59],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1,"icon":"node_3"},"properties":{},"effects":[],"id":64},{"display_name":"Haemorrhage","desc":"Reduce Blood Pact's health cost. (0.5% health per mana)","archetype":"Fallen","archetype_req":0,"parents":[64],"dependencies":[64],"blockers":[],"cost":1,"display":{"row":35,"col":2,"icon":"node_1"},"properties":{},"effects":[],"id":65},{"display_name":"Brink of Madness","desc":"If your health is 25% full or less, gain +40% Resistance","archetype":"","archetype_req":0,"parents":[64,67],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":4,"icon":"node_2"},"properties":{},"effects":[],"id":66},{"display_name":"Cheaper Uppercut 2","desc":"Reduce the Mana cost of Uppercut","archetype":"","archetype_req":0,"parents":[63,66],"dependencies":[],"blockers":[],"cost":1,"display":{"row":35,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":67},{"display_name":"Martyr","desc":"When you receive a fatal blow, all nearby allies become invincible","archetype":"Paladin","archetype_req":0,"parents":[63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":8,"icon":"node_1"},"properties":{"duration":3,"aoe":12},"effects":[],"id":68}]} \ No newline at end of file diff --git a/js/atree_ids.json b/js/atree_ids.json new file mode 100644 index 0000000..a7cd98e --- /dev/null +++ b/js/atree_ids.json @@ -0,0 +1,146 @@ +{ + "Archer": { + "Arrow Shield": 0, + "Escape": 1, + "Arrow Bomb": 2, + "Heart Shatter": 3, + "Fire Creep": 4, + "Bryophyte Roots": 5, + "Nimble String": 6, + "Arrow Storm": 7, + "Guardian Angels": 8, + "Windy Feet": 9, + "Basaltic Trap": 10, + "Windstorm": 11, + "Grappling Hook": 12, + "Implosion": 13, + "Twain's Arc": 14, + "Fierce Stomp": 15, + "Scorched Earth": 16, + "Leap": 17, + "Shocking Bomb": 18, + "Mana Trap": 19, + "Escape Artist": 20, + "Initiator": 21, + "Call of the Hound": 22, + "Arrow Hurricane": 23, + "Geyser Stomp": 24, + "Crepuscular Ray": 25, + "Grape Bomb": 26, + "Tangled Traps": 27, + "Snow Storm": 28, + "All-Seeing Panoptes": 29, + "Minefield": 30, + "Bow Proficiency I": 31, + "Cheaper Arrow Bomb": 32, + "Cheaper Arrow Storm": 33, + "Cheaper Escape": 34, + "Earth Mastery": 35, + "Thunder Mastery": 36, + "Water Mastery": 37, + "Air Mastery": 38, + "Fire Mastery": 39, + "More Shields": 40, + "Stormy Feet": 41, + "Refined Gunpowder": 42, + "More Traps": 43, + "Better Arrow Shield": 44, + "Better Leap": 45, + "Better Guardian Angels": 46, + "Cheaper Arrow Storm (2)": 47, + "Precise Shot": 48, + "Cheaper Arrow Shield": 49, + "Rocket Jump": 50, + "Cheaper Escape (2)": 51, + "Stronger Hook": 52, + "Cheaper Arrow Bomb (2)": 53, + "Bouncing Bomb": 54, + "Homing Shots": 55, + "Shrapnel Bomb": 56, + "Elusive": 57, + "Double Shots": 58, + "Triple Shots": 59, + "Power Shots": 60, + "Focus": 61, + "More Focus": 62, + "More Focus (2)": 63, + "Traveler": 64, + "Patient Hunter": 65, + "Stronger Patient Hunter": 66, + "Frenzy": 67, + "Phantom Ray": 68, + "Arrow Rain": 69, + "Decimator": 70 + }, + "Warrior": { + "Bash": 0, + "Spear Proficiency 1": 1, + "Cheaper Bash": 2, + "Double Bash": 3, + "Charge": 4, + "Heavy Impact": 5, + "Vehement": 6, + "Tougher Skin": 7, + "Uppercut": 8, + "Cheaper Charge": 9, + "War Scream": 10, + "Earth Mastery": 11, + "Thunder Mastery": 12, + "Water Mastery": 13, + "Air Mastery": 14, + "Fire Mastery": 15, + "Quadruple Bash": 16, + "Fireworks": 17, + "Half-Moon Swipe": 18, + "Flyby Jab": 19, + "Flaming Uppercut": 20, + "Iron Lungs": 21, + "Generalist": 22, + "Counter": 23, + "Mantle of the Bovemists": 24, + "Bak'al's Grasp": 25, + "Spear Proficiency 2": 26, + "Cheaper Uppercut": 27, + "Aerodynamics": 28, + "Provoke": 29, + "Precise Strikes": 30, + "Air Shout": 31, + "Enraged Blow": 32, + "Flying Kick": 33, + "Stronger Mantle": 34, + "Manachism": 35, + "Boiling Blood": 36, + "Ragnarokkr": 37, + "Ambidextrous": 38, + "Burning Heart": 39, + "Stronger Bash": 40, + "Intoxicating Blood": 41, + "Comet": 42, + "Collide": 43, + "Rejuvenating Skin": 44, + "Uncontainable Corruption": 45, + "Radiant Devotee": 46, + "Whirlwind Strike": 47, + "Mythril Skin": 48, + "Armour Breaker": 49, + "Shield Strike": 50, + "Sparkling Hope": 51, + "Massive Bash": 52, + "Tempest": 53, + "Spirit of the Rabbit": 54, + "Massacre": 55, + "Axe Kick": 56, + "Radiance": 57, + "Cheaper Bash 2": 58, + "Cheaper War Scream": 59, + "Discombobulate": 60, + "Thunderclap": 61, + "Cyclone": 62, + "Second Chance": 63, + "Blood Pact": 64, + "Haemorrhage": 65, + "Brink of Madness": 66, + "Cheaper Uppercut 2": 67, + "Martyr": 68 + } +} \ No newline at end of file diff --git a/py_script/atree-convertID.py b/py_script/atree-convertID.py new file mode 100644 index 0000000..74c40dd --- /dev/null +++ b/py_script/atree-convertID.py @@ -0,0 +1,29 @@ +""" +Generate a JSON Ability Tree [atree_constants_idfied.json] with: + - All references replaced by numerical IDs +given a JSON Ability Tree with reference as string AND a JSON Ability Names to IDs. +""" +import json + +# Ability names to IDs data +with open("atree_ids.json") as f: + id_data = json.loads(f.read()) + +# Ability tree data with reference as string +with open("atree_constants.json") as f: + atree_data = json.loads(f.read()) + +for _class, info in atree_data.items(): + for abil in range(len(info)): + info[abil]["id"] = id_data[_class][info[abil]["display_name"]] + for ref in range(len(info[abil]["parents"])): + info[abil]["parents"][ref] = id_data[_class][info[abil]["parents"][ref]] + + for ref in range(len(info[abil]["dependencies"])): + info[abil]["dependencies"][ref] = id_data[_class][info[abil]["dependencies"][ref]] + + for ref in range(len(info[abil]["blockers"])): + info[abil]["blockers"][ref] = id_data[_class][info[abil]["blockers"][ref]] + +with open('atree_constants_idfied.json', 'w', encoding='utf-8') as abil_dest: + json.dump(atree_data, abil_dest, ensure_ascii=False, indent=4) \ No newline at end of file diff --git a/py_script/atree-generateID.py b/py_script/atree-generateID.py index 4657cf1..d8d6419 100644 --- a/py_script/atree-generateID.py +++ b/py_script/atree-generateID.py @@ -1,37 +1,35 @@ """ -Generate a JSON Ability Tree with: +Generate a JSON Ability Tree [atree_constants_id.json] with: - All references replaced by numerical IDs - - Extra JSON File with Original name as key and Assigned IDs as value. -given a JSON Ability Tree. + - Extra JSON File with Class: [Original name as key and Assigned IDs as value]. +given a JSON Ability Tree with reference as string. """ import json -id = 0 abilDict = {} -with open("atree-parse.json") as f: +with open("atree_constants.json") as f: data = json.loads(f.read()) for classType, info in data.items(): - #reset IDs for every class and start at 1 - id = 1 + _id = 0 + abilDict[classType] = {} for abil in info: - abilDict[abil["display_name"]] = id - id += 1 + abilDict[classType][abil["display_name"]] = _id + _id += 1 - with open("atree-ids.json", "w", encoding='utf-8') as id_dest: + with open("atree_ids.json", "w", encoding='utf-8') as id_dest: json.dump(abilDict, id_dest, ensure_ascii=False, indent=4) for classType, info in data.items(): for abil in range(len(info)): - info[abil]["id"] = abilDict[info[abil]["display_name"]] + info[abil]["id"] = abilDict[classType][info[abil]["display_name"]] for ref in range(len(info[abil]["parents"])): - info[abil]["parents"][ref] = abilDict[info[abil]["parents"][ref]] + info[abil]["parents"][ref] = abilDict[classType][info[abil]["parents"][ref]] for ref in range(len(info[abil]["dependencies"])): - info[abil]["dependencies"][ref] = abilDict[info[abil]["dependencies"][ref]] + info[abil]["dependencies"][ref] = abilDict[classType][info[abil]["dependencies"][ref]] for ref in range(len(info[abil]["blockers"])): - info[abil]["blockers"][ref] = abilDict[info[abil]["blockers"][ref]] - data[classType] = info + info[abil]["blockers"][ref] = abilDict[classType][info[abil]["blockers"][ref]] - with open('atree-constants-id.json', 'w', encoding='utf-8') as abil_dest: - json.dump(data, abil_dest, ensure_ascii=False, indent=4) \ No newline at end of file + with open('atree_constants_id.json', 'w', encoding='utf-8') as abil_dest: + json.dump(data, abil_dest, ensure_ascii=False, indent=4) From ebcdbc14fc19c4a70419bf75de7c3fb54219d1b9 Mon Sep 17 00:00:00 2001 From: reschan Date: Mon, 27 Jun 2022 16:53:18 +0700 Subject: [PATCH 120/155] remove redundant atree data files --- js/atree_constants.js | 4063 +++++++++++----------------- js/atree_constants_old.js | 171 -- js/atree_constants_str_old.js | 4160 ----------------------------- js/atree_constants_str_old_min.js | 1 - 4 files changed, 1592 insertions(+), 6803 deletions(-) delete mode 100644 js/atree_constants_old.js delete mode 100644 js/atree_constants_str_old.js delete mode 100644 js/atree_constants_str_old_min.js diff --git a/js/atree_constants.js b/js/atree_constants.js index 48fd997..cbd42b1 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -5,10 +5,7 @@ const atrees = { "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", "archetype": "", "archetype_req": 0, - "parents": [ - 60, - 34 - ], + "parents": ["Power Shots", "Cheaper Escape"], "dependencies": [], "blockers": [], "cost": 1, @@ -33,14 +30,7 @@ const atrees = { { "name": "Shield Damage", "type": "damage", - "multipliers": [ - 90, - 0, - 0, - 0, - 0, - 10 - ] + "multipliers": [90, 0, 0, 0, 0, 10] }, { "name": "Total Damage", @@ -51,1258 +41,955 @@ const atrees = { } ] } - ], - "id": 0 + ] }, + { "display_name": "Escape", "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", - "archetype": "", - "archetype_req": 0, - "parents": [ - 3 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Heart Shatter"], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 7, - "col": 4 + "row": 7, + "col": 4 }, "properties": { - "aoe": 0, - "range": 0 + "aoe": 0, + "range": 0 }, "effects": [ + { + "type": "replace_spell", + "name": "Escape", + "cost": 25, + "display_text": "Max Damage", + "base_spell": 2, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "None", + "type": "damage", + "multipliers": [0, 0, 0, 0, 0, 0] + }, { - "type": "replace_spell", - "name": "Escape", - "cost": 25, - "display_text": "Max Damage", - "base_spell": 2, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "None", - "type": "damage", - "multipliers": [ - 0, - 0, - 0, - 0, - 0, - 0 - ] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "None": 0 - } - } - ] + "name": "Total Damage", + "type": "total", + "hits": { + "None": 0 + } } - ], - "id": 1 + ] + } + ] }, { "display_name": "Arrow Bomb", "desc": "Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)", - "archetype": "", - "archetype_req": 0, - "parents": [], + "archetype": "", + "archetype_req": 0, + "parents": [], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 0, - "col": 4 + "row": 0, + "col": 4 }, "properties": { - "aoe": 4.5, - "range": 26 + "aoe": 4.5, + "range": 26 }, "effects": [ - { - "type": "replace_spell", + { + "type": "replace_spell", + "name": "Arrow Bomb", + "cost": 50, + "display_text": "Average Damage", + "base_spell": 3, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { "name": "Arrow Bomb", - "cost": 50, - "display_text": "Average Damage", - "base_spell": 3, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Arrow Bomb", - "type": "damage", - "multipliers": [ - 160, - 0, - 0, - 0, - 20, - 0 - ] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Arrow Bomb": 1 - } - } - ] + "type": "damage", + "multipliers": [160, 0, 0, 0, 20, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Arrow Bomb": 1 + } } - ], - "id": 2 + ] + } + ] }, { "display_name": "Heart Shatter", "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 31 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Bow Proficiency I"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 4, - "col": 4 + "row": 4, + "col": 4 }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [ - 100, - 0, - 0, - 0, - 0, - 0 - ] - }, - {} - ], - "id": 3 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + }, + { + + } + ] }, { "display_name": "Fire Creep", "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 68, - 39, - 5 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 16, - "col": 6 + "row": 16, + "col": 6 }, - "properties": { - "aoe": 0.8, - "duration": 6 + "properties": { + "aoe": 0.8, + "duration": 6 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fire Creep", - "cost": 0, - "multipliers": [ - 30, - 0, - 0, - 0, - 20, - 0 - ] - }, - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fire Creep": 15 - } + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [30, 0, 0, 0, 20, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fire Creep": 15 } - ], - "id": 4 + } + ] }, { "display_name": "Bryophyte Roots", "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.", - "archetype": "Trapper", - "archetype_req": 1, - "parents": [ - 4, - 35 - ], - "dependencies": [ - 7 - ], + "archetype": "Trapper", + "archetype_req": 1, + "parents": ["Fire Creep", "Earth Mastery"], + "dependencies": ["Arrow Storm"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 16, - "col": 8 + "row": 16, + "col": 8 }, "properties": { - "aoe": 2, - "duration": 5, - "slowness": 0.4 - }, + "aoe": 2, + "duration": 5, + "slowness": 0.4 + }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Bryophyte Roots", - "cost": 0, - "multipliers": [ - 40, - 20, - 0, - 0, - 0, - 0 - ] - } - ], - "id": 5 + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Bryophyte Roots", + "cost": 0, + "multipliers": [40, 20, 0, 0, 0, 0] + } + ] }, { "display_name": "Nimble String", "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 36, - 69 - ], - "dependencies": [ - 7 - ], - "blockers": [ - 68 - ], - "cost": 2, + "archetype": "", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Arrow Rain"], + "dependencies": ["Arrow Storm"], + "blockers": ["Phantom Ray"], + "cost": 2, "display": { - "row": 15, - "col": 2 + "row": 15, + "col": 2 }, "properties": { - "shootspeed": 2 - }, + "shootspeed": 2 + }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [ - -15, - 0, - 0, - 0, - 0, - 0 - ] - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Stream", - "cost": 0, - "hits": { - "Single Arrow": 8 - } + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [-15, 0, 0, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Stream", + "cost": 0, + "hits": { + "Single Arrow": 8 } - ], - "id": 6 + } + ] }, { "display_name": "Arrow Storm", "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 58, - 34 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Double Shots", "Cheaper Escape"], "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 9, - "col": 2 + "row": 9, + "col": 2 }, "properties": { - "aoe": 0, - "range": 16 + "aoe": 0, + "range": 16 }, "effects": [ - { - "type": "replace_spell", - "name": "Arrow Storm", - "cost": 40, - "display_text": "Max Damage", - "base_spell": 1, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [ - 30, - 0, - 10, - 0, - 0, - 0 - ] - }, - { - "name": "Single Stream", - "type": "total", - "hits": { - "Single Arrow": 8 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Stream": 2 - } - } - ] + { + "type": "replace_spell", + "name": "Arrow Storm", + "cost": 40, + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [30, 0, 10, 0, 0, 0] + }, + { + "name": "Single Stream", + "type": "total", + "hits": { + "Single Arrow": 8 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Stream": 2 + } } - ], - "id": 7 + ] + } + ] }, { "display_name": "Guardian Angels", "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)", "archetype": "Boltslinger", - "archetype_req": 3, - "parents": [ - 59, - 67 - ], - "dependencies": [ - 0 - ], + "archetype_req": 3, + "parents": ["Triple Shots", "Frenzy"], + "dependencies": ["Arrow Shield"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 19, - "col": 1 + "row": 19, + "col": 1 }, "properties": { - "range": 4, - "duration": 60, - "shots": 8, - "count": 2 - }, + "range": 4, + "duration": 60, + "shots": 8, + "count": 2 + }, "effects": [ - { - "type": "replace_spell", - "name": "Guardian Angels", - "cost": 30, - "display_text": "Total Damage Average", - "base_spell": 4, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [ - 30, - 0, - 0, - 0, - 0, - 10 - ] - }, - { - "name": "Single Bow", - "type": "total", - "hits": { - "Single Arrow": 8 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Bow": 2 - } + { + "type": "replace_spell", + "name": "Guardian Angels", + "cost": 30, + "display_text": "Total Damage Average", + "base_spell": 4, + "spell_type": "damage", + "scaling": "spell", + "display": "Total Damage", + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [30, 0, 0, 0, 0, 10] + }, + { + "name": "Single Bow", + "type": "total", + "hits": { + "Single Arrow": 8 } - ] - } - ], - "id": 8 + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Bow": 2 + } + } + ] + } + ] }, { "display_name": "Windy Feet", "base_abil": "Escape", "desc": "When casting Escape, give speed to yourself and nearby allies.", "archetype": "", - "archetype_req": 0, - "parents": [ - 7 - ], - "dependencies": [], + "archetype_req": 0, + "parents": ["Arrow Storm"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 10, - "col": 1 + "row": 10, + "col": 1 }, "properties": { - "aoe": 8, - "duration": 120 - }, + "aoe": 8, + "duration": 120 + }, "type": "stat_bonus", "bonuses": [ - { - "type": "stat", - "name": "spd", - "value": 20 + { + "type": "stat", + "name": "spd", + "value": 20 } - ], - "id": 9 + ] }, { "display_name": "Basaltic Trap", "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)", "archetype": "Trapper", - "archetype_req": 2, - "parents": [ - 5 - ], - "dependencies": [], + "archetype_req": 2, + "parents": ["Bryophyte Roots"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 19, - "col": 8 + "row": 19, + "col": 8 }, "properties": { - "aoe": 7, - "traps": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 0, - "multipliers": [ - 140, - 30, - 0, - 0, - 30, - 0 - ] - } - ], - "id": 10 + "aoe": 7, + "traps": 2 }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [140, 30, 0, 0, 30, 0] + } + ] + }, { "display_name": "Windstorm", "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 8, - 33 - ], - "dependencies": [], - "blockers": [ - 68 - ], - "cost": 2, + "archetype": "", + "archetype_req": 0, + "parents": ["Guardian Angels", "Cheaper Arrow Storm"], + "dependencies": [], + "blockers": ["Phantom Ray"], + "cost": 2, "display": { "row": 21, "col": 1 - }, + }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [ - -11, - 0, - -7, - 0, - 0, - 3 - ] - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Stream": 1 - } + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [-11, 0, -7, 0, 0, 3] + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 1 } - ], - "id": 11 - }, + } + ] + }, { "display_name": "Grappling Hook", "base_abil": "Escape", "desc": "When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)", - "archetype": "Trapper", - "archetype_req": 0, - "parents": [ - 61, - 40, - 33 - ], - "dependencies": [], - "blockers": [ - 20 - ], - "cost": 2, + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Focus", "More Shields", "Cheaper Arrow Storm"], + "dependencies": [], + "blockers": ["Escape Artist"], + "cost": 2, "display": { "row": 21, "col": 5 - }, + }, "properties": { "range": 20 }, - "effects": [], - "id": 12 - }, + "effects": [ + ] + }, { "display_name": "Implosion", "desc": "Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": [ - 12, - 40 - ], - "dependencies": [], + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grappling Hook", "More Shields"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 22, "col": 6 - }, + }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [ - 40, - 0, - 0, - 0, - 0, - 0 - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [40, 0, 0, 0, 0, 0] } - ], - "id": 13 - }, + ] + }, { "display_name": "Twain's Arc", "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", - "archetype": "Sharpshooter", - "archetype_req": 4, - "parents": [ - 62, - 64 - ], - "dependencies": [ - 61 - ], + "archetype": "Sharpshooter", + "archetype_req": 4, + "parents": ["More Focus", "Traveler"], + "dependencies": ["Focus"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 25, "col": 4 - }, + }, "properties": { - "range": 64, - "focusReq": 2 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Twain's Arc", - "cost": 0, - "display_text": "Twain's Arc", - "base_spell": 5, - "spell_type": "damage", - "scaling": "melee", - "display": "Twain's Arc Damage", - "parts": [ - { - "name": "Twain's Arc Damage", - "type": "damage", - "multipliers": [ - 200, - 0, - 0, - 0, - 0, - 0 - ] - } - ] - } - ], - "id": 14 + "range": 64, + "focusReq": 2 }, + "effects": [ + + { + "type": "replace_spell", + "name": "Twain's Arc", + "cost": 0, + "display_text": "Twain's Arc", + "base_spell": 5, + "spell_type": "damage", + "scaling": "melee", + "display": "Twain's Arc Damage", + "parts": [ + { + "name": "Twain's Arc Damage", + "type": "damage", + "multipliers": [200, 0, 0, 0, 0, 0] + } + ] + } + ] + }, { "display_name": "Fierce Stomp", "desc": "When using Escape, hold shift to quickly drop down and deal damage.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": [ - 42, - 64 - ], - "dependencies": [], + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Refined Gunpowder", "Traveler"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 26, - "col": 1 + "row": 26, + "col": 1 }, "properties": { - "aoe": 4 + "aoe": 4 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Fierce Stomp", - "cost": 0, - "multipliers": [ - 100, - 0, - 0, - 0, - 0, - 0 - ] - }, - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fierce Stomp": 1 - } + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [100, 0, 0, 0, 0, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Fierce Stomp": 1 } - ], - "id": 15 + } + ] }, { "display_name": "Scorched Earth", "desc": "Fire Creep become much stronger.", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": [ - 14 - ], - "dependencies": [ - 4 - ], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Twain's Arc"], + "dependencies": ["Fire Creep"], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 26, - "col": 5 + "row": 26 , + "col": 5 }, "properties": { - "duration": 2, - "aoe": 0.4 + "duration": 2, + "aoe": 0.4 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fire Creep", - "cost": 0, - "multipliers": [ - 10, - 0, - 0, - 0, - 5, - 0 - ] - } - ], - "id": 16 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Fire Creep", + "cost": 0, + "multipliers": [10, 0, 0, 0, 5, 0] + } + ] }, { "display_name": "Leap", "desc": "When you double tap jump, leap foward. (2s Cooldown)", - "archetype": "Boltslinger", - "archetype_req": 5, - "parents": [ - 42, - 55 - ], - "dependencies": [], + "archetype": "Boltslinger", + "archetype_req": 5, + "parents": ["Refined Gunpowder", "Homing Shots"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 28, - "col": 0 + "row": 28, + "col": 0 }, "properties": { - "cooldown": 2 - }, - "effects": [], - "id": 17 + "cooldown": 2 }, + "effects": [ + + ] + }, { "display_name": "Shocking Bomb", "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.", - "archetype": "Sharpshooter", - "archetype_req": 5, - "parents": [ - 14, - 44, - 55 - ], - "dependencies": [ - 2 - ], + "archetype": "Sharpshooter", + "archetype_req": 5, + "parents": ["Twain's Arc", "Better Arrow Shield", "Homing Shots"], + "dependencies": ["Arrow Bomb"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 28, - "col": 4 + "row": 28, + "col": 4 }, "properties": { - "gravity": 0 + "gravity": 0 }, "effects": [ - { - "type": "convert_spell_conv", - "target_part": "all", - "conversion": "thunder" - } - ], - "id": 18 + { + "type": "convert_spell_conv", + "target_part": "all", + "conversion": "thunder" + } + ] }, { "display_name": "Mana Trap", "desc": "Your Traps will give you 4 Mana per second when you stay close to them.", - "archetype": "Trapper", - "archetype_req": 5, - "parents": [ - 43, - 44 - ], - "dependencies": [ - 4 - ], + "archetype": "Trapper", + "archetype_req": 5, + "parents": ["More Traps", "Better Arrow Shield"], + "dependencies": ["Fire Creep"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 28, - "col": 8 + "row": 28, + "col": 8 }, "properties": { - "range": 12, - "manaRegen": 4 + "range": 12, + "manaRegen": 4 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 10, - "multipliers": [ - 0, - 0, - 0, - 0, - 0, - 0 - ] - } - ], - "id": 19 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 10, + "multipliers": [0, 0, 0, 0, 0, 0] + } + ] }, { "display_name": "Escape Artist", "desc": "When casting Escape, release 100 arrows towards the ground.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": [ - 46, - 17 - ], - "dependencies": [], - "blockers": [ - 12 - ], - "cost": 2, + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Better Guardian Angels", "Leap"], + "dependencies": [], + "blockers": ["Grappling Hook"], + "cost": 2, "display": { - "row": 31, - "col": 0 + "row": 31, + "col": 0 + }, + "properties": { }, - "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Escape Artist", - "cost": 0, - "multipliers": [ - 30, - 0, - 10, - 0, - 0, - 0 - ] - } - ], - "id": 20 + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Escape Artist", + "cost": 0, + "multipliers": [30, 0, 10, 0, 0, 0] + } + ] }, { "display_name": "Initiator", "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", "archetype": "Sharpshooter", - "archetype_req": 5, - "parents": [ - 18, - 44, - 47 - ], - "dependencies": [ - 61 - ], + "archetype_req": 5, + "parents": ["Shocking Bomb", "Better Arrow Shield", "Cheaper Arrow Storm (2)"], + "dependencies": ["Focus"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 31, - "col": 5 + "row": 31, + "col": 5 }, "properties": { - "focus": 1, - "timer": 5 - }, - "type": "stat_bonus", + "focus": 1, + "timer": 5 + }, + "type": "stat_bonus", "bonuses": [ - { - "type": "stat", - "name": "damPct", - "value": 50 + { + "type": "stat", + "name": "damPct", + "value": 50 } - ], - "id": 21 + ] }, { "display_name": "Call of the Hound", "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", "archetype": "Trapper", - "archetype_req": 0, - "parents": [ - 21, - 47 - ], - "dependencies": [ - 0 - ], + "archetype_req": 0, + "parents": ["Initiator", "Cheaper Arrow Storm (2)"], + "dependencies": ["Arrow Shield"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 32, - "col": 7 + "row": 32, + "col": 7 }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Call of the Hound", - "cost": 0, - "multipliers": [ - 40, - 0, - 0, - 0, - 0, - 0 - ] - } - ], - "id": 22 + "properties": { + }, + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Call of the Hound", + "cost": 0, + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] }, { "display_name": "Arrow Hurricane", "desc": "Arrow Storm will shoot +2 stream of arrows.", - "archetype": "Boltslinger", - "archetype_req": 8, - "parents": [ - 48, - 20 - ], - "dependencies": [], - "blockers": [ - 68 - ], - "cost": 2, + "archetype": "Boltslinger", + "archetype_req": 8, + "parents": ["Precise Shot", "Escape Artist"], + "dependencies": [], + "blockers": ["Phantom Ray"], + "cost": 2, "display": { - "row": 33, - "col": 0 + "row": 33, + "col": 0 }, "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Stream": 2 - } + "effects": [ + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Total Damage", + "cost": 0, + "hits": { + "Single Stream": 2 } - ], - "id": 23 + } + ] }, { "display_name": "Geyser Stomp", "desc": "Fierce Stomp will create geysers, dealing more damage and vertical knockback.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 56 - ], - "dependencies": [ - 15 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Shrapnel Bomb"], + "dependencies": ["Fierce Stomp"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 37, - "col": 1 + "row": 37, + "col": 1 }, "properties": { - "aoe": 1 + "aoe": 1 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Fierce Stomp", - "cost": 0, - "multipliers": [ - 0, - 0, - 0, - 50, - 0, - 0 - ] - } - ], - "id": 24 + { + "type": "add_spell_prop", + "base_spell": 2, + "target_part": "Fierce Stomp", + "cost": 0, + "multipliers": [0, 0, 0, 50, 0, 0] + } + ] }, { "display_name": "Crepuscular Ray", "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.", - "archetype": "Sharpshooter", - "archetype_req": 10, - "parents": [ - 49 - ], - "dependencies": [ - 7 - ], + "archetype": "Sharpshooter", + "archetype_req": 10, + "parents": ["Cheaper Arrow Shield"], + "dependencies": ["Arrow Storm"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 37, - "col": 4 + "row": 37, + "col": 4 }, "properties": { - "focusReq": 5, - "focusRegen": -1 - }, + "focusReq": 5, + "focusRegen": -1 + }, "effects": [ + { + "type": "replace_spell", + "name": "Crepuscular Ray", + "base_spell": 5, + "spell_type": "damage", + "scaling": "spell", + "display": "One Focus", + "cost": 0, + + "parts": [ + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [10, 0, 0, 5, 0, 0] + }, { - "type": "replace_spell", - "name": "Crepuscular Ray", - "base_spell": 5, - "spell_type": "damage", - "scaling": "spell", - "display": "One Focus", - "cost": 0, - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [ - 10, - 0, - 0, - 5, - 0, - 0 - ] - }, - { - "name": "One Focus", - "type": "total", - "hits": { - "Single Arrow": 20 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "One Focus": 7 - } - } - ] + "name": "One Focus", + "type": "total", + "hits": { + "Single Arrow": 20 + } + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "One Focus": 7 + } } - ], - "id": 25 + ] + } + ] }, { "display_name": "Grape Bomb", "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 51 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Escape (2)"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 37, - "col": 7 + "row": 37, + "col": 7 }, "properties": { - "miniBombs": 3, - "aoe": 2 + "miniBombs": 3, + "aoe": 2 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Grape Bomb", - "cost": 0, - "multipliers": [ - 30, - 0, - 0, - 0, - 10, - 0 - ] - } - ], - "id": 26 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Grape Bomb", + "cost": 0, + "multipliers": [30, 0, 0, 0, 10, 0] + } + ] }, { "display_name": "Tangled Traps", "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": [ - 26 - ], - "dependencies": [ - 10 - ], + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Grape Bomb"], + "dependencies": ["Basaltic Trap"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 38, - "col": 6 + "row": 38, + "col": 6 }, "properties": { - "attackSpeed": 0.2 + "attackSpeed": 0.2 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Tangled Traps", - "cost": 0, - "multipliers": [ - 20, - 0, - 0, - 0, - 0, - 20 - ] - } - ], - "id": 27 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Tangled Traps", + "cost": 0, + "multipliers": [20, 0, 0, 0, 0, 20] + } + ] }, { "display_name": "Snow Storm", "desc": "Enemies near you will be slowed down.", "archetype": "", - "archetype_req": 0, - "parents": [ - 24, - 63 - ], - "dependencies": [], + "archetype_req": 0, + "parents": ["Geyser Stomp", "More Focus (2)"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 39, - "col": 2 + "row": 39, + "col": 2 }, "properties": { - "range": 2.5, - "slowness": 0.3 - }, - "id": 28 + "range": 2.5, + "slowness": 0.3 + } }, { "display_name": "All-Seeing Panoptes", "desc": "Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.", "archetype": "Boltslinger", - "archetype_req": 11, - "parents": [ - 28 - ], - "dependencies": [ - 8 - ], + "archetype_req": 11, + "parents": ["Snow Storm"], + "dependencies": ["Guardian Angels"], "blockers": [], - "cost": 2, + "cost": 2, "display": { - "row": 40, - "col": 1 + "row": 40, + "col": 1 }, "properties": { - "range": 10, - "shots": 5 - }, + "range": 10, + "shots": 5 + }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [ - 0, - 0, - 0, - 0, - 20, - 0 - ] - }, - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Bow", - "cost": 0, - "hits": { - "Single Arrow": 5 - } + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Arrow", + "cost": 0, + "multipliers": [0, 0, 0, 0, 20, 0] + }, + { + "type": "add_spell_prop", + "base_spell": 4, + "target_part": "Single Bow", + "cost": 0, + "hits": { + "Single Arrow": 5 } - ], - "id": 29 + } + ] }, { "display_name": "Minefield", "desc": "Allow you to place +6 Traps, but with reduced damage and range.", "archetype": "Trapper", - "archetype_req": 10, - "parents": [ - 26, - 53 - ], - "dependencies": [ - 10 - ], + "archetype_req": 10, + "parents": ["Grape Bomb", "Cheaper Arrow Bomb (2)"], + "dependencies": ["Basaltic Trap"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 40, "col": 7 - }, + }, "properties": { "aoe": -2, "traps": 6 }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 0, - "multipliers": [ - -80, - 0, - 0, - 0, - 0, - 0 - ] + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Basaltic Trap", + "cost": 0, + "multipliers": [-80, 0, 0, 0, 0, 0] } - ], - "id": 30 - }, + ] + }, { "display_name": "Bow Proficiency I", "desc": "Improve your Main Attack's damage and range when using a bow.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 2 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Bomb"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 4 - }, + }, "properties": { "mainAtk_range": 6 }, @@ -1317,103 +1004,94 @@ const atrees = { } ] } - ], - "id": 31 + ] }, { "display_name": "Cheaper Arrow Bomb", "desc": "Reduce the Mana cost of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 31 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Bow Proficiency I"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 6 + }, + "properties": { + }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "cost": -10 } - ], - "id": 32 + ] }, { "display_name": "Cheaper Arrow Storm", "desc": "Reduce the Mana cost of Arrow Storm.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 12, - 11, - 61 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "Windstorm", "Focus"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 21, "col": 3 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -5 } - ], - "id": 33 + ] }, { "display_name": "Cheaper Escape", "desc": "Reduce the Mana cost of Escape.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 7, - 0 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Arrow Storm", "Arrow Shield"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 9, "col": 4 + }, + "properties": { + }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "cost": -5 } - ], - "id": 34 + ] }, { "display_name": "Earth Mastery", "desc": "Increases your base damage from all Earth attacks", - "archetype": "Trapper", - "archetype_req": 0, - "parents": [ - 0 - ], - "dependencies": [], + "archetype": "Trapper", + "archetype_req": 0, + "parents": ["Arrow Shield"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 8 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "raw_stat", @@ -1426,34 +1104,27 @@ const atrees = { { "type": "stat", "name": "eDam", - "value": [ - 2, - 4 - ] + "value": [2, 4] } ] } - ], - "id": 35 + ] }, { "display_name": "Thunder Mastery", "desc": "Increases your base damage from all Thunder attacks", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": [ - 7, - 39, - 34 - ], - "dependencies": [], + "archetype": "Boltslinger", + "archetype_req": 0, + "parents": ["Arrow Storm", "Fire Mastery", "Cheaper Escape"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 2 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "raw_stat", @@ -1466,34 +1137,27 @@ const atrees = { { "type": "stat", "name": "tDam", - "value": [ - 1, - 8 - ] + "value": [1, 8] } ] } - ], - "id": 36 + ] }, { "display_name": "Water Mastery", "desc": "Increases your base damage from all Water attacks", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": [ - 34, - 36, - 39 - ], - "dependencies": [], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Cheaper Escape", "Thunder Mastery", "Fire Mastery"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 14, "col": 4 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "raw_stat", @@ -1506,32 +1170,27 @@ const atrees = { { "type": "stat", "name": "wDam", - "value": [ - 2, - 4 - ] + "value": [2, 4] } ] } - ], - "id": 37 + ] }, { "display_name": "Air Mastery", "desc": "Increases base damage from all Air attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 7 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Arrow Storm"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 0 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "raw_stat", @@ -1544,34 +1203,27 @@ const atrees = { { "type": "stat", "name": "aDam", - "value": [ - 3, - 4 - ] + "value": [3, 4] } ] } - ], - "id": 38 + ] }, { "display_name": "Fire Mastery", "desc": "Increases base damage from all Earth attacks", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": [ - 36, - 0, - 34 - ], - "dependencies": [], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Arrow Shield", "Cheaper Escape"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 6 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "raw_stat", @@ -1584,210 +1236,156 @@ const atrees = { { "type": "stat", "name": "fDam", - "value": [ - 3, - 5 - ] + "value": [3, 5] } ] } - ], - "id": 39 + ] }, { "display_name": "More Shields", "desc": "Give +2 charges to Arrow Shield.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 12, - 10 - ], - "dependencies": [ - 0 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Grappling Hook", "Basaltic Trap"], + "dependencies": ["Arrow Shield"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 21, "col": 7 - }, + }, "properties": { "shieldCharges": 2 - }, - "id": 40 + } }, { "display_name": "Stormy Feet", "desc": "Windy Feet will last longer and add more speed.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 11 - ], - "dependencies": [ - 9 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Windstorm"], + "dependencies": ["Windy Feet"], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 23, - "col": 1 + "row": 23, + "col": 1 }, "properties": { - "duration": 60 + "duration": 60 }, "effects": [ - { - "type": "stat_bonus", - "bonuses": [ - { - "type": "stat", - "name": "spdPct", - "value": 20 - } - ] + { + "type": "stat_bonus", + "bonuses": [ + { + "type": "stat", + "name": "spdPct", + "value": 20 } - ], - "id": 41 + ] + } + ] }, { "display_name": "Refined Gunpowder", "desc": "Increase the damage of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 11 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Windstorm"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 25, - "col": 0 + "row": 25, + "col": 0 }, "properties": {}, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [ - 50, - 0, - 0, - 0, - 0, - 0 - ] - } - ], - "id": 42 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Bomb", + "cost": 0, + "multipliers": [50, 0, 0, 0, 0, 0] + } + ] }, { "display_name": "More Traps", "desc": "Increase the maximum amount of active Traps you can have by +2.", "archetype": "Trapper", - "archetype_req": 10, - "parents": [ - 54 - ], - "dependencies": [ - 10 - ], + "archetype_req": 10, + "parents": ["Bouncing Bomb"], + "dependencies": ["Basaltic Trap"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 26, "col": 8 - }, + }, "properties": { "traps": 2 - }, - "id": 43 + } }, { "display_name": "Better Arrow Shield", "desc": "Arrow Shield will gain additional area of effect, knockback and damage.", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": [ - 19, - 18, - 14 - ], - "dependencies": [ - 0 - ], + "archetype": "Sharpshooter", + "archetype_req": 0, + "parents": ["Mana Trap", "Shocking Bomb", "Twain's Arc"], + "dependencies": ["Arrow Shield"], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 28, - "col": 6 + "row": 28, + "col": 6 }, "properties": { - "aoe": 1 - }, + "aoe": 1 + }, "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Shield", - "multipliers": [ - 40, - 0, - 0, - 0, - 0, - 0 - ] - } - ], - "id": 44 + { + "type": "add_spell_prop", + "base_spell": 3, + "target_part": "Arrow Shield", + "multipliers": [40, 0, 0, 0, 0, 0] + } + ] }, { "display_name": "Better Leap", "desc": "Reduce leap's cooldown by 1s.", "archetype": "Boltslinger", - "archetype_req": 0, - "parents": [ - 17, - 55 - ], - "dependencies": [ - 17 - ], + "archetype_req": 0, + "parents": ["Leap", "Homing Shots"], + "dependencies": ["Leap"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 29, "col": 1 - }, + }, "properties": { "cooldown": -1 - }, - "id": 45 + } }, { "display_name": "Better Guardian Angels", "desc": "Your Guardian Angels can shoot +4 arrows before disappearing.", "archetype": "Boltslinger", - "archetype_req": 0, - "parents": [ - 20, - 55 - ], - "dependencies": [ - 8 - ], + "archetype_req": 0, + "parents": ["Escape Artist", "Homing Shots"], + "dependencies": ["Guardian Angels"], "blockers": [], - "cost": 1, + "cost": 1, "display": { - "row": 31, - "col": 2 + "row": 31, + "col": 2 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "add_spell_prop", @@ -1798,52 +1396,44 @@ const atrees = { "Single Arrow": 4 } } - ], - "id": 46 + ] }, { "display_name": "Cheaper Arrow Storm (2)", "desc": "Reduce the Mana cost of Arrow Storm.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 21, - 19 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Initiator", "Mana Trap"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 31, "col": 8 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -5 } - ], - "id": 47 + ] }, { "display_name": "Precise Shot", "desc": "+30% Critical Hit Damage", - "archetype": "", - "archetype_req": 0, - "parents": [ - 46, - 49, - 23 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Better Guardian Angels", "Cheaper Arrow Shield", "Arrow Hurricane"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 33, "col": 2 - }, + }, "properties": { "mainAtk_range": 6 }, @@ -1858,138 +1448,118 @@ const atrees = { } ] } - ], - "id": 48 + ] }, { "display_name": "Cheaper Arrow Shield", "desc": "Reduce the Mana cost of Arrow Shield.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 48, - 21 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Precise Shot", "Initiator"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 33, "col": 4 + }, + "properties": { }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "cost": -5 } - ], - "id": 49 + ] }, { "display_name": "Rocket Jump", "desc": "Arrow Bomb's self-damage will knockback you farther away.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 47, - 21 - ], - "dependencies": [ - 2 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Arrow Storm (2)", "Initiator"], + "dependencies": ["Arrow Bomb"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 33, "col": 6 - }, - "properties": {}, - "id": 50 + }, + "properties": { + } }, { "display_name": "Cheaper Escape (2)", "desc": "Reduce the Mana cost of Escape.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 22, - 70 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Call of the Hound", "Decimator"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 34, "col": 7 + }, + "properties": { + }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "cost": -5 } - ], - "id": 51 + ] }, { "display_name": "Stronger Hook", "desc": "Increase your Grappling Hook's range, speed and strength.", - "archetype": "Trapper", - "archetype_req": 5, - "parents": [ - 51 - ], - "dependencies": [ - 12 - ], + "archetype": "Trapper", + "archetype_req": 5, + "parents": ["Cheaper Escape (2)"], + "dependencies": ["Grappling Hook"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 35, "col": 8 - }, + }, "properties": { - "range": 8 - }, - "id": 52 + "range": 8 + } }, { "display_name": "Cheaper Arrow Bomb (2)", "desc": "Reduce the Mana cost of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": [ - 63, - 30 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["More Focus (2)", "Minefield"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 40, "col": 5 + }, + "properties": { + }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "cost": -5 } - ], - "id": 53 + ] }, { "display_name": "Bouncing Bomb", "desc": "Arrow Bomb will bounce once when hitting a block or enemy", "archetype": "", "archetype_req": 0, - "parents": [ - 40 - ], + "parents": ["More Shields"], "dependencies": [], "blockers": [], "cost": 2, @@ -1997,7 +1567,9 @@ const atrees = { "row": 25, "col": 7 }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "add_spell_prop", @@ -2008,18 +1580,14 @@ const atrees = { "Arrow Bomb": 2 } } - ], - "id": 54 + ] }, { "display_name": "Homing Shots", "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity", "archetype": "", "archetype_req": 0, - "parents": [ - 17, - 18 - ], + "parents": ["Leap", "Shocking Bomb"], "dependencies": [], "blockers": [], "cost": 2, @@ -2027,53 +1595,45 @@ const atrees = { "row": 28, "col": 2 }, - "properties": {}, - "effects": [], - "id": 55 + "properties": { + + }, + "effects": [ + + ] }, { "display_name": "Shrapnel Bomb", "desc": "Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area", "archetype": "Boltslinger", "archetype_req": 8, - "parents": [ - 23, - 48 - ], + "parents": ["Arrow Hurricane", "Precise Shot"], "dependencies": [], "blockers": [], "cost": 2, "display": { "row": 34, - "col": 1 + "col": 1 + }, + "properties": { + }, - "properties": {}, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Shrapnel Bomb", "cost": 0, - "multipliers": [ - 40, - 0, - 0, - 0, - 20, - 0 - ] + "multipliers": [40, 0, 0, 0, 20, 0] } - ], - "id": 56 + ] }, { "display_name": "Elusive", "desc": "If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)", "archetype": "Boltslinger", "archetype_req": 0, - "parents": [ - 24 - ], + "parents": ["Geyser Stomp"], "dependencies": [], "blockers": [], "cost": 2, @@ -2081,22 +1641,21 @@ const atrees = { "row": 38, "col": 0 }, - "properties": {}, - "effects": [], - "id": 57 + "properties": { + + }, + "effects": [ + + ] }, { "display_name": "Double Shots", "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)", "archetype": "Boltslinger", "archetype_req": 0, - "parents": [ - 1 - ], + "parents": ["Escape"], "dependencies": [], - "blockers": [ - 60 - ], + "blockers": ["Power Shots"], "cost": 1, "display": { "row": 7, @@ -2113,21 +1672,15 @@ const atrees = { "cost": 0, "multipliers": 0.7 } - ], - "id": 58 + ] }, { "display_name": "Triple Shots", "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow", "archetype": "Boltslinger", "archetype_req": 0, - "parents": [ - 69, - 67 - ], - "dependencies": [ - 58 - ], + "parents": ["Arrow Rain", "Frenzy"], + "dependencies": ["Double Shots"], "blockers": [], "cost": 1, "display": { @@ -2145,38 +1698,34 @@ const atrees = { "cost": 0, "multipliers": 0.7 } - ], - "id": 59 + ] }, { "display_name": "Power Shots", "desc": "Main Attack arrows have increased speed and knockback", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": [ - 1 - ], + "parents": ["Escape"], "dependencies": [], - "blockers": [ - 58 - ], + "blockers": ["Double Shots"], "cost": 1, "display": { "row": 7, "col": 6 }, - "properties": {}, - "effects": [], - "id": 60 + "properties": { + + }, + "effects": [ + + ] }, { "display_name": "Focus", "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once", "archetype": "Sharpshooter", "archetype_req": 2, - "parents": [ - 68 - ], + "parents": ["Phantom Ray"], "dependencies": [], "blockers": [], "cost": 2, @@ -2184,7 +1733,9 @@ const atrees = { "row": 19, "col": 4 }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "stat_scaling", @@ -2195,23 +1746,17 @@ const atrees = { "abil_name": "Focus", "name": "damMult" }, - "scaling": [ - 3 - ], + "scaling": [3], "max": 3 } - ], - "id": 61 + ] }, { "display_name": "More Focus", "desc": "Add +2 max Focus", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": [ - 33, - 12 - ], + "parents": ["Cheaper Arrow Storm", "Grappling Hook"], "dependencies": [], "blockers": [], "cost": 1, @@ -2219,7 +1764,9 @@ const atrees = { "row": 22, "col": 4 }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "stat_scaling", @@ -2230,23 +1777,17 @@ const atrees = { "abil_name": "Focus", "name": "damMult" }, - "scaling": [ - 35 - ], + "scaling": [35], "max": 5 } - ], - "id": 62 + ] }, { "display_name": "More Focus (2)", "desc": "Add +2 max Focus", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": [ - 25, - 28 - ], + "parents": ["Crepuscular Ray", "Snow Storm"], "dependencies": [], "blockers": [], "cost": 1, @@ -2254,7 +1795,9 @@ const atrees = { "row": 39, "col": 4 }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "stat_scaling", @@ -2265,23 +1808,17 @@ const atrees = { "abil_name": "Focus", "name": "damMult" }, - "scaling": [ - 35 - ], + "scaling": [35], "max": 7 } - ], - "id": 63 + ] }, { "display_name": "Traveler", "desc": "For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)", "archetype": "", "archetype_req": 0, - "parents": [ - 42, - 14 - ], + "parents": ["Refined Gunpowder", "Twain's Arc"], "dependencies": [], "blockers": [], "cost": 1, @@ -2289,7 +1826,9 @@ const atrees = { "row": 25, "col": 2 }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "stat_scaling", @@ -2304,25 +1843,18 @@ const atrees = { "type": "stat", "name": "sdRaw" }, - "scaling": [ - 1 - ], + "scaling": [1], "max": 100 } - ], - "id": 64 + ] }, { "display_name": "Patient Hunter", "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%)", "archetype": "Trapper", "archetype_req": 0, - "parents": [ - 40 - ], - "dependencies": [ - 10 - ], + "parents": ["More Shields"], + "dependencies": ["Basaltic Trap"], "blockers": [], "cost": 2, "display": { @@ -2332,20 +1864,17 @@ const atrees = { "properties": { "max": 80 }, - "effects": [], - "id": 65 + "effects": [ + + ] }, { "display_name": "Stronger Patient Hunter", "desc": "Add +80% Max Damage to Patient Hunter", "archetype": "Trapper", "archetype_req": 0, - "parents": [ - 26 - ], - "dependencies": [ - 65 - ], + "parents": ["Grape Bomb"], + "dependencies": ["Patient Hunter"], "blockers": [], "cost": 1, "display": { @@ -2355,18 +1884,16 @@ const atrees = { "properties": { "max": 80 }, - "effects": [], - "id": 66 + "effects": [ + + ] }, { "display_name": "Frenzy", "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second", "archetype": "Boltslinger", "archetype_req": 0, - "parents": [ - 59, - 6 - ], + "parents": ["Triple Shots", "Nimble String"], "dependencies": [], "blockers": [], "cost": 2, @@ -2374,7 +1901,9 @@ const atrees = { "row": 17, "col": 2 }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "stat_scaling", @@ -2384,127 +1913,93 @@ const atrees = { "type": "stat", "name": "spd" }, - "scaling": [ - 6 - ], + "scaling": [6], "max": 200 } - ], - "id": 67 + ] }, { "display_name": "Phantom Ray", "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": [ - 37, - 4 - ], - "dependencies": [ - 7 - ], - "blockers": [ - 11, - 6, - 23 - ], + "parents": ["Water Mastery", "Fire Creep"], + "dependencies": ["Arrow Storm"], + "blockers": ["Windstorm", "Nimble String", "Arrow Hurricane"], "cost": 2, "display": { "row": 16, "col": 4 }, - "properties": {}, + "properties": { + }, "effects": [ - { + { "type": "replace_spell", "name": "Phantom Ray", "cost": 40, - "display_text": "Max Damage", - "base_spell": 1, - "spell_type": "damage", + "display_text": "Max Damage", + "base_spell": 1, + "spell_type": "damage", "scaling": "spell", - "display": "Total Damage", + "display": "Total Damage", "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [ - 25, - 0, - 5, - 0, - 0, - 0 - ] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Arrow": 16 - } + { + "name": "Single Arrow", + "type": "damage", + "multipliers": [25, 0, 5, 0, 0, 0] + }, + { + "name": "Total Damage", + "type": "total", + "hits": { + "Single Arrow": 16 } + } ] } - ], - "id": 68 + ] }, { "display_name": "Arrow Rain", "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies", "archetype": "Trapper", "archetype_req": 0, - "parents": [ - 6, - 38 - ], - "dependencies": [ - 0 - ], + "parents": ["Nimble String", "Air Mastery"], + "dependencies": ["Arrow Shield"], "blockers": [], "cost": 2, "display": { "row": 15, "col": 0 }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "target_part": "Arrow Rain", "cost": 0, - "multipliers": [ - 120, - 0, - 0, - 0, - 0, - 80 - ] + "multipliers": [120, 0, 0, 0, 0, 80] } - ], - "id": 69 + ] }, { "display_name": "Decimator", "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)", "archetype": "Sharpshooter", "archetype_req": 0, - "parents": [ - 49 - ], - "dependencies": [ - 68 - ], + "parents": ["Cheaper Arrow Shield"], + "dependencies": ["Phantom Ray"], "blockers": [], "cost": 1, "display": { "row": 34, "col": 5 }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -2517,20 +2012,19 @@ const atrees = { "scaling": 10, "max": 50 } - ], - "id": 70 + ] } ], "Warrior": [ { "display_name": "Bash", "desc": "Violently bash the ground, dealing high damage in a large area", - "archetype": "", - "archetype_req": 0, - "parents": [], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": [], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 0, "col": 4, @@ -2554,14 +2048,7 @@ const atrees = { { "name": "Single Hit", "type": "damage", - "multipliers": [ - 130, - 20, - 0, - 0, - 0, - 0 - ] + "multipliers": [130, 20, 0, 0, 0, 0] }, { "name": "Total Damage", @@ -2572,20 +2059,17 @@ const atrees = { } ] } - ], - "id": 0 + ] }, { "display_name": "Spear Proficiency 1", "desc": "Improve your Main Attack's damage and range w/ spear", - "archetype": "", - "archetype_req": 0, - "parents": [ - 0 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Bash"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 4, @@ -2605,46 +2089,43 @@ const atrees = { } ] } - ], - "id": 1 + ] }, + { "display_name": "Cheaper Bash", "desc": "Reduce the Mana cost of Bash", - "archetype": "", - "archetype_req": 0, - "parents": [ - 1 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 1"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 2, "col": 2, "icon": "node_0" }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -10 } - ], - "id": 2 + ] }, { "display_name": "Double Bash", "desc": "Bash will hit a second time at a farther range", - "archetype": "", - "archetype_req": 0, - "parents": [ - 1 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 1"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 4, "col": 4, @@ -2669,35 +2150,27 @@ const atrees = { "base_spell": 1, "target_part": "Single Hit", "cost": 0, - "multipliers": [ - -50, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [-50, 0, 0, 0, 0, 0] } - ], - "id": 3 + ] }, + { "display_name": "Charge", "desc": "Charge forward at high speed (hold shift to cancel)", - "archetype": "", - "archetype_req": 0, - "parents": [ - 3 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Double Bash"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 6, "col": 4, "icon": "node_4" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "replace_spell", @@ -2712,14 +2185,7 @@ const atrees = { { "name": "None", "type": "damage", - "multipliers": [ - 0, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [0, 0, 0, 0, 0, 0] }, { "name": "Total Damage", @@ -2730,20 +2196,18 @@ const atrees = { } ] } - ], - "id": 4 + ] }, + { "display_name": "Heavy Impact", "desc": "After using Charge, violently crash down into the ground and deal damage", - "archetype": "", - "archetype_req": 0, - "parents": [ - 8 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Uppercut"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 9, "col": 1, @@ -2758,37 +2222,27 @@ const atrees = { "base_spell": 2, "target_part": "Heavy Impact", "cost": 0, - "multipliers": [ - 100, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [100, 0, 0, 0, 0, 0] } - ], - "id": 5 + ] }, + { "display_name": "Vehement", "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 4 - ], - "dependencies": [], - "blockers": [ - 7 - ], - "cost": 1, + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Charge"], + "dependencies": [], + "blockers": ["Tougher Skin"], + "cost": 1, "display": { "row": 6, "col": 2, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -2807,34 +2261,28 @@ const atrees = { "type": "stat", "name": "spd" }, - "scaling": [ - 1, - 1 - ], + "scaling": [1, 1], "max": 20 } - ], - "id": 6 + ] }, + { "display_name": "Tougher Skin", "desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 4 - ], - "dependencies": [], - "blockers": [ - 6 - ], - "cost": 1, + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Charge"], + "dependencies": [], + "blockers": ["Vehement"], + "cost": 1, "display": { "row": 6, "col": 6, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -2863,26 +2311,21 @@ const atrees = { "type": "stat", "name": "hpBonus" }, - "scaling": [ - 10, - 10 - ], + "scaling": [10, 10], "max": 100 } - ], - "id": 7 + ] }, + { "display_name": "Uppercut", "desc": "Rocket enemies in the air and deal massive damage", - "archetype": "", - "archetype_req": 0, - "parents": [ - 6 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Vehement"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 8, "col": 2, @@ -2906,14 +2349,7 @@ const atrees = { { "name": "Uppercut", "type": "damage", - "multipliers": [ - 150, - 50, - 50, - 0, - 0, - 0 - ] + "multipliers": [150, 50, 50, 0, 0, 0] }, { "name": "Total Damage", @@ -2924,47 +2360,43 @@ const atrees = { } ] } - ], - "id": 8 + ] }, + { "display_name": "Cheaper Charge", "desc": "Reduce the Mana cost of Charge", - "archetype": "", - "archetype_req": 0, - "parents": [ - 8, - 10 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Uppercut", "War Scream"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 8, "col": 4, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "cost": -5 } - ], - "id": 9 + ] }, + { "display_name": "War Scream", "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies", - "archetype": "", - "archetype_req": 0, - "parents": [ - 7 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Tougher Skin"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 8, "col": 6, @@ -2989,37 +2421,29 @@ const atrees = { { "name": "War Scream", "type": "damage", - "multipliers": [ - 50, - 0, - 0, - 0, - 50, - 0 - ] + "multipliers": [50, 0, 0, 0, 50, 0] } ] } - ], - "id": 10 + ] }, + { "display_name": "Earth Mastery", "desc": "Increases base damage from all Earth attacks", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 8 - ], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uppercut"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 0, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -3032,35 +2456,29 @@ const atrees = { { "type": "stat", "name": "eDam", - "value": [ - 2, - 4 - ] + "value": [2, 4] } ] } - ], - "id": 11 + ] }, + { "display_name": "Thunder Mastery", "desc": "Increases base damage from all Thunder attacks", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 8, - 14, - 9 - ], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uppercut", "Air Mastery", "Cheaper Charge"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 2, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -3073,35 +2491,29 @@ const atrees = { { "type": "stat", "name": "tDam", - "value": [ - 1, - 8 - ] + "value": [1, 8] } ] } - ], - "id": 12 + ] }, + { "display_name": "Water Mastery", "desc": "Increases base damage from all Water attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 9, - 12, - 14 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Cheaper Charge", "Thunder Mastery", "Air Mastery"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 11, "col": 4, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -3114,35 +2526,29 @@ const atrees = { { "type": "stat", "name": "wDam", - "value": [ - 2, - 4 - ] + "value": [2, 4] } ] } - ], - "id": 13 + ] }, + { "display_name": "Air Mastery", "desc": "Increases base damage from all Air attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 10, - 12, - 9 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["War Scream", "Thunder Mastery", "Cheaper Charge"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 6, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -3155,33 +2561,29 @@ const atrees = { { "type": "stat", "name": "aDam", - "value": [ - 3, - 4 - ] + "value": [3, 4] } ] } - ], - "id": 14 + ] }, + { "display_name": "Fire Mastery", "desc": "Increases base damage from all Earth attacks", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 10 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["War Scream"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 10, "col": 8, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -3194,28 +2596,22 @@ const atrees = { { "type": "stat", "name": "fDam", - "value": [ - 3, - 5 - ] + "value": [3, 5] } ] } - ], - "id": 15 + ] }, + { "display_name": "Quadruple Bash", "desc": "Bash will hit 4 times at an even larger range", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 11, - 17 - ], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Earth Mastery", "Fireworks"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 0, @@ -3232,57 +2628,41 @@ const atrees = { "cost": 0, "hits": { "Single Hit": 2 - } + } }, { "type": "add_spell_prop", "base_spell": 1, "target_part": "Single Hit", "cost": 0, - "multipliers": [ - -20, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [-20, 0, 0, 0, 0, 0] } - ], - "id": 16 + ] }, + { "display_name": "Fireworks", "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 12, - 16 - ], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Thunder Mastery", "Quadruple Bash"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 2, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Fireworks", "cost": 0, - "multipliers": [ - 80, - 0, - 20, - 0, - 0, - 0 - ] + "multipliers": [80, 0, 20, 0, 0, 0] }, { "type": "add_spell_prop", @@ -3293,22 +2673,18 @@ const atrees = { "Fireworks": 1 } } - ], - "id": 17 + ] }, + { "display_name": "Half-Moon Swipe", "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": [ - 13 - ], - "dependencies": [ - 8 - ], + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Water Mastery"], + "dependencies": ["Uppercut"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 13, "col": 4, @@ -3323,35 +2699,25 @@ const atrees = { "base_spell": 3, "target_part": "Uppercut", "cost": -10, - "multipliers": [ - -70, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [-70, 0, 0, 0, 0, 0] }, { "type": "convert_spell_conv", "target_part": "all", "conversion": "water" } - ], - "id": 18 + ] }, + { "display_name": "Flyby Jab", "desc": "Damage enemies in your way when using Charge", - "archetype": "", - "archetype_req": 0, - "parents": [ - 14, - 20 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Air Mastery", "Flaming Uppercut"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 6, @@ -3366,32 +2732,20 @@ const atrees = { "base_spell": 2, "target_part": "Flyby Jab", "cost": 0, - "multipliers": [ - 20, - 0, - 0, - 0, - 0, - 40 - ] + "multipliers": [20, 0, 0, 0, 0, 40] } - ], - "id": 19 + ] }, + { "display_name": "Flaming Uppercut", "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 15, - 19 - ], - "dependencies": [ - 8 - ], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Fire Mastery", "Flyby Jab"], + "dependencies": ["Uppercut"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 12, "col": 8, @@ -3407,14 +2761,7 @@ const atrees = { "base_spell": 3, "target_part": "Flaming Uppercut", "cost": 0, - "multipliers": [ - 0, - 0, - 0, - 0, - 50, - 0 - ] + "multipliers": [0, 0, 0, 0, 50, 0] }, { "type": "add_spell_prop", @@ -3434,76 +2781,66 @@ const atrees = { "Flaming Uppercut": 5 } } - ], - "id": 20 + ] }, + { "display_name": "Iron Lungs", "desc": "War Scream deals more damage", - "archetype": "", - "archetype_req": 0, - "parents": [ - 19, - 20 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Flyby Jab", "Flaming Uppercut"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 13, "col": 7, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "target_part": "War Scream", "cost": 0, - "multipliers": [ - 30, - 0, - 0, - 0, - 0, - 30 - ] + "multipliers": [30, 0, 0, 0, 0, 30] } - ], - "id": 21 + ] }, + { "display_name": "Generalist", "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana", - "archetype": "Battle Monk", - "archetype_req": 3, - "parents": [ - 23 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 3, + "parents": ["Counter"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 15, "col": 2, "icon": "node_3" }, - "properties": {}, - "effects": [], - "id": 22 + "properties": { + }, + "effects": [ + + ] }, + { "display_name": "Counter", "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 18 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Half-Moon Swipe"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 15, "col": 4, @@ -3518,31 +2855,20 @@ const atrees = { "base_spell": 5, "target_part": "Counter", "cost": 0, - "multipliers": [ - 60, - 0, - 20, - 0, - 0, - 20 - ] + "multipliers": [60, 0, 20, 0, 0, 20] } - ], - "id": 23 + ] }, + { "display_name": "Mantle of the Bovemists", "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)", - "archetype": "Paladin", - "archetype_req": 3, - "parents": [ - 21 - ], - "dependencies": [ - 10 - ], + "archetype": "Paladin", + "archetype_req": 3, + "parents": ["Iron Lungs"], + "dependencies": ["War Scream"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 15, "col": 7, @@ -3551,23 +2877,20 @@ const atrees = { "properties": { "mantle_charge": 3 }, - "effects": [], - "id": 24 + "effects": [ + + ] }, + { "display_name": "Bak'al's Grasp", "desc": "After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)", - "archetype": "Fallen", - "archetype_req": 2, - "parents": [ - 16, - 17 - ], - "dependencies": [ - 10 - ], + "archetype": "Fallen", + "archetype_req": 2, + "parents": ["Quadruple Bash", "Fireworks"], + "dependencies": ["War Scream"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 16, "col": 1, @@ -3583,29 +2906,24 @@ const atrees = { "slider_name": "Corrupted", "output": { "type": "stat", - "name": "raw" + "name": "raw" }, - "scaling": [ - 4 - ], + "scaling": [4], "slider_step": 2, "max": 120 } - ], - "id": 25 + ] }, + { "display_name": "Spear Proficiency 2", "desc": "Improve your Main Attack's damage and range w/ spear", - "archetype": "", - "archetype_req": 0, - "parents": [ - 25, - 27 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Bak'al's Grasp", "Cheaper Uppercut"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 17, "col": 0, @@ -3625,103 +2943,96 @@ const atrees = { } ] } - ], - "id": 26 + ] }, + { "display_name": "Cheaper Uppercut", "desc": "Reduce the Mana Cost of Uppercut", - "archetype": "", - "archetype_req": 0, - "parents": [ - 26, - 28, - 23 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 17, "col": 3, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "cost": -5 } - ], - "id": 27 + ] }, + { "display_name": "Aerodynamics", "desc": "During Charge, you can steer and change direction", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 27, - 29 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Cheaper Uppercut", "Provoke"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 17, "col": 5, "icon": "node_1" }, - "properties": {}, - "effects": [], - "id": 28 + "properties": { + }, + "effects": [ + + ] }, + { "display_name": "Provoke", "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 28, - 24 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Aerodynamics", "Mantle of the Bovemists"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 17, "col": 7, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "cost": -5 } - ], - "id": 29 + ] }, + { "display_name": "Precise Strikes", "desc": "+30% Critical Hit Damage", - "archetype": "", - "archetype_req": 0, - "parents": [ - 27, - 26 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Cheaper Uppercut", "Spear Proficiency 2"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 18, "col": 2, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -3733,66 +3044,53 @@ const atrees = { } ] } - ], - "id": 30 + ] }, + { "display_name": "Air Shout", "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", - "archetype": "", - "archetype_req": 0, - "parents": [ - 28, - 29 - ], - "dependencies": [ - 10 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Aerodynamics", "Provoke"], + "dependencies": ["War Scream"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 18, "col": 6, "icon": "node_1" }, - "properties": {}, + "properties": { + + }, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "target_part": "Air Shout", "cost": 0, - "multipliers": [ - 20, - 0, - 0, - 0, - 0, - 5 - ] + "multipliers": [20, 0, 0, 0, 0, 5] } - ], - "id": 31 + ] }, + { "display_name": "Enraged Blow", "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 26 - ], - "dependencies": [ - 25 - ], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Spear Proficiency 2"], + "dependencies": ["Bak'al's Grasp"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 20, "col": 0, "icon": "node_2" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -3805,66 +3103,50 @@ const atrees = { ], "output": { "type": "stat", - "name": "damMult" + "name": "damMult" }, - "scaling": [ - 3 - ], + "scaling": [3], "max": 300 } - ], - "id": 32 + ] }, + { "display_name": "Flying Kick", "desc": "When using Charge, mobs hit will halt your momentum and get knocked back", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": [ - 27, - 34 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Cheaper Uppercut", "Stronger Mantle"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 20, "col": 3, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 2, "target_part": "Flying Kick", "cost": 0, - "multipliers": [ - 120, - 0, - 0, - 10, - 0, - 20 - ] + "multipliers": [120, 0, 0, 10, 0, 20] } - ], - "id": 33 + ] }, + { "display_name": "Stronger Mantle", "desc": "Add +2 additional charges to Mantle of the Bovemists", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 35, - 33 - ], - "dependencies": [ - 24 - ], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Manachism", "Flying Kick"], + "dependencies": ["Mantle of the Bovemists"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 20, "col": 6, @@ -3873,21 +3155,20 @@ const atrees = { "properties": { "mantle_charge": 2 }, - "effects": [], - "id": 34 + "effects": [ + + ] }, + { "display_name": "Manachism", "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)", - "archetype": "Paladin", - "archetype_req": 3, - "parents": [ - 34, - 29 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 3, + "parents": ["Stronger Mantle", "Provoke"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 20, "col": 8, @@ -3896,59 +3177,47 @@ const atrees = { "properties": { "cooldown": 1 }, - "effects": [], - "id": 35 + "effects": [ + + ] }, + { "display_name": "Boiling Blood", "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds", - "archetype": "", - "archetype_req": 0, - "parents": [ - 32, - 37 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Enraged Blow", "Ragnarokkr"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 22, "col": 0, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "target_part": "Boiling Blood", "cost": 0, - "multipliers": [ - 25, - 0, - 0, - 0, - 5, - 0 - ] + "multipliers": [25, 0, 0, 0, 5, 0] } - ], - "id": 36 + ] }, + { "display_name": "Ragnarokkr", "desc": "War Scream become deafening, increasing its range and giving damage bonus to players", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 36, - 33 - ], - "dependencies": [ - 10 - ], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Boiling Blood", "Flying Kick"], + "dependencies": ["War Scream"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 22, "col": 2, @@ -3964,24 +3233,18 @@ const atrees = { "base_spell": 4, "cost": 10 } - ], - "id": 37 + ] }, + { "display_name": "Ambidextrous", "desc": "Increase your chance to attack with Counter by +30%", - "archetype": "", - "archetype_req": 0, - "parents": [ - 33, - 34, - 39 - ], - "dependencies": [ - 23 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"], + "dependencies": ["Counter"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 22, "col": 4, @@ -3990,27 +3253,27 @@ const atrees = { "properties": { "chance": 30 }, - "effects": [], - "id": 38 + "effects": [ + + ] }, + { "display_name": "Burning Heart", "desc": "For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 38, - 40 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Ambidextrous", "Stronger Bash"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 22, "col": 6, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -4025,134 +3288,106 @@ const atrees = { "type": "stat", "name": "fDamPct" }, - "scaling": [ - 2 - ], + "scaling": [2], "max": 100, "slider_step": 100 } - ], - "id": 39 + ] }, + { "display_name": "Stronger Bash", "desc": "Increase the damage of Bash", - "archetype": "", - "archetype_req": 0, - "parents": [ - 39, - 35 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Burning Heart", "Manachism"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 22, "col": 8, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "target_part": "Single Hit", "cost": 0, - "multipliers": [ - 30, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [30, 0, 0, 0, 0, 0] } - ], - "id": 40 + ] }, + { "display_name": "Intoxicating Blood", "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", - "archetype": "Fallen", - "archetype_req": 5, - "parents": [ - 37, - 36 - ], - "dependencies": [ - 25 - ], + "archetype": "Fallen", + "archetype_req": 5, + "parents": ["Ragnarokkr", "Boiling Blood"], + "dependencies": ["Bak'al's Grasp"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 23, "col": 1, "icon": "node_1" }, - "properties": {}, - "effects": [], - "id": 41 + "properties": { + }, + "effects": [ + + ] }, + { "display_name": "Comet", "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 37 - ], - "dependencies": [ - 17 - ], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Ragnarokkr"], + "dependencies": ["Fireworks"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 24, "col": 2, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Comet", "cost": 0, - "multipliers": [ - 80, - 20, - 0, - 0, - 0, - 0 - ] + "multipliers": [80, 20, 0, 0, 0, 0] }, { - "type": "add_spell_prop", + "type":"add_spell_prop", "base_spell": 3, "target_part": "Total Damage", - "cost": 0, + "cost": 0, "hits": { "Comet": 1 } } - ], - "id": 42 + ] }, + { "display_name": "Collide", "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", - "archetype": "Battle Monk", - "archetype_req": 4, - "parents": [ - 38, - 39 - ], - "dependencies": [ - 33 - ], + "archetype": "Battle Monk", + "archetype_req": 4, + "parents": ["Ambidextrous", "Burning Heart"], + "dependencies": ["Flying Kick"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 23, "col": 5, @@ -4167,53 +3402,41 @@ const atrees = { "base_spell": 2, "target_part": "Collide", "cost": 0, - "multipliers": [ - 100, - 0, - 0, - 0, - 50, - 0 - ] + "multipliers": [100, 0, 0, 0, 50, 0] } - ], - "id": 43 + ] }, + { "display_name": "Rejuvenating Skin", "desc": "Regain back 30% of the damage you take as healing over 30s", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 39, - 40 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Burning Heart", "Stronger Bash"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 23, "col": 7, "icon": "node_3" }, - "properties": {}, - "effects": [], - "id": 44 + "properties": { + }, + "effects": [ + + ] }, + { "display_name": "Uncontainable Corruption", "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", - "archetype": "", - "archetype_req": 0, - "parents": [ - 36, - 46 - ], - "dependencies": [ - 25 - ], + "archetype": "", + "archetype_req": 0, + "parents": ["Boiling Blood", "Radiant Devotee"], + "dependencies": ["Bak'al's Grasp"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 26, "col": 0, @@ -4229,35 +3452,31 @@ const atrees = { "slider_name": "Corrupted", "output": { "type": "stat", - "name": "raw" + "name": "raw" }, - "scaling": [ - 1 - ], + "scaling": [1], "slider_step": 2, "max": 50 } - ], - "id": 45 + ] }, + { "display_name": "Radiant Devotee", "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": [ - 47, - 45 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 1, + "parents": ["Whirlwind Strike", "Uncontainable Corruption"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 26, "col": 2, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -4271,36 +3490,29 @@ const atrees = { "type": "stat", "name": "mr" }, - "scaling": [ - 1 - ], + "scaling": [1], "max": 10, "slider_step": 4 } - ], - "id": 46 + ] }, + { "display_name": "Whirlwind Strike", "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", - "archetype": "Battle Monk", - "archetype_req": 5, - "parents": [ - 38, - 46 - ], - "dependencies": [ - 8 - ], + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": ["Ambidextrous", "Radiant Devotee"], + "dependencies": ["Uppercut"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 26, "col": 4, "icon": "node_1" }, "properties": { - "range": 2 + "range": 2 }, "effects": [ { @@ -4308,35 +3520,27 @@ const atrees = { "base_spell": 3, "target_part": "Uppercut", "cost": 0, - "multipliers": [ - 0, - 0, - 0, - 0, - 0, - 50 - ] + "multipliers": [0, 0, 0, 0, 0, 50] } - ], - "id": 47 + ] }, + { "display_name": "Mythril Skin", "desc": "Gain +5% Base Resistance and become immune to knockback", - "archetype": "Paladin", - "archetype_req": 6, - "parents": [ - 44 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 6, + "parents": ["Rejuvenating Skin"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 26, "col": 7, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "raw_stat", @@ -4348,23 +3552,18 @@ const atrees = { } ] } - ], - "id": 48 + ] }, + { "display_name": "Armour Breaker", "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 45, - 46 - ], - "dependencies": [ - 25 - ], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Uncontainable Corruption", "Radiant Devotee"], + "dependencies": ["Bak'al's Grasp"], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 27, "col": 1, @@ -4373,56 +3572,47 @@ const atrees = { "properties": { "duration": 5 }, - "effects": [], - "id": 49 + "effects": [ + + ] }, + { "display_name": "Shield Strike", "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 48, - 51 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Mythril Skin", "Sparkling Hope"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 27, "col": 6, "icon": "node_1" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 5, "target_part": "Shield Strike", "cost": 0, - "multipliers": [ - 60, - 0, - 20, - 0, - 0, - 0 - ] + "multipliers": [60, 0, 20, 0, 0, 0] } - ], - "id": 50 + ] }, + { "display_name": "Sparkling Hope", "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 48 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Mythril Skin"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 27, "col": 8, @@ -4437,36 +3627,27 @@ const atrees = { "base_spell": 5, "target_part": "Sparkling Hope", "cost": 0, - "multipliers": [ - 10, - 0, - 5, - 0, - 0, - 0 - ] + "multipliers": [10, 0, 5, 0, 0, 0] } - ], - "id": 51 + ] }, + { "display_name": "Massive Bash", "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", - "archetype": "Fallen", - "archetype_req": 8, - "parents": [ - 53, - 45 - ], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 8, + "parents": ["Tempest", "Uncontainable Corruption"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 28, "col": 0, "icon": "node_2" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -4474,29 +3655,24 @@ const atrees = { "slider_name": "Corrupted", "output": { "type": "stat", - "name": "bashAoE" + "name": "bashAoE" }, - "scaling": [ - 1 - ], + "scaling": [1], "max": 10, "slider_step": 3 } - ], - "id": 52 + ] }, + { "display_name": "Tempest", "desc": "War Scream will ripple the ground and deal damage 3 times in a large area", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 52, - 54 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Massive Bash", "Spirit of the Rabbit"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 28, "col": 2, @@ -4511,14 +3687,7 @@ const atrees = { "base_spell": 4, "target_part": "Tempest", "cost": "0", - "multipliers": [ - 30, - 10, - 0, - 0, - 0, - 10 - ] + "multipliers": [30, 10, 0, 0, 0, 10] }, { "type": "add_spell_prop", @@ -4538,27 +3707,25 @@ const atrees = { "Tempest": 3 } } - ], - "id": 53 + ] }, + { "display_name": "Spirit of the Rabbit", "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", - "archetype": "Battle Monk", - "archetype_req": 5, - "parents": [ - 53, - 47 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 5, + "parents": ["Tempest", "Whirlwind Strike"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 28, "col": 4, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", @@ -4575,78 +3742,66 @@ const atrees = { } ] } - ], - "id": 54 + ] }, + { "display_name": "Massacre", "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", - "archetype": "Fallen", - "archetype_req": 5, - "parents": [ - 53, - 52 - ], - "dependencies": [], + "archetype": "Fallen", + "archetype_req": 5, + "parents": ["Tempest", "Massive Bash"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 29, "col": 1, "icon": "node_1" }, - "properties": {}, - "effects": [], - "id": 55 + "properties": { + }, + "effects": [ + + ] }, + { "display_name": "Axe Kick", "desc": "Increase the damage of Uppercut, but also increase its mana cost", - "archetype": "", - "archetype_req": 0, - "parents": [ - 53, - 54 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Tempest", "Spirit of the Rabbit"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 29, "col": 3, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 3, "target_part": "Uppercut", "cost": 10, - "multipliers": [ - 100, - 0, - 0, - 0, - 0, - 0 - ] + "multipliers": [100, 0, 0, 0, 0, 0] } - ], - "id": 56 + ] }, + { "display_name": "Radiance", "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", - "archetype": "Paladin", - "archetype_req": 2, - "parents": [ - 54, - 58 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 2, + "parents": ["Spirit of the Rabbit", "Cheaper Bash 2"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 29, "col": 5, @@ -4655,80 +3810,77 @@ const atrees = { "properties": { "cooldown": 15 }, - "effects": [], - "id": 57 + "effects": [ + + ] }, + { "display_name": "Cheaper Bash 2", "desc": "Reduce the Mana cost of Bash", - "archetype": "", - "archetype_req": 0, - "parents": [ - 57, - 50, - 51 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Radiance", "Shield Strike", "Sparkling Hope"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 29, "col": 7, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 1, "cost": -5 } - ], - "id": 58 + ] }, + { "display_name": "Cheaper War Scream", "desc": "Reduce the Mana cost of War Scream", - "archetype": "", - "archetype_req": 0, - "parents": [ - 52 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Massive Bash"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 31, "col": 0, "icon": "node_0" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "add_spell_prop", "base_spell": 4, "cost": -5 } - ], - "id": 59 + ] }, + { "display_name": "Discombobulate", "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", - "archetype": "Battle Monk", - "archetype_req": 11, - "parents": [ - 62 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 11, + "parents": ["Cyclone"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 31, "col": 2, "icon": "node_3" }, - "properties": {}, + "properties": { + }, "effects": [ { "type": "stat_scaling", @@ -4736,27 +3888,23 @@ const atrees = { "slider_name": "Hits dealt", "output": { "type": "stat", - "name": "rainrawButDifferent" + "name": "rainrawButDifferent" }, - "scaling": [ - 2 - ], + "scaling": [2], "max": 50 } - ], - "id": 60 + ] }, + { "display_name": "Thunderclap", "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", - "archetype": "Battle Monk", - "archetype_req": 8, - "parents": [ - 62 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 8, + "parents": ["Cyclone"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 32, "col": 5, @@ -4771,29 +3919,25 @@ const atrees = { }, { "type": "raw_stat", - "bonuses": [ - { - "type": "prop", - "abil_name": "Bash", - "name": "aoe", - "value": 3 - } - ] + "bonuses": [{ + "type": "prop", + "abil_name": "Bash", + "name": "aoe", + "value": 3 + }] } - ], - "id": 61 + ] }, + { "display_name": "Cyclone", "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": [ - 54 - ], - "dependencies": [], + "archetype": "Battle Monk", + "archetype_req": 0, + "parents": ["Spirit of the Rabbit"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 31, "col": 4, @@ -4809,14 +3953,7 @@ const atrees = { "base_spell": 4, "target_part": "Cyclone", "cost": 0, - "multipliers": [ - 10, - 0, - 0, - 0, - 5, - 10 - ] + "multipliers": [10, 0, 0, 0, 5, 10] }, { "type": "add_spell_prop", @@ -4826,105 +3963,92 @@ const atrees = { "hits": { "Cyclone": 40 } + } - ], - "id": 62 + ] }, + { "display_name": "Second Chance", "desc": "When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)", - "archetype": "Paladin", - "archetype_req": 12, - "parents": [ - 58 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 12, + "parents": ["Cheaper Bash 2"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 32, "col": 7, "icon": "node_3" }, "properties": {}, - "effects": [], - "id": 63 + "effects": [] }, + { "display_name": "Blood Pact", "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", - "archetype": "", - "archetype_req": 10, - "parents": [ - 59 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 10, + "parents": ["Cheaper War Scream"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 34, "col": 1, "icon": "node_3" }, "properties": {}, - "effects": [], - "id": 64 + "effects": [] }, + { "display_name": "Haemorrhage", "desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": [ - 64 - ], - "dependencies": [ - 64 - ], + "archetype": "Fallen", + "archetype_req": 0, + "parents": ["Blood Pact"], + "dependencies": ["Blood Pact"], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 35, "col": 2, "icon": "node_1" }, "properties": {}, - "effects": [], - "id": 65 + "effects": [] }, + { "display_name": "Brink of Madness", "desc": "If your health is 25% full or less, gain +40% Resistance", - "archetype": "", - "archetype_req": 0, - "parents": [ - 64, - 67 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Blood Pact", "Cheaper Uppercut 2"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 35, "col": 4, "icon": "node_2" }, "properties": {}, - "effects": [], - "id": 66 + "effects": [] }, + { "display_name": "Cheaper Uppercut 2", "desc": "Reduce the Mana cost of Uppercut", - "archetype": "", - "archetype_req": 0, - "parents": [ - 63, - 66 - ], - "dependencies": [], + "archetype": "", + "archetype_req": 0, + "parents": ["Second Chance", "Brink of Madness"], + "dependencies": [], "blockers": [], - "cost": 1, + "cost": 1, "display": { "row": 35, "col": 6, @@ -4937,20 +4061,18 @@ const atrees = { "base_spell": 3, "cost": -5 } - ], - "id": 67 + ] }, + { "display_name": "Martyr", "desc": "When you receive a fatal blow, all nearby allies become invincible", - "archetype": "Paladin", - "archetype_req": 0, - "parents": [ - 63 - ], - "dependencies": [], + "archetype": "Paladin", + "archetype_req": 0, + "parents": ["Second Chance"], + "dependencies": [], "blockers": [], - "cost": 2, + "cost": 2, "display": { "row": 35, "col": 8, @@ -4960,8 +4082,7 @@ const atrees = { "duration": 3, "aoe": 12 }, - "effects": [], - "id": 68 + "effects": [] } ] -} \ No newline at end of file +} diff --git a/js/atree_constants_old.js b/js/atree_constants_old.js deleted file mode 100644 index e325247..0000000 --- a/js/atree_constants_old.js +++ /dev/null @@ -1,171 +0,0 @@ -const atrees_old = { - "Assassin": [ - {"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4}, - {"title": "Dagger Proficiency I", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 2, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 3, "col": 4}, - {"title": "Double Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 5, "col": 4}, - {"title": "Dash", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 2}, - {"title": "Smoke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 6}, - {"title": "Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 8, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 6}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 8, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 8}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 1}, - {"title": "Backstab", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 90, "row": 10, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 10, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 7}, - {"title": "Fatality", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 11, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 0}, - {"title": "Violent Vortex", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 11, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 2}, - {"title": "Vanish", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 13, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 13, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 7}, - {"title": "Lacerate", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 15, "col": 1}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 8}, - {"title": "Wall of Smoke", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 8}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 16, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 0}, - {"title": "Silent Killer", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 16, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 2}, - {"title": "Shadow Travel", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 8}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 8}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 4}, - {"title": "Exploding Clones", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 19, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 18, "col": 6}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 7}, - {"title": "Weightless", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 20, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 20, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 4}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 20, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 21, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 6}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 8}, - {"title": "Dancing Blade", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 21, "col": 0}, - {"title": "Spin Attack Damage", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 21, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 3}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 22, "col": 1}, - {"title": "Marked", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 5}, - {"title": "Shurikens", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 8}, - {"title": "Far Reach", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8}, - {"title": "Stronger Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 5}, - {"title": "Psithurism", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 1}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 1}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 3}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 4}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 5}, - {"title": "Choke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 6}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 7}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 5}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 25, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 0}, - {"title": "Death Magnet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 25, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 2}, - {"title": "Cheaper Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 7}, - {"title": "Parry", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 7}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 1}, - {"title": "Fatal Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1}, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 3}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 3}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 27, "col": 6}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 6}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 27, "col": 8}, - {"title": "Wall Jump", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 8}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 28, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 0}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 1}, - {"title": "Harvester", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 4}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 7}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 7}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7 }, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 30, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 2 }, - {"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 30, "col": 5}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 30, "col": 6}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 5}, - {"title": "Ricochet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 1}, - {"title": "Satsujin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 1}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 4}, - {"title": "Forbidden Art", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 4}, - {"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 7}, - {"title": "Jasmine Bloom", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 7}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 32, "col": 0}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 0}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 2}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2}, - {"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 5}, - {"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8}, - ] -} \ No newline at end of file diff --git a/js/atree_constants_str_old.js b/js/atree_constants_str_old.js deleted file mode 100644 index b604ecc..0000000 --- a/js/atree_constants_str_old.js +++ /dev/null @@ -1,4160 +0,0 @@ -const atrees = -{ - "Archer": [ - { - "display_name": "Arrow Shield", - "desc": "Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)", - "archetype": "", - "archetype_req": 0, - "parents": ["Power Shots", "Cheaper Escape"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 9, - "col": 6 - }, - "properties": { - "duration": 60 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Arrow Shield", - "cost": 30, - "display_text": "Max Damage", - "base_spell": 4, - "spell_type": "damage", - "scaling": "spell", - "display": "", - "parts": [ - { - "name": "Shield Damage", - "type": "damage", - "multipliers": [90, 0, 0, 0, 0, 10] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Shield Damage": 2 - } - } - ] - } - ] - }, - - { - "display_name": "Escape", - "desc": "Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)", - "archetype": "", - "archetype_req": 0, - "parents": ["Heart Shatter"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 7, - "col": 4 - }, - "properties": { - "aoe": 0, - "range": 0 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Escape", - "cost": 25, - "display_text": "Max Damage", - "base_spell": 2, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "None", - "type": "damage", - "multipliers": [0, 0, 0, 0, 0, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "None": 0 - } - } - ] - } - ] - }, - { - "display_name": "Arrow Bomb", - "desc": "Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)", - "archetype": "", - "archetype_req": 0, - "parents": [], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 0, - "col": 4 - }, - "properties": { - "aoe": 4.5, - "range": 26 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Arrow Bomb", - "cost": 50, - "display_text": "Average Damage", - "base_spell": 3, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Arrow Bomb", - "type": "damage", - "multipliers": [160, 0, 0, 0, 20, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Arrow Bomb": 1 - } - } - ] - } - ] - }, - { - "display_name": "Heart Shatter", - "desc": "If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.", - "archetype": "", - "archetype_req": 0, - "parents": ["Bow Proficiency I"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 4, - "col": 4 - }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [100, 0, 0, 0, 0, 0] - }, - { - - } - ] - }, - { - "display_name": "Fire Creep", - "desc": "Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.", - "archetype": "", - "archetype_req": 0, - "parents": ["Phantom Ray", "Fire Mastery", "Bryophyte Roots"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 16, - "col": 6 - }, - "properties": { - "aoe": 0.8, - "duration": 6 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fire Creep", - "cost": 0, - "multipliers": [30, 0, 0, 0, 20, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fire Creep": 15 - } - } - ] - }, - { - "display_name": "Bryophyte Roots", - "desc": "When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.", - "archetype": "Trapper", - "archetype_req": 1, - "parents": ["Fire Creep", "Earth Mastery"], - "dependencies": ["Arrow Storm"], - "blockers": [], - "cost": 2, - "display": { - "row": 16, - "col": 8 - }, - "properties": { - "aoe": 2, - "duration": 5, - "slowness": 0.4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Bryophyte Roots", - "cost": 0, - "multipliers": [40, 20, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "Nimble String", - "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", - "archetype": "", - "archetype_req": 0, - "parents": ["Thunder Mastery", "Arrow Rain"], - "dependencies": ["Arrow Storm"], - "blockers": ["Phantom Ray"], - "cost": 2, - "display": { - "row": 15, - "col": 2 - }, - "properties": { - "shootspeed": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [-15, 0, 0, 0, 0, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Stream", - "cost": 0, - "hits": { - "Single Arrow": 8 - } - } - ] - }, - { - "display_name": "Arrow Storm", - "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", - "archetype": "", - "archetype_req": 0, - "parents": ["Double Shots", "Cheaper Escape"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 9, - "col": 2 - }, - "properties": { - "aoe": 0, - "range": 16 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Arrow Storm", - "cost": 40, - "display_text": "Max Damage", - "base_spell": 1, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [30, 0, 10, 0, 0, 0] - }, - { - "name": "Single Stream", - "type": "total", - "hits": { - "Single Arrow": 8 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Stream": 2 - } - } - ] - } - ] - }, - { - "display_name": "Guardian Angels", - "desc": "Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)", - "archetype": "Boltslinger", - "archetype_req": 3, - "parents": ["Triple Shots", "Frenzy"], - "dependencies": ["Arrow Shield"], - "blockers": [], - "cost": 2, - "display": { - "row": 19, - "col": 1 - }, - "properties": { - "range": 4, - "duration": 60, - "shots": 8, - "count": 2 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Guardian Angels", - "cost": 30, - "display_text": "Total Damage Average", - "base_spell": 4, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [40, 0, 0, 0, 0, 20] - }, - { - "name": "Single Bow", - "type": "total", - "hits": { - "Single Arrow": 8 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Bow": 2 - } - } - ] - } - ] - }, - { - "display_name": "Windy Feet", - "base_abil": "Escape", - "desc": "When casting Escape, give speed to yourself and nearby allies.", - "archetype": "", - "archetype_req": 0, - "parents": ["Arrow Storm"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 10, - "col": 1 - }, - "properties": { - "aoe": 8, - "duration": 120 - }, - "type": "stat_bonus", - "bonuses": [ - { - "type": "stat", - "name": "spd", - "value": 20 - } - ] - }, - { - "display_name": "Basaltic Trap", - "desc": "When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)", - "archetype": "Trapper", - "archetype_req": 2, - "parents": ["Bryophyte Roots"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 19, - "col": 8 - }, - "properties": { - "aoe": 7, - "traps": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 0, - "multipliers": [140, 30, 0, 0, 30, 0] - } - ] - }, - { - "display_name": "Windstorm", - "desc": "Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.", - "archetype": "", - "archetype_req": 0, - "parents": ["Guardian Angels", "Cheaper Arrow Storm"], - "dependencies": [], - "blockers": ["Phantom Ray"], - "cost": 2, - "display": { - "row": 21, - "col": 1 - }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [-11, 0, -7, 0, 0, 3] - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Stream": 1 - } - } - ] - }, - { - "display_name": "Grappling Hook", - "base_abil": "Escape", - "desc": "When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Focus", "More Shields", "Cheaper Arrow Storm"], - "dependencies": [], - "blockers": ["Escape Artist"], - "cost": 2, - "display": { - "row": 21, - "col": 5 - }, - "properties": { - "range": 20 - }, - "effects": [ - ] - }, - { - "display_name": "Implosion", - "desc": "Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Grappling Hook", "More Shields"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 22, - "col": 6 - }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [40, 0, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "Twain's Arc", - "desc": "When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)", - "archetype": "Sharpshooter", - "archetype_req": 4, - "parents": ["More Focus", "Traveler"], - "dependencies": ["Focus"], - "blockers": [], - "cost": 2, - "display": { - "row": 25, - "col": 4 - }, - "properties": { - "range": 64, - "focusReq": 2 - }, - "effects": [ - - { - "type": "replace_spell", - "name": "Twain's Arc", - "cost": 0, - "display_text": "Twain's Arc", - "base_spell": 5, - "spell_type": "damage", - "scaling": "melee", - "display": "Twain's Arc Damage", - "parts": [ - { - "name": "Twain's Arc Damage", - "type": "damage", - "multipliers": [200, 0, 0, 0, 0, 0] - } - ] - } - ] - }, - { - "display_name": "Fierce Stomp", - "desc": "When using Escape, hold shift to quickly drop down and deal damage.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Refined Gunpowder", "Traveler"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 26, - "col": 1 - }, - "properties": { - "aoe": 4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Fierce Stomp", - "cost": 0, - "multipliers": [100, 0, 0, 0, 0, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fierce Stomp": 1 - } - } - ] - }, - { - "display_name": "Scorched Earth", - "desc": "Fire Creep become much stronger.", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Twain's Arc"], - "dependencies": ["Fire Creep"], - "blockers": [], - "cost": 1, - "display": { - "row": 26 , - "col": 5 - }, - "properties": { - "duration": 2, - "aoe": 0.4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fire Creep", - "cost": 0, - "multipliers": [10, 0, 0, 0, 5, 0] - } - ] - }, - { - "display_name": "Leap", - "desc": "When you double tap jump, leap foward. (2s Cooldown)", - "archetype": "Boltslinger", - "archetype_req": 5, - "parents": ["Refined Gunpowder", "Homing Shots"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 28, - "col": 0 - }, - "properties": { - "cooldown": 2 - }, - "effects": [ - - ] - }, - { - "display_name": "Shocking Bomb", - "desc": "Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.", - "archetype": "Sharpshooter", - "archetype_req": 5, - "parents": ["Twain's Arc", "Better Arrow Shield", "Homing Shots"], - "dependencies": ["Arrow Bomb"], - "blockers": [], - "cost": 2, - "display": { - "row": 28, - "col": 4 - }, - "properties": { - "gravity": 0 - }, - "effects": [ - { - "type": "convert_spell_conv", - "target_part": "all", - "conversion": "thunder" - } - ] - }, - { - "display_name": "Mana Trap", - "desc": "Your Traps will give you 4 Mana per second when you stay close to them.", - "archetype": "Trapper", - "archetype_req": 5, - "parents": ["More Traps", "Better Arrow Shield"], - "dependencies": ["Fire Creep"], - "blockers": [], - "cost": 2, - "display": { - "row": 28, - "col": 8 - }, - "properties": { - "range": 12, - "manaRegen": 4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 10, - "multipliers": [0, 0, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "Escape Artist", - "desc": "When casting Escape, release 100 arrows towards the ground.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Better Guardian Angels", "Leap"], - "dependencies": [], - "blockers": ["Grappling Hook"], - "cost": 2, - "display": { - "row": 31, - "col": 0 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Escape Artist", - "cost": 0, - "multipliers": [30, 0, 10, 0, 0, 0] - } - ] - }, - { - "display_name": "Initiator", - "desc": "If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.", - "archetype": "Sharpshooter", - "archetype_req": 5, - "parents": ["Shocking Bomb", "Better Arrow Shield", "Cheaper Arrow Storm (2)"], - "dependencies": ["Focus"], - "blockers": [], - "cost": 2, - "display": { - "row": 31, - "col": 5 - }, - "properties": { - "focus": 1, - "timer": 5 - }, - "type": "stat_bonus", - "bonuses": [ - { - "type": "stat", - "name": "damPct", - "value": 50 - } - ] - }, - { - "display_name": "Call of the Hound", - "desc": "Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Initiator", "Cheaper Arrow Storm (2)"], - "dependencies": ["Arrow Shield"], - "blockers": [], - "cost": 2, - "display": { - "row": 32, - "col": 7 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Call of the Hound", - "cost": 0, - "multipliers": [40, 0, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "Arrow Hurricane", - "desc": "Arrow Storm will shoot +2 stream of arrows.", - "archetype": "Boltslinger", - "archetype_req": 8, - "parents": ["Precise Shot", "Escape Artist"], - "dependencies": [], - "blockers": ["Phantom Ray"], - "cost": 2, - "display": { - "row": 33, - "col": 0 - }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Stream": 2 - } - } - ] - }, - { - "display_name": "Geyser Stomp", - "desc": "Fierce Stomp will create geysers, dealing more damage and vertical knockback.", - "archetype": "", - "archetype_req": 0, - "parents": ["Shrapnel Bomb"], - "dependencies": ["Fierce Stomp"], - "blockers": [], - "cost": 2, - "display": { - "row": 37, - "col": 1 - }, - "properties": { - "aoe": 1 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Fierce Stomp", - "cost": 0, - "multipliers": [0, 0, 0, 50, 0, 0] - } - ] - }, - { - "display_name": "Crepuscular Ray", - "desc": "If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.", - "archetype": "Sharpshooter", - "archetype_req": 10, - "parents": ["Cheaper Arrow Shield"], - "dependencies": ["Arrow Storm"], - "blockers": [], - "cost": 2, - "display": { - "row": 37, - "col": 4 - }, - "properties": { - "focusReq": 5, - "focusRegen": -1 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Crepuscular Ray", - "base_spell": 5, - "spell_type": "damage", - "scaling": "spell", - "display": "One Focus", - "cost": 0, - - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [10, 0, 0, 5, 0, 0] - }, - { - "name": "One Focus", - "type": "total", - "hits": { - "Single Arrow": 20 - } - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "One Focus": 7 - } - } - ] - } - ] - }, - { - "display_name": "Grape Bomb", - "desc": "Arrow bomb will throw 3 additional smaller bombs when exploding.", - "archetype": "", - "archetype_req": 0, - "parents": ["Cheaper Escape (2)"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 37, - "col": 7 - }, - "properties": { - "miniBombs": 3, - "aoe": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Grape Bomb", - "cost": 0, - "multipliers": [30, 0, 0, 0, 10, 0] - } - ] - }, - { - "display_name": "Tangled Traps", - "desc": "Your Traps will be connected by a rope that deals damage to enemies every 0.2s.", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Grape Bomb"], - "dependencies": ["Basaltic Trap"], - "blockers": [], - "cost": 2, - "display": { - "row": 38, - "col": 6 - }, - "properties": { - "attackSpeed": 0.2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Tangled Traps", - "cost": 0, - "multipliers": [20, 0, 0, 0, 0, 20] - } - ] - }, - { - "display_name": "Snow Storm", - "desc": "Enemies near you will be slowed down.", - "archetype": "", - "archetype_req": 0, - "parents": ["Geyser Stomp", "More Focus (2)"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 39, - "col": 2 - }, - "properties": { - "range": 2.5, - "slowness": 0.3 - } - }, - { - "display_name": "All-Seeing Panoptes", - "desc": "Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.", - "archetype": "Boltslinger", - "archetype_req": 11, - "parents": ["Snow Storm"], - "dependencies": ["Guardian Angels"], - "blockers": [], - "cost": 2, - "display": { - "row": 40, - "col": 1 - }, - "properties": { - "range": 10, - "shots": 5 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Arrow", - "cost": 0, - "multipliers": [0, 0, 0, 0, 20, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Bow", - "cost": 0, - "hits": { - "Single Arrow": 5 - } - } - ] - }, - { - "display_name": "Minefield", - "desc": "Allow you to place +6 Traps, but with reduced damage and range.", - "archetype": "Trapper", - "archetype_req": 10, - "parents": ["Grape Bomb", "Cheaper Arrow Bomb (2)"], - "dependencies": ["Basaltic Trap"], - "blockers": [], - "cost": 2, - "display": { - "row": 40, - "col": 7 - }, - "properties": { - "aoe": -2, - "traps": 6 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Basaltic Trap", - "cost": 0, - "multipliers": [-80, 0, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "Bow Proficiency I", - "desc": "Improve your Main Attack's damage and range when using a bow.", - "archetype": "", - "archetype_req": 0, - "parents": ["Arrow Bomb"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 2, - "col": 4 - }, - "properties": { - "mainAtk_range": 6 - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "mdPct", - "value": 5 - } - ] - } - ] - }, - { - "display_name": "Cheaper Arrow Bomb", - "desc": "Reduce the Mana cost of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": ["Bow Proficiency I"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 2, - "col": 6 - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "cost": -10 - } - ] - }, - { - "display_name": "Cheaper Arrow Storm", - "desc": "Reduce the Mana cost of Arrow Storm.", - "archetype": "", - "archetype_req": 0, - "parents": ["Grappling Hook", "Windstorm", "Focus"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 21, - "col": 3 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "cost": -5 - } - ] - }, - { - "display_name": "Cheaper Escape", - "desc": "Reduce the Mana cost of Escape.", - "archetype": "", - "archetype_req": 0, - "parents": ["Arrow Storm", "Arrow Shield"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 9, - "col": 4 - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "cost": -5 - } - ] - }, - { - "display_name": "Earth Mastery", - "desc": "Increases your base damage from all Earth attacks", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Arrow Shield"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 13, - "col": 8 - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "eDamPct", - "value": 20 - }, - { - "type": "stat", - "name": "eDam", - "value": [2, 4] - } - ] - } - ] - }, - { - "display_name": "Thunder Mastery", - "desc": "Increases your base damage from all Thunder attacks", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Arrow Storm", "Fire Mastery"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 13, - "col": 2 - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "tDamPct", - "value": 10 - }, - { - "type": "stat", - "name": "tDam", - "value": [1, 8] - } - ] - } - ] - }, - { - "display_name": "Water Mastery", - "desc": "Increases your base damage from all Water attacks", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Cheaper Escape", "Thunder Mastery", "Fire Mastery"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 14, - "col": 4 - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "wDamPct", - "value": 15 - }, - { - "type": "stat", - "name": "wDam", - "value": [2, 4] - } - ] - } - ] - }, - { - "display_name": "Air Mastery", - "desc": "Increases base damage from all Air attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Arrow Storm"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 13, - "col": 0 - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "aDamPct", - "value": 15 - }, - { - "type": "stat", - "name": "aDam", - "value": [3, 4] - } - ] - } - ] - }, - { - "display_name": "Fire Mastery", - "desc": "Increases base damage from all Earth attacks", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Thunder Mastery", "Arrow Shield"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 13, - "col": 6 - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "fDamPct", - "value": 15 - }, - { - "type": "stat", - "name": "fDam", - "value": [3, 5] - } - ] - } - ] - }, - { - "display_name": "More Shields", - "desc": "Give +2 charges to Arrow Shield.", - "archetype": "", - "archetype_req": 0, - "parents": ["Grappling Hook", "Basaltic Trap"], - "dependencies": ["Arrow Shield"], - "blockers": [], - "cost": 1, - "display": { - "row": 21, - "col": 7 - }, - "properties": { - "shieldCharges": 2 - } - }, - { - "display_name": "Stormy Feet", - "desc": "Windy Feet will last longer and add more speed.", - "archetype": "", - "archetype_req": 0, - "parents": ["Windstorm"], - "dependencies": ["Windy Feet"], - "blockers": [], - "cost": 1, - "display": { - "row": 23, - "col": 1 - }, - "properties": { - "duration": 60 - }, - "effects": [ - { - "type": "stat_bonus", - "bonuses": [ - { - "type": "stat", - "name": "spdPct", - "value": 20 - } - ] - } - ] - }, - { - "display_name": "Refined Gunpowder", - "desc": "Increase the damage of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": ["Windstorm"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 25, - "col": 0 - }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Bomb", - "cost": 0, - "multipliers": [50, 0, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "More Traps", - "desc": "Increase the maximum amount of active Traps you can have by +2.", - "archetype": "Trapper", - "archetype_req": 10, - "parents": ["Bouncing Bomb"], - "dependencies": ["Basaltic Trap"], - "blockers": [], - "cost": 1, - "display": { - "row": 26, - "col": 8 - }, - "properties": { - "traps": 2 - } - }, - { - "display_name": "Better Arrow Shield", - "desc": "Arrow Shield will gain additional area of effect, knockback and damage.", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Mana Trap", "Shocking Bomb", "Twain's Arc"], - "dependencies": ["Arrow Shield"], - "blockers": [], - "cost": 1, - "display": { - "row": 28, - "col": 6 - }, - "properties": { - "aoe": 1 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Arrow Shield", - "multipliers": [40, 0, 0, 0, 0, 0] - } - ] - }, - { - "display_name": "Better Leap", - "desc": "Reduce leap's cooldown by 1s.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Leap", "Homing Shots"], - "dependencies": ["Leap"], - "blockers": [], - "cost": 1, - "display": { - "row": 29, - "col": 1 - }, - "properties": { - "cooldown": -1 - } - }, - { - "display_name": "Better Guardian Angels", - "desc": "Your Guardian Angels can shoot +4 arrows before disappearing.", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Escape Artist", "Homing Shots"], - "dependencies": ["Guardian Angels"], - "blockers": [], - "cost": 1, - "display": { - "row": 31, - "col": 2 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Single Bow", - "cost": 0, - "hits": { - "Single Arrow": 4 - } - } - ] - }, - { - "display_name": "Cheaper Arrow Storm (2)", - "desc": "Reduce the Mana cost of Arrow Storm.", - "archetype": "", - "archetype_req": 0, - "parents": ["Initiator", "Mana Trap"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 31, - "col": 8 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "cost": -5 - } - ] - }, - { - "display_name": "Precise Shot", - "desc": "+30% Critical Hit Damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Better Guardian Angels", "Cheaper Arrow Shield", "Arrow Hurricane"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 33, - "col": 2 - }, - "properties": { - "mainAtk_range": 6 - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "mdCritPct", - "value": 30 - } - ] - } - ] - }, - { - "display_name": "Cheaper Arrow Shield", - "desc": "Reduce the Mana cost of Arrow Shield.", - "archetype": "", - "archetype_req": 0, - "parents": ["Precise Shot", "Initiator"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 33, - "col": 4 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "cost": -5 - } - ] - }, - { - "display_name": "Rocket Jump", - "desc": "Arrow Bomb's self-damage will knockback you farther away.", - "archetype": "", - "archetype_req": 0, - "parents": ["Cheaper Arrow Storm (2)", "Initiator"], - "dependencies": ["Arrow Bomb"], - "blockers": [], - "cost": 1, - "display": { - "row": 33, - "col": 6 - }, - "properties": { - } - }, - { - "display_name": "Cheaper Escape (2)", - "desc": "Reduce the Mana cost of Escape.", - "archetype": "", - "archetype_req": 0, - "parents": ["Call of the Hound", "Decimator"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 34, - "col": 7 - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "cost": -5 - } - ] - }, - { - "display_name": "Stronger Hook", - "desc": "Increase your Grappling Hook's range, speed and strength.", - "archetype": "Trapper", - "archetype_req": 5, - "parents": ["Cheaper Escape (2)"], - "dependencies": ["Grappling Hook"], - "blockers": [], - "cost": 1, - "display": { - "row": 35, - "col": 8 - }, - "properties": { - "range": 8 - } - }, - { - "display_name": "Cheaper Arrow Bomb (2)", - "desc": "Reduce the Mana cost of Arrow Bomb.", - "archetype": "", - "archetype_req": 0, - "parents": ["More Focus (2)", "Minefield"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 40, - "col": 5 - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "cost": -5 - } - ] - }, - { - "display_name": "Bouncing Bomb", - "desc": "Arrow Bomb will bounce once when hitting a block or enemy", - "archetype": "", - "archetype_req": 0, - "parents": ["More Shields"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 25, - "col": 7 - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Arrow Bomb": 2 - } - } - ] - }, - { - "display_name": "Homing Shots", - "desc": "Your Main Attack arrows will follow nearby enemies and not be affected by gravity", - "archetype": "", - "archetype_req": 0, - "parents": ["Leap", "Shocking Bomb"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 28, - "col": 2 - }, - "properties": { - - }, - "effects": [ - - ] - }, - { - "display_name": "Shrapnel Bomb", - "desc": "Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area", - "archetype": "Boltslinger", - "archetype_req": 8, - "parents": ["Arrow Hurricane", "Precise Shot"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 34, - "col": 1 - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Shrapnel Bomb", - "cost": 0, - "multipliers": [40, 0, 0, 0, 20, 0] - } - ] - }, - { - "display_name": "Elusive", - "desc": "If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Geyser Stomp"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 38, - "col": 0 - }, - "properties": { - - }, - "effects": [ - - ] - }, - { - "display_name": "Double Shots", - "desc": "Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Escape"], - "dependencies": [], - "blockers": ["Power Shots"], - "cost": 1, - "display": { - "row": 7, - "col": 2 - }, - "properties": { - "arrow": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 0, - "target_part": "Melee Damage", - "cost": 0, - "multipliers": 0.7 - } - ] - }, - { - "display_name": "Triple Shots", - "desc": "Triple Main Attack arrows, but they deal -20% damage per arrow", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Arrow Rain", "Frenzy"], - "dependencies": ["Double Shots"], - "blockers": [], - "cost": 1, - "display": { - "row": 17, - "col": 0 - }, - "properties": { - "arrow": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 0, - "target_part": "Melee Damage", - "cost": 0, - "multipliers": 0.7 - } - ] - }, - { - "display_name": "Power Shots", - "desc": "Main Attack arrows have increased speed and knockback", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Escape"], - "dependencies": [], - "blockers": ["Double Shots"], - "cost": 1, - "display": { - "row": 7, - "col": 6 - }, - "properties": { - - }, - "effects": [ - - ] - }, - { - "display_name": "Focus", - "desc": "When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once", - "archetype": "Sharpshooter", - "archetype_req": 2, - "parents": ["Phantom Ray"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 19, - "col": 4 - }, - "properties": { - - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Focus", - "output": { - "type": "stat", - "abil_name": "Focus", - "name": "damMult" - }, - "scaling": [3], - "max": 3 - } - ] - }, - { - "display_name": "More Focus", - "desc": "Add +2 max Focus", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Cheaper Arrow Storm", "Grappling Hook"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 22, - "col": 4 - }, - "properties": { - - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Focus", - "output": { - "type": "stat", - "abil_name": "Focus", - "name": "damMult" - }, - "scaling": [35], - "max": 5 - } - ] - }, - { - "display_name": "More Focus (2)", - "desc": "Add +2 max Focus", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Crepuscular Ray", "Snow Storm"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 39, - "col": 4 - }, - "properties": { - - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Focus", - "output": { - "type": "stat", - "abil_name": "Focus", - "name": "damMult" - }, - "scaling": [35], - "max": 7 - } - ] - }, - { - "display_name": "Traveler", - "desc": "For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)", - "archetype": "", - "archetype_req": 0, - "parents": ["Refined Gunpowder", "Twain's Arc"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 25, - "col": 2 - }, - "properties": { - - }, - "effects": [ - { - "type": "stat_scaling", - "slider": false, - "inputs": [ - { - "type": "stat", - "name": "spd" - } - ], - "output": { - "type": "stat", - "name": "sdRaw" - }, - "scaling": [1], - "max": 100 - } - ] - }, - { - "display_name": "Patient Hunter", - "desc": "Your Traps will deal +20% more damage for every second they are active (Max +80%)", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["More Shields"], - "dependencies": ["Basaltic Trap"], - "blockers": [], - "cost": 2, - "display": { - "row": 22, - "col": 8 - }, - "properties": { - "max": 80 - }, - "effects": [ - - ] - }, - { - "display_name": "Stronger Patient Hunter", - "desc": "Add +80% Max Damage to Patient Hunter", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Grape Bomb"], - "dependencies": ["Patient Hunter"], - "blockers": [], - "cost": 1, - "display": { - "row": 38, - "col": 8 - }, - "properties": { - "max": 80 - }, - "effects": [ - - ] - }, - { - "display_name": "Frenzy", - "desc": "Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second", - "archetype": "Boltslinger", - "archetype_req": 0, - "parents": ["Triple Shots", "Nimble String"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 17, - "col": 2 - }, - "properties": { - - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Hits dealt", - "output": { - "type": "stat", - "name": "spd" - }, - "scaling": [6], - "max": 200 - } - ] - }, - { - "display_name": "Phantom Ray", - "desc": "Condense Arrow Storm into a single ray that damages enemies 10 times per second", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Water Mastery", "Fire Creep"], - "dependencies": ["Arrow Storm"], - "blockers": ["Windstorm", "Nimble String", "Arrow Hurricane"], - "cost": 2, - "display": { - "row": 16, - "col": 4 - }, - "properties": { - }, - "effects": [ - { - "type": "replace_spell", - "name": "Phantom Ray", - "cost": 40, - "display_text": "Max Damage", - "base_spell": 1, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Arrow", - "type": "damage", - "multipliers": [25, 0, 5, 0, 0, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Arrow": 16 - } - } - ] - } - ] - }, - { - "display_name": "Arrow Rain", - "desc": "When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies", - "archetype": "Trapper", - "archetype_req": 0, - "parents": ["Nimble String", "Air Mastery"], - "dependencies": ["Arrow Shield"], - "blockers": [], - "cost": 2, - "display": { - "row": 15, - "col": 0 - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Arrow Rain", - "cost": 0, - "multipliers": [120, 0, 0, 0, 0, 80] - } - ] - }, - { - "display_name": "Decimator", - "desc": "Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)", - "archetype": "Sharpshooter", - "archetype_req": 0, - "parents": ["Cheaper Arrow Shield"], - "dependencies": ["Phantom Ray"], - "blockers": [], - "cost": 1, - "display": { - "row": 34, - "col": 5 - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Phantom Ray hits", - "output": { - "type": "stat", - "name": "PhRayDmg" - }, - "scaling": 10, - "max": 50 - } - ] - } - ], - "Warrior": [ - { - "display_name": "Bash", - "desc": "Violently bash the ground, dealing high damage in a large area", - "archetype": "", - "archetype_req": 0, - "parents": [], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 0, - "col": 4, - "icon": "node_4" - }, - "properties": { - "aoe": 4, - "range": 3 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Bash", - "cost": 45, - "display_text": "Total Damage Average", - "base_spell": 1, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "Single Hit", - "type": "damage", - "multipliers": [130, 20, 0, 0, 0, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Single Hit": 1 - } - } - ] - } - ] - }, - { - "display_name": "Spear Proficiency 1", - "desc": "Improve your Main Attack's damage and range w/ spear", - "archetype": "", - "archetype_req": 0, - "parents": ["Bash"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 2, - "col": 4, - "icon": "node_0" - }, - "properties": { - "melee_range": 1 - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "mdPct", - "value": 5 - } - ] - } - ] - }, - - { - "display_name": "Cheaper Bash", - "desc": "Reduce the Mana cost of Bash", - "archetype": "", - "archetype_req": 0, - "parents": ["Spear Proficiency 1"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 2, - "col": 2, - "icon": "node_0" - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "cost": -10 - } - ] - }, - { - "display_name": "Double Bash", - "desc": "Bash will hit a second time at a farther range", - "archetype": "", - "archetype_req": 0, - "parents": ["Spear Proficiency 1"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 4, - "col": 4, - "icon": "node_1" - }, - "properties": { - "range": 3 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "name": "Single Hit", - "value": 1 - } - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Hit", - "cost": 0, - "multipliers": [-50, 0, 0, 0, 0, 0] - } - ] - }, - - { - "display_name": "Charge", - "desc": "Charge forward at high speed (hold shift to cancel)", - "archetype": "", - "archetype_req": 0, - "parents": ["Double Bash"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 6, - "col": 4, - "icon": "node_4" - }, - "properties": { - }, - "effects": [ - { - "type": "replace_spell", - "name": "Charge", - "cost": 25, - "display_text": "Total Damage Average", - "base_spell": 2, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage", - "parts": [ - { - "name": "None", - "type": "damage", - "multipliers": [0, 0, 0, 0, 0, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "None": 0 - } - } - ] - } - ] - }, - - { - "display_name": "Heavy Impact", - "desc": "After using Charge, violently crash down into the ground and deal damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Uppercut"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 9, - "col": 1, - "icon": "node_1" - }, - "properties": { - "aoe": 4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Heavy Impact", - "cost": 0, - "multipliers": [100, 0, 0, 0, 0, 0] - } - ] - }, - - { - "display_name": "Vehement", - "desc": "For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Charge"], - "dependencies": [], - "blockers": ["Tougher Skin"], - "cost": 1, - "display": { - "row": 6, - "col": 2, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "slider": false, - "inputs": [ - { - "type": "stat", - "name": "mdPct" - }, - { - "type": "stat", - "name": "mdRaw" - } - ], - "output": { - "type": "stat", - "name": "spd" - }, - "scaling": [1, 1], - "max": 20 - } - ] - }, - - { - "display_name": "Tougher Skin", - "desc": "Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Charge"], - "dependencies": [], - "blockers": ["Vehement"], - "cost": 1, - "display": { - "row": 6, - "col": 6, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "baseResist", - "value": "5" - } - ] - }, - { - "type": "stat_scaling", - "slider": false, - "inputs": [ - { - "type": "stat", - "name": "hprRaw" - }, - { - "type": "stat", - "name": "hprPct" - } - ], - "output": { - "type": "stat", - "name": "hpBonus" - }, - "scaling": [10, 10], - "max": 100 - } - ] - }, - - { - "display_name": "Uppercut", - "desc": "Rocket enemies in the air and deal massive damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Vehement"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 8, - "col": 2, - "icon": "node_4" - }, - "properties": { - "aoe": 3, - "range": 5 - }, - "effects": [ - { - "type": "replace_spell", - "name": "Uppercut", - "cost": 45, - "display_text": "Total Damage Average", - "base_spell": 3, - "spell_type": "damage", - "scaling": "spell", - "display": "total", - "parts": [ - { - "name": "Uppercut", - "type": "damage", - "multipliers": [150, 50, 50, 0, 0, 0] - }, - { - "name": "Total Damage", - "type": "total", - "hits": { - "Uppercut": 1 - } - } - ] - } - ] - }, - - { - "display_name": "Cheaper Charge", - "desc": "Reduce the Mana cost of Charge", - "archetype": "", - "archetype_req": 0, - "parents": ["Uppercut", "War Scream"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 8, - "col": 4, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "cost": -5 - } - ] - }, - - { - "display_name": "War Scream", - "desc": "Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies", - "archetype": "", - "archetype_req": 0, - "parents": ["Tougher Skin"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 8, - "col": 6, - "icon": "node_4" - }, - "properties": { - "duration": 30, - "aoe": 12, - "defense_bonus": 10 - }, - "effects": [ - { - "type": "replace_spell", - "name": "War Scream", - "cost": 35, - "display_text": "War Scream", - "base_spell": 4, - "spell_type": "damage", - "scaling": "spell", - "display": "Total Damage Average", - "parts": [ - { - "name": "War Scream", - "type": "damage", - "multipliers": [50, 0, 0, 0, 50, 0] - } - ] - } - ] - }, - - { - "display_name": "Earth Mastery", - "desc": "Increases base damage from all Earth attacks", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Uppercut"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 10, - "col": 0, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "eDamPct", - "value": 20 - }, - { - "type": "stat", - "name": "eDam", - "value": [2, 4] - } - ] - } - ] - }, - - { - "display_name": "Thunder Mastery", - "desc": "Increases base damage from all Thunder attacks", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Uppercut", "Air Mastery", "Cheaper Charge"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 10, - "col": 2, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "tDamPct", - "value": 10 - }, - { - "type": "stat", - "name": "tDam", - "value": [1, 8] - } - ] - } - ] - }, - - { - "display_name": "Water Mastery", - "desc": "Increases base damage from all Water attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Cheaper Charge", "Thunder Mastery", "Air Mastery"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 11, - "col": 4, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "wDamPct", - "value": 15 - }, - { - "type": "stat", - "name": "wDam", - "value": [2, 4] - } - ] - } - ] - }, - - { - "display_name": "Air Mastery", - "desc": "Increases base damage from all Air attacks", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["War Scream", "Thunder Mastery", "Cheaper Charge"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 10, - "col": 6, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "aDamPct", - "value": 15 - }, - { - "type": "stat", - "name": "aDam", - "value": [3, 4] - } - ] - } - ] - }, - - { - "display_name": "Fire Mastery", - "desc": "Increases base damage from all Earth attacks", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["War Scream"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 10, - "col": 8, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "fDamPct", - "value": 15 - }, - { - "type": "stat", - "name": "fDam", - "value": [3, 5] - } - ] - } - ] - }, - - { - "display_name": "Quadruple Bash", - "desc": "Bash will hit 4 times at an even larger range", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Earth Mastery", "Fireworks"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 12, - "col": 0, - "icon": "node_1" - }, - "properties": { - "range": 6 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Single Hit": 2 - } - }, - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Hit", - "cost": 0, - "multipliers": [-20, 0, 0, 0, 0, 0] - } - ] - }, - - { - "display_name": "Fireworks", - "desc": "Mobs hit by Uppercut will explode mid-air and receive additional damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Thunder Mastery", "Quadruple Bash"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 12, - "col": 2, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Fireworks", - "cost": 0, - "multipliers": [80, 0, 20, 0, 0, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Fireworks": 1 - } - } - ] - }, - - { - "display_name": "Half-Moon Swipe", - "desc": "Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": ["Water Mastery"], - "dependencies": ["Uppercut"], - "blockers": [], - "cost": 2, - "display": { - "row": 13, - "col": 4, - "icon": "node_1" - }, - "properties": { - "range": 4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Uppercut", - "cost": -10, - "multipliers": [-70, 0, 0, 0, 0, 0] - }, - { - "type": "convert_spell_conv", - "target_part": "all", - "conversion": "water" - } - ] - }, - - { - "display_name": "Flyby Jab", - "desc": "Damage enemies in your way when using Charge", - "archetype": "", - "archetype_req": 0, - "parents": ["Air Mastery", "Flaming Uppercut"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 12, - "col": 6, - "icon": "node_1" - }, - "properties": { - "aoe": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Flyby Jab", - "cost": 0, - "multipliers": [20, 0, 0, 0, 0, 40] - } - ] - }, - - { - "display_name": "Flaming Uppercut", - "desc": "Uppercut will light mobs on fire, dealing damage every 0.6 seconds", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Fire Mastery", "Flyby Jab"], - "dependencies": ["Uppercut"], - "blockers": [], - "cost": 2, - "display": { - "row": 12, - "col": 8, - "icon": "node_1" - }, - "properties": { - "duration": 3, - "tick": 0.6 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Flaming Uppercut", - "cost": 0, - "multipliers": [0, 0, 0, 0, 50, 0] - }, - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Flaming Uppercut Total Damage", - "cost": 0, - "hits": { - "Flaming Uppercut": 5 - } - }, - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Flaming Uppercut": 5 - } - } - ] - }, - - { - "display_name": "Iron Lungs", - "desc": "War Scream deals more damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Flyby Jab", "Flaming Uppercut"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 13, - "col": 7, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "War Scream", - "cost": 0, - "multipliers": [30, 0, 0, 0, 0, 30] - } - ] - }, - - { - "display_name": "Generalist", - "desc": "After casting 3 different spells in a row, your next spell will cost 5 mana", - "archetype": "Battle Monk", - "archetype_req": 3, - "parents": ["Counter"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 15, - "col": 2, - "icon": "node_3" - }, - "properties": { - }, - "effects": [ - - ] - }, - - { - "display_name": "Counter", - "desc": "When dodging a nearby enemy attack, get 30% chance to instantly attack back", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Half-Moon Swipe"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 15, - "col": 4, - "icon": "node_1" - }, - "properties": { - "chance": 30 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 5, - "target_part": "Counter", - "cost": 0, - "multipliers": [60, 0, 20, 0, 0, 20] - } - ] - }, - - { - "display_name": "Mantle of the Bovemists", - "desc": "When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)", - "archetype": "Paladin", - "archetype_req": 3, - "parents": ["Iron Lungs"], - "dependencies": ["War Scream"], - "blockers": [], - "cost": 2, - "display": { - "row": 15, - "col": 7, - "icon": "node_3" - }, - "properties": { - "mantle_charge": 3 - }, - "effects": [ - - ] - }, - - { - "display_name": "Bak'al's Grasp", - "desc": "After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)", - "archetype": "Fallen", - "archetype_req": 2, - "parents": ["Quadruple Bash", "Fireworks"], - "dependencies": ["War Scream"], - "blockers": [], - "cost": 2, - "display": { - "row": 16, - "col": 1, - "icon": "node_3" - }, - "properties": { - "cooldown": 15 - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Corrupted", - "output": { - "type": "stat", - "name": "raw" - }, - "scaling": [4], - "slider_step": 2, - "max": 120 - } - ] - }, - - { - "display_name": "Spear Proficiency 2", - "desc": "Improve your Main Attack's damage and range w/ spear", - "archetype": "", - "archetype_req": 0, - "parents": ["Bak'al's Grasp", "Cheaper Uppercut"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 17, - "col": 0, - "icon": "node_0" - }, - "properties": { - "melee_range": 1 - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "mdPct", - "value": 5 - } - ] - } - ] - }, - - { - "display_name": "Cheaper Uppercut", - "desc": "Reduce the Mana Cost of Uppercut", - "archetype": "", - "archetype_req": 0, - "parents": ["Spear Proficiency 2", "Aerodynamics", "Counter"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 17, - "col": 3, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "cost": -5 - } - ] - }, - - { - "display_name": "Aerodynamics", - "desc": "During Charge, you can steer and change direction", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Cheaper Uppercut", "Provoke"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 17, - "col": 5, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - - ] - }, - - { - "display_name": "Provoke", - "desc": "Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Aerodynamics", "Mantle of the Bovemists"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 17, - "col": 7, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "cost": -5 - } - ] - }, - - { - "display_name": "Precise Strikes", - "desc": "+30% Critical Hit Damage", - "archetype": "", - "archetype_req": 0, - "parents": ["Cheaper Uppercut", "Spear Proficiency 2"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 18, - "col": 2, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "critDmg", - "value": 30 - } - ] - } - ] - }, - - { - "display_name": "Air Shout", - "desc": "War Scream will fire a projectile that can go through walls and deal damage multiple times", - "archetype": "", - "archetype_req": 0, - "parents": ["Aerodynamics", "Provoke"], - "dependencies": ["War Scream"], - "blockers": [], - "cost": 2, - "display": { - "row": 18, - "col": 6, - "icon": "node_1" - }, - "properties": { - - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Air Shout", - "cost": 0, - "multipliers": [20, 0, 0, 0, 0, 5] - } - ] - }, - - { - "display_name": "Enraged Blow", - "desc": "While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Spear Proficiency 2"], - "dependencies": ["Bak'al's Grasp"], - "blockers": [], - "cost": 2, - "display": { - "row": 20, - "col": 0, - "icon": "node_2" - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "slider": false, - "inputs": [ - { - "type": "stat", - "name": "hpBonus" - } - ], - "output": { - "type": "stat", - "name": "damMult" - }, - "scaling": [3], - "max": 300 - } - ] - }, - - { - "display_name": "Flying Kick", - "desc": "When using Charge, mobs hit will halt your momentum and get knocked back", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": ["Cheaper Uppercut", "Stronger Mantle"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 20, - "col": 3, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Flying Kick", - "cost": 0, - "multipliers": [120, 0, 0, 10, 0, 20] - } - ] - }, - - { - "display_name": "Stronger Mantle", - "desc": "Add +2 additional charges to Mantle of the Bovemists", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Manachism", "Flying Kick"], - "dependencies": ["Mantle of the Bovemists"], - "blockers": [], - "cost": 1, - "display": { - "row": 20, - "col": 6, - "icon": "node_0" - }, - "properties": { - "mantle_charge": 2 - }, - "effects": [ - - ] - }, - - { - "display_name": "Manachism", - "desc": "If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)", - "archetype": "Paladin", - "archetype_req": 3, - "parents": ["Stronger Mantle", "Provoke"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 20, - "col": 8, - "icon": "node_2" - }, - "properties": { - "cooldown": 1 - }, - "effects": [ - - ] - }, - - { - "display_name": "Boiling Blood", - "desc": "Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds", - "archetype": "", - "archetype_req": 0, - "parents": ["Enraged Blow", "Ragnarokkr"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 22, - "col": 0, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Boiling Blood", - "cost": 0, - "multipliers": [25, 0, 0, 0, 5, 0] - } - ] - }, - - { - "display_name": "Ragnarokkr", - "desc": "War Scream become deafening, increasing its range and giving damage bonus to players", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Boiling Blood", "Flying Kick"], - "dependencies": ["War Scream"], - "blockers": [], - "cost": 2, - "display": { - "row": 22, - "col": 2, - "icon": "node_2" - }, - "properties": { - "damage_bonus": 30, - "aoe": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "cost": 10 - } - ] - }, - - { - "display_name": "Ambidextrous", - "desc": "Increase your chance to attack with Counter by +30%", - "archetype": "", - "archetype_req": 0, - "parents": ["Flying Kick", "Stronger Mantle", "Burning Heart"], - "dependencies": ["Counter"], - "blockers": [], - "cost": 1, - "display": { - "row": 22, - "col": 4, - "icon": "node_0" - }, - "properties": { - "chance": 30 - }, - "effects": [ - - ] - }, - - { - "display_name": "Burning Heart", - "desc": "For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Ambidextrous", "Stronger Bash"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 22, - "col": 6, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "slider": false, - "inputs": [ - { - "type": "stat", - "name": "hpBonus" - } - ], - "output": { - "type": "stat", - "name": "fDamPct" - }, - "scaling": [2], - "max": 100, - "slider_step": 100 - } - ] - }, - - { - "display_name": "Stronger Bash", - "desc": "Increase the damage of Bash", - "archetype": "", - "archetype_req": 0, - "parents": ["Burning Heart", "Manachism"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 22, - "col": 8, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "target_part": "Single Hit", - "cost": 0, - "multipliers": [30, 0, 0, 0, 0, 0] - } - ] - }, - - { - "display_name": "Intoxicating Blood", - "desc": "After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted", - "archetype": "Fallen", - "archetype_req": 5, - "parents": ["Ragnarokkr", "Boiling Blood"], - "dependencies": ["Bak'al's Grasp"], - "blockers": [], - "cost": 2, - "display": { - "row": 23, - "col": 1, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - - ] - }, - - { - "display_name": "Comet", - "desc": "After being hit by Fireworks, enemies will crash into the ground and receive more damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Ragnarokkr"], - "dependencies": ["Fireworks"], - "blockers": [], - "cost": 2, - "display": { - "row": 24, - "col": 2, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Comet", - "cost": 0, - "multipliers": [80, 20, 0, 0, 0, 0] - }, - { - "type":"add_spell_prop", - "base_spell": 3, - "target_part": "Total Damage", - "cost": 0, - "hits": { - "Comet": 1 - } - } - ] - }, - - { - "display_name": "Collide", - "desc": "Mobs thrown into walls from Flying Kick will explode and receive additonal damage", - "archetype": "Battle Monk", - "archetype_req": 4, - "parents": ["Ambidextrous", "Burning Heart"], - "dependencies": ["Flying Kick"], - "blockers": [], - "cost": 2, - "display": { - "row": 23, - "col": 5, - "icon": "node_1" - }, - "properties": { - "aoe": 4 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "target_part": "Collide", - "cost": 0, - "multipliers": [100, 0, 0, 0, 50, 0] - } - ] - }, - - { - "display_name": "Rejuvenating Skin", - "desc": "Regain back 30% of the damage you take as healing over 30s", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Burning Heart", "Stronger Bash"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 23, - "col": 7, - "icon": "node_3" - }, - "properties": { - }, - "effects": [ - - ] - }, - - { - "display_name": "Uncontainable Corruption", - "desc": "Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1", - "archetype": "", - "archetype_req": 0, - "parents": ["Boiling Blood", "Radiant Devotee"], - "dependencies": ["Bak'al's Grasp"], - "blockers": [], - "cost": 1, - "display": { - "row": 26, - "col": 0, - "icon": "node_0" - }, - "properties": { - "cooldown": -5 - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Corrupted", - "output": { - "type": "stat", - "name": "raw" - }, - "scaling": [1], - "slider_step": 2, - "max": 50 - } - ] - }, - - { - "display_name": "Radiant Devotee", - "desc": "For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)", - "archetype": "Battle Monk", - "archetype_req": 1, - "parents": ["Whirlwind Strike", "Uncontainable Corruption"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 26, - "col": 2, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "inputs": [ - { - "type": "stat", - "name": "ref" - } - ], - "output": { - "type": "stat", - "name": "mr" - }, - "scaling": [1], - "max": 10, - "slider_step": 4 - } - ] - }, - - { - "display_name": "Whirlwind Strike", - "desc": "Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)", - "archetype": "Battle Monk", - "archetype_req": 5, - "parents": ["Ambidextrous", "Radiant Devotee"], - "dependencies": ["Uppercut"], - "blockers": [], - "cost": 2, - "display": { - "row": 26, - "col": 4, - "icon": "node_1" - }, - "properties": { - "range": 2 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Uppercut", - "cost": 0, - "multipliers": [0, 0, 0, 0, 0, 50] - } - ] - }, - - { - "display_name": "Mythril Skin", - "desc": "Gain +5% Base Resistance and become immune to knockback", - "archetype": "Paladin", - "archetype_req": 6, - "parents": ["Rejuvenating Skin"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 26, - "col": 7, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "baseResist", - "value": 5 - } - ] - } - ] - }, - - { - "display_name": "Armour Breaker", - "desc": "While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Uncontainable Corruption", "Radiant Devotee"], - "dependencies": ["Bak'al's Grasp"], - "blockers": [], - "cost": 2, - "display": { - "row": 27, - "col": 1, - "icon": "node_2" - }, - "properties": { - "duration": 5 - }, - "effects": [ - - ] - }, - - { - "display_name": "Shield Strike", - "desc": "When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Mythril Skin", "Sparkling Hope"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 27, - "col": 6, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 5, - "target_part": "Shield Strike", - "cost": 0, - "multipliers": [60, 0, 20, 0, 0, 0] - } - ] - }, - - { - "display_name": "Sparkling Hope", - "desc": "Everytime you heal 5% of your max health, deal damage to all nearby enemies", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Mythril Skin"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 27, - "col": 8, - "icon": "node_2" - }, - "properties": { - "aoe": 6 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 5, - "target_part": "Sparkling Hope", - "cost": 0, - "multipliers": [10, 0, 5, 0, 0, 0] - } - ] - }, - - { - "display_name": "Massive Bash", - "desc": "While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)", - "archetype": "Fallen", - "archetype_req": 8, - "parents": ["Tempest", "Uncontainable Corruption"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 28, - "col": 0, - "icon": "node_2" - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Corrupted", - "output": { - "type": "stat", - "name": "bashAoE" - }, - "scaling": [1], - "max": 10, - "slider_step": 3 - } - ] - }, - - { - "display_name": "Tempest", - "desc": "War Scream will ripple the ground and deal damage 3 times in a large area", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Massive Bash", "Spirit of the Rabbit"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 28, - "col": 2, - "icon": "node_1" - }, - "properties": { - "aoe": 16 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Tempest", - "cost": "0", - "multipliers": [30, 10, 0, 0, 0, 10] - }, - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Tempest Total Damage", - "cost": "0", - "hits": { - "Tempest": 3 - } - }, - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Total Damage", - "cost": "0", - "hits": { - "Tempest": 3 - } - } - ] - }, - - { - "display_name": "Spirit of the Rabbit", - "desc": "Reduce the Mana cost of Charge and increase your Walk Speed by +20%", - "archetype": "Battle Monk", - "archetype_req": 5, - "parents": ["Tempest", "Whirlwind Strike"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 28, - "col": 4, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 2, - "cost": -5 - }, - { - "type": "raw_stat", - "bonuses": [ - { - "type": "stat", - "name": "spd", - "value": 20 - } - ] - } - ] - }, - - { - "display_name": "Massacre", - "desc": "While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar", - "archetype": "Fallen", - "archetype_req": 5, - "parents": ["Tempest", "Massive Bash"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 29, - "col": 1, - "icon": "node_1" - }, - "properties": { - }, - "effects": [ - - ] - }, - - { - "display_name": "Axe Kick", - "desc": "Increase the damage of Uppercut, but also increase its mana cost", - "archetype": "", - "archetype_req": 0, - "parents": ["Tempest", "Spirit of the Rabbit"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 29, - "col": 3, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "target_part": "Uppercut", - "cost": 10, - "multipliers": [100, 0, 0, 0, 0, 0] - } - ] - }, - - { - "display_name": "Radiance", - "desc": "Bash will buff your allies' positive IDs. (15s Cooldown)", - "archetype": "Paladin", - "archetype_req": 2, - "parents": ["Spirit of the Rabbit", "Cheaper Bash 2"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 29, - "col": 5, - "icon": "node_2" - }, - "properties": { - "cooldown": 15 - }, - "effects": [ - - ] - }, - - { - "display_name": "Cheaper Bash 2", - "desc": "Reduce the Mana cost of Bash", - "archetype": "", - "archetype_req": 0, - "parents": ["Radiance", "Shield Strike", "Sparkling Hope"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 29, - "col": 7, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 1, - "cost": -5 - } - ] - }, - - { - "display_name": "Cheaper War Scream", - "desc": "Reduce the Mana cost of War Scream", - "archetype": "", - "archetype_req": 0, - "parents": ["Massive Bash"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 31, - "col": 0, - "icon": "node_0" - }, - "properties": { - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "cost": -5 - } - ] - }, - - { - "display_name": "Discombobulate", - "desc": "Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second", - "archetype": "Battle Monk", - "archetype_req": 11, - "parents": ["Cyclone"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 31, - "col": 2, - "icon": "node_3" - }, - "properties": { - }, - "effects": [ - { - "type": "stat_scaling", - "slider": true, - "slider_name": "Hits dealt", - "output": { - "type": "stat", - "name": "rainrawButDifferent" - }, - "scaling": [2], - "max": 50 - } - ] - }, - - { - "display_name": "Thunderclap", - "desc": "Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder", - "archetype": "Battle Monk", - "archetype_req": 8, - "parents": ["Cyclone"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 32, - "col": 5, - "icon": "node_1" - }, - "properties": {}, - "effects": [ - { - "type": "convert_spell_conv", - "target_part": "all", - "conversion": "thunder" - }, - { - "type": "raw_stat", - "bonuses": [{ - "type": "prop", - "abil_name": "Bash", - "name": "aoe", - "value": 3 - }] - } - ] - }, - - { - "display_name": "Cyclone", - "desc": "After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s", - "archetype": "Battle Monk", - "archetype_req": 0, - "parents": ["Spirit of the Rabbit"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 31, - "col": 4, - "icon": "node_1" - }, - "properties": { - "aoe": 4, - "duration": 20 - }, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Cyclone", - "cost": 0, - "multipliers": [10, 0, 0, 0, 5, 10] - }, - { - "type": "add_spell_prop", - "base_spell": 4, - "target_part": "Cyclone Total Damage", - "cost": 0, - "hits": { - "Cyclone": 40 - } - - } - ] - }, - - { - "display_name": "Second Chance", - "desc": "When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)", - "archetype": "Paladin", - "archetype_req": 12, - "parents": ["Cheaper Bash 2"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 32, - "col": 7, - "icon": "node_3" - }, - "properties": {}, - "effects": [] - }, - - { - "display_name": "Blood Pact", - "desc": "If you do not have enough mana to cast a spell, spend health instead (1% health per mana)", - "archetype": "", - "archetype_req": 10, - "parents": ["Cheaper War Scream"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 34, - "col": 1, - "icon": "node_3" - }, - "properties": {}, - "effects": [] - }, - - { - "display_name": "Haemorrhage", - "desc": "Reduce Blood Pact's health cost. (0.5% health per mana)", - "archetype": "Fallen", - "archetype_req": 0, - "parents": ["Blood Pact"], - "dependencies": ["Blood Pact"], - "blockers": [], - "cost": 1, - "display": { - "row": 35, - "col": 2, - "icon": "node_1" - }, - "properties": {}, - "effects": [] - }, - - { - "display_name": "Brink of Madness", - "desc": "If your health is 25% full or less, gain +40% Resistance", - "archetype": "", - "archetype_req": 0, - "parents": ["Blood Pact", "Cheaper Uppercut 2"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 35, - "col": 4, - "icon": "node_2" - }, - "properties": {}, - "effects": [] - }, - - { - "display_name": "Cheaper Uppercut 2", - "desc": "Reduce the Mana cost of Uppercut", - "archetype": "", - "archetype_req": 0, - "parents": ["Second Chance", "Brink of Madness"], - "dependencies": [], - "blockers": [], - "cost": 1, - "display": { - "row": 35, - "col": 6, - "icon": "node_0" - }, - "properties": {}, - "effects": [ - { - "type": "add_spell_prop", - "base_spell": 3, - "cost": -5 - } - ] - }, - - { - "display_name": "Martyr", - "desc": "When you receive a fatal blow, all nearby allies become invincible", - "archetype": "Paladin", - "archetype_req": 0, - "parents": ["Second Chance"], - "dependencies": [], - "blockers": [], - "cost": 2, - "display": { - "row": 35, - "col": 8, - "icon": "node_1" - }, - "properties": { - "duration": 3, - "aoe": 12 - }, - "effects": [] - } - ], -} - -const atree_example = [ - { - "title": "skill", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 5, - "col": 3, - }, - { - "image": "../media/atree/connect_angle.png", - "connector": true, - "rotate": 270, - "row": 4, - "col": 3, - }, - { - "title": "skill2", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 0, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 1, - "col": 2 - }, - { - "title": "skill3", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 2, - "col": 2 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 90, - "row": 2, - "col": 3 - }, - { - "title": "skill4", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 2, - "col": 4 - }, - { - "image": "../media/atree/connect_line.png", - "connector": true, - "rotate": 0, - "row": 3, - "col": 2 - }, - { - "title": "skill5", - "desc": "desc", - "image": "../media/atree/node.png", - "connector": false, - "row": 4, - "col": 2 - }, -]; diff --git a/js/atree_constants_str_old_min.js b/js/atree_constants_str_old_min.js deleted file mode 100644 index 73d3e29..0000000 --- a/js/atree_constants_str_old_min.js +++ /dev/null @@ -1 +0,0 @@ -const atrees={Archer:[{display_name:"Arrow Shield",desc:"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)",archetype:"",archetype_req:0,parents:["Power Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:6},properties:{duration:60},effects:[{type:"replace_spell",name:"Arrow Shield",cost:30,display_text:"Max Damage",base_spell:4,spell_type:"damage",scaling:"spell",display:"",parts:[{name:"Shield Damage",type:"damage",multipliers:[90,0,0,0,0,10]},{name:"Total Damage",type:"total",hits:{"Shield Damage":2}}]}]},{display_name:"Escape",desc:"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)",archetype:"",archetype_req:0,parents:["Heart Shatter"],dependencies:[],blockers:[],cost:1,display:{row:7,col:4},properties:{aoe:0,range:0},effects:[{type:"replace_spell",name:"Escape",cost:25,display_text:"Max Damage",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Arrow Bomb",desc:"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4},properties:{aoe:4.5,range:26},effects:[{type:"replace_spell",name:"Arrow Bomb",cost:50,display_text:"Average Damage",base_spell:3,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Arrow Bomb",type:"damage",multipliers:[160,0,0,0,20,0]},{name:"Total Damage",type:"total",hits:{"Arrow Bomb":1}}]}]},{display_name:"Heart Shatter",desc:"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[100,0,0,0,0,0]},{}]},{display_name:"Fire Creep",desc:"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.",archetype:"",archetype_req:0,parents:["Phantom Ray","Fire Mastery","Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:16,col:6},properties:{aoe:.8,duration:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[30,0,0,0,20,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Fire Creep":15}}]},{display_name:"Bryophyte Roots",desc:"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.",archetype:"Trapper",archetype_req:1,parents:["Fire Creep","Earth Mastery"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:16,col:8},properties:{aoe:2,duration:5,slowness:.4},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Bryophyte Roots",cost:0,multipliers:[40,20,0,0,0,0]}]},{display_name:"Nimble String",desc:"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.",archetype:"",archetype_req:0,parents:["Thunder Mastery","Arrow Rain"],dependencies:["Arrow Storm"],blockers:["Phantom Ray"],cost:2,display:{row:15,col:2},properties:{shootspeed:2},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-15,0,0,0,0,0]},{type:"add_spell_prop",base_spell:1,target_part:"Single Stream",cost:0,hits:{"Single Arrow":8}}]},{display_name:"Arrow Storm",desc:"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.",archetype:"",archetype_req:0,parents:["Double Shots","Cheaper Escape"],dependencies:[],blockers:[],cost:1,display:{row:9,col:2},properties:{aoe:0,range:16},effects:[{type:"replace_spell",name:"Arrow Storm",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[30,0,10,0,0,0]},{name:"Single Stream",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Stream":2}}]}]},{display_name:"Guardian Angels",desc:"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)",archetype:"Boltslinger",archetype_req:3,parents:["Triple Shots","Frenzy"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:19,col:1},properties:{range:4,duration:60,shots:8,count:2},effects:[{type:"replace_spell",name:"Guardian Angels",cost:30,display_text:"Total Damage Average",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[40,0,0,0,0,20]},{name:"Single Bow",type:"total",hits:{"Single Arrow":8}},{name:"Total Damage",type:"total",hits:{"Single Bow":2}}]}]},{display_name:"Windy Feet",base_abil:"Escape",desc:"When casting Escape, give speed to yourself and nearby allies.",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:10,col:1},properties:{aoe:8,duration:120},type:"stat_bonus",bonuses:[{type:"stat",name:"spd",value:20}]},{display_name:"Basaltic Trap",desc:"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)",archetype:"Trapper",archetype_req:2,parents:["Bryophyte Roots"],dependencies:[],blockers:[],cost:2,display:{row:19,col:8},properties:{aoe:7,traps:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[140,30,0,0,30,0]}]},{display_name:"Windstorm",desc:"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.",archetype:"",archetype_req:0,parents:["Guardian Angels","Cheaper Arrow Storm"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:21,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Arrow",cost:0,multipliers:[-11,0,-7,0,0,3]},{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":1}}]},{display_name:"Grappling Hook",base_abil:"Escape",desc:"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)",archetype:"Trapper",archetype_req:0,parents:["Focus","More Shields","Cheaper Arrow Storm"],dependencies:[],blockers:["Escape Artist"],cost:2,display:{row:21,col:5},properties:{range:20},effects:[]},{display_name:"Implosion",desc:"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.",archetype:"Trapper",archetype_req:0,parents:["Grappling Hook","More Shields"],dependencies:[],blockers:[],cost:2,display:{row:22,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Twain's Arc",desc:"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)",archetype:"Sharpshooter",archetype_req:4,parents:["More Focus","Traveler"],dependencies:["Focus"],blockers:[],cost:2,display:{row:25,col:4},properties:{range:64,focusReq:2},effects:[{type:"replace_spell",name:"Twain's Arc",cost:0,display_text:"Twain's Arc",base_spell:5,spell_type:"damage",scaling:"melee",display:"Twain's Arc Damage",parts:[{name:"Twain's Arc Damage",type:"damage",multipliers:[200,0,0,0,0,0]}]}]},{display_name:"Fierce Stomp",desc:"When using Escape, hold shift to quickly drop down and deal damage.",archetype:"Boltslinger",archetype_req:0,parents:["Refined Gunpowder","Traveler"],dependencies:[],blockers:[],cost:2,display:{row:26,col:1},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[100,0,0,0,0,0]},{type:"add_spell_prop",base_spell:2,target_part:"Total Damage",cost:0,hits:{"Fierce Stomp":1}}]},{display_name:"Scorched Earth",desc:"Fire Creep become much stronger.",archetype:"Sharpshooter",archetype_req:0,parents:["Twain's Arc"],dependencies:["Fire Creep"],blockers:[],cost:1,display:{row:26,col:5},properties:{duration:2,aoe:.4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fire Creep",cost:0,multipliers:[10,0,0,0,5,0]}]},{display_name:"Leap",desc:"When you double tap jump, leap foward. (2s Cooldown)",archetype:"Boltslinger",archetype_req:5,parents:["Refined Gunpowder","Homing Shots"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0},properties:{cooldown:2},effects:[]},{display_name:"Shocking Bomb",desc:"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.",archetype:"Sharpshooter",archetype_req:5,parents:["Twain's Arc","Better Arrow Shield","Homing Shots"],dependencies:["Arrow Bomb"],blockers:[],cost:2,display:{row:28,col:4},properties:{gravity:0},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"}]},{display_name:"Mana Trap",desc:"Your Traps will give you 4 Mana per second when you stay close to them.",archetype:"Trapper",archetype_req:5,parents:["More Traps","Better Arrow Shield"],dependencies:["Fire Creep"],blockers:[],cost:2,display:{row:28,col:8},properties:{range:12,manaRegen:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:10,multipliers:[0,0,0,0,0,0]}]},{display_name:"Escape Artist",desc:"When casting Escape, release 100 arrows towards the ground.",archetype:"Boltslinger",archetype_req:0,parents:["Better Guardian Angels","Leap"],dependencies:[],blockers:["Grappling Hook"],cost:2,display:{row:31,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Escape Artist",cost:0,multipliers:[30,0,10,0,0,0]}]},{display_name:"Initiator",desc:"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.",archetype:"Sharpshooter",archetype_req:5,parents:["Shocking Bomb","Better Arrow Shield","Cheaper Arrow Storm (2)"],dependencies:["Focus"],blockers:[],cost:2,display:{row:31,col:5},properties:{focus:1,timer:5},type:"stat_bonus",bonuses:[{type:"stat",name:"damPct",value:50}]},{display_name:"Call of the Hound",desc:"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.",archetype:"Trapper",archetype_req:0,parents:["Initiator","Cheaper Arrow Storm (2)"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:32,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Call of the Hound",cost:0,multipliers:[40,0,0,0,0,0]}]},{display_name:"Arrow Hurricane",desc:"Arrow Storm will shoot +2 stream of arrows.",archetype:"Boltslinger",archetype_req:8,parents:["Precise Shot","Escape Artist"],dependencies:[],blockers:["Phantom Ray"],cost:2,display:{row:33,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Stream":2}}]},{display_name:"Geyser Stomp",desc:"Fierce Stomp will create geysers, dealing more damage and vertical knockback.",archetype:"",archetype_req:0,parents:["Shrapnel Bomb"],dependencies:["Fierce Stomp"],blockers:[],cost:2,display:{row:37,col:1},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Fierce Stomp",cost:0,multipliers:[0,0,0,50,0,0]}]},{display_name:"Crepuscular Ray",desc:"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.",archetype:"Sharpshooter",archetype_req:10,parents:["Cheaper Arrow Shield"],dependencies:["Arrow Storm"],blockers:[],cost:2,display:{row:37,col:4},properties:{focusReq:5,focusRegen:-1},effects:[{type:"replace_spell",name:"Crepuscular Ray",base_spell:5,spell_type:"damage",scaling:"spell",display:"One Focus",cost:0,parts:[{name:"Single Arrow",type:"damage",multipliers:[10,0,0,5,0,0]},{name:"One Focus",type:"total",hits:{"Single Arrow":20}},{name:"Total Damage",type:"total",hits:{"One Focus":7}}]}]},{display_name:"Grape Bomb",desc:"Arrow bomb will throw 3 additional smaller bombs when exploding.",archetype:"",archetype_req:0,parents:["Cheaper Escape (2)"],dependencies:[],blockers:[],cost:2,display:{row:37,col:7},properties:{miniBombs:3,aoe:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Grape Bomb",cost:0,multipliers:[30,0,0,0,10,0]}]},{display_name:"Tangled Traps",desc:"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:38,col:6},properties:{attackSpeed:.2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Tangled Traps",cost:0,multipliers:[20,0,0,0,0,20]}]},{display_name:"Snow Storm",desc:"Enemies near you will be slowed down.",archetype:"",archetype_req:0,parents:["Geyser Stomp","More Focus (2)"],dependencies:[],blockers:[],cost:2,display:{row:39,col:2},properties:{range:2.5,slowness:.3}},{display_name:"All-Seeing Panoptes",desc:"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.",archetype:"Boltslinger",archetype_req:11,parents:["Snow Storm"],dependencies:["Guardian Angels"],blockers:[],cost:2,display:{row:40,col:1},properties:{range:10,shots:5},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Arrow",cost:0,multipliers:[0,0,0,0,20,0]},{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":5}}]},{display_name:"Minefield",desc:"Allow you to place +6 Traps, but with reduced damage and range.",archetype:"Trapper",archetype_req:10,parents:["Grape Bomb","Cheaper Arrow Bomb (2)"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:40,col:7},properties:{aoe:-2,traps:6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Basaltic Trap",cost:0,multipliers:[-80,0,0,0,0,0]}]},{display_name:"Bow Proficiency I",desc:"Improve your Main Attack's damage and range when using a bow.",archetype:"",archetype_req:0,parents:["Arrow Bomb"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Arrow Bomb",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Bow Proficiency I"],dependencies:[],blockers:[],cost:1,display:{row:2,col:6},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-10}]},{display_name:"Cheaper Arrow Storm",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Grappling Hook","Windstorm","Focus"],dependencies:[],blockers:[],cost:1,display:{row:21,col:3},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper Escape",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Arrow Storm","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:9,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Earth Mastery",desc:"Increases your base damage from all Earth attacks",archetype:"Trapper",archetype_req:0,parents:["Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:8},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases your base damage from all Thunder attacks",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Storm","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:13,col:2},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases your base damage from all Water attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Escape","Thunder Mastery","Fire Mastery"],dependencies:[],blockers:[],cost:1,display:{row:14,col:4},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["Arrow Storm"],dependencies:[],blockers:[],cost:1,display:{row:13,col:0},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Sharpshooter",archetype_req:0,parents:["Thunder Mastery","Arrow Shield"],dependencies:[],blockers:[],cost:1,display:{row:13,col:6},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"More Shields",desc:"Give +2 charges to Arrow Shield.",archetype:"",archetype_req:0,parents:["Grappling Hook","Basaltic Trap"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:21,col:7},properties:{shieldCharges:2}},{display_name:"Stormy Feet",desc:"Windy Feet will last longer and add more speed.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:["Windy Feet"],blockers:[],cost:1,display:{row:23,col:1},properties:{duration:60},effects:[{type:"stat_bonus",bonuses:[{type:"stat",name:"spdPct",value:20}]}]},{display_name:"Refined Gunpowder",desc:"Increase the damage of Arrow Bomb.",archetype:"",archetype_req:0,parents:["Windstorm"],dependencies:[],blockers:[],cost:1,display:{row:25,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Bomb",cost:0,multipliers:[50,0,0,0,0,0]}]},{display_name:"More Traps",desc:"Increase the maximum amount of active Traps you can have by +2.",archetype:"Trapper",archetype_req:10,parents:["Bouncing Bomb"],dependencies:["Basaltic Trap"],blockers:[],cost:1,display:{row:26,col:8},properties:{traps:2}},{display_name:"Better Arrow Shield",desc:"Arrow Shield will gain additional area of effect, knockback and damage.",archetype:"Sharpshooter",archetype_req:0,parents:["Mana Trap","Shocking Bomb","Twain's Arc"],dependencies:["Arrow Shield"],blockers:[],cost:1,display:{row:28,col:6},properties:{aoe:1},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Arrow Shield",multipliers:[40,0,0,0,0,0]}]},{display_name:"Better Leap",desc:"Reduce leap's cooldown by 1s.",archetype:"Boltslinger",archetype_req:0,parents:["Leap","Homing Shots"],dependencies:["Leap"],blockers:[],cost:1,display:{row:29,col:1},properties:{cooldown:-1}},{display_name:"Better Guardian Angels",desc:"Your Guardian Angels can shoot +4 arrows before disappearing.",archetype:"Boltslinger",archetype_req:0,parents:["Escape Artist","Homing Shots"],dependencies:["Guardian Angels"],blockers:[],cost:1,display:{row:31,col:2},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Single Bow",cost:0,hits:{"Single Arrow":4}}]},{display_name:"Cheaper Arrow Storm (2)",desc:"Reduce the Mana cost of Arrow Storm.",archetype:"",archetype_req:0,parents:["Initiator","Mana Trap"],dependencies:[],blockers:[],cost:1,display:{row:31,col:8},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Precise Shot",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Better Guardian Angels","Cheaper Arrow Shield","Arrow Hurricane"],dependencies:[],blockers:[],cost:1,display:{row:33,col:2},properties:{mainAtk_range:6},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdCritPct",value:30}]}]},{display_name:"Cheaper Arrow Shield",desc:"Reduce the Mana cost of Arrow Shield.",archetype:"",archetype_req:0,parents:["Precise Shot","Initiator"],dependencies:[],blockers:[],cost:1,display:{row:33,col:4},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Rocket Jump",desc:"Arrow Bomb's self-damage will knockback you farther away.",archetype:"",archetype_req:0,parents:["Cheaper Arrow Storm (2)","Initiator"],dependencies:["Arrow Bomb"],blockers:[],cost:1,display:{row:33,col:6},properties:{}},{display_name:"Cheaper Escape (2)",desc:"Reduce the Mana cost of Escape.",archetype:"",archetype_req:0,parents:["Call of the Hound","Decimator"],dependencies:[],blockers:[],cost:1,display:{row:34,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"Stronger Hook",desc:"Increase your Grappling Hook's range, speed and strength.",archetype:"Trapper",archetype_req:5,parents:["Cheaper Escape (2)"],dependencies:["Grappling Hook"],blockers:[],cost:1,display:{row:35,col:8},properties:{range:8}},{display_name:"Cheaper Arrow Bomb (2)",desc:"Reduce the Mana cost of Arrow Bomb.",archetype:"",archetype_req:0,parents:["More Focus (2)","Minefield"],dependencies:[],blockers:[],cost:1,display:{row:40,col:5},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Bouncing Bomb",desc:"Arrow Bomb will bounce once when hitting a block or enemy",archetype:"",archetype_req:0,parents:["More Shields"],dependencies:[],blockers:[],cost:2,display:{row:25,col:7},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Arrow Bomb":2}}]},{display_name:"Homing Shots",desc:"Your Main Attack arrows will follow nearby enemies and not be affected by gravity",archetype:"",archetype_req:0,parents:["Leap","Shocking Bomb"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2},properties:{},effects:[]},{display_name:"Shrapnel Bomb",desc:"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area",archetype:"Boltslinger",archetype_req:8,parents:["Arrow Hurricane","Precise Shot"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Shrapnel Bomb",cost:0,multipliers:[40,0,0,0,20,0]}]},{display_name:"Elusive",desc:"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)",archetype:"Boltslinger",archetype_req:0,parents:["Geyser Stomp"],dependencies:[],blockers:[],cost:2,display:{row:38,col:0},properties:{},effects:[]},{display_name:"Double Shots",desc:"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)",archetype:"Boltslinger",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Power Shots"],cost:1,display:{row:7,col:2},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Triple Shots",desc:"Triple Main Attack arrows, but they deal -20% damage per arrow",archetype:"Boltslinger",archetype_req:0,parents:["Arrow Rain","Frenzy"],dependencies:["Double Shots"],blockers:[],cost:1,display:{row:17,col:0},properties:{arrow:2},effects:[{type:"add_spell_prop",base_spell:0,target_part:"Melee Damage",cost:0,multipliers:.7}]},{display_name:"Power Shots",desc:"Main Attack arrows have increased speed and knockback",archetype:"Sharpshooter",archetype_req:0,parents:["Escape"],dependencies:[],blockers:["Double Shots"],cost:1,display:{row:7,col:6},properties:{},effects:[]},{display_name:"Focus",desc:"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once",archetype:"Sharpshooter",archetype_req:2,parents:["Phantom Ray"],dependencies:[],blockers:[],cost:2,display:{row:19,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:3}]},{display_name:"More Focus",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Storm","Grappling Hook"],dependencies:[],blockers:[],cost:1,display:{row:22,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:5}]},{display_name:"More Focus (2)",desc:"Add +2 max Focus",archetype:"Sharpshooter",archetype_req:0,parents:["Crepuscular Ray","Snow Storm"],dependencies:[],blockers:[],cost:1,display:{row:39,col:4},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Focus",output:{type:"stat",abil_name:"Focus",name:"dmgPct"},scaling:[35],max:7}]},{display_name:"Traveler",desc:"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)",archetype:"",archetype_req:0,parents:["Refined Gunpowder","Twain's Arc"],dependencies:[],blockers:[],cost:1,display:{row:25,col:2},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"spd"}],output:{type:"stat",name:"sdRaw"},scaling:[1],max:100}]},{display_name:"Patient Hunter",desc:"Your Traps will deal +20% more damage for every second they are active (Max +80%)",archetype:"Trapper",archetype_req:0,parents:["More Shields"],dependencies:["Basaltic Trap"],blockers:[],cost:2,display:{row:22,col:8},properties:{max:80},effects:[]},{display_name:"Stronger Patient Hunter",desc:"Add +80% Max Damage to Patient Hunter",archetype:"Trapper",archetype_req:0,parents:["Grape Bomb"],dependencies:["Patient Hunter"],blockers:[],cost:1,display:{row:38,col:8},properties:{max:80},effects:[]},{display_name:"Frenzy",desc:"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second",archetype:"Boltslinger",archetype_req:0,parents:["Triple Shots","Nimble String"],dependencies:[],blockers:[],cost:2,display:{row:17,col:2},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"spd"},scaling:[6],max:200}]},{display_name:"Phantom Ray",desc:"Condense Arrow Storm into a single ray that damages enemies 10 times per second",archetype:"Sharpshooter",archetype_req:0,parents:["Water Mastery","Fire Creep"],dependencies:["Arrow Storm"],blockers:["Windstorm","Nimble String","Arrow Hurricane"],cost:2,display:{row:16,col:4},properties:{},effects:[{type:"replace_spell",name:"Phantom Ray",cost:40,display_text:"Max Damage",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Arrow",type:"damage",multipliers:[25,0,5,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Arrow":16}}]}]},{display_name:"Arrow Rain",desc:"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies",archetype:"Trapper",archetype_req:0,parents:["Nimble String","Air Mastery"],dependencies:["Arrow Shield"],blockers:[],cost:2,display:{row:15,col:0},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Arrow Rain",cost:0,multipliers:[120,0,0,0,0,80]}]},{display_name:"Decimator",desc:"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)",archetype:"Sharpshooter",archetype_req:0,parents:["Cheaper Arrow Shield"],dependencies:["Phantom Ray"],blockers:[],cost:1,display:{row:34,col:5},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Phantom Ray hits",output:{type:"stat",name:"PhRayDmg"},scaling:10,max:50}]}],Warrior:[{display_name:"Bash",desc:"Violently bash the ground, dealing high damage in a large area",archetype:"",archetype_req:0,parents:[],dependencies:[],blockers:[],cost:1,display:{row:0,col:4,icon:"node_4"},properties:{aoe:4,range:3},effects:[{type:"replace_spell",name:"Bash",cost:45,display_text:"Total Damage Average",base_spell:1,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"Single Hit",type:"damage",multipliers:[130,20,0,0,0,0]},{name:"Total Damage",type:"total",hits:{"Single Hit":1}}]}]},{display_name:"Spear Proficiency 1",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bash"],dependencies:[],blockers:[],cost:1,display:{row:2,col:4,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Bash",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:2,col:2,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-10}]},{display_name:"Double Bash",desc:"Bash will hit a second time at a farther range",archetype:"",archetype_req:0,parents:["Spear Proficiency 1"],dependencies:[],blockers:[],cost:1,display:{row:4,col:4,icon:"node_1"},properties:{range:3},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{name:"Single Hit",value:1}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-50,0,0,0,0,0]}]},{display_name:"Charge",desc:"Charge forward at high speed (hold shift to cancel)",archetype:"",archetype_req:0,parents:["Double Bash"],dependencies:[],blockers:[],cost:1,display:{row:6,col:4,icon:"node_4"},properties:{},effects:[{type:"replace_spell",name:"Charge",cost:25,display_text:"Total Damage Average",base_spell:2,spell_type:"damage",scaling:"spell",display:"Total Damage",parts:[{name:"None",type:"damage",multipliers:[0,0,0,0,0,0]},{name:"Total Damage",type:"total",hits:{None:0}}]}]},{display_name:"Heavy Impact",desc:"After using Charge, violently crash down into the ground and deal damage",archetype:"",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:9,col:1,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Heavy Impact",cost:0,multipliers:[100,0,0,0,0,0]}]},{display_name:"Vehement",desc:"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)",archetype:"Fallen",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Tougher Skin"],cost:1,display:{row:6,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"mdPct"},{type:"stat",name:"mdRaw"}],output:{type:"stat",name:"spd"},scaling:[1,1],max:20}]},{display_name:"Tougher Skin",desc:"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)",archetype:"Paladin",archetype_req:0,parents:["Charge"],dependencies:[],blockers:["Vehement"],cost:1,display:{row:6,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:"5"}]},{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hprRaw"},{type:"stat",name:"hprPct"}],output:{type:"stat",name:"hpBonus"},scaling:[10,10],max:100}]},{display_name:"Uppercut",desc:"Rocket enemies in the air and deal massive damage",archetype:"",archetype_req:0,parents:["Vehement"],dependencies:[],blockers:[],cost:1,display:{row:8,col:2,icon:"node_4"},properties:{aoe:3,range:5},effects:[{type:"replace_spell",name:"Uppercut",cost:45,display_text:"Total Damage Average",base_spell:3,spell_type:"damage",scaling:"spell",display:"total",parts:[{name:"Uppercut",type:"damage",multipliers:[150,50,50,0,0,0]},{name:"Total Damage",type:"total",hits:{Uppercut:1}}]}]},{display_name:"Cheaper Charge",desc:"Reduce the Mana cost of Charge",archetype:"",archetype_req:0,parents:["Uppercut","War Scream"],dependencies:[],blockers:[],cost:1,display:{row:8,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5}]},{display_name:"War Scream",desc:"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies",archetype:"",archetype_req:0,parents:["Tougher Skin"],dependencies:[],blockers:[],cost:1,display:{row:8,col:6,icon:"node_4"},properties:{duration:30,aoe:12,defense_bonus:10},effects:[{type:"replace_spell",name:"War Scream",cost:35,display_text:"War Scream",base_spell:4,spell_type:"damage",scaling:"spell",display:"Total Damage Average",parts:[{name:"War Scream",type:"damage",multipliers:[50,0,0,0,50,0]}]}]},{display_name:"Earth Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:10,col:0,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"eDamPct",value:20},{type:"stat",name:"eDam",value:[2,4]}]}]},{display_name:"Thunder Mastery",desc:"Increases base damage from all Thunder attacks",archetype:"Fallen",archetype_req:0,parents:["Uppercut","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"tDamPct",value:10},{type:"stat",name:"tDam",value:[1,8]}]}]},{display_name:"Water Mastery",desc:"Increases base damage from all Water attacks",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Charge","Thunder Mastery","Air Mastery"],dependencies:[],blockers:[],cost:1,display:{row:11,col:4,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"wDamPct",value:15},{type:"stat",name:"wDam",value:[2,4]}]}]},{display_name:"Air Mastery",desc:"Increases base damage from all Air attacks",archetype:"Battle Monk",archetype_req:0,parents:["War Scream","Thunder Mastery"],dependencies:[],blockers:[],cost:1,display:{row:10,col:6,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"aDamPct",value:15},{type:"stat",name:"aDam",value:[3,4]}]}]},{display_name:"Fire Mastery",desc:"Increases base damage from all Earth attacks",archetype:"Paladin",archetype_req:0,parents:["War Scream"],dependencies:[],blockers:[],cost:1,display:{row:10,col:8,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"fDamPct",value:15},{type:"stat",name:"fDam",value:[3,5]}]}]},{display_name:"Quadruple Bash",desc:"Bash will hit 4 times at an even larger range",archetype:"Fallen",archetype_req:0,parents:["Earth Mastery","Fireworks"],dependencies:[],blockers:[],cost:2,display:{row:12,col:0,icon:"node_1"},properties:{range:6},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Total Damage",cost:0,hits:{"Single Hit":2}},{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[-20,0,0,0,0,0]}]},{display_name:"Fireworks",desc:"Mobs hit by Uppercut will explode mid-air and receive additional damage",archetype:"Fallen",archetype_req:0,parents:["Thunder Mastery","Quadruple Bash"],dependencies:[],blockers:[],cost:2,display:{row:12,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Fireworks",cost:0,multipliers:[80,0,20,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Fireworks:1}}]},{display_name:"Half-Moon Swipe",desc:"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water",archetype:"Battle Monk",archetype_req:1,parents:["Water Mastery"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:13,col:4,icon:"node_1"},properties:{range:4},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:-10,multipliers:[-70,0,0,0,0,0]},{type:"convert_spell_conv",target_part:"all",conversion:"water"}]},{display_name:"Flyby Jab",desc:"Damage enemies in your way when using Charge",archetype:"",archetype_req:0,parents:["Air Mastery","Flaming Uppercut"],dependencies:[],blockers:[],cost:2,display:{row:12,col:6,icon:"node_1"},properties:{aoe:2},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flyby Jab",cost:0,multipliers:[20,0,0,0,0,40]}]},{display_name:"Flaming Uppercut",desc:"Uppercut will light mobs on fire, dealing damage every 0.6 seconds",archetype:"Paladin",archetype_req:0,parents:["Fire Mastery","Flyby Jab"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:12,col:8,icon:"node_1"},properties:{duration:3,tick:.6},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut",cost:0,multipliers:[0,0,0,0,50,0]},{type:"add_spell_prop",base_spell:3,target_part:"Flaming Uppercut Total Damage",cost:0,hits:{"Flaming Uppercut":5}},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{"Flaming Uppercut":5}}]},{display_name:"Iron Lungs",desc:"War Scream deals more damage",archetype:"",archetype_req:0,parents:["Flyby Jab","Flaming Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:13,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"War Scream",cost:0,multipliers:[30,0,0,0,0,30]}]},{display_name:"Generalist",desc:"After casting 3 different spells in a row, your next spell will cost 5 mana",archetype:"Battle Monk",archetype_req:3,parents:["Counter"],dependencies:[],blockers:[],cost:2,display:{row:15,col:2,icon:"node_3"},properties:{},effects:[]},{display_name:"Counter",desc:"When dodging a nearby enemy attack, get 30% chance to instantly attack back",archetype:"Battle Monk",archetype_req:0,parents:["Half-Moon Swipe"],dependencies:[],blockers:[],cost:2,display:{row:15,col:4,icon:"node_1"},properties:{chance:30},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Counter",cost:0,multipliers:[60,0,20,0,0,20]}]},{display_name:"Mantle of the Bovemists",desc:"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)",archetype:"Paladin",archetype_req:3,parents:["Iron Lungs"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:15,col:7,icon:"node_3"},properties:{mantle_charge:3},effects:[]},{display_name:"Bak'al's Grasp",desc:"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)",archetype:"Fallen",archetype_req:2,parents:["Quadruple Bash","Fireworks"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:16,col:1,icon:"node_3"},properties:{cooldown:15},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[4],slider_step:2,max:120}]},{display_name:"Spear Proficiency 2",desc:"Improve your Main Attack's damage and range w/ spear",archetype:"",archetype_req:0,parents:["Bak'al's Grasp","Cheaper Uppercut"],dependencies:[],blockers:[],cost:1,display:{row:17,col:0,icon:"node_0"},properties:{melee_range:1},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"mdPct",value:5}]}]},{display_name:"Cheaper Uppercut",desc:"Reduce the Mana Cost of Uppercut",archetype:"",archetype_req:0,parents:["Spear Proficiency 2","Aerodynamics","Counter"],dependencies:[],blockers:[],cost:1,display:{row:17,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Aerodynamics",desc:"During Charge, you can steer and change direction",archetype:"Battle Monk",archetype_req:0,parents:["Cheaper Uppercut","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:17,col:5,icon:"node_1"},properties:{},effects:[]},{display_name:"Provoke",desc:"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream",archetype:"Paladin",archetype_req:0,parents:["Aerodynamics","Mantle of the Bovemists"],dependencies:[],blockers:[],cost:1,display:{row:17,col:7,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Precise Strikes",desc:"+30% Critical Hit Damage",archetype:"",archetype_req:0,parents:["Cheaper Uppercut","Spear Proficiency 2"],dependencies:[],blockers:[],cost:1,display:{row:18,col:2,icon:"node_0"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"critDmg",value:30}]}]},{display_name:"Air Shout",desc:"War Scream will fire a projectile that can go through walls and deal damage multiple times",archetype:"",archetype_req:0,parents:["Aerodynamics","Provoke"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:18,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Air Shout",cost:0,multipliers:[20,0,0,0,0,5]}]},{display_name:"Enraged Blow",desc:"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)",archetype:"Fallen",archetype_req:0,parents:["Spear Proficiency 2"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:20,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"dmgPct"},scaling:[2],max:200}]},{display_name:"Flying Kick",desc:"When using Charge, mobs hit will halt your momentum and get knocked back",archetype:"Battle Monk",archetype_req:1,parents:["Cheaper Uppercut","Stronger Mantle"],dependencies:[],blockers:[],cost:2,display:{row:20,col:3,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Flying Kick",cost:0,multipliers:[120,0,0,10,0,20]}]},{display_name:"Stronger Mantle",desc:"Add +2 additional charges to Mantle of the Bovemists",archetype:"Paladin",archetype_req:0,parents:["Manachism","Flying Kick"],dependencies:["Mantle of the Bovemists"],blockers:[],cost:1,display:{row:20,col:6,icon:"node_0"},properties:{mantle_charge:2},effects:[]},{display_name:"Manachism",desc:"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)",archetype:"Paladin",archetype_req:3,parents:["Stronger Mantle","Provoke"],dependencies:[],blockers:[],cost:2,display:{row:20,col:8,icon:"node_2"},properties:{cooldown:1},effects:[]},{display_name:"Boiling Blood",desc:"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds",archetype:"",archetype_req:0,parents:["Enraged Blow","Ragnarokkr"],dependencies:[],blockers:[],cost:2,display:{row:22,col:0,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Boiling Blood",cost:0,multipliers:[25,0,0,0,5,0]}]},{display_name:"Ragnarokkr",desc:"War Scream become deafening, increasing its range and giving damage bonus to players",archetype:"Fallen",archetype_req:0,parents:["Boiling Blood","Flying Kick"],dependencies:["War Scream"],blockers:[],cost:2,display:{row:22,col:2,icon:"node_2"},properties:{damage_bonus:30,aoe:2},effects:[{type:"add_spell_prop",base_spell:4,cost:10}]},{display_name:"Ambidextrous",desc:"Increase your chance to attack with Counter by +30%",archetype:"",archetype_req:0,parents:["Flying Kick","Stronger Mantle","Burning Heart"],dependencies:["Counter"],blockers:[],cost:1,display:{row:22,col:4,icon:"node_0"},properties:{chance:30},effects:[]},{display_name:"Burning Heart",desc:"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)",archetype:"Paladin",archetype_req:0,parents:["Ambidextrous","Stronger Bash"],dependencies:[],blockers:[],cost:1,display:{row:22,col:6,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",slider:!1,inputs:[{type:"stat",name:"hpBonus"}],output:{type:"stat",name:"fDamPct"},scaling:[2],max:100,slider_step:100}]},{display_name:"Stronger Bash",desc:"Increase the damage of Bash",archetype:"",archetype_req:0,parents:["Burning Heart","Manachism"],dependencies:[],blockers:[],cost:1,display:{row:22,col:8,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,target_part:"Single Hit",cost:0,multipliers:[30,0,0,0,0,0]}]},{display_name:"Intoxicating Blood",desc:"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted",archetype:"Fallen",archetype_req:5,parents:["Ragnarokkr","Boiling Blood"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:23,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Comet",desc:"After being hit by Fireworks, enemies will crash into the ground and receive more damage",archetype:"Fallen",archetype_req:0,parents:["Ragnarokkr"],dependencies:["Fireworks"],blockers:[],cost:2,display:{row:24,col:2,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Comet",cost:0,multipliers:[80,20,0,0,0,0]},{type:"add_spell_prop",base_spell:3,target_part:"Total Damage",cost:0,hits:{Comet:1}}]},{display_name:"Collide",desc:"Mobs thrown into walls from Flying Kick will explode and receive additonal damage",archetype:"Battle Monk",archetype_req:4,parents:["Ambidextrous","Burning Heart"],dependencies:["Flying Kick"],blockers:[],cost:2,display:{row:23,col:5,icon:"node_1"},properties:{aoe:4},effects:[{type:"add_spell_prop",base_spell:2,target_part:"Collide",cost:0,multipliers:[100,0,0,0,50,0]}]},{display_name:"Rejuvenating Skin",desc:"Regain back 30% of the damage you take as healing over 30s",archetype:"Paladin",archetype_req:0,parents:["Burning Heart","Stronger Bash"],dependencies:[],blockers:[],cost:2,display:{row:23,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Uncontainable Corruption",desc:"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1",archetype:"",archetype_req:0,parents:["Boiling Blood","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:1,display:{row:26,col:0,icon:"node_0"},properties:{cooldown:-5},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"raw"},scaling:[1],slider_step:2,max:50}]},{display_name:"Radiant Devotee",desc:"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)",archetype:"Battle Monk",archetype_req:1,parents:["Whirlwind Strike","Uncontainable Corruption"],dependencies:[],blockers:[],cost:1,display:{row:26,col:2,icon:"node_0"},properties:{},effects:[{type:"stat_scaling",inputs:[{type:"stat",name:"ref"}],output:{type:"stat",name:"mr"},scaling:[1],max:10,slider_step:4}]},{display_name:"Whirlwind Strike",desc:"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)",archetype:"Battle Monk",archetype_req:5,parents:["Ambidextrous","Radiant Devotee"],dependencies:["Uppercut"],blockers:[],cost:2,display:{row:26,col:4,icon:"node_1"},properties:{range:2},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:0,multipliers:[0,0,0,0,0,50]}]},{display_name:"Mythril Skin",desc:"Gain +5% Base Resistance and become immune to knockback",archetype:"Paladin",archetype_req:6,parents:["Rejuvenating Skin"],dependencies:[],blockers:[],cost:2,display:{row:26,col:7,icon:"node_1"},properties:{},effects:[{type:"raw_stat",bonuses:[{type:"stat",name:"baseResist",value:5}]}]},{display_name:"Armour Breaker",desc:"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage",archetype:"Fallen",archetype_req:0,parents:["Uncontainable Corruption","Radiant Devotee"],dependencies:["Bak'al's Grasp"],blockers:[],cost:2,display:{row:27,col:1,icon:"node_2"},properties:{duration:5},effects:[]},{display_name:"Shield Strike",desc:"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin","Sparkling Hope"],dependencies:[],blockers:[],cost:2,display:{row:27,col:6,icon:"node_1"},properties:{},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Shield Strike",cost:0,multipliers:[60,0,20,0,0,0]}]},{display_name:"Sparkling Hope",desc:"Everytime you heal 5% of your max health, deal damage to all nearby enemies",archetype:"Paladin",archetype_req:0,parents:["Mythril Skin"],dependencies:[],blockers:[],cost:2,display:{row:27,col:8,icon:"node_2"},properties:{aoe:6},effects:[{type:"add_spell_prop",base_spell:5,target_part:"Sparkling Hope",cost:0,multipliers:[10,0,5,0,0,0]}]},{display_name:"Massive Bash",desc:"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)",archetype:"Fallen",archetype_req:8,parents:["Tempest","Uncontainable Corruption"],dependencies:[],blockers:[],cost:2,display:{row:28,col:0,icon:"node_2"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Corrupted",output:{type:"stat",name:"bashAoE"},scaling:[1],max:10,slider_step:3}]},{display_name:"Tempest",desc:"War Scream will ripple the ground and deal damage 3 times in a large area",archetype:"Battle Monk",archetype_req:0,parents:["Massive Bash","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:2,display:{row:28,col:2,icon:"node_1"},properties:{aoe:16},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Tempest",cost:"0",multipliers:[30,10,0,0,0,10]},{type:"add_spell_prop",base_spell:4,target_part:"Tempest Total Damage",cost:"0",hits:{Tempest:3}},{type:"add_spell_prop",base_spell:4,target_part:"Total Damage",cost:"0",hits:{Tempest:3}}]},{display_name:"Spirit of the Rabbit",desc:"Reduce the Mana cost of Charge and increase your Walk Speed by +20%",archetype:"Battle Monk",archetype_req:5,parents:["Tempest","Whirlwind Strike"],dependencies:[],blockers:[],cost:1,display:{row:28,col:4,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:2,cost:-5},{type:"raw_stat",bonuses:[{type:"stat",name:"spd",value:20}]}]},{display_name:"Massacre",desc:"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar",archetype:"Fallen",archetype_req:5,parents:["Tempest","Massive Bash"],dependencies:[],blockers:[],cost:2,display:{row:29,col:1,icon:"node_1"},properties:{},effects:[]},{display_name:"Axe Kick",desc:"Increase the damage of Uppercut, but also increase its mana cost",archetype:"",archetype_req:0,parents:["Tempest","Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:29,col:3,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,target_part:"Uppercut",cost:10,multipliers:[100,0,0,0,0,0]}]},{display_name:"Radiance",desc:"Bash will buff your allies' positive IDs. (15s Cooldown)",archetype:"Paladin",archetype_req:2,parents:["Spirit of the Rabbit","Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:29,col:5,icon:"node_2"},properties:{cooldown:15},effects:[]},{display_name:"Cheaper Bash 2",desc:"Reduce the Mana cost of Bash",archetype:"",archetype_req:0,parents:["Radiance","Shield Strike","Sparkling Hope"],dependencies:[],blockers:[],cost:1,display:{row:29,col:7,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:1,cost:-5}]},{display_name:"Cheaper War Scream",desc:"Reduce the Mana cost of War Scream",archetype:"",archetype_req:0,parents:["Massive Bash"],dependencies:[],blockers:[],cost:1,display:{row:31,col:0,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:4,cost:-5}]},{display_name:"Discombobulate",desc:"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second",archetype:"Battle Monk",archetype_req:12,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:31,col:2,icon:"node_3"},properties:{},effects:[{type:"stat_scaling",slider:!0,slider_name:"Hits dealt",output:{type:"stat",name:"rainrawButDifferent"},scaling:[2],max:50}]},{display_name:"Thunderclap",desc:"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder",archetype:"Battle Monk",archetype_req:8,parents:["Cyclone"],dependencies:[],blockers:[],cost:2,display:{row:32,col:5,icon:"node_1"},properties:{},effects:[{type:"convert_spell_conv",target_part:"all",conversion:"thunder"},{type:"raw_stat",bonuses:[{type:"prop",abil_name:"Bash",name:"aoe",value:3}]}]},{display_name:"Cyclone",desc:"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s",archetype:"Battle Monk",archetype_req:0,parents:["Spirit of the Rabbit"],dependencies:[],blockers:[],cost:1,display:{row:31,col:4,icon:"node_1"},properties:{aoe:4,duration:20},effects:[{type:"add_spell_prop",base_spell:4,target_part:"Cyclone",cost:0,multipliers:[10,0,0,0,5,10]},{type:"add_spell_prop",base_spell:4,target_part:"Cyclone Total Damage",cost:0,hits:{Cyclone:40}}]},{display_name:"Second Chance",desc:"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)",archetype:"Paladin",archetype_req:12,parents:["Cheaper Bash 2"],dependencies:[],blockers:[],cost:2,display:{row:32,col:7,icon:"node_3"},properties:{},effects:[]},{display_name:"Blood Pact",desc:"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)",archetype:"",archetype_req:10,parents:["Cheaper War Scream"],dependencies:[],blockers:[],cost:2,display:{row:34,col:1,icon:"node_3"},properties:{},effects:[]},{display_name:"Haemorrhage",desc:"Reduce Blood Pact's health cost. (0.5% health per mana)",archetype:"Fallen",archetype_req:0,parents:["Blood Pact"],dependencies:["Blood Pact"],blockers:[],cost:1,display:{row:35,col:2,icon:"node_1"},properties:{},effects:[]},{display_name:"Brink of Madness",desc:"If your health is 25% full or less, gain +40% Resistance",archetype:"",archetype_req:0,parents:["Blood Pact","Cheaper Uppercut 2"],dependencies:[],blockers:[],cost:2,display:{row:35,col:4,icon:"node_2"},properties:{},effects:[]},{display_name:"Cheaper Uppercut 2",desc:"Reduce the Mana cost of Uppercut",archetype:"",archetype_req:0,parents:["Second Chance","Brink of Madness"],dependencies:[],blockers:[],cost:1,display:{row:35,col:6,icon:"node_0"},properties:{},effects:[{type:"add_spell_prop",base_spell:3,cost:-5}]},{display_name:"Martyr",desc:"When you receive a fatal blow, all nearby allies become invincible",archetype:"Paladin",archetype_req:0,parents:["Second Chance"],dependencies:[],blockers:[],cost:2,display:{row:35,col:8,icon:"node_1"},properties:{duration:3,aoe:12},effects:[]}]},atree_example=[{title:"skill",desc:"desc",image:"../media/atree/node.png",connector:!1,row:5,col:3},{image:"../media/atree/connect_angle.png",connector:!0,rotate:270,row:4,col:3},{title:"skill2",desc:"desc",image:"../media/atree/node.png",connector:!1,row:0,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:1,col:2},{title:"skill3",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:2},{image:"../media/atree/connect_line.png",connector:!0,rotate:90,row:2,col:3},{title:"skill4",desc:"desc",image:"../media/atree/node.png",connector:!1,row:2,col:4},{image:"../media/atree/connect_line.png",connector:!0,rotate:0,row:3,col:2},{title:"skill5",desc:"desc",image:"../media/atree/node.png",connector:!1,row:4,col:2},] \ No newline at end of file From 2db1a3a336af5bfa6a01fbf8dea149050f9180bd Mon Sep 17 00:00:00 2001 From: ferricles Date: Mon, 27 Jun 2022 16:49:21 -0700 Subject: [PATCH 121/155] quick documentation + refactoring to add new param for render_AT() --- js/atree.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/js/atree.js b/js/atree.js index 47e80b3..63c75be 100644 --- a/js/atree.js +++ b/js/atree.js @@ -61,7 +61,7 @@ const atree_render = new (class extends ComputeNode { } //for some reason we have to cast to string - if (atree) { render_AT(document.getElementById("atree-ui"), atree); } + if (atree) { render_AT(document.getElementById("atree-ui"), document.getElementById("atree-active"), atree); } if (document.getElementById("toggle-atree").classList.contains("toggleOn")) { toggle_tab('atree-dropdown'); @@ -101,10 +101,17 @@ function topological_sort_tree(tree, res, mark_state) { // } } -function render_AT(elem, tree) { + +/** The main function for rendering an ability tree. + * + * @param {Element} UI_elem - the DOM element to draw the atree within. + * @param {Element} list_elem - the DOM element to list selected abilities within. + * @param {*} tree - the ability tree to work with. + */ +function render_AT(UI_elem, list_elem, tree) { console.log("constructing ability tree UI"); - document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later - elem.innerHTML = ""; //reset the atree in the DOM + list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later + UI_elem.innerHTML = ""; //reset the atree in the DOM // add in the "Active" title to atree let active_row = document.createElement("div"); @@ -144,7 +151,7 @@ function render_AT(elem, tree) { active_row.appendChild(active_word); active_row.appendChild(active_AP_container); - document.getElementById("atree-active").appendChild(active_row); + list_elem.appendChild(active_row); let atree_map = new Map(); let atree_connectors_map = new Map() @@ -173,18 +180,17 @@ function render_AT(elem, tree) { let row = document.createElement('div'); row.classList.add("row"); row.id = "atree-row-" + j; - //was causing atree rows to be 0 height // TODO: do this more dynamically - row.style.minHeight = elem.scrollWidth / 9 + "px"; - //row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px"; + row.style.minHeight = UI_elem.scrollWidth / 9 + "px"; + //row.style.minHeight = UI_elem.getBoundingClientRect().width / 9 + "px"; for (let k = 0; k < 9; k++) { col = document.createElement('div'); col.classList.add('col', 'px-0'); - col.style.minHeight = elem.scrollWidth / 9 + "px"; + col.style.minHeight = UI_elem.scrollWidth / 9 + "px"; row.appendChild(col); } - elem.appendChild(row); + UI_elem.appendChild(row); } for (const _node of tree) { @@ -258,8 +264,7 @@ function render_AT(elem, tree) { let active_tooltip = document.createElement('div'); active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow"); - //was causing active element boxes to be 0 width - active_tooltip.style.maxWidth = elem.getBoundingClientRect().width * .80 + "px"; + active_tooltip.style.maxWidth = UI_elem.getBoundingClientRect().width * .80 + "px"; active_tooltip.style.display = "none"; // tooltip text formatting @@ -288,7 +293,7 @@ function render_AT(elem, tree) { node_tooltip.style.zIndex = "100"; node_elem.appendChild(node_tooltip); - document.getElementById("atree-active").appendChild(active_tooltip); + list_elem.appendChild(active_tooltip); node_elem.addEventListener('click', function(e) { if (e.target !== this && e.target!== this.children[0]) {return;} From e427532424e1184c900e2fd4e7d5767c006ac69e Mon Sep 17 00:00:00 2001 From: aspiepuppy Date: Mon, 27 Jun 2022 22:41:56 -0500 Subject: [PATCH 122/155] wa --- js/atree_constants.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index cbd42b1..00f0a5a 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -225,7 +225,7 @@ const atrees = { }, { "display_name": "Nimble String", - "desc": "Arrow Storm throw out +8 arrows per stream and shoot twice as fast.", + "desc": "Arrow Storm throw out +6 arrows per stream and shoot twice as fast.", "archetype": "", "archetype_req": 0, "parents": ["Thunder Mastery", "Arrow Rain"], @@ -253,14 +253,14 @@ const atrees = { "target_part": "Single Stream", "cost": 0, "hits": { - "Single Arrow": 8 + "Single Arrow": 6 } } ] }, { "display_name": "Arrow Storm", - "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", + "desc": "Shoot one stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", "archetype": "", "archetype_req": 0, "parents": ["Double Shots", "Cheaper Escape"], @@ -302,7 +302,7 @@ const atrees = { "name": "Total Damage", "type": "total", "hits": { - "Single Stream": 2 + "Single Stream": 1 } } ] @@ -436,7 +436,7 @@ const atrees = { "base_spell": 1, "target_part": "Single Arrow", "cost": 0, - "multipliers": [-11, 0, -7, 0, 0, 3] + "multipliers": [-10, 0, -2, 0, 0, 2] }, { "type": "add_spell_prop", @@ -444,7 +444,16 @@ const atrees = { "target_part": "Total Damage", "cost": 0, "hits": { - "Single Stream": 1 + "Single Stream": 1 + } + }, + { + "type": "add_spell_prop", + "base_spell": 1, + "target_part": "Single Stream", + "cost": 0, + "hits": { + "Single Arrow": 2 } } ] @@ -928,7 +937,7 @@ const atrees = { "col": 1 }, "properties": { - "range": 10, + "range": 8, "shots": 5 }, "effects": [ @@ -937,7 +946,7 @@ const atrees = { "base_spell": 4, "target_part": "Single Arrow", "cost": 0, - "multipliers": [0, 0, 0, 0, 20, 0] + "multipliers": [0, 0, 0, 0, 10, 0] }, { "type": "add_spell_prop", From 1a14f230f2e5fdbfc9d66bd7d5dea3ffe3e8dd62 Mon Sep 17 00:00:00 2001 From: aspiepuppy Date: Mon, 27 Jun 2022 22:42:19 -0500 Subject: [PATCH 123/155] wawa --- js/atree_constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/atree_constants.js b/js/atree_constants.js index 00f0a5a..dbdc33a 100644 --- a/js/atree_constants.js +++ b/js/atree_constants.js @@ -260,7 +260,7 @@ const atrees = { }, { "display_name": "Arrow Storm", - "desc": "Shoot one stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", + "desc": "Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.", "archetype": "", "archetype_req": 0, "parents": ["Double Shots", "Cheaper Escape"], From f23895ee48cdf4e703d856d5a855ceec7f0db0ab Mon Sep 17 00:00:00 2001 From: hppeng Date: Mon, 27 Jun 2022 22:16:23 -0700 Subject: [PATCH 124/155] Address PR comments --- js/damage_calc.js | 4 ++-- js/display.js | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/js/damage_calc.js b/js/damage_calc.js index 9a18fbd..95d5f92 100644 --- a/js/damage_calc.js +++ b/js/damage_calc.js @@ -240,7 +240,7 @@ spell_heal: { const default_spells = { wand: [{ - name: "Magic Strike", // TODO: name for melee attacks? + name: "Wand Melee", // TODO: name for melee attacks? display_text: "Mage basic attack", base_spell: 0, scaling: "melee", use_atkspd: false, @@ -282,7 +282,7 @@ const default_spells = { parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }] }], relik: [{ - name: "Spread Beam", // TODO: name for melee attacks? + name: "Relik Melee", // TODO: name for melee attacks? display_text: "Shaman basic attack", base_spell: 0, spell_type: "damage", diff --git a/js/display.js b/js/display.js index 6f01d41..c3e52e4 100644 --- a/js/display.js +++ b/js/display.js @@ -1640,18 +1640,18 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell } for (let i = 0; i < spell_results.length; ++i) { - const damage_info = spell_results[i]; + const spell_info = spell_results[i]; let part_div = document.createElement("p"); parent_elem.append(part_div); let subtitle_elem = document.createElement("p"); - subtitle_elem.textContent = damage_info.name + subtitle_elem.textContent = spell_info.name part_div.append(subtitle_elem); - if (damage_info.type === "damage") { - let totalDamNormal = damage_info.normal_total; - let totalDamCrit = damage_info.crit_total; + if (spell_info.type === "damage") { + let totalDamNormal = spell_info.normal_total; + let totalDamCrit = spell_info.crit_total; let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0; let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0; @@ -1663,8 +1663,8 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell part_div.append(averageLabel); - if (damage_info.name === spell.display) { - _summary(damage_info.name+ " Average: ", averageDamage, "Damage"); + if (spell_info.name === spell.display) { + _summary(spell_info.name+ " Average: ", averageDamage, "Damage"); } function _damage_display(label_text, average, dmg_min, dmg_max) { @@ -1681,16 +1681,16 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell } } } - _damage_display("Non-Crit Average: ", nonCritAverage, damage_info.normal_min, damage_info.normal_max); - _damage_display("Crit Average: ", critAverage, damage_info.crit_min, damage_info.crit_max); - } else if (damage_info.type === "heal") { - let heal_amount = damage_info.heal_amount; + _damage_display("Non-Crit Average: ", nonCritAverage, spell_info.normal_min, spell_info.normal_max); + _damage_display("Crit Average: ", critAverage, spell_info.crit_min, spell_info.crit_max); + } else if (spell_info.type === "heal") { + let heal_amount = spell_info.heal_amount; let healLabel = document.createElement("p"); healLabel.textContent = heal_amount; // healLabel.classList.add("damagep"); part_div.append(healLabel); - if (damage_info.name === spell.display) { - _summary(damage_info.name+ ": ", heal_amount, "Set"); + if (spell_info.name === spell.display) { + _summary(spell_info.name+ ": ", heal_amount, "Set"); } } } From c5951195fea1501f52fa57aea581bdc023bde9bc Mon Sep 17 00:00:00 2001 From: reschan Date: Tue, 28 Jun 2022 13:01:36 +0700 Subject: [PATCH 125/155] bump latest change --- js/atree_constants_min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/atree_constants_min.js b/js/atree_constants_min.js index 52d1082..f3825a2 100644 --- a/js/atree_constants_min.js +++ b/js/atree_constants_min.js @@ -1 +1 @@ -const atrees={"Archer":[{"display_name":"Arrow Shield","desc":"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)","archetype":"","archetype_req":0,"parents":[60,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":6},"properties":{"duration":60},"effects":[{"type":"replace_spell","name":"Arrow Shield","cost":30,"display_text":"Max Damage","base_spell":4,"spell_type":"damage","scaling":"spell","display":"","parts":[{"name":"Shield Damage","type":"damage","multipliers":[90,0,0,0,0,10]},{"name":"Total Damage","type":"total","hits":{"Shield Damage":2}}]}],"id":0},{"display_name":"Escape","desc":"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":7,"col":4},"properties":{"aoe":0,"range":0},"effects":[{"type":"replace_spell","name":"Escape","cost":25,"display_text":"Max Damage","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":1},{"display_name":"Arrow Bomb","desc":"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4},"properties":{"aoe":4.5,"range":26},"effects":[{"type":"replace_spell","name":"Arrow Bomb","cost":50,"display_text":"Average Damage","base_spell":3,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Arrow Bomb","type":"damage","multipliers":[160,0,0,0,20,0]},{"name":"Total Damage","type":"total","hits":{"Arrow Bomb":1}}]}],"id":2},{"display_name":"Heart Shatter","desc":"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[100,0,0,0,0,0]},{}],"id":3},{"display_name":"Fire Creep","desc":"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.","archetype":"","archetype_req":0,"parents":[68,39,5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":16,"col":6},"properties":{"aoe":0.8,"duration":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[30,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fire Creep":15}}],"id":4},{"display_name":"Bryophyte Roots","desc":"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.","archetype":"Trapper","archetype_req":1,"parents":[4,35],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":16,"col":8},"properties":{"aoe":2,"duration":5,"slowness":0.4},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Bryophyte Roots","cost":0,"multipliers":[40,20,0,0,0,0]}],"id":5},{"display_name":"Nimble String","desc":"Arrow Storm throw out +8 arrows per stream and shoot twice as fast.","archetype":"","archetype_req":0,"parents":[36,69],"dependencies":[7],"blockers":[68],"cost":2,"display":{"row":15,"col":2},"properties":{"shootspeed":2},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-15,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","cost":0,"hits":{"Single Arrow":8}}],"id":6},{"display_name":"Arrow Storm","desc":"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.","archetype":"","archetype_req":0,"parents":[58,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":2},"properties":{"aoe":0,"range":16},"effects":[{"type":"replace_spell","name":"Arrow Storm","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,10,0,0,0]},{"name":"Single Stream","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Stream":2}}]}],"id":7},{"display_name":"Guardian Angels","desc":"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)","archetype":"Boltslinger","archetype_req":3,"parents":[59,67],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":19,"col":1},"properties":{"range":4,"duration":60,"shots":8,"count":2},"effects":[{"type":"replace_spell","name":"Guardian Angels","cost":30,"display_text":"Total Damage Average","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,0,0,0,10]},{"name":"Single Bow","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Bow":2}}]}],"id":8},{"display_name":"Windy Feet","base_abil":"Escape","desc":"When casting Escape, give speed to yourself and nearby allies.","archetype":"","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":1},"properties":{"aoe":8,"duration":120},"type":"stat_bonus","bonuses":[{"type":"stat","name":"spd","value":20}],"id":9},{"display_name":"Basaltic Trap","desc":"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)","archetype":"Trapper","archetype_req":2,"parents":[5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":8},"properties":{"aoe":7,"traps":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[140,30,0,0,30,0]}],"id":10},{"display_name":"Windstorm","desc":"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.","archetype":"","archetype_req":0,"parents":[8,33],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":21,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-11,0,-7,0,0,3]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":1}}],"id":11},{"display_name":"Grappling Hook","base_abil":"Escape","desc":"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)","archetype":"Trapper","archetype_req":0,"parents":[61,40,33],"dependencies":[],"blockers":[20],"cost":2,"display":{"row":21,"col":5},"properties":{"range":20},"effects":[],"id":12},{"display_name":"Implosion","desc":"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.","archetype":"Trapper","archetype_req":0,"parents":[12,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":13},{"display_name":"Twain's Arc","desc":"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)","archetype":"Sharpshooter","archetype_req":4,"parents":[62,64],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":25,"col":4},"properties":{"range":64,"focusReq":2},"effects":[{"type":"replace_spell","name":"Twain's Arc","cost":0,"display_text":"Twain's Arc","base_spell":5,"spell_type":"damage","scaling":"melee","display":"Twain's Arc Damage","parts":[{"name":"Twain's Arc Damage","type":"damage","multipliers":[200,0,0,0,0,0]}]}],"id":14},{"display_name":"Fierce Stomp","desc":"When using Escape, hold shift to quickly drop down and deal damage.","archetype":"Boltslinger","archetype_req":0,"parents":[42,64],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":1},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[100,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":2,"target_part":"Total Damage","cost":0,"hits":{"Fierce Stomp":1}}],"id":15},{"display_name":"Scorched Earth","desc":"Fire Creep become much stronger.","archetype":"Sharpshooter","archetype_req":0,"parents":[14],"dependencies":[4],"blockers":[],"cost":1,"display":{"row":26,"col":5},"properties":{"duration":2,"aoe":0.4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[10,0,0,0,5,0]}],"id":16},{"display_name":"Leap","desc":"When you double tap jump, leap foward. (2s Cooldown)","archetype":"Boltslinger","archetype_req":5,"parents":[42,55],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0},"properties":{"cooldown":2},"effects":[],"id":17},{"display_name":"Shocking Bomb","desc":"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.","archetype":"Sharpshooter","archetype_req":5,"parents":[14,44,55],"dependencies":[2],"blockers":[],"cost":2,"display":{"row":28,"col":4},"properties":{"gravity":0},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"}],"id":18},{"display_name":"Mana Trap","desc":"Your Traps will give you 4 Mana per second when you stay close to them.","archetype":"Trapper","archetype_req":5,"parents":[43,44],"dependencies":[4],"blockers":[],"cost":2,"display":{"row":28,"col":8},"properties":{"range":12,"manaRegen":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":10,"multipliers":[0,0,0,0,0,0]}],"id":19},{"display_name":"Escape Artist","desc":"When casting Escape, release 100 arrows towards the ground.","archetype":"Boltslinger","archetype_req":0,"parents":[46,17],"dependencies":[],"blockers":[12],"cost":2,"display":{"row":31,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Escape Artist","cost":0,"multipliers":[30,0,10,0,0,0]}],"id":20},{"display_name":"Initiator","desc":"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.","archetype":"Sharpshooter","archetype_req":5,"parents":[18,44,47],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":31,"col":5},"properties":{"focus":1,"timer":5},"type":"stat_bonus","bonuses":[{"type":"stat","name":"damPct","value":50}],"id":21},{"display_name":"Call of the Hound","desc":"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.","archetype":"Trapper","archetype_req":0,"parents":[21,47],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":32,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Call of the Hound","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":22},{"display_name":"Arrow Hurricane","desc":"Arrow Storm will shoot +2 stream of arrows.","archetype":"Boltslinger","archetype_req":8,"parents":[48,20],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":33,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":2}}],"id":23},{"display_name":"Geyser Stomp","desc":"Fierce Stomp will create geysers, dealing more damage and vertical knockback.","archetype":"","archetype_req":0,"parents":[56],"dependencies":[15],"blockers":[],"cost":2,"display":{"row":37,"col":1},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[0,0,0,50,0,0]}],"id":24},{"display_name":"Crepuscular Ray","desc":"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.","archetype":"Sharpshooter","archetype_req":10,"parents":[49],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":37,"col":4},"properties":{"focusReq":5,"focusRegen":-1},"effects":[{"type":"replace_spell","name":"Crepuscular Ray","base_spell":5,"spell_type":"damage","scaling":"spell","display":"One Focus","cost":0,"parts":[{"name":"Single Arrow","type":"damage","multipliers":[10,0,0,5,0,0]},{"name":"One Focus","type":"total","hits":{"Single Arrow":20}},{"name":"Total Damage","type":"total","hits":{"One Focus":7}}]}],"id":25},{"display_name":"Grape Bomb","desc":"Arrow bomb will throw 3 additional smaller bombs when exploding.","archetype":"","archetype_req":0,"parents":[51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":37,"col":7},"properties":{"miniBombs":3,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Grape Bomb","cost":0,"multipliers":[30,0,0,0,10,0]}],"id":26},{"display_name":"Tangled Traps","desc":"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":38,"col":6},"properties":{"attackSpeed":0.2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Tangled Traps","cost":0,"multipliers":[20,0,0,0,0,20]}],"id":27},{"display_name":"Snow Storm","desc":"Enemies near you will be slowed down.","archetype":"","archetype_req":0,"parents":[24,63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":39,"col":2},"properties":{"range":2.5,"slowness":0.3},"id":28},{"display_name":"All-Seeing Panoptes","desc":"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.","archetype":"Boltslinger","archetype_req":11,"parents":[28],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":40,"col":1},"properties":{"range":10,"shots":5},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Arrow","cost":0,"multipliers":[0,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":5}}],"id":29},{"display_name":"Minefield","desc":"Allow you to place +6 Traps, but with reduced damage and range.","archetype":"Trapper","archetype_req":10,"parents":[26,53],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":40,"col":7},"properties":{"aoe":-2,"traps":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[-80,0,0,0,0,0]}],"id":30},{"display_name":"Bow Proficiency I","desc":"Improve your Main Attack's damage and range when using a bow.","archetype":"","archetype_req":0,"parents":[2],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":31},{"display_name":"Cheaper Arrow Bomb","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-10}],"id":32},{"display_name":"Cheaper Arrow Storm","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[12,11,61],"dependencies":[],"blockers":[],"cost":1,"display":{"row":21,"col":3},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":33},{"display_name":"Cheaper Escape","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[7,0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":34},{"display_name":"Earth Mastery","desc":"Increases your base damage from all Earth attacks","archetype":"Trapper","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":8},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":35},{"display_name":"Thunder Mastery","desc":"Increases your base damage from all Thunder attacks","archetype":"Boltslinger","archetype_req":0,"parents":[7,39,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":2},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":36},{"display_name":"Water Mastery","desc":"Increases your base damage from all Water attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[34,36,39],"dependencies":[],"blockers":[],"cost":1,"display":{"row":14,"col":4},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":37},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":0},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":38},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[36,0,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":6},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":39},{"display_name":"More Shields","desc":"Give +2 charges to Arrow Shield.","archetype":"","archetype_req":0,"parents":[12,10],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":21,"col":7},"properties":{"shieldCharges":2},"id":40},{"display_name":"Stormy Feet","desc":"Windy Feet will last longer and add more speed.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[9],"blockers":[],"cost":1,"display":{"row":23,"col":1},"properties":{"duration":60},"effects":[{"type":"stat_bonus","bonuses":[{"type":"stat","name":"spdPct","value":20}]}],"id":41},{"display_name":"Refined Gunpowder","desc":"Increase the damage of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[50,0,0,0,0,0]}],"id":42},{"display_name":"More Traps","desc":"Increase the maximum amount of active Traps you can have by +2.","archetype":"Trapper","archetype_req":10,"parents":[54],"dependencies":[10],"blockers":[],"cost":1,"display":{"row":26,"col":8},"properties":{"traps":2},"id":43},{"display_name":"Better Arrow Shield","desc":"Arrow Shield will gain additional area of effect, knockback and damage.","archetype":"Sharpshooter","archetype_req":0,"parents":[19,18,14],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":28,"col":6},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Shield","multipliers":[40,0,0,0,0,0]}],"id":44},{"display_name":"Better Leap","desc":"Reduce leap's cooldown by 1s.","archetype":"Boltslinger","archetype_req":0,"parents":[17,55],"dependencies":[17],"blockers":[],"cost":1,"display":{"row":29,"col":1},"properties":{"cooldown":-1},"id":45},{"display_name":"Better Guardian Angels","desc":"Your Guardian Angels can shoot +4 arrows before disappearing.","archetype":"Boltslinger","archetype_req":0,"parents":[20,55],"dependencies":[8],"blockers":[],"cost":1,"display":{"row":31,"col":2},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":4}}],"id":46},{"display_name":"Cheaper Arrow Storm (2)","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[21,19],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":8},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":47},{"display_name":"Precise Shot","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[46,49,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":2},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdCritPct","value":30}]}],"id":48},{"display_name":"Cheaper Arrow Shield","desc":"Reduce the Mana cost of Arrow Shield.","archetype":"","archetype_req":0,"parents":[48,21],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":49},{"display_name":"Rocket Jump","desc":"Arrow Bomb's self-damage will knockback you farther away.","archetype":"","archetype_req":0,"parents":[47,21],"dependencies":[2],"blockers":[],"cost":1,"display":{"row":33,"col":6},"properties":{},"id":50},{"display_name":"Cheaper Escape (2)","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[22,70],"dependencies":[],"blockers":[],"cost":1,"display":{"row":34,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":51},{"display_name":"Stronger Hook","desc":"Increase your Grappling Hook's range, speed and strength.","archetype":"Trapper","archetype_req":5,"parents":[51],"dependencies":[12],"blockers":[],"cost":1,"display":{"row":35,"col":8},"properties":{"range":8},"id":52},{"display_name":"Cheaper Arrow Bomb (2)","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[63,30],"dependencies":[],"blockers":[],"cost":1,"display":{"row":40,"col":5},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":53},{"display_name":"Bouncing Bomb","desc":"Arrow Bomb will bounce once when hitting a block or enemy","archetype":"","archetype_req":0,"parents":[40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":25,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Arrow Bomb":2}}],"id":54},{"display_name":"Homing Shots","desc":"Your Main Attack arrows will follow nearby enemies and not be affected by gravity","archetype":"","archetype_req":0,"parents":[17,18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2},"properties":{},"effects":[],"id":55},{"display_name":"Shrapnel Bomb","desc":"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area","archetype":"Boltslinger","archetype_req":8,"parents":[23,48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Shrapnel Bomb","cost":0,"multipliers":[40,0,0,0,20,0]}],"id":56},{"display_name":"Elusive","desc":"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)","archetype":"Boltslinger","archetype_req":0,"parents":[24],"dependencies":[],"blockers":[],"cost":2,"display":{"row":38,"col":0},"properties":{},"effects":[],"id":57},{"display_name":"Double Shots","desc":"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)","archetype":"Boltslinger","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[60],"cost":1,"display":{"row":7,"col":2},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":58},{"display_name":"Triple Shots","desc":"Triple Main Attack arrows, but they deal -20% damage per arrow","archetype":"Boltslinger","archetype_req":0,"parents":[69,67],"dependencies":[58],"blockers":[],"cost":1,"display":{"row":17,"col":0},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":59},{"display_name":"Power Shots","desc":"Main Attack arrows have increased speed and knockback","archetype":"Sharpshooter","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[58],"cost":1,"display":{"row":7,"col":6},"properties":{},"effects":[],"id":60},{"display_name":"Focus","desc":"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once","archetype":"Sharpshooter","archetype_req":2,"parents":[68],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[3],"max":3}],"id":61},{"display_name":"More Focus","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[33,12],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[35],"max":5}],"id":62},{"display_name":"More Focus (2)","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[25,28],"dependencies":[],"blockers":[],"cost":1,"display":{"row":39,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[35],"max":7}],"id":63},{"display_name":"Traveler","desc":"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)","archetype":"","archetype_req":0,"parents":[42,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"spd"}],"output":{"type":"stat","name":"sdRaw"},"scaling":[1],"max":100}],"id":64},{"display_name":"Patient Hunter","desc":"Your Traps will deal +20% more damage for every second they are active (Max +80%)","archetype":"Trapper","archetype_req":0,"parents":[40],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":8},"properties":{"max":80},"effects":[],"id":65},{"display_name":"Stronger Patient Hunter","desc":"Add +80% Max Damage to Patient Hunter","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[65],"blockers":[],"cost":1,"display":{"row":38,"col":8},"properties":{"max":80},"effects":[],"id":66},{"display_name":"Frenzy","desc":"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second","archetype":"Boltslinger","archetype_req":0,"parents":[59,6],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"spd"},"scaling":[6],"max":200}],"id":67},{"display_name":"Phantom Ray","desc":"Condense Arrow Storm into a single ray that damages enemies 10 times per second","archetype":"Sharpshooter","archetype_req":0,"parents":[37,4],"dependencies":[7],"blockers":[11,6,23],"cost":2,"display":{"row":16,"col":4},"properties":{},"effects":[{"type":"replace_spell","name":"Phantom Ray","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[25,0,5,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Arrow":16}}]}],"id":68},{"display_name":"Arrow Rain","desc":"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies","archetype":"Trapper","archetype_req":0,"parents":[6,38],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":15,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Arrow Rain","cost":0,"multipliers":[120,0,0,0,0,80]}],"id":69},{"display_name":"Decimator","desc":"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)","archetype":"Sharpshooter","archetype_req":0,"parents":[49],"dependencies":[68],"blockers":[],"cost":1,"display":{"row":34,"col":5},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Phantom Ray hits","output":{"type":"stat","name":"PhRayDmg"},"scaling":10,"max":50}],"id":70}],"Warrior":[{"display_name":"Bash","desc":"Violently bash the ground, dealing high damage in a large area","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4,"icon":"node_4"},"properties":{"aoe":4,"range":3},"effects":[{"type":"replace_spell","name":"Bash","cost":45,"display_text":"Total Damage Average","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Hit","type":"damage","multipliers":[130,20,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Hit":1}}]}],"id":0},{"display_name":"Spear Proficiency 1","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":1},{"display_name":"Cheaper Bash","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-10}],"id":2},{"display_name":"Double Bash","desc":"Bash will hit a second time at a farther range","archetype":"","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4,"icon":"node_1"},"properties":{"range":3},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"name":"Single Hit","value":1}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-50,0,0,0,0,0]}],"id":3},{"display_name":"Charge","desc":"Charge forward at high speed (hold shift to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":6,"col":4,"icon":"node_4"},"properties":{},"effects":[{"type":"replace_spell","name":"Charge","cost":25,"display_text":"Total Damage Average","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":4},{"display_name":"Heavy Impact","desc":"After using Charge, violently crash down into the ground and deal damage","archetype":"","archetype_req":0,"parents":[8],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":1,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Heavy Impact","cost":0,"multipliers":[100,0,0,0,0,0]}],"id":5},{"display_name":"Vehement","desc":"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)","archetype":"Fallen","archetype_req":0,"parents":[4],"dependencies":[],"blockers":[7],"cost":1,"display":{"row":6,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"mdPct"},{"type":"stat","name":"mdRaw"}],"output":{"type":"stat","name":"spd"},"scaling":[1,1],"max":20}],"id":6},{"display_name":"Tougher Skin","desc":"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)","archetype":"Paladin","archetype_req":0,"parents":[4],"dependencies":[],"blockers":[6],"cost":1,"display":{"row":6,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":"5"}]},{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hprRaw"},{"type":"stat","name":"hprPct"}],"output":{"type":"stat","name":"hpBonus"},"scaling":[10,10],"max":100}],"id":7},{"display_name":"Uppercut","desc":"Rocket enemies in the air and deal massive damage","archetype":"","archetype_req":0,"parents":[6],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":2,"icon":"node_4"},"properties":{"aoe":3,"range":5},"effects":[{"type":"replace_spell","name":"Uppercut","cost":45,"display_text":"Total Damage Average","base_spell":3,"spell_type":"damage","scaling":"spell","display":"total","parts":[{"name":"Uppercut","type":"damage","multipliers":[150,50,50,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Uppercut":1}}]}],"id":8},{"display_name":"Cheaper Charge","desc":"Reduce the Mana cost of Charge","archetype":"","archetype_req":0,"parents":[8,10],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":9},{"display_name":"War Scream","desc":"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies","archetype":"","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":6,"icon":"node_4"},"properties":{"duration":30,"aoe":12,"defense_bonus":10},"effects":[{"type":"replace_spell","name":"War Scream","cost":35,"display_text":"War Scream","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage Average","parts":[{"name":"War Scream","type":"damage","multipliers":[50,0,0,0,50,0]}]}],"id":10},{"display_name":"Earth Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Fallen","archetype_req":0,"parents":[8],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":11},{"display_name":"Thunder Mastery","desc":"Increases base damage from all Thunder attacks","archetype":"Fallen","archetype_req":0,"parents":[8,14,9],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":12},{"display_name":"Water Mastery","desc":"Increases base damage from all Water attacks","archetype":"Battle Monk","archetype_req":0,"parents":[9,12,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":11,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":13},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[10,12,9],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":14},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Paladin","archetype_req":0,"parents":[10],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":15},{"display_name":"Quadruple Bash","desc":"Bash will hit 4 times at an even larger range","archetype":"Fallen","archetype_req":0,"parents":[11,17],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":0,"icon":"node_1"},"properties":{"range":6},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Hit":2}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-20,0,0,0,0,0]}],"id":16},{"display_name":"Fireworks","desc":"Mobs hit by Uppercut will explode mid-air and receive additional damage","archetype":"Fallen","archetype_req":0,"parents":[12,16],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fireworks","cost":0,"multipliers":[80,0,20,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fireworks":1}}],"id":17},{"display_name":"Half-Moon Swipe","desc":"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water","archetype":"Battle Monk","archetype_req":1,"parents":[13],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":13,"col":4,"icon":"node_1"},"properties":{"range":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":-10,"multipliers":[-70,0,0,0,0,0]},{"type":"convert_spell_conv","target_part":"all","conversion":"water"}],"id":18},{"display_name":"Flyby Jab","desc":"Damage enemies in your way when using Charge","archetype":"","archetype_req":0,"parents":[14,20],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":6,"icon":"node_1"},"properties":{"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flyby Jab","cost":0,"multipliers":[20,0,0,0,0,40]}],"id":19},{"display_name":"Flaming Uppercut","desc":"Uppercut will light mobs on fire, dealing damage every 0.6 seconds","archetype":"Paladin","archetype_req":0,"parents":[15,19],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":12,"col":8,"icon":"node_1"},"properties":{"duration":3,"tick":0.6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut","cost":0,"multipliers":[0,0,0,0,50,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut Total Damage","cost":0,"hits":{"Flaming Uppercut":5}},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Flaming Uppercut":5}}],"id":20},{"display_name":"Iron Lungs","desc":"War Scream deals more damage","archetype":"","archetype_req":0,"parents":[19,20],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"War Scream","cost":0,"multipliers":[30,0,0,0,0,30]}],"id":21},{"display_name":"Generalist","desc":"After casting 3 different spells in a row, your next spell will cost 5 mana","archetype":"Battle Monk","archetype_req":3,"parents":[23],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":2,"icon":"node_3"},"properties":{},"effects":[],"id":22},{"display_name":"Counter","desc":"When dodging a nearby enemy attack, get 30% chance to instantly attack back","archetype":"Battle Monk","archetype_req":0,"parents":[18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":4,"icon":"node_1"},"properties":{"chance":30},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Counter","cost":0,"multipliers":[60,0,20,0,0,20]}],"id":23},{"display_name":"Mantle of the Bovemists","desc":"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)","archetype":"Paladin","archetype_req":3,"parents":[21],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":15,"col":7,"icon":"node_3"},"properties":{"mantle_charge":3},"effects":[],"id":24},{"display_name":"Bak'al's Grasp","desc":"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)","archetype":"Fallen","archetype_req":2,"parents":[16,17],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":16,"col":1,"icon":"node_3"},"properties":{"cooldown":15},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[4],"slider_step":2,"max":120}],"id":25},{"display_name":"Spear Proficiency 2","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[25,27],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":0,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":26},{"display_name":"Cheaper Uppercut","desc":"Reduce the Mana Cost of Uppercut","archetype":"","archetype_req":0,"parents":[26,28,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":27},{"display_name":"Aerodynamics","desc":"During Charge, you can steer and change direction","archetype":"Battle Monk","archetype_req":0,"parents":[27,29],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":5,"icon":"node_1"},"properties":{},"effects":[],"id":28},{"display_name":"Provoke","desc":"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream","archetype":"Paladin","archetype_req":0,"parents":[28,24],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":29},{"display_name":"Precise Strikes","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[27,26],"dependencies":[],"blockers":[],"cost":1,"display":{"row":18,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"critDmg","value":30}]}],"id":30},{"display_name":"Air Shout","desc":"War Scream will fire a projectile that can go through walls and deal damage multiple times","archetype":"","archetype_req":0,"parents":[28,29],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":18,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Air Shout","cost":0,"multipliers":[20,0,0,0,0,5]}],"id":31},{"display_name":"Enraged Blow","desc":"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)","archetype":"Fallen","archetype_req":0,"parents":[26],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":20,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"damMult"},"scaling":[3],"max":300}],"id":32},{"display_name":"Flying Kick","desc":"When using Charge, mobs hit will halt your momentum and get knocked back","archetype":"Battle Monk","archetype_req":1,"parents":[27,34],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":3,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flying Kick","cost":0,"multipliers":[120,0,0,10,0,20]}],"id":33},{"display_name":"Stronger Mantle","desc":"Add +2 additional charges to Mantle of the Bovemists","archetype":"Paladin","archetype_req":0,"parents":[35,33],"dependencies":[24],"blockers":[],"cost":1,"display":{"row":20,"col":6,"icon":"node_0"},"properties":{"mantle_charge":2},"effects":[],"id":34},{"display_name":"Manachism","desc":"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)","archetype":"Paladin","archetype_req":3,"parents":[34,29],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":8,"icon":"node_2"},"properties":{"cooldown":1},"effects":[],"id":35},{"display_name":"Boiling Blood","desc":"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds","archetype":"","archetype_req":0,"parents":[32,37],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":0,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Boiling Blood","cost":0,"multipliers":[25,0,0,0,5,0]}],"id":36},{"display_name":"Ragnarokkr","desc":"War Scream become deafening, increasing its range and giving damage bonus to players","archetype":"Fallen","archetype_req":0,"parents":[36,33],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":2,"icon":"node_2"},"properties":{"damage_bonus":30,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":10}],"id":37},{"display_name":"Ambidextrous","desc":"Increase your chance to attack with Counter by +30%","archetype":"","archetype_req":0,"parents":[33,34,39],"dependencies":[23],"blockers":[],"cost":1,"display":{"row":22,"col":4,"icon":"node_0"},"properties":{"chance":30},"effects":[],"id":38},{"display_name":"Burning Heart","desc":"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)","archetype":"Paladin","archetype_req":0,"parents":[38,40],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":!1,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"fDamPct"},"scaling":[2],"max":100,"slider_step":100}],"id":39},{"display_name":"Stronger Bash","desc":"Increase the damage of Bash","archetype":"","archetype_req":0,"parents":[39,35],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[30,0,0,0,0,0]}],"id":40},{"display_name":"Intoxicating Blood","desc":"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted","archetype":"Fallen","archetype_req":5,"parents":[37,36],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":23,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":41},{"display_name":"Comet","desc":"After being hit by Fireworks, enemies will crash into the ground and receive more damage","archetype":"Fallen","archetype_req":0,"parents":[37],"dependencies":[17],"blockers":[],"cost":2,"display":{"row":24,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Comet","cost":0,"multipliers":[80,20,0,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Comet":1}}],"id":42},{"display_name":"Collide","desc":"Mobs thrown into walls from Flying Kick will explode and receive additonal damage","archetype":"Battle Monk","archetype_req":4,"parents":[38,39],"dependencies":[33],"blockers":[],"cost":2,"display":{"row":23,"col":5,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Collide","cost":0,"multipliers":[100,0,0,0,50,0]}],"id":43},{"display_name":"Rejuvenating Skin","desc":"Regain back 30% of the damage you take as healing over 30s","archetype":"Paladin","archetype_req":0,"parents":[39,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":23,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":44},{"display_name":"Uncontainable Corruption","desc":"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1","archetype":"","archetype_req":0,"parents":[36,46],"dependencies":[25],"blockers":[],"cost":1,"display":{"row":26,"col":0,"icon":"node_0"},"properties":{"cooldown":-5},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[1],"slider_step":2,"max":50}],"id":45},{"display_name":"Radiant Devotee","desc":"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)","archetype":"Battle Monk","archetype_req":1,"parents":[47,45],"dependencies":[],"blockers":[],"cost":1,"display":{"row":26,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","inputs":[{"type":"stat","name":"ref"}],"output":{"type":"stat","name":"mr"},"scaling":[1],"max":10,"slider_step":4}],"id":46},{"display_name":"Whirlwind Strike","desc":"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)","archetype":"Battle Monk","archetype_req":5,"parents":[38,46],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":26,"col":4,"icon":"node_1"},"properties":{"range":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":0,"multipliers":[0,0,0,0,0,50]}],"id":47},{"display_name":"Mythril Skin","desc":"Gain +5% Base Resistance and become immune to knockback","archetype":"Paladin","archetype_req":6,"parents":[44],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":5}]}],"id":48},{"display_name":"Armour Breaker","desc":"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage","archetype":"Fallen","archetype_req":0,"parents":[45,46],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":27,"col":1,"icon":"node_2"},"properties":{"duration":5},"effects":[],"id":49},{"display_name":"Shield Strike","desc":"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost","archetype":"Paladin","archetype_req":0,"parents":[48,51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Shield Strike","cost":0,"multipliers":[60,0,20,0,0,0]}],"id":50},{"display_name":"Sparkling Hope","desc":"Everytime you heal 5% of your max health, deal damage to all nearby enemies","archetype":"Paladin","archetype_req":0,"parents":[48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":8,"icon":"node_2"},"properties":{"aoe":6},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Sparkling Hope","cost":0,"multipliers":[10,0,5,0,0,0]}],"id":51},{"display_name":"Massive Bash","desc":"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)","archetype":"Fallen","archetype_req":8,"parents":[53,45],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Corrupted","output":{"type":"stat","name":"bashAoE"},"scaling":[1],"max":10,"slider_step":3}],"id":52},{"display_name":"Tempest","desc":"War Scream will ripple the ground and deal damage 3 times in a large area","archetype":"Battle Monk","archetype_req":0,"parents":[52,54],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2,"icon":"node_1"},"properties":{"aoe":16},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest","cost":"0","multipliers":[30,10,0,0,0,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest Total Damage","cost":"0","hits":{"Tempest":3}},{"type":"add_spell_prop","base_spell":4,"target_part":"Total Damage","cost":"0","hits":{"Tempest":3}}],"id":53},{"display_name":"Spirit of the Rabbit","desc":"Reduce the Mana cost of Charge and increase your Walk Speed by +20%","archetype":"Battle Monk","archetype_req":5,"parents":[53,47],"dependencies":[],"blockers":[],"cost":1,"display":{"row":28,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5},{"type":"raw_stat","bonuses":[{"type":"stat","name":"spd","value":20}]}],"id":54},{"display_name":"Massacre","desc":"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar","archetype":"Fallen","archetype_req":5,"parents":[53,52],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":55},{"display_name":"Axe Kick","desc":"Increase the damage of Uppercut, but also increase its mana cost","archetype":"","archetype_req":0,"parents":[53,54],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":10,"multipliers":[100,0,0,0,0,0]}],"id":56},{"display_name":"Radiance","desc":"Bash will buff your allies' positive IDs. (15s Cooldown)","archetype":"Paladin","archetype_req":2,"parents":[54,58],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":5,"icon":"node_2"},"properties":{"cooldown":15},"effects":[],"id":57},{"display_name":"Cheaper Bash 2","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[57,50,51],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":58},{"display_name":"Cheaper War Scream","desc":"Reduce the Mana cost of War Scream","archetype":"","archetype_req":0,"parents":[52],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":59},{"display_name":"Discombobulate","desc":"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second","archetype":"Battle Monk","archetype_req":11,"parents":[62],"dependencies":[],"blockers":[],"cost":2,"display":{"row":31,"col":2,"icon":"node_3"},"properties":{},"effects":[{"type":"stat_scaling","slider":!0,"slider_name":"Hits dealt","output":{"type":"stat","name":"rainrawButDifferent"},"scaling":[2],"max":50}],"id":60},{"display_name":"Thunderclap","desc":"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder","archetype":"Battle Monk","archetype_req":8,"parents":[62],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":5,"icon":"node_1"},"properties":{},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"},{"type":"raw_stat","bonuses":[{"type":"prop","abil_name":"Bash","name":"aoe","value":3}]}],"id":61},{"display_name":"Cyclone","desc":"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s","archetype":"Battle Monk","archetype_req":0,"parents":[54],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":4,"icon":"node_1"},"properties":{"aoe":4,"duration":20},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone","cost":0,"multipliers":[10,0,0,0,5,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone Total Damage","cost":0,"hits":{"Cyclone":40}}],"id":62},{"display_name":"Second Chance","desc":"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)","archetype":"Paladin","archetype_req":12,"parents":[58],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":63},{"display_name":"Blood Pact","desc":"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)","archetype":"","archetype_req":10,"parents":[59],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1,"icon":"node_3"},"properties":{},"effects":[],"id":64},{"display_name":"Haemorrhage","desc":"Reduce Blood Pact's health cost. (0.5% health per mana)","archetype":"Fallen","archetype_req":0,"parents":[64],"dependencies":[64],"blockers":[],"cost":1,"display":{"row":35,"col":2,"icon":"node_1"},"properties":{},"effects":[],"id":65},{"display_name":"Brink of Madness","desc":"If your health is 25% full or less, gain +40% Resistance","archetype":"","archetype_req":0,"parents":[64,67],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":4,"icon":"node_2"},"properties":{},"effects":[],"id":66},{"display_name":"Cheaper Uppercut 2","desc":"Reduce the Mana cost of Uppercut","archetype":"","archetype_req":0,"parents":[63,66],"dependencies":[],"blockers":[],"cost":1,"display":{"row":35,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":67},{"display_name":"Martyr","desc":"When you receive a fatal blow, all nearby allies become invincible","archetype":"Paladin","archetype_req":0,"parents":[63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":8,"icon":"node_1"},"properties":{"duration":3,"aoe":12},"effects":[],"id":68}]} \ No newline at end of file +const atrees={"Archer":[{"display_name":"Arrow Shield","desc":"Create a shield around you that deal damage and knockback mobs when triggered. (2 Charges)","archetype":"","archetype_req":0,"parents":[60,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":6},"properties":{"duration":60},"effects":[{"type":"replace_spell","name":"Arrow Shield","cost":30,"display_text":"Max Damage","base_spell":4,"spell_type":"damage","scaling":"spell","display":"","parts":[{"name":"Shield Damage","type":"damage","multipliers":[90,0,0,0,0,10]},{"name":"Total Damage","type":"total","hits":{"Shield Damage":2}}]}],"id":0},{"display_name":"Escape","desc":"Throw yourself backward to avoid danger. (Hold shift while escaping to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":7,"col":4},"properties":{"aoe":0,"range":0},"effects":[{"type":"replace_spell","name":"Escape","cost":25,"display_text":"Max Damage","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":1},{"display_name":"Arrow Bomb","desc":"Throw a long-range arrow that explodes and deal high damage in a large area. (Self-damage for 25% of your DPS)","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4},"properties":{"aoe":4.5,"range":26},"effects":[{"type":"replace_spell","name":"Arrow Bomb","cost":50,"display_text":"Average Damage","base_spell":3,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Arrow Bomb","type":"damage","multipliers":[160,0,0,0,20,0]},{"name":"Total Damage","type":"total","hits":{"Arrow Bomb":1}}]}],"id":2},{"display_name":"Heart Shatter","desc":"If you hit a mob directly with Arrow Bomb, shatter its heart and deal bonus damage.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[100,0,0,0,0,0]},{}],"id":3},{"display_name":"Fire Creep","desc":"Arrow Bomb will leak a trail of fire for 6s, Damaging enemies that walk into it every 0.4s.","archetype":"","archetype_req":0,"parents":[68,39,5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":16,"col":6},"properties":{"aoe":0.8,"duration":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[30,0,0,0,20,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fire Creep":15}}],"id":4},{"display_name":"Bryophyte Roots","desc":"When you hit an enemy with Arrow Storm, create an area that slows them down and deals damage every 0.4s.","archetype":"Trapper","archetype_req":1,"parents":[4,35],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":16,"col":8},"properties":{"aoe":2,"duration":5,"slowness":0.4},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Bryophyte Roots","cost":0,"multipliers":[40,20,0,0,0,0]}],"id":5},{"display_name":"Nimble String","desc":"Arrow Storm throw out +6 arrows per stream and shoot twice as fast.","archetype":"","archetype_req":0,"parents":[36,69],"dependencies":[7],"blockers":[68],"cost":2,"display":{"row":15,"col":2},"properties":{"shootspeed":2},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-15,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","cost":0,"hits":{"Single Arrow":6}}],"id":6},{"display_name":"Arrow Storm","desc":"Shoot two stream of 8 arrows, dealing significant damage to close mobs and pushing them back.","archetype":"","archetype_req":0,"parents":[58,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":2},"properties":{"aoe":0,"range":16},"effects":[{"type":"replace_spell","name":"Arrow Storm","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,10,0,0,0]},{"name":"Single Stream","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Stream":1}}]}],"id":7},{"display_name":"Guardian Angels","desc":"Your protective arrows from Arrow Shield will become sentient bows, dealing damage up to 8 times each to nearby enemies. (Arrow Shield will no longer push nearby enemies)","archetype":"Boltslinger","archetype_req":3,"parents":[59,67],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":19,"col":1},"properties":{"range":4,"duration":60,"shots":8,"count":2},"effects":[{"type":"replace_spell","name":"Guardian Angels","cost":30,"display_text":"Total Damage Average","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[30,0,0,0,0,10]},{"name":"Single Bow","type":"total","hits":{"Single Arrow":8}},{"name":"Total Damage","type":"total","hits":{"Single Bow":2}}]}],"id":8},{"display_name":"Windy Feet","base_abil":"Escape","desc":"When casting Escape, give speed to yourself and nearby allies.","archetype":"","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":1},"properties":{"aoe":8,"duration":120},"type":"stat_bonus","bonuses":[{"type":"stat","name":"spd","value":20}],"id":9},{"display_name":"Basaltic Trap","desc":"When you hit the ground with Arrow Bomb, leave a Trap that damages enemies. (Max 2 Traps)","archetype":"Trapper","archetype_req":2,"parents":[5],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":8},"properties":{"aoe":7,"traps":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[140,30,0,0,30,0]}],"id":10},{"display_name":"Windstorm","desc":"Arrow Storm shoot +1 stream of arrows, effectively doubling its damage.","archetype":"","archetype_req":0,"parents":[8,33],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":21,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Arrow","cost":0,"multipliers":[-10,0,-2,0,0,2]},{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":1}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Stream","cost":0,"hits":{"Single Arrow":2}}],"id":11},{"display_name":"Grappling Hook","base_abil":"Escape","desc":"When casting Escape, throw a hook that pulls you when hitting a block. If you hit an enemy, pull them towards you instead. (Escape will not throw you backward anymore)","archetype":"Trapper","archetype_req":0,"parents":[61,40,33],"dependencies":[],"blockers":[20],"cost":2,"display":{"row":21,"col":5},"properties":{"range":20},"effects":[],"id":12},{"display_name":"Implosion","desc":"Arrow bomb will pull enemies towards you. If a trap is nearby, it will pull them towards it instead. Increase Heart Shatter's damage.","archetype":"Trapper","archetype_req":0,"parents":[12,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":13},{"display_name":"Twain's Arc","desc":"When you have 2+ Focus, holding shift will summon the Twain's Arc. Charge it up to shoot a destructive long-range beam. (Damage is dealt as Main Attack Damage)","archetype":"Sharpshooter","archetype_req":4,"parents":[62,64],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":25,"col":4},"properties":{"range":64,"focusReq":2},"effects":[{"type":"replace_spell","name":"Twain's Arc","cost":0,"display_text":"Twain's Arc","base_spell":5,"spell_type":"damage","scaling":"melee","display":"Twain's Arc Damage","parts":[{"name":"Twain's Arc Damage","type":"damage","multipliers":[200,0,0,0,0,0]}]}],"id":14},{"display_name":"Fierce Stomp","desc":"When using Escape, hold shift to quickly drop down and deal damage.","archetype":"Boltslinger","archetype_req":0,"parents":[42,64],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":1},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[100,0,0,0,0,0]},{"type":"add_spell_prop","base_spell":2,"target_part":"Total Damage","cost":0,"hits":{"Fierce Stomp":1}}],"id":15},{"display_name":"Scorched Earth","desc":"Fire Creep become much stronger.","archetype":"Sharpshooter","archetype_req":0,"parents":[14],"dependencies":[4],"blockers":[],"cost":1,"display":{"row":26,"col":5},"properties":{"duration":2,"aoe":0.4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fire Creep","cost":0,"multipliers":[10,0,0,0,5,0]}],"id":16},{"display_name":"Leap","desc":"When you double tap jump, leap foward. (2s Cooldown)","archetype":"Boltslinger","archetype_req":5,"parents":[42,55],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0},"properties":{"cooldown":2},"effects":[],"id":17},{"display_name":"Shocking Bomb","desc":"Arrow Bomb will not be affected by gravity, and all damage conversions become Thunder.","archetype":"Sharpshooter","archetype_req":5,"parents":[14,44,55],"dependencies":[2],"blockers":[],"cost":2,"display":{"row":28,"col":4},"properties":{"gravity":0},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"}],"id":18},{"display_name":"Mana Trap","desc":"Your Traps will give you 4 Mana per second when you stay close to them.","archetype":"Trapper","archetype_req":5,"parents":[43,44],"dependencies":[4],"blockers":[],"cost":2,"display":{"row":28,"col":8},"properties":{"range":12,"manaRegen":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":10,"multipliers":[0,0,0,0,0,0]}],"id":19},{"display_name":"Escape Artist","desc":"When casting Escape, release 100 arrows towards the ground.","archetype":"Boltslinger","archetype_req":0,"parents":[46,17],"dependencies":[],"blockers":[12],"cost":2,"display":{"row":31,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Escape Artist","cost":0,"multipliers":[30,0,10,0,0,0]}],"id":20},{"display_name":"Initiator","desc":"If you do not damage an enemy for 5s or more, your next sucessful hit will deal +50% damage and add +1 Focus.","archetype":"Sharpshooter","archetype_req":5,"parents":[18,44,47],"dependencies":[61],"blockers":[],"cost":2,"display":{"row":31,"col":5},"properties":{"focus":1,"timer":5},"type":"stat_bonus","bonuses":[{"type":"stat","name":"damPct","value":50}],"id":21},{"display_name":"Call of the Hound","desc":"Arrow Shield summon a Hound that will attack and drag aggressive enemies towards your traps.","archetype":"Trapper","archetype_req":0,"parents":[21,47],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":32,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Call of the Hound","cost":0,"multipliers":[40,0,0,0,0,0]}],"id":22},{"display_name":"Arrow Hurricane","desc":"Arrow Storm will shoot +2 stream of arrows.","archetype":"Boltslinger","archetype_req":8,"parents":[48,20],"dependencies":[],"blockers":[68],"cost":2,"display":{"row":33,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Stream":2}}],"id":23},{"display_name":"Geyser Stomp","desc":"Fierce Stomp will create geysers, dealing more damage and vertical knockback.","archetype":"","archetype_req":0,"parents":[56],"dependencies":[15],"blockers":[],"cost":2,"display":{"row":37,"col":1},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Fierce Stomp","cost":0,"multipliers":[0,0,0,50,0,0]}],"id":24},{"display_name":"Crepuscular Ray","desc":"If you have 5 Focus, casting Arrow Storm will make you levitate and shoot 20 homing arrows per second until you run out of Focus. While in that state, you will lose 1 Focus per second.","archetype":"Sharpshooter","archetype_req":10,"parents":[49],"dependencies":[7],"blockers":[],"cost":2,"display":{"row":37,"col":4},"properties":{"focusReq":5,"focusRegen":-1},"effects":[{"type":"replace_spell","name":"Crepuscular Ray","base_spell":5,"spell_type":"damage","scaling":"spell","display":"One Focus","cost":0,"parts":[{"name":"Single Arrow","type":"damage","multipliers":[10,0,0,5,0,0]},{"name":"One Focus","type":"total","hits":{"Single Arrow":20}},{"name":"Total Damage","type":"total","hits":{"One Focus":7}}]}],"id":25},{"display_name":"Grape Bomb","desc":"Arrow bomb will throw 3 additional smaller bombs when exploding.","archetype":"","archetype_req":0,"parents":[51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":37,"col":7},"properties":{"miniBombs":3,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Grape Bomb","cost":0,"multipliers":[30,0,0,0,10,0]}],"id":26},{"display_name":"Tangled Traps","desc":"Your Traps will be connected by a rope that deals damage to enemies every 0.2s.","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":38,"col":6},"properties":{"attackSpeed":0.2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Tangled Traps","cost":0,"multipliers":[20,0,0,0,0,20]}],"id":27},{"display_name":"Snow Storm","desc":"Enemies near you will be slowed down.","archetype":"","archetype_req":0,"parents":[24,63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":39,"col":2},"properties":{"range":2.5,"slowness":0.3},"id":28},{"display_name":"All-Seeing Panoptes","desc":"Your bows from Guardian Angels become all-seeing, increasing their range, damage and letting them shoot up to +5 times each.","archetype":"Boltslinger","archetype_req":11,"parents":[28],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":40,"col":1},"properties":{"range":8,"shots":5},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Arrow","cost":0,"multipliers":[0,0,0,0,10,0]},{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":5}}],"id":29},{"display_name":"Minefield","desc":"Allow you to place +6 Traps, but with reduced damage and range.","archetype":"Trapper","archetype_req":10,"parents":[26,53],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":40,"col":7},"properties":{"aoe":-2,"traps":6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Basaltic Trap","cost":0,"multipliers":[-80,0,0,0,0,0]}],"id":30},{"display_name":"Bow Proficiency I","desc":"Improve your Main Attack's damage and range when using a bow.","archetype":"","archetype_req":0,"parents":[2],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":31},{"display_name":"Cheaper Arrow Bomb","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[31],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":6},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-10}],"id":32},{"display_name":"Cheaper Arrow Storm","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[12,11,61],"dependencies":[],"blockers":[],"cost":1,"display":{"row":21,"col":3},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":33},{"display_name":"Cheaper Escape","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[7,0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":34},{"display_name":"Earth Mastery","desc":"Increases your base damage from all Earth attacks","archetype":"Trapper","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":8},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":35},{"display_name":"Thunder Mastery","desc":"Increases your base damage from all Thunder attacks","archetype":"Boltslinger","archetype_req":0,"parents":[7,39,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":2},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":36},{"display_name":"Water Mastery","desc":"Increases your base damage from all Water attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[34,36,39],"dependencies":[],"blockers":[],"cost":1,"display":{"row":14,"col":4},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":37},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":0},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":38},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Sharpshooter","archetype_req":0,"parents":[36,0,34],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":6},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":39},{"display_name":"More Shields","desc":"Give +2 charges to Arrow Shield.","archetype":"","archetype_req":0,"parents":[12,10],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":21,"col":7},"properties":{"shieldCharges":2},"id":40},{"display_name":"Stormy Feet","desc":"Windy Feet will last longer and add more speed.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[9],"blockers":[],"cost":1,"display":{"row":23,"col":1},"properties":{"duration":60},"effects":[{"type":"stat_bonus","bonuses":[{"type":"stat","name":"spdPct","value":20}]}],"id":41},{"display_name":"Refined Gunpowder","desc":"Increase the damage of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[11],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Bomb","cost":0,"multipliers":[50,0,0,0,0,0]}],"id":42},{"display_name":"More Traps","desc":"Increase the maximum amount of active Traps you can have by +2.","archetype":"Trapper","archetype_req":10,"parents":[54],"dependencies":[10],"blockers":[],"cost":1,"display":{"row":26,"col":8},"properties":{"traps":2},"id":43},{"display_name":"Better Arrow Shield","desc":"Arrow Shield will gain additional area of effect, knockback and damage.","archetype":"Sharpshooter","archetype_req":0,"parents":[19,18,14],"dependencies":[0],"blockers":[],"cost":1,"display":{"row":28,"col":6},"properties":{"aoe":1},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Arrow Shield","multipliers":[40,0,0,0,0,0]}],"id":44},{"display_name":"Better Leap","desc":"Reduce leap's cooldown by 1s.","archetype":"Boltslinger","archetype_req":0,"parents":[17,55],"dependencies":[17],"blockers":[],"cost":1,"display":{"row":29,"col":1},"properties":{"cooldown":-1},"id":45},{"display_name":"Better Guardian Angels","desc":"Your Guardian Angels can shoot +4 arrows before disappearing.","archetype":"Boltslinger","archetype_req":0,"parents":[20,55],"dependencies":[8],"blockers":[],"cost":1,"display":{"row":31,"col":2},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Single Bow","cost":0,"hits":{"Single Arrow":4}}],"id":46},{"display_name":"Cheaper Arrow Storm (2)","desc":"Reduce the Mana cost of Arrow Storm.","archetype":"","archetype_req":0,"parents":[21,19],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":8},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":47},{"display_name":"Precise Shot","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[46,49,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":2},"properties":{"mainAtk_range":6},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdCritPct","value":30}]}],"id":48},{"display_name":"Cheaper Arrow Shield","desc":"Reduce the Mana cost of Arrow Shield.","archetype":"","archetype_req":0,"parents":[48,21],"dependencies":[],"blockers":[],"cost":1,"display":{"row":33,"col":4},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":49},{"display_name":"Rocket Jump","desc":"Arrow Bomb's self-damage will knockback you farther away.","archetype":"","archetype_req":0,"parents":[47,21],"dependencies":[2],"blockers":[],"cost":1,"display":{"row":33,"col":6},"properties":{},"id":50},{"display_name":"Cheaper Escape (2)","desc":"Reduce the Mana cost of Escape.","archetype":"","archetype_req":0,"parents":[22,70],"dependencies":[],"blockers":[],"cost":1,"display":{"row":34,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":51},{"display_name":"Stronger Hook","desc":"Increase your Grappling Hook's range, speed and strength.","archetype":"Trapper","archetype_req":5,"parents":[51],"dependencies":[12],"blockers":[],"cost":1,"display":{"row":35,"col":8},"properties":{"range":8},"id":52},{"display_name":"Cheaper Arrow Bomb (2)","desc":"Reduce the Mana cost of Arrow Bomb.","archetype":"","archetype_req":0,"parents":[63,30],"dependencies":[],"blockers":[],"cost":1,"display":{"row":40,"col":5},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":53},{"display_name":"Bouncing Bomb","desc":"Arrow Bomb will bounce once when hitting a block or enemy","archetype":"","archetype_req":0,"parents":[40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":25,"col":7},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Arrow Bomb":2}}],"id":54},{"display_name":"Homing Shots","desc":"Your Main Attack arrows will follow nearby enemies and not be affected by gravity","archetype":"","archetype_req":0,"parents":[17,18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2},"properties":{},"effects":[],"id":55},{"display_name":"Shrapnel Bomb","desc":"Arrow Bomb's explosion will fling 15 shrapnel, dealing damage in a large area","archetype":"Boltslinger","archetype_req":8,"parents":[23,48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Shrapnel Bomb","cost":0,"multipliers":[40,0,0,0,20,0]}],"id":56},{"display_name":"Elusive","desc":"If you do not get hit for 8+ seconds, become immune to self-damage and remove Arrow Storm's recoil. (Dodging counts as not getting hit)","archetype":"Boltslinger","archetype_req":0,"parents":[24],"dependencies":[],"blockers":[],"cost":2,"display":{"row":38,"col":0},"properties":{},"effects":[],"id":57},{"display_name":"Double Shots","desc":"Double Main Attack arrows, but they deal -30% damage per arrow (harder to hit far enemies)","archetype":"Boltslinger","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[60],"cost":1,"display":{"row":7,"col":2},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":58},{"display_name":"Triple Shots","desc":"Triple Main Attack arrows, but they deal -20% damage per arrow","archetype":"Boltslinger","archetype_req":0,"parents":[69,67],"dependencies":[58],"blockers":[],"cost":1,"display":{"row":17,"col":0},"properties":{"arrow":2},"effects":[{"type":"add_spell_prop","base_spell":0,"target_part":"Melee Damage","cost":0,"multipliers":0.7}],"id":59},{"display_name":"Power Shots","desc":"Main Attack arrows have increased speed and knockback","archetype":"Sharpshooter","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[58],"cost":1,"display":{"row":7,"col":6},"properties":{},"effects":[],"id":60},{"display_name":"Focus","desc":"When hitting an aggressive mob 5+ blocks away, gain +1 Focus (Max 3). Resets if you miss once","archetype":"Sharpshooter","archetype_req":2,"parents":[68],"dependencies":[],"blockers":[],"cost":2,"display":{"row":19,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[3],"max":3}],"id":61},{"display_name":"More Focus","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[33,12],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[35],"max":5}],"id":62},{"display_name":"More Focus (2)","desc":"Add +2 max Focus","archetype":"Sharpshooter","archetype_req":0,"parents":[25,28],"dependencies":[],"blockers":[],"cost":1,"display":{"row":39,"col":4},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Focus","output":{"type":"stat","abil_name":"Focus","name":"damMult"},"scaling":[35],"max":7}],"id":63},{"display_name":"Traveler","desc":"For every 1% Walk Speed you have from items, gain +1 Raw Spell Damage (Max 100)","archetype":"","archetype_req":0,"parents":[42,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":25,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":false,"inputs":[{"type":"stat","name":"spd"}],"output":{"type":"stat","name":"sdRaw"},"scaling":[1],"max":100}],"id":64},{"display_name":"Patient Hunter","desc":"Your Traps will deal +20% more damage for every second they are active (Max +80%)","archetype":"Trapper","archetype_req":0,"parents":[40],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":8},"properties":{"max":80},"effects":[],"id":65},{"display_name":"Stronger Patient Hunter","desc":"Add +80% Max Damage to Patient Hunter","archetype":"Trapper","archetype_req":0,"parents":[26],"dependencies":[65],"blockers":[],"cost":1,"display":{"row":38,"col":8},"properties":{"max":80},"effects":[],"id":66},{"display_name":"Frenzy","desc":"Every time you hit an enemy, briefly gain +6% Walk Speed (Max 200%). Decay -40% of the bonus every second","archetype":"Boltslinger","archetype_req":0,"parents":[59,6],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":2},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Hits dealt","output":{"type":"stat","name":"spd"},"scaling":[6],"max":200}],"id":67},{"display_name":"Phantom Ray","desc":"Condense Arrow Storm into a single ray that damages enemies 10 times per second","archetype":"Sharpshooter","archetype_req":0,"parents":[37,4],"dependencies":[7],"blockers":[11,6,23],"cost":2,"display":{"row":16,"col":4},"properties":{},"effects":[{"type":"replace_spell","name":"Phantom Ray","cost":40,"display_text":"Max Damage","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Arrow","type":"damage","multipliers":[25,0,5,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Arrow":16}}]}],"id":68},{"display_name":"Arrow Rain","desc":"When Arrow Shield loses its last charge, unleash 200 arrows raining down on enemies","archetype":"Trapper","archetype_req":0,"parents":[6,38],"dependencies":[0],"blockers":[],"cost":2,"display":{"row":15,"col":0},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Arrow Rain","cost":0,"multipliers":[120,0,0,0,0,80]}],"id":69},{"display_name":"Decimator","desc":"Phantom Ray will increase its damage by 10% everytime you do not miss with it (Max 50%)","archetype":"Sharpshooter","archetype_req":0,"parents":[49],"dependencies":[68],"blockers":[],"cost":1,"display":{"row":34,"col":5},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Phantom Ray hits","output":{"type":"stat","name":"PhRayDmg"},"scaling":10,"max":50}],"id":70}],"Warrior":[{"display_name":"Bash","desc":"Violently bash the ground, dealing high damage in a large area","archetype":"","archetype_req":0,"parents":[],"dependencies":[],"blockers":[],"cost":1,"display":{"row":0,"col":4,"icon":"node_4"},"properties":{"aoe":4,"range":3},"effects":[{"type":"replace_spell","name":"Bash","cost":45,"display_text":"Total Damage Average","base_spell":1,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"Single Hit","type":"damage","multipliers":[130,20,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Single Hit":1}}]}],"id":0},{"display_name":"Spear Proficiency 1","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[0],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":4,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":1},{"display_name":"Cheaper Bash","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[],"cost":1,"display":{"row":2,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-10}],"id":2},{"display_name":"Double Bash","desc":"Bash will hit a second time at a farther range","archetype":"","archetype_req":0,"parents":[1],"dependencies":[],"blockers":[],"cost":1,"display":{"row":4,"col":4,"icon":"node_1"},"properties":{"range":3},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"name":"Single Hit","value":1}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-50,0,0,0,0,0]}],"id":3},{"display_name":"Charge","desc":"Charge forward at high speed (hold shift to cancel)","archetype":"","archetype_req":0,"parents":[3],"dependencies":[],"blockers":[],"cost":1,"display":{"row":6,"col":4,"icon":"node_4"},"properties":{},"effects":[{"type":"replace_spell","name":"Charge","cost":25,"display_text":"Total Damage Average","base_spell":2,"spell_type":"damage","scaling":"spell","display":"Total Damage","parts":[{"name":"None","type":"damage","multipliers":[0,0,0,0,0,0]},{"name":"Total Damage","type":"total","hits":{"None":0}}]}],"id":4},{"display_name":"Heavy Impact","desc":"After using Charge, violently crash down into the ground and deal damage","archetype":"","archetype_req":0,"parents":[8],"dependencies":[],"blockers":[],"cost":1,"display":{"row":9,"col":1,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Heavy Impact","cost":0,"multipliers":[100,0,0,0,0,0]}],"id":5},{"display_name":"Vehement","desc":"For every 1% or 1 Raw Main Attack Damage you have from items, gain +2% Walk Speed (Max 20%)","archetype":"Fallen","archetype_req":0,"parents":[4],"dependencies":[],"blockers":[7],"cost":1,"display":{"row":6,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":false,"inputs":[{"type":"stat","name":"mdPct"},{"type":"stat","name":"mdRaw"}],"output":{"type":"stat","name":"spd"},"scaling":[1,1],"max":20}],"id":6},{"display_name":"Tougher Skin","desc":"Harden your skin and become permanently +5% more resistant\nFor every 1% or 1 Raw Heath Regen you have from items, gain +10 Health (Max 100)","archetype":"Paladin","archetype_req":0,"parents":[4],"dependencies":[],"blockers":[6],"cost":1,"display":{"row":6,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":"5"}]},{"type":"stat_scaling","slider":false,"inputs":[{"type":"stat","name":"hprRaw"},{"type":"stat","name":"hprPct"}],"output":{"type":"stat","name":"hpBonus"},"scaling":[10,10],"max":100}],"id":7},{"display_name":"Uppercut","desc":"Rocket enemies in the air and deal massive damage","archetype":"","archetype_req":0,"parents":[6],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":2,"icon":"node_4"},"properties":{"aoe":3,"range":5},"effects":[{"type":"replace_spell","name":"Uppercut","cost":45,"display_text":"Total Damage Average","base_spell":3,"spell_type":"damage","scaling":"spell","display":"total","parts":[{"name":"Uppercut","type":"damage","multipliers":[150,50,50,0,0,0]},{"name":"Total Damage","type":"total","hits":{"Uppercut":1}}]}],"id":8},{"display_name":"Cheaper Charge","desc":"Reduce the Mana cost of Charge","archetype":"","archetype_req":0,"parents":[8,10],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5}],"id":9},{"display_name":"War Scream","desc":"Emit a terrorizing roar that deals damage, pull nearby enemies, and add damage resistance to yourself and allies","archetype":"","archetype_req":0,"parents":[7],"dependencies":[],"blockers":[],"cost":1,"display":{"row":8,"col":6,"icon":"node_4"},"properties":{"duration":30,"aoe":12,"defense_bonus":10},"effects":[{"type":"replace_spell","name":"War Scream","cost":35,"display_text":"War Scream","base_spell":4,"spell_type":"damage","scaling":"spell","display":"Total Damage Average","parts":[{"name":"War Scream","type":"damage","multipliers":[50,0,0,0,50,0]}]}],"id":10},{"display_name":"Earth Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Fallen","archetype_req":0,"parents":[8],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"eDamPct","value":20},{"type":"stat","name":"eDam","value":[2,4]}]}],"id":11},{"display_name":"Thunder Mastery","desc":"Increases base damage from all Thunder attacks","archetype":"Fallen","archetype_req":0,"parents":[8,14,9],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"tDamPct","value":10},{"type":"stat","name":"tDam","value":[1,8]}]}],"id":12},{"display_name":"Water Mastery","desc":"Increases base damage from all Water attacks","archetype":"Battle Monk","archetype_req":0,"parents":[9,12,14],"dependencies":[],"blockers":[],"cost":1,"display":{"row":11,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"wDamPct","value":15},{"type":"stat","name":"wDam","value":[2,4]}]}],"id":13},{"display_name":"Air Mastery","desc":"Increases base damage from all Air attacks","archetype":"Battle Monk","archetype_req":0,"parents":[10,12,9],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"aDamPct","value":15},{"type":"stat","name":"aDam","value":[3,4]}]}],"id":14},{"display_name":"Fire Mastery","desc":"Increases base damage from all Earth attacks","archetype":"Paladin","archetype_req":0,"parents":[10],"dependencies":[],"blockers":[],"cost":1,"display":{"row":10,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"fDamPct","value":15},{"type":"stat","name":"fDam","value":[3,5]}]}],"id":15},{"display_name":"Quadruple Bash","desc":"Bash will hit 4 times at an even larger range","archetype":"Fallen","archetype_req":0,"parents":[11,17],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":0,"icon":"node_1"},"properties":{"range":6},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Total Damage","cost":0,"hits":{"Single Hit":2}},{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[-20,0,0,0,0,0]}],"id":16},{"display_name":"Fireworks","desc":"Mobs hit by Uppercut will explode mid-air and receive additional damage","archetype":"Fallen","archetype_req":0,"parents":[12,16],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Fireworks","cost":0,"multipliers":[80,0,20,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Fireworks":1}}],"id":17},{"display_name":"Half-Moon Swipe","desc":"Uppercut will deal a footsweep attack at a longer and wider angle. All elemental conversions become Water","archetype":"Battle Monk","archetype_req":1,"parents":[13],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":13,"col":4,"icon":"node_1"},"properties":{"range":4},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":-10,"multipliers":[-70,0,0,0,0,0]},{"type":"convert_spell_conv","target_part":"all","conversion":"water"}],"id":18},{"display_name":"Flyby Jab","desc":"Damage enemies in your way when using Charge","archetype":"","archetype_req":0,"parents":[14,20],"dependencies":[],"blockers":[],"cost":2,"display":{"row":12,"col":6,"icon":"node_1"},"properties":{"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flyby Jab","cost":0,"multipliers":[20,0,0,0,0,40]}],"id":19},{"display_name":"Flaming Uppercut","desc":"Uppercut will light mobs on fire, dealing damage every 0.6 seconds","archetype":"Paladin","archetype_req":0,"parents":[15,19],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":12,"col":8,"icon":"node_1"},"properties":{"duration":3,"tick":0.6},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut","cost":0,"multipliers":[0,0,0,0,50,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Flaming Uppercut Total Damage","cost":0,"hits":{"Flaming Uppercut":5}},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Flaming Uppercut":5}}],"id":20},{"display_name":"Iron Lungs","desc":"War Scream deals more damage","archetype":"","archetype_req":0,"parents":[19,20],"dependencies":[],"blockers":[],"cost":1,"display":{"row":13,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"War Scream","cost":0,"multipliers":[30,0,0,0,0,30]}],"id":21},{"display_name":"Generalist","desc":"After casting 3 different spells in a row, your next spell will cost 5 mana","archetype":"Battle Monk","archetype_req":3,"parents":[23],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":2,"icon":"node_3"},"properties":{},"effects":[],"id":22},{"display_name":"Counter","desc":"When dodging a nearby enemy attack, get 30% chance to instantly attack back","archetype":"Battle Monk","archetype_req":0,"parents":[18],"dependencies":[],"blockers":[],"cost":2,"display":{"row":15,"col":4,"icon":"node_1"},"properties":{"chance":30},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Counter","cost":0,"multipliers":[60,0,20,0,0,20]}],"id":23},{"display_name":"Mantle of the Bovemists","desc":"When casting War Scream, create a holy shield around you that reduces all incoming damage by 70% for 3 hits (20s cooldown)","archetype":"Paladin","archetype_req":3,"parents":[21],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":15,"col":7,"icon":"node_3"},"properties":{"mantle_charge":3},"effects":[],"id":24},{"display_name":"Bak'al's Grasp","desc":"After casting War Scream, become Corrupted (15s Cooldown). You cannot heal while in that state\n\nWhile Corrupted, every 2% of Health you lose will add +4 Raw Damage to your attacks (Max 120)","archetype":"Fallen","archetype_req":2,"parents":[16,17],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":16,"col":1,"icon":"node_3"},"properties":{"cooldown":15},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[4],"slider_step":2,"max":120}],"id":25},{"display_name":"Spear Proficiency 2","desc":"Improve your Main Attack's damage and range w/ spear","archetype":"","archetype_req":0,"parents":[25,27],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":0,"icon":"node_0"},"properties":{"melee_range":1},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"mdPct","value":5}]}],"id":26},{"display_name":"Cheaper Uppercut","desc":"Reduce the Mana Cost of Uppercut","archetype":"","archetype_req":0,"parents":[26,28,23],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":27},{"display_name":"Aerodynamics","desc":"During Charge, you can steer and change direction","archetype":"Battle Monk","archetype_req":0,"parents":[27,29],"dependencies":[],"blockers":[],"cost":2,"display":{"row":17,"col":5,"icon":"node_1"},"properties":{},"effects":[],"id":28},{"display_name":"Provoke","desc":"Mobs damaged by War Scream will target only you for at least 5s \n\nReduce the Mana cost of War Scream","archetype":"Paladin","archetype_req":0,"parents":[28,24],"dependencies":[],"blockers":[],"cost":1,"display":{"row":17,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":29},{"display_name":"Precise Strikes","desc":"+30% Critical Hit Damage","archetype":"","archetype_req":0,"parents":[27,26],"dependencies":[],"blockers":[],"cost":1,"display":{"row":18,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"critDmg","value":30}]}],"id":30},{"display_name":"Air Shout","desc":"War Scream will fire a projectile that can go through walls and deal damage multiple times","archetype":"","archetype_req":0,"parents":[28,29],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":18,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Air Shout","cost":0,"multipliers":[20,0,0,0,0,5]}],"id":31},{"display_name":"Enraged Blow","desc":"While Corriupted, every 1% of Health you lose will increase your damage by +2% (Max 200%)","archetype":"Fallen","archetype_req":0,"parents":[26],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":20,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":false,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"damMult"},"scaling":[3],"max":300}],"id":32},{"display_name":"Flying Kick","desc":"When using Charge, mobs hit will halt your momentum and get knocked back","archetype":"Battle Monk","archetype_req":1,"parents":[27,34],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":3,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Flying Kick","cost":0,"multipliers":[120,0,0,10,0,20]}],"id":33},{"display_name":"Stronger Mantle","desc":"Add +2 additional charges to Mantle of the Bovemists","archetype":"Paladin","archetype_req":0,"parents":[35,33],"dependencies":[24],"blockers":[],"cost":1,"display":{"row":20,"col":6,"icon":"node_0"},"properties":{"mantle_charge":2},"effects":[],"id":34},{"display_name":"Manachism","desc":"If you receive a hit that's less than 5% of your max HP, gain 10 Mana (1s Cooldown)","archetype":"Paladin","archetype_req":3,"parents":[34,29],"dependencies":[],"blockers":[],"cost":2,"display":{"row":20,"col":8,"icon":"node_2"},"properties":{"cooldown":1},"effects":[],"id":35},{"display_name":"Boiling Blood","desc":"Bash leaves a trail of boiling blood behind its first explosion, slowing down and damaging enemies above it every 0.4 seconds","archetype":"","archetype_req":0,"parents":[32,37],"dependencies":[],"blockers":[],"cost":2,"display":{"row":22,"col":0,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Boiling Blood","cost":0,"multipliers":[25,0,0,0,5,0]}],"id":36},{"display_name":"Ragnarokkr","desc":"War Scream become deafening, increasing its range and giving damage bonus to players","archetype":"Fallen","archetype_req":0,"parents":[36,33],"dependencies":[10],"blockers":[],"cost":2,"display":{"row":22,"col":2,"icon":"node_2"},"properties":{"damage_bonus":30,"aoe":2},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":10}],"id":37},{"display_name":"Ambidextrous","desc":"Increase your chance to attack with Counter by +30%","archetype":"","archetype_req":0,"parents":[33,34,39],"dependencies":[23],"blockers":[],"cost":1,"display":{"row":22,"col":4,"icon":"node_0"},"properties":{"chance":30},"effects":[],"id":38},{"display_name":"Burning Heart","desc":"For every 100 Health Bonus you have from item IDs, gain +2% Fire Damage (Max 100%)","archetype":"Paladin","archetype_req":0,"parents":[38,40],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","slider":false,"inputs":[{"type":"stat","name":"hpBonus"}],"output":{"type":"stat","name":"fDamPct"},"scaling":[2],"max":100,"slider_step":100}],"id":39},{"display_name":"Stronger Bash","desc":"Increase the damage of Bash","archetype":"","archetype_req":0,"parents":[39,35],"dependencies":[],"blockers":[],"cost":1,"display":{"row":22,"col":8,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"target_part":"Single Hit","cost":0,"multipliers":[30,0,0,0,0,0]}],"id":40},{"display_name":"Intoxicating Blood","desc":"After leaving Corrupted, gain 2% of the health lost back for each enemy killed while Corrupted","archetype":"Fallen","archetype_req":5,"parents":[37,36],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":23,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":41},{"display_name":"Comet","desc":"After being hit by Fireworks, enemies will crash into the ground and receive more damage","archetype":"Fallen","archetype_req":0,"parents":[37],"dependencies":[17],"blockers":[],"cost":2,"display":{"row":24,"col":2,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Comet","cost":0,"multipliers":[80,20,0,0,0,0]},{"type":"add_spell_prop","base_spell":3,"target_part":"Total Damage","cost":0,"hits":{"Comet":1}}],"id":42},{"display_name":"Collide","desc":"Mobs thrown into walls from Flying Kick will explode and receive additonal damage","archetype":"Battle Monk","archetype_req":4,"parents":[38,39],"dependencies":[33],"blockers":[],"cost":2,"display":{"row":23,"col":5,"icon":"node_1"},"properties":{"aoe":4},"effects":[{"type":"add_spell_prop","base_spell":2,"target_part":"Collide","cost":0,"multipliers":[100,0,0,0,50,0]}],"id":43},{"display_name":"Rejuvenating Skin","desc":"Regain back 30% of the damage you take as healing over 30s","archetype":"Paladin","archetype_req":0,"parents":[39,40],"dependencies":[],"blockers":[],"cost":2,"display":{"row":23,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":44},{"display_name":"Uncontainable Corruption","desc":"Reduce the cooldown of Bak'al's Grasp by -5s, and increase the raw damage gained for every 2% of health lost by +1","archetype":"","archetype_req":0,"parents":[36,46],"dependencies":[25],"blockers":[],"cost":1,"display":{"row":26,"col":0,"icon":"node_0"},"properties":{"cooldown":-5},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Corrupted","output":{"type":"stat","name":"raw"},"scaling":[1],"slider_step":2,"max":50}],"id":45},{"display_name":"Radiant Devotee","desc":"For every 4% Reflection you have from items, gain +1/5s Mana Regen (Max 10/5s)","archetype":"Battle Monk","archetype_req":1,"parents":[47,45],"dependencies":[],"blockers":[],"cost":1,"display":{"row":26,"col":2,"icon":"node_0"},"properties":{},"effects":[{"type":"stat_scaling","inputs":[{"type":"stat","name":"ref"}],"output":{"type":"stat","name":"mr"},"scaling":[1],"max":10,"slider_step":4}],"id":46},{"display_name":"Whirlwind Strike","desc":"Uppercut will create a strong gust of air, launching you upward with enemies (Hold shift to stay grounded)","archetype":"Battle Monk","archetype_req":5,"parents":[38,46],"dependencies":[8],"blockers":[],"cost":2,"display":{"row":26,"col":4,"icon":"node_1"},"properties":{"range":2},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":0,"multipliers":[0,0,0,0,0,50]}],"id":47},{"display_name":"Mythril Skin","desc":"Gain +5% Base Resistance and become immune to knockback","archetype":"Paladin","archetype_req":6,"parents":[44],"dependencies":[],"blockers":[],"cost":2,"display":{"row":26,"col":7,"icon":"node_1"},"properties":{},"effects":[{"type":"raw_stat","bonuses":[{"type":"stat","name":"baseResist","value":5}]}],"id":48},{"display_name":"Armour Breaker","desc":"While Corrupted, losing 30% Health will make your next Uppercut destroy enemies' defense, rendering them weaker to damage","archetype":"Fallen","archetype_req":0,"parents":[45,46],"dependencies":[25],"blockers":[],"cost":2,"display":{"row":27,"col":1,"icon":"node_2"},"properties":{"duration":5},"effects":[],"id":49},{"display_name":"Shield Strike","desc":"When your Mantle of the Bovemist loses all charges, deal damage around you for each Mantle individually lost","archetype":"Paladin","archetype_req":0,"parents":[48,51],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":6,"icon":"node_1"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Shield Strike","cost":0,"multipliers":[60,0,20,0,0,0]}],"id":50},{"display_name":"Sparkling Hope","desc":"Everytime you heal 5% of your max health, deal damage to all nearby enemies","archetype":"Paladin","archetype_req":0,"parents":[48],"dependencies":[],"blockers":[],"cost":2,"display":{"row":27,"col":8,"icon":"node_2"},"properties":{"aoe":6},"effects":[{"type":"add_spell_prop","base_spell":5,"target_part":"Sparkling Hope","cost":0,"multipliers":[10,0,5,0,0,0]}],"id":51},{"display_name":"Massive Bash","desc":"While Corrupted, every 3% Health you lose will add +1 AoE to Bash (Max 10)","archetype":"Fallen","archetype_req":8,"parents":[53,45],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":0,"icon":"node_2"},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Corrupted","output":{"type":"stat","name":"bashAoE"},"scaling":[1],"max":10,"slider_step":3}],"id":52},{"display_name":"Tempest","desc":"War Scream will ripple the ground and deal damage 3 times in a large area","archetype":"Battle Monk","archetype_req":0,"parents":[52,54],"dependencies":[],"blockers":[],"cost":2,"display":{"row":28,"col":2,"icon":"node_1"},"properties":{"aoe":16},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest","cost":"0","multipliers":[30,10,0,0,0,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Tempest Total Damage","cost":"0","hits":{"Tempest":3}},{"type":"add_spell_prop","base_spell":4,"target_part":"Total Damage","cost":"0","hits":{"Tempest":3}}],"id":53},{"display_name":"Spirit of the Rabbit","desc":"Reduce the Mana cost of Charge and increase your Walk Speed by +20%","archetype":"Battle Monk","archetype_req":5,"parents":[53,47],"dependencies":[],"blockers":[],"cost":1,"display":{"row":28,"col":4,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":2,"cost":-5},{"type":"raw_stat","bonuses":[{"type":"stat","name":"spd","value":20}]}],"id":54},{"display_name":"Massacre","desc":"While Corrupted, if your effective attack speed is Slow or lower, hitting an enemy with your Main Attack will add +1% to your Corrupted bar","archetype":"Fallen","archetype_req":5,"parents":[53,52],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":1,"icon":"node_1"},"properties":{},"effects":[],"id":55},{"display_name":"Axe Kick","desc":"Increase the damage of Uppercut, but also increase its mana cost","archetype":"","archetype_req":0,"parents":[53,54],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":3,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"target_part":"Uppercut","cost":10,"multipliers":[100,0,0,0,0,0]}],"id":56},{"display_name":"Radiance","desc":"Bash will buff your allies' positive IDs. (15s Cooldown)","archetype":"Paladin","archetype_req":2,"parents":[54,58],"dependencies":[],"blockers":[],"cost":2,"display":{"row":29,"col":5,"icon":"node_2"},"properties":{"cooldown":15},"effects":[],"id":57},{"display_name":"Cheaper Bash 2","desc":"Reduce the Mana cost of Bash","archetype":"","archetype_req":0,"parents":[57,50,51],"dependencies":[],"blockers":[],"cost":1,"display":{"row":29,"col":7,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":1,"cost":-5}],"id":58},{"display_name":"Cheaper War Scream","desc":"Reduce the Mana cost of War Scream","archetype":"","archetype_req":0,"parents":[52],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":0,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":4,"cost":-5}],"id":59},{"display_name":"Discombobulate","desc":"Every time you hit an enemy, briefly increase your elemental damage dealt to them by +2 (Additive, Max +50). This bonus decays -5 every second","archetype":"Battle Monk","archetype_req":11,"parents":[62],"dependencies":[],"blockers":[],"cost":2,"display":{"row":31,"col":2,"icon":"node_3"},"properties":{},"effects":[{"type":"stat_scaling","slider":true,"slider_name":"Hits dealt","output":{"type":"stat","name":"rainrawButDifferent"},"scaling":[2],"max":50}],"id":60},{"display_name":"Thunderclap","desc":"Bash will cast at the player's position and gain additional AoE.\n\n All elemental conversions become Thunder","archetype":"Battle Monk","archetype_req":8,"parents":[62],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":5,"icon":"node_1"},"properties":{},"effects":[{"type":"convert_spell_conv","target_part":"all","conversion":"thunder"},{"type":"raw_stat","bonuses":[{"type":"prop","abil_name":"Bash","name":"aoe","value":3}]}],"id":61},{"display_name":"Cyclone","desc":"After casting War Scream, envelop yourself with a vortex that damages nearby enemies every 0.5s","archetype":"Battle Monk","archetype_req":0,"parents":[54],"dependencies":[],"blockers":[],"cost":1,"display":{"row":31,"col":4,"icon":"node_1"},"properties":{"aoe":4,"duration":20},"effects":[{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone","cost":0,"multipliers":[10,0,0,0,5,10]},{"type":"add_spell_prop","base_spell":4,"target_part":"Cyclone Total Damage","cost":0,"hits":{"Cyclone":40}}],"id":62},{"display_name":"Second Chance","desc":"When you receive a fatal blow, survive and regain 30% of your Health (10m Cooldown)","archetype":"Paladin","archetype_req":12,"parents":[58],"dependencies":[],"blockers":[],"cost":2,"display":{"row":32,"col":7,"icon":"node_3"},"properties":{},"effects":[],"id":63},{"display_name":"Blood Pact","desc":"If you do not have enough mana to cast a spell, spend health instead (1% health per mana)","archetype":"","archetype_req":10,"parents":[59],"dependencies":[],"blockers":[],"cost":2,"display":{"row":34,"col":1,"icon":"node_3"},"properties":{},"effects":[],"id":64},{"display_name":"Haemorrhage","desc":"Reduce Blood Pact's health cost. (0.5% health per mana)","archetype":"Fallen","archetype_req":0,"parents":[64],"dependencies":[64],"blockers":[],"cost":1,"display":{"row":35,"col":2,"icon":"node_1"},"properties":{},"effects":[],"id":65},{"display_name":"Brink of Madness","desc":"If your health is 25% full or less, gain +40% Resistance","archetype":"","archetype_req":0,"parents":[64,67],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":4,"icon":"node_2"},"properties":{},"effects":[],"id":66},{"display_name":"Cheaper Uppercut 2","desc":"Reduce the Mana cost of Uppercut","archetype":"","archetype_req":0,"parents":[63,66],"dependencies":[],"blockers":[],"cost":1,"display":{"row":35,"col":6,"icon":"node_0"},"properties":{},"effects":[{"type":"add_spell_prop","base_spell":3,"cost":-5}],"id":67},{"display_name":"Martyr","desc":"When you receive a fatal blow, all nearby allies become invincible","archetype":"Paladin","archetype_req":0,"parents":[63],"dependencies":[],"blockers":[],"cost":2,"display":{"row":35,"col":8,"icon":"node_1"},"properties":{"duration":3,"aoe":12},"effects":[],"id":68}]} \ No newline at end of file From 815cbfacd18b46ba96a995d6354dd0349ef90055 Mon Sep 17 00:00:00 2001 From: reschan Date: Tue, 28 Jun 2022 13:04:18 +0700 Subject: [PATCH 126/155] accept js atree directly and output usable js file and json --- py_script/atree-generateID.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/py_script/atree-generateID.py b/py_script/atree-generateID.py index d8d6419..9fcfece 100644 --- a/py_script/atree-generateID.py +++ b/py_script/atree-generateID.py @@ -1,14 +1,16 @@ """ -Generate a JSON Ability Tree [atree_constants_id.json] with: +Generate a minified JSON Ability Tree [atree_constants_min.json] AND a minified .js form [atree_constants_min.js] of the Ability Tree with: - All references replaced by numerical IDs - Extra JSON File with Class: [Original name as key and Assigned IDs as value]. -given a JSON Ability Tree with reference as string. +given [atree_constants.js] .js form of the Ability Tree with reference as string. """ import json abilDict = {} -with open("atree_constants.json") as f: - data = json.loads(f.read()) +with open("atree_constants.js") as f: + data = f.read() + data = data.replace("const atrees = ", "") + data = json.loads(data) for classType, info in data.items(): _id = 0 abilDict[classType] = {} @@ -31,5 +33,10 @@ with open("atree_constants.json") as f: for ref in range(len(info[abil]["blockers"])): info[abil]["blockers"][ref] = abilDict[classType][info[abil]["blockers"][ref]] - with open('atree_constants_id.json', 'w', encoding='utf-8') as abil_dest: - json.dump(data, abil_dest, ensure_ascii=False, indent=4) + data_str = json.dumps(data, ensure_ascii=False, separators=(',', ':')) + data_str = "const atrees=" + data_str + with open('atree_constants_min.js', 'w', encoding='utf-8') as abil_dest: + abil_dest.write(data_str) + + with open('atree_constants_min.json', 'w', encoding='utf-8') as json_dest: + json.dump(data, json_dest, ensure_ascii=False, separators=(',', ':')) From 881467cf0be4de58590946a3b638dba3ad68e9e5 Mon Sep 17 00:00:00 2001 From: reschan Date: Tue, 28 Jun 2022 14:10:25 +0700 Subject: [PATCH 127/155] fix: atree img display relatively to parent --- builder/index.html | 4 ++-- js/atree.js | 34 ++++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/builder/index.html b/builder/index.html index 2c3ceda..101c64d 100644 --- a/builder/index.html +++ b/builder/index.html @@ -618,10 +618,10 @@