fixed merge conflicts
1438
builder/doc.html
Normal file
|
@ -406,6 +406,8 @@
|
|||
</div>
|
||||
<div class="col text-center">
|
||||
<div id="summary-box"></div>
|
||||
<div id="err-box"></div>
|
||||
<div id="stack-box"></div>
|
||||
<div id="str-warnings"></div>
|
||||
<div id="dex-warnings"></div>
|
||||
<div id="int-warnings"></div>
|
||||
|
@ -1420,7 +1422,7 @@
|
|||
<script type="text/javascript" src="../js/build.js"></script>
|
||||
<script type="text/javascript" src="../js/build_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/build_encode_decode.js"></script>
|
||||
<script type="text/javascript" src="../js/display_atree.js"></script>
|
||||
<script type="text/javascript" src="../js/atree.js"></script>
|
||||
<script type="text/javascript" src="../js/builder.js"></script>
|
||||
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||
|
|
|
@ -301,8 +301,6 @@
|
|||
<script type="text/javascript" src="../js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="../js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/display.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="../js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="../js/load.js"></script>
|
||||
<script type="text/javascript" src="../js/craft.js"></script>
|
||||
|
|
|
@ -474,6 +474,11 @@ a:hover {
|
|||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.rotate-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.hide-scroll {
|
||||
|
@ -483,3 +488,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%;
|
||||
}
|
BIN
dev/builder_colorcode.png
Executable file
After Width: | Height: | Size: 178 KiB |
12
dev/compute_graph.svg
Executable file
After Width: | Height: | Size: 63 KiB |
|
@ -892,9 +892,70 @@
|
|||
Last updated: 30 May 2022
|
||||
</p>
|
||||
</div>
|
||||
<div class="row section" title="Wynnbuilder Internals (compute graph)">
|
||||
<p>
|
||||
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).
|
||||
</p>
|
||||
<div class="row section" title="Why?">
|
||||
<p>
|
||||
Modeling wynnbuilder's internal computations as a directed graph has a few advantages:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>Each compute "node" is small(er); easier to debug.</li>
|
||||
<li>Information flow is specified explicitly (easier to debug).</li>
|
||||
<li>Easy to build caching for arbitrary computations (only calculate what u need)</li>
|
||||
<li>Stateless builder! Abstract the entire builder as a chain of function calls</li>
|
||||
<li>Makes for pretty pictures</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row section" title="TODO ComputeNode details">
|
||||
TODO
|
||||
</div>
|
||||
<p>
|
||||
An overview of wynnbuilder's internal structure can be seen <a href = "./compute_graph.svg" target = "_blank">here</a>. Arrows indicate flow of information.
|
||||
Colors correspond roughly as follows:
|
||||
</p>
|
||||
<img src="./builder_colorcode.png"/>
|
||||
<p>
|
||||
The overall logic flow is as follows:
|
||||
<ul class = "indent">
|
||||
<li>Item and Powder inputs are parsed. Powders are applied to items.</li>
|
||||
<li>Items and level information are combined to make a build.</li>
|
||||
<li>Information from input fields for skill points and edit IDs is collected into an ID bonuses table.</li>
|
||||
<li>Information about active powder specials, strength boosts, etc. are collected into their own ID tables.</li>
|
||||
<li>All of the above tables are merged with the build's stats table to produce the "Final" ID bonus table.</li>
|
||||
<li>Which spell variant (think: major id) to use for each of the 4 spells is computed based on the build.</li>
|
||||
<li>Spell damage is calculated, using the merged stat table, spell info, and weapon info.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
Outputs are computed as follows:
|
||||
<ul class = "indent">
|
||||
<li>Input box highlights are computed from the items produced by item input box nodes.</li>
|
||||
<li>Item display is computed from item input boxes.</li>
|
||||
<li>Build hash/URL is computed from the build, and skillpoint assignment.</li>
|
||||
<li>Spell damage is displayed based on calculated spell damage results.</li>
|
||||
<li>Build stats are displayed by builder-stats-display (this same node also displays a bunch of stuff at the bottom of the screen...)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="row section" title="Gotchas">
|
||||
<p>
|
||||
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).
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
For example another soft link (not shown) is used to implement the reset button.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="row section" title="Test Section">
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/dev.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2icons.js"></script>
|
||||
|
|
|
@ -62,8 +62,6 @@
|
|||
<script type="text/javascript" src="/js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="/js/item.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -79,8 +79,6 @@
|
|||
<script type="text/javascript" src="/js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="/js/query_2.js"></script>
|
||||
<script type="text/javascript" src="/js/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
|
|
137
js/atree-ids.json
Normal file
|
@ -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
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
let atree_map;
|
||||
let atree_head;
|
||||
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
|
||||
|
@ -51,7 +53,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.id, {display: i.display, parents: i.parents, connectors: new Map(), active: false});
|
||||
}
|
||||
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
|
@ -60,8 +62,13 @@ 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);
|
||||
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) {
|
||||
|
@ -89,9 +96,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.id).connectors.set(parent, []);
|
||||
|
||||
let parent_node = atree_map.get(parent);
|
||||
|
||||
let connect_elem = document.createElement("div");
|
||||
|
@ -100,8 +108,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.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
|
||||
|
@ -111,8 +119,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.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);
|
||||
}
|
||||
|
||||
|
@ -121,8 +129,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.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");
|
||||
}
|
||||
|
@ -134,17 +142,29 @@ 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";
|
||||
}
|
||||
let node_img = document.createElement('img');
|
||||
node_img.src = '../media/atree/' + icon + '.png';
|
||||
node_img.style = 'width: 100%; height:100%';
|
||||
// node_elem.style = "background-image: url('../media/atree/"+icon+".png'); background-size: cover; width: 100%; height: 100%;";
|
||||
node_elem.style = 'background-size: cover; width: 100%; height:100%; padding: 8%;'
|
||||
node_elem.appendChild(node_img);
|
||||
node_elem.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) {
|
||||
if (e.target !== this) {return;}
|
||||
let tooltip = this.children[0];
|
||||
tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px";
|
||||
tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .2 / 2) + "px";
|
||||
tooltip.style.display = "block";
|
||||
});
|
||||
|
||||
node_elem.addEventListener('mouseout', function(e) {
|
||||
if (e.target !== this) {return;}
|
||||
let tooltip = this.children[0];
|
||||
tooltip.style.display = "none";
|
||||
});
|
||||
|
||||
node_elem.classList.add("fake-button");
|
||||
|
||||
let active_tooltip = document.createElement('div');
|
||||
|
@ -173,7 +193,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";
|
||||
|
@ -183,7 +203,7 @@ function construct_AT(elem, tree) {
|
|||
|
||||
node_elem.addEventListener('click', function(e) {
|
||||
if (e.target !== this && e.target!== this.children[0]) {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");
|
||||
|
@ -193,9 +213,10 @@ function construct_AT(elem, tree) {
|
|||
else {
|
||||
tooltip.style.display = "block";
|
||||
this.classList.add("atree-selected");
|
||||
this.style.backgroundImage = 'url("../media/atree/node_highlight.png")';
|
||||
document.getElementById("active_AP_cost").textContent = parseInt(document.getElementById("active_AP_cost").textContent) + node.cost;
|
||||
}
|
||||
};
|
||||
atree_toggle_state(node);
|
||||
atree_update_connector();
|
||||
});
|
||||
|
||||
// add tooltip
|
||||
|
@ -220,51 +241,176 @@ 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;}
|
||||
|
||||
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)
|
||||
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 (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)
|
||||
}
|
||||
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) {
|
||||
if (atree_map.get(node.id).active) {
|
||||
atree_map.get(node.id).active = false;
|
||||
} else {
|
||||
atree_map.get(node.id).active = true;
|
||||
};
|
||||
};
|
||||
|
||||
// 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) {
|
||||
v[0].connector.style.backgroundImage = "url('../media/atree/connect_" + v[0].type + ".png')";
|
||||
}
|
||||
});
|
||||
atree_map.forEach((v) => {
|
||||
atree_compute_highlight(v);
|
||||
});
|
||||
}
|
||||
|
||||
// set the correct connector highlight for an active node, given a node.
|
||||
function atree_compute_highlight(node) {
|
||||
node.connectors.forEach((v, k) => {
|
||||
if (node.active && atree_map.get(k).active) {
|
||||
for (let i of v) {
|
||||
connector_data = atree_connectors_map.get(i)[0];
|
||||
if (connector_data.type == "c" || connector_data.type == "t") {
|
||||
connector_data.connector_state = atree_get_state(i);
|
||||
let connector_img = atree_parse_connector(connector_data.connector_state, connector_data.type);
|
||||
connector_data.connector.className = "";
|
||||
connector_data.connector.classList.add("rotate-" + connector_img.rotate);
|
||||
connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + connector_img.attrib + ".png')";
|
||||
} else {
|
||||
connector_data.connector.style.backgroundImage = "url('../media/atree/highlight_" + connector_data.type + ".png')";
|
||||
};
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// get the current active state of different directions, given a connector coordinate.
|
||||
function atree_get_state(connector) {
|
||||
let connector_state = [0, 0, 0, 0]; // left, right, up, down
|
||||
|
||||
for (let abil_name of atree_connectors_map.get(connector)[0].owner) {
|
||||
|
||||
state = atree_map.get(abil_name).active;
|
||||
if (atree_map.get(abil_name).display.col > parseInt(connector.split(",")[1])) {
|
||||
if (state) {
|
||||
connector_state[1] = 1;
|
||||
} else if (!connector_state[1]) {
|
||||
connector_state[1] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (atree_map.get(abil_name).display.col < parseInt(connector.split(",")[1])) {
|
||||
if (state) {
|
||||
connector_state[0] = 1;
|
||||
} else if (!connector_state[0]) {
|
||||
connector_state[0] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (atree_map.get(abil_name).display.row < parseInt(connector.split(",")[0])) {
|
||||
if (state) {
|
||||
connector_state[2] = 1;
|
||||
} else if (!connector_state[2]) {
|
||||
connector_state[2] = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (atree_map.get(abil_name).display.row > parseInt(connector.split(",")[0])) {
|
||||
if (state) {
|
||||
connector_state[3] = 1;
|
||||
} else if (!connector_state[3]) {
|
||||
connector_state[3] = 0;
|
||||
};
|
||||
continue;
|
||||
};
|
||||
};
|
||||
return connector_state;
|
||||
}
|
||||
|
||||
// parse a sequence of left, right, up, down to appropriate connector image
|
||||
function atree_parse_connector(orient, type) {
|
||||
// left, right, up, down
|
||||
let c_connector_dict = {
|
||||
"1100": {attrib: "_2_l", rotate: 0},
|
||||
"1010": {attrib: "_2_a", rotate: 0},
|
||||
"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}
|
||||
};
|
||||
|
||||
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 of orient) {
|
||||
res += i;
|
||||
};
|
||||
|
||||
if (type == "c") {
|
||||
return c_connector_dict[res];
|
||||
} else {
|
||||
return t_connector_dict[res];
|
||||
};
|
||||
};
|
4160
js/atree_constants_str_old.js
Normal file
1
js/atree_constants_str_old_min.js
Normal file
35
js/build.js
|
@ -153,16 +153,29 @@ 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",
|
||||
"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);
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
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();
|
||||
|
@ -176,23 +189,17 @@ class Build{
|
|||
}
|
||||
for (const staticID of staticIDs) {
|
||||
if (item_stats.get(staticID)) {
|
||||
if (staticID === "dmgMobs") {
|
||||
statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID));
|
||||
}
|
||||
else if (staticID === "defMobs") {
|
||||
statMap.set('defMultiplier', statMap.get('defMultiplier') * item_stats.get(staticID));
|
||||
}
|
||||
else {
|
||||
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item_stats.get("majorIds")) {
|
||||
for (const major_id of item_stats.get("majorIds")) {
|
||||
major_ids.add(major_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
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];
|
||||
|
@ -211,13 +218,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")]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
@ -66,13 +66,25 @@ 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
|
||||
];
|
||||
// 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", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include dmgMobs (wep tomes) and defMobs (armor tomes)
|
||||
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 damMobs (wep tomes) and defMobs (armor tomes)
|
||||
for (const [k, v] of translations) {
|
||||
reversetranslations.set(v, k);
|
||||
}
|
||||
|
@ -103,7 +115,19 @@ let nonRolledIDs = [
|
|||
"skillpoints",
|
||||
"reqs",
|
||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
||||
"majorIds"];
|
||||
"majorIds",
|
||||
"damMobs",
|
||||
"defMobs",
|
||||
// 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 +155,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" ];
|
||||
|
||||
|
@ -188,7 +221,6 @@ function expandItem(item) {
|
|||
}
|
||||
expandedItem.set("minRolls",minRolls);
|
||||
expandedItem.set("maxRolls",maxRolls);
|
||||
expandedItem.set("powders", []);
|
||||
return expandedItem;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -443,6 +442,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);
|
||||
|
|
|
@ -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,11 +153,15 @@ 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);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -198,14 +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' && powdering !== undefined) {
|
||||
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);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
@ -243,6 +229,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;
|
||||
}
|
||||
|
@ -267,10 +254,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) {
|
||||
|
@ -313,9 +308,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 +320,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 = 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")) {
|
||||
|
@ -522,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);
|
||||
|
@ -561,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');
|
||||
|
@ -577,9 +579,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
|
||||
|
@ -627,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'),
|
||||
|
@ -645,14 +650,12 @@ 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.
|
||||
}
|
||||
// 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];
|
||||
|
||||
|
@ -722,6 +725,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");
|
||||
|
@ -909,7 +913,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!!!
|
||||
|
@ -929,7 +932,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;
|
||||
|
@ -973,7 +976,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 +987,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'));
|
||||
|
@ -1057,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();
|
||||
|
@ -1074,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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,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;
|
||||
|
|
|
@ -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();
|
||||
})();
|
||||
|
|
|
@ -1,143 +1,177 @@
|
|||
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);
|
||||
function get_base_dps(item) {
|
||||
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||
//SUPER JANK @HPP PLS FIX
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
let total_damage = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage += damages[0] + damages[1];
|
||||
}
|
||||
|
||||
// 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();
|
||||
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
|
||||
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)];
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
// 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] += powder.min;
|
||||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
damages[0] = neutralRemainingRaw;
|
||||
|
||||
let damageMult = damageMultiplier;
|
||||
let melee = false;
|
||||
// If we are doing melee calculations:
|
||||
if (spellMultiplier == 0) {
|
||||
spellMultiplier = 1;
|
||||
melee = true;
|
||||
return total_damage * attack_speed_mult / 2;
|
||||
}
|
||||
else {
|
||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
||||
let total_damage_min = 0;
|
||||
let total_damage_max = 0;
|
||||
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];
|
||||
}
|
||||
//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];
|
||||
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];
|
||||
}
|
||||
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.);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
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]);
|
||||
}
|
||||
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;
|
||||
|
||||
return [totalDamNorm, totalDamCrit, damages_results];
|
||||
}
|
||||
|
||||
|
||||
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_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.
|
||||
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 damage_prefix = damage_elements[i] + specific_boost_str;
|
||||
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
|
||||
total_min += damages[i][0];
|
||||
total_max += damages[i][1];
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
damages_obj[1] = new_max;
|
||||
}
|
||||
|
||||
// 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");
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
const spell_table = {
|
||||
"wand": [
|
||||
|
|
|
@ -173,51 +173,9 @@ 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") {
|
||||
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") {
|
||||
}
|
||||
|
||||
|
@ -384,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") {
|
||||
|
@ -447,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");
|
||||
|
@ -555,19 +527,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 +1473,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 = [];
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -39,9 +48,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];
|
||||
|
|
131
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];
|
||||
}
|
||||
|
|
205
js/render_compute_graph.js
Normal file
|
@ -0,0 +1,205 @@
|
|||
// 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(/^<g/, '<svg');
|
||||
source = source.replace(/<\/g>$/, '</svg>');
|
||||
//add name spaces.
|
||||
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
|
||||
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
||||
}
|
||||
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
|
||||
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
|
||||
}
|
||||
|
||||
//add xml declaration
|
||||
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
|
||||
|
||||
//convert svg source to URI data scheme.
|
||||
var url = "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);
|
||||
|
||||
//set url value to a element's href attribute.
|
||||
document.getElementById(output_id).href = url;
|
||||
});
|
||||
}
|
||||
|
||||
d3.select("#graph_body")
|
||||
.append("div")
|
||||
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
|
||||
.append("svg")
|
||||
.attr("preserveAspectRatio", "xMinYMin meet")
|
||||
.classed("svg-content-responsive", true);
|
||||
let graph = d3.select("svg");
|
||||
let svg = graph.append('g');
|
||||
let margin = {top: 20, right: 20, bottom: 35, left: 40};
|
||||
|
||||
function bbox() {
|
||||
let ret = graph.node().parentNode.getBoundingClientRect();
|
||||
return ret;
|
||||
}
|
||||
let _bbox = bbox();
|
||||
|
||||
const colors = ['aqua', 'yellow', 'fuchsia', 'white', 'teal', 'olive', 'purple', 'gray', 'blue', 'lime', 'red', 'silver', 'navy', 'green', 'maroon'];
|
||||
const n_colors = colors.length;
|
||||
|
||||
const view = svg.append("rect")
|
||||
.attr("class", "view")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0);
|
||||
|
||||
|
||||
function convert_data(nodes_raw) {
|
||||
let edges = [];
|
||||
let node_id = new Map();
|
||||
nodes = [];
|
||||
for (let i in nodes_raw) {
|
||||
node_id.set(nodes_raw[i], i);
|
||||
nodes.push({id: i, color: 0, data: nodes_raw[i]});
|
||||
}
|
||||
for (const node of nodes_raw) {
|
||||
const to = node_id.get(node);
|
||||
for (const input of node.inputs) {
|
||||
const from = node_id.get(input);
|
||||
let name = input.name;
|
||||
let link_name = node.input_translation.get(name);
|
||||
edges.push({
|
||||
source: from,
|
||||
target: to,
|
||||
name: link_name
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: edges
|
||||
}
|
||||
}
|
||||
|
||||
function create_svg(data, redraw_func) {
|
||||
// Initialize the links
|
||||
var link = svg
|
||||
.selectAll("line")
|
||||
.data(data.links)
|
||||
.enter()
|
||||
.append("line")
|
||||
.style("stroke", "#aaa")
|
||||
|
||||
// Initialize the nodes
|
||||
let node = svg
|
||||
.selectAll("g")
|
||||
.data(data.nodes);
|
||||
|
||||
let node_enter = node.enter()
|
||||
.append('g')
|
||||
|
||||
let circles = node_enter.append("circle")
|
||||
.attr("r", 20)
|
||||
.style("fill", ({id, color, data}) => 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");
|
||||
|
||||
})();
|
109
js/utils.js
|
@ -390,3 +390,112 @@ function capitalizeFirst(str) {
|
|||
return str[0].toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
/** 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). Defaults to -1E5.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
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 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** 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.");
|
||||
}
|
||||
}
|
||||
|
||||
/** 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();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
throw new Error(msg ? msg : "Function didn't throw an error.");
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 962 B |
BIN
media/atree/highlight_c_2_a.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_c_2_l.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_c_3.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 632 B |
BIN
media/atree/highlight_t_2_a.png
Normal file
After Width: | Height: | Size: 708 B |
BIN
media/atree/highlight_t_2_l.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
media/atree/highlight_t_3.png
Normal file
After Width: | Height: | Size: 654 B |
|
@ -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).
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#parses all CI and creates a json file with all of them
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#looks like something that hpp does with curl
|
||||
|
||||
import os
|
||||
|
||||
with open("ci.txt.2") as infile:
|
||||
|
|
19
py_script/clean_json.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
'''
|
||||
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]
|
||||
'''
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
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)
|
|
@ -1,8 +1,18 @@
|
|||
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 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]
|
||||
'''
|
||||
|
||||
if __name__ == "__main__":
|
||||
import json
|
||||
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"))
|
||||
|
|
|
@ -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
|
65
py_script/get.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
"""
|
||||
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/
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
||||
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
|
||||
|
||||
#default to empty file output
|
||||
response = {}
|
||||
|
||||
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+"))
|
|
@ -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
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
"""
|
||||
Used for grabbing image files at some point. Not used recently.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
"""Json diff checker for manual testing."""
|
||||
"""
|
||||
Json diff checker for manual testing - mainly debug
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
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))
|
|
@ -1,3 +1,7 @@
|
|||
"""
|
||||
Generates data for dps_vis
|
||||
"""
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import json
|
||||
import numpy as np
|
||||
|
|
|
@ -1,15 +1,30 @@
|
|||
"""
|
||||
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
|
||||
import argparse
|
||||
|
||||
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())
|
||||
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 +161,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 +215,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)
|
|
@ -1,44 +1,35 @@
|
|||
"""
|
||||
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
|
||||
import argparse
|
||||
|
||||
with open("dump.json", "r") as infile:
|
||||
data = json.load(infile)
|
||||
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())
|
||||
|
||||
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 +132,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 +146,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 +182,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)
|
||||
|
59
py_script/process_recipes.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
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
|
||||
import argparse
|
||||
|
||||
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())
|
||||
recipes = recipe_data["recipes"]
|
||||
|
||||
if os.path.exists("recipe_map.json"):
|
||||
with open("recipe_map.json","r") as recipe_mapfile:
|
||||
recipe_map = json.load(recipe_mapfile)
|
||||
else:
|
||||
recipe_map = {recipe["name"]: i for i, recipe in enumerate(recipes)}
|
||||
|
||||
recipe_translate_mappings = {
|
||||
"level" : "lvl",
|
||||
"id" : "name",
|
||||
}
|
||||
recipe_delete_keys = [ #lol
|
||||
|
||||
]
|
||||
|
||||
for recipe in recipes:
|
||||
for key in recipe_delete_keys:
|
||||
if key in recipe:
|
||||
del recipe[key]
|
||||
for k, v in recipe_translate_mappings.items():
|
||||
if k in recipe:
|
||||
recipe[v] = recipe[k]
|
||||
del recipe[k]
|
||||
if not (recipe["name"] in recipe_map):
|
||||
recipe_map[recipe["name"]] = len(recipe_map)
|
||||
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)
|
||||
|
||||
#save recipe data
|
||||
with open(outfile, "w+") as out_file:
|
||||
json.dump(recipe_data, out_file)
|
|
@ -1,46 +0,0 @@
|
|||
|
||||
import os
|
||||
|
||||
with open("../recipes_compress.json", "r") as infile:
|
||||
recipe_data = json.loads(infile.read())
|
||||
recipes = recipe_data["recipes"]
|
||||
|
||||
if os.path.exists("recipe_map.json"):
|
||||
with open("recipe_map.json","r") as recipe_mapfile:
|
||||
recipe_map = json.load(recipe_mapfile)
|
||||
else:
|
||||
recipe_map = {recipe["name"]: i for i, recipe in enumerate(recipes)}
|
||||
|
||||
recipe_translate_mappings = {
|
||||
"level" : "lvl",
|
||||
"id" : "name",
|
||||
}
|
||||
recipe_delete_keys = [ #lol
|
||||
|
||||
]
|
||||
|
||||
print("loaded all files.")
|
||||
|
||||
for recipe in recipes:
|
||||
for key in recipe_delete_keys:
|
||||
if key in recipe:
|
||||
del recipe[key]
|
||||
for k, v in recipe_translate_mappings.items():
|
||||
if k in recipe:
|
||||
recipe[v] = recipe[k]
|
||||
del recipe[k]
|
||||
if not (recipe["name"] in recipe_map):
|
||||
recipe_map[recipe["name"]] = len(recipe_map)
|
||||
print(f'New Recipe: {recipe["name"]}')
|
||||
recipe["id"] = recipe_map[recipe["name"]]
|
||||
|
||||
|
||||
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.')
|
|
@ -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)
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Beachside Headwrap",
|
||||
"Beachside Conch"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"lb": 20,
|
||||
"wDamPct": 35,
|
||||
"wDefPct": 25
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Bear Mask",
|
||||
"Bear Head",
|
||||
"Bear Body"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"mdPct": 14,
|
||||
"hpBonus": 30,
|
||||
"mdRaw": 20
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Black Catalyst"
|
||||
],
|
||||
"bonuses": [
|
||||
{
|
||||
"xpb": -5
|
||||
},
|
||||
{
|
||||
"mr": 1,
|
||||
"sdPct": 10,
|
||||
"xpb": 30,
|
||||
"expd": 10,
|
||||
"hpBonus": 325,
|
||||
"spRegen": 10,
|
||||
"sdRaw": 90
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Blue Team Boots",
|
||||
"Blue Team Leggings",
|
||||
"Blue Team Chestplate",
|
||||
"Blue Team Helmet"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Bony Circlet",
|
||||
"Bony Bow"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"agi": 8,
|
||||
"mdRaw": 45,
|
||||
"aDamPct": 15
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Builder's Helmet",
|
||||
"Builder's Boots",
|
||||
"Builder's Trousers",
|
||||
"Builder's Breastplate"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"xpb": 5
|
||||
},
|
||||
{
|
||||
"xpb": 10
|
||||
},
|
||||
{
|
||||
"xpb": 15
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"GM's Helmet",
|
||||
"GM's Boots",
|
||||
"GM's Trousers",
|
||||
"GM's Breastplate"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"xpb": 5
|
||||
},
|
||||
{
|
||||
"xpb": 10
|
||||
},
|
||||
{
|
||||
"xpb": 15
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Treat",
|
||||
"Trick"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"xpb": 15,
|
||||
"spRegen": 10,
|
||||
"eSteal": 5
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Horse Mask",
|
||||
"Horse Hoof"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"mdPct": 11,
|
||||
"xpb": 10,
|
||||
"spd": 20,
|
||||
"aDamPct": 15,
|
||||
"eDamPct": 15
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Kaerynn's Mind",
|
||||
"Kaerynn's Body"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"mr": 2,
|
||||
"xpb": 12,
|
||||
"str": 4,
|
||||
"hpBonus": 400,
|
||||
"sdRaw": 100,
|
||||
"mdRaw": 50
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Pigman Helmet",
|
||||
"Pigman Battle Hammer"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"str": 20,
|
||||
"eDamPct": 40
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Red Team Boots",
|
||||
"Red Team Leggings",
|
||||
"Red Team Chestplate",
|
||||
"Red Team Helmet"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Silverfish Helm",
|
||||
"Silverfish Boots"
|
||||
],
|
||||
"bonuses": [
|
||||
{
|
||||
"spd": 5
|
||||
},
|
||||
{
|
||||
"agi": 10,
|
||||
"thorns": 20,
|
||||
"spd": 20,
|
||||
"poison": 290
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
"Slime Boots",
|
||||
"Slime Plate"
|
||||
],
|
||||
"bonuses": [
|
||||
{},
|
||||
{
|
||||
"hprPct": 35,
|
||||
"thorns": 15,
|
||||
"spd": -6,
|
||||
"poison": 300,
|
||||
"hpBonus": 600,
|
||||
"jh": 1
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|