From d4357d5d6b26068f092c07edca6902401680e0d0 Mon Sep 17 00:00:00 2001 From: ferricles Date: Sun, 12 Jun 2022 20:54:53 -0700 Subject: [PATCH 01/40] 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 02/40] 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 623037eda0394e4a04c4d969970ac928c3b65edb Mon Sep 17 00:00:00 2001 From: hppeng Date: Thu, 23 Jun 2022 22:33:17 -0700 Subject: [PATCH 03/40] 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 04/40] 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 39e6ade1421ef8e3c19e24fddaec270789f515cf Mon Sep 17 00:00:00 2001 From: hppeng Date: Fri, 24 Jun 2022 06:06:24 -0700 Subject: [PATCH 05/40] 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 06/40] 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 07/40] 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 08/40] 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 09/40] 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 10/40] 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 11/40] 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 12/40] 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 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 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 13/40] 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 ab0934b77da63c131686882dbf616b27db69e5fe Mon Sep 17 00:00:00 2001 From: ferricles Date: Sat, 25 Jun 2022 16:35:09 -0700 Subject: [PATCH 14/40] 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 15/40] 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 16/40] 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 056ff84972e98d815678566f74457cd368672845 Mon Sep 17 00:00:00 2001 From: hppeng Date: Sun, 26 Jun 2022 01:07:43 -0700 Subject: [PATCH 17/40] 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 92f4df365913e7cc1b6666075599010985c1c26d Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 26 Jun 2022 15:59:02 +0700 Subject: [PATCH 18/40] 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 19/40] 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 20/40] 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 21/40] 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 22/40] 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 23/40] 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 24/40] 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 25/40] 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 26/40] 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 27/40] 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 28/40] 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 29/40] 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 30/40] 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 31/40] 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 32/40] 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 33/40] 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 34/40] 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 35/40] 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 36/40] 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 37/40] 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 38/40] 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 39/40] 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 40/40] 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(/^