fixed merge conflicts

This commit is contained in:
ferricles 2022-06-26 10:25:57 -07:00
commit f854889846
120 changed files with 9576 additions and 4083 deletions

1438
builder/doc.html Normal file

File diff suppressed because it is too large Load diff

View 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>

View file

@ -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>

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

12
dev/compute_graph.svg Executable file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -892,12 +892,73 @@
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>
</body>
</html>
</html>

View file

@ -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>

View file

@ -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
View 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
}

View file

@ -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];
};
};

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -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,15 +189,7 @@ 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")) {
@ -193,6 +198,8 @@ class Build{
}
}
}
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
statMap.set("activeMajorIDs", major_ids);
for (const [setName, count] of this.activeSetCounts) {
const bonus = sets.get(setName).bonuses[count-1];
@ -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")]);
}
}

View file

@ -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!";
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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();
})();

View file

@ -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);
}
// 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 );
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];
}
//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];
}
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];
}
//console.log(damages);
//console.log(damageMult);
rawModifier *= spellMultiplier * damageMultiplier;
let totalDamNorm = [0, 0];
let totalDamCrit = [0, 0];
let damages_results = [];
// 0th skillpoint is strength, 1st is dex.
let str = total_skillpoints[0];
let strBoost = 1 + skillPointsToPercentage(str);
if(!melee){
let baseDam = rawModifier * strBoost;
let baseDamCrit = rawModifier * (1 + strBoost);
totalDamNorm = [baseDam, baseDam];
totalDamCrit = [baseDamCrit, baseDamCrit];
}
let staticBoost = (pctModifier / 100.);
let skillBoost = [0];
for (let i in total_skillpoints) {
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get(skp_elements[i]+"DamPct") / 100.);
}
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": [

View file

@ -64,4 +64,4 @@ function toggleSection(section) {
}
init_dev();
init_dev();

View file

@ -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 = [];

View file

@ -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()

View file

@ -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);

View file

@ -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;

View file

@ -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];

View file

@ -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
View 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");
})();

View file

@ -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.");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

View file

@ -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).

View file

@ -1,3 +1,5 @@
#parses all CI and creates a json file with all of them
import os
import re

View file

@ -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
View 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)

View file

@ -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"))

View file

@ -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
View 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+"))

View file

@ -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
}

View file

@ -1,3 +1,7 @@
"""
Used for grabbing image files at some point. Not used recently.
"""
import os
import json

View file

@ -1,4 +1,8 @@
"""Json diff checker for manual testing."""
"""
Json diff checker for manual testing - mainly debug
"""
import argparse
import json

View file

@ -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

View file

@ -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]

View file

@ -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))

View file

@ -1,3 +1,7 @@
"""
Generates data for dps_vis
"""
import matplotlib.pyplot as plt
import json
import numpy as np

View file

@ -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)

View 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)

View 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)

View 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.')

File diff suppressed because one or more lines are too long

View file

@ -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)

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -1,14 +0,0 @@
{
"items": [
"Beachside Headwrap",
"Beachside Conch"
],
"bonuses": [
{},
{
"lb": 20,
"wDamPct": 35,
"wDefPct": 25
}
]
}

View file

@ -1,15 +0,0 @@
{
"items": [
"Bear Mask",
"Bear Head",
"Bear Body"
],
"bonuses": [
{},
{
"mdPct": 14,
"hpBonus": 30,
"mdRaw": 20
}
]
}

View file

@ -1,19 +0,0 @@
{
"items": [
"Black Catalyst"
],
"bonuses": [
{
"xpb": -5
},
{
"mr": 1,
"sdPct": 10,
"xpb": 30,
"expd": 10,
"hpBonus": 325,
"spRegen": 10,
"sdRaw": 90
}
]
}

View file

@ -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
}
]
}

View file

@ -1,14 +0,0 @@
{
"items": [
"Blue Team Boots",
"Blue Team Leggings",
"Blue Team Chestplate",
"Blue Team Helmet"
],
"bonuses": [
{},
{},
{},
{}
]
}

View file

@ -1,14 +0,0 @@
{
"items": [
"Bony Circlet",
"Bony Bow"
],
"bonuses": [
{},
{
"agi": 8,
"mdRaw": 45,
"aDamPct": 15
}
]
}

View file

@ -1,20 +0,0 @@
{
"items": [
"Builder's Helmet",
"Builder's Boots",
"Builder's Trousers",
"Builder's Breastplate"
],
"bonuses": [
{},
{
"xpb": 5
},
{
"xpb": 10
},
{
"xpb": 15
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -1,20 +0,0 @@
{
"items": [
"GM's Helmet",
"GM's Boots",
"GM's Trousers",
"GM's Breastplate"
],
"bonuses": [
{},
{
"xpb": 5
},
{
"xpb": 10
},
{
"xpb": 15
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -1,14 +0,0 @@
{
"items": [
"Treat",
"Trick"
],
"bonuses": [
{},
{
"xpb": 15,
"spRegen": 10,
"eSteal": 5
}
]
}

View file

@ -1,16 +0,0 @@
{
"items": [
"Horse Mask",
"Horse Hoof"
],
"bonuses": [
{},
{
"mdPct": 11,
"xpb": 10,
"spd": 20,
"aDamPct": 15,
"eDamPct": 15
}
]
}

View file

@ -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
}
]
}

View file

@ -1,17 +0,0 @@
{
"items": [
"Kaerynn's Mind",
"Kaerynn's Body"
],
"bonuses": [
{},
{
"mr": 2,
"xpb": 12,
"str": 4,
"hpBonus": 400,
"sdRaw": 100,
"mdRaw": 50
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -1,13 +0,0 @@
{
"items": [
"Pigman Helmet",
"Pigman Battle Hammer"
],
"bonuses": [
{},
{
"str": 20,
"eDamPct": 40
}
]
}

View file

@ -1,14 +0,0 @@
{
"items": [
"Red Team Boots",
"Red Team Leggings",
"Red Team Chestplate",
"Red Team Helmet"
],
"bonuses": [
{},
{},
{},
{}
]
}

View file

@ -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
}
]
}

View file

@ -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
}
]
}

View file

@ -1,17 +0,0 @@
{
"items": [
"Silverfish Helm",
"Silverfish Boots"
],
"bonuses": [
{
"spd": 5
},
{
"agi": 10,
"thorns": 20,
"spd": 20,
"poison": 290
}
]
}

View file

@ -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
}
]
}

View file

@ -1,17 +0,0 @@
{
"items": [
"Slime Boots",
"Slime Plate"
],
"bonuses": [
{},
{
"hprPct": 35,
"thorns": 15,
"spd": -6,
"poison": 300,
"hpBonus": 600,
"jh": 1
}
]
}

View file

@ -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
}
]
}

Some files were not shown because too many files have changed in this diff Show more