Merge branch 'atree' into UI_test
|
@ -1420,7 +1420,7 @@
|
||||||
<script type="text/javascript" src="../js/build.js"></script>
|
<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_constants.js"></script>
|
||||||
<script type="text/javascript" src="../js/build_encode_decode.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.js"></script>
|
||||||
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||||
|
@ -1433,7 +1433,6 @@
|
||||||
<a id="saveLink">savelink</a>
|
<a id="saveLink">savelink</a>
|
||||||
</div>
|
</div>
|
||||||
<script src="https://d3js.org/d3.v7.js"></script>
|
<script src="https://d3js.org/d3.v7.js"></script>
|
||||||
<script type="text/javascript" src="../js/d3_export.js"></script>
|
|
||||||
<script type="text/javascript" src="../js/render_compute_graph.js"></script>
|
<script type="text/javascript" src="../js/render_compute_graph.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -429,7 +429,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class = "col-3 py-2">
|
<div class = "col-3 py-2">
|
||||||
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
|
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
|
||||||
Show Ability Tree
|
Edit Abilities
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col-3 py-2">
|
<div class = "col-3 py-2">
|
||||||
|
@ -618,10 +618,14 @@
|
||||||
</div>
|
</div>
|
||||||
<div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;">
|
<div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;">
|
||||||
<div class="row row-cols-1 row-cols-xl-2">
|
<div class="row row-cols-1 row-cols-xl-2">
|
||||||
<div class="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 500px; overflow-y: auto;">
|
<div class="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 90vh; overflow-y: auto;">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col mx-auto" id="atree-active">
|
<div class="col mx-auto" style="height: 90vh; overflow-y: auto;" id="atree-rhs">
|
||||||
|
<div class="col mx-auto" style="height: 2em; overflow-y: auto;" id="atree-header">
|
||||||
|
</div>
|
||||||
|
<div class="col mx-auto" style="overflow-y: auto;" id="atree-active">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1264,22 +1268,24 @@
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div id="all-spells-display" class="row row-cols-1 gy-3 text-center scaled-font pe-0">
|
||||||
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell0-infoAvg">spell1</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell0-infoAvg">spell1</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell1-infoAvg">spell2</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell1-infoAvg">spell2</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell2-infoAvg">spell3</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell2-infoAvg">spell3</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell3-infoAvg">spell4</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell3-infoAvg">spell4</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1330,7 +1336,7 @@
|
||||||
<div class="col-12 dark-5 scaled-font">
|
<div class="col-12 dark-5 scaled-font">
|
||||||
<footer class="text-center">
|
<footer class="text-center">
|
||||||
<div id="header2">
|
<div id="header2">
|
||||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="credits">
|
<div id="credits">
|
||||||
|
@ -1422,7 +1428,7 @@
|
||||||
<script type="text/javascript" src="../js/build.js"></script>
|
<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_constants.js"></script>
|
||||||
<script type="text/javascript" src="../js/build_encode_decode.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.js"></script>
|
||||||
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||||
|
|
|
@ -282,7 +282,7 @@
|
||||||
<div class="col dark-5 scaled-font">
|
<div class="col dark-5 scaled-font">
|
||||||
<footer class="text-center">
|
<footer class="text-center">
|
||||||
<div id="header2">
|
<div id="header2">
|
||||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="credits">
|
<div id="credits">
|
||||||
|
@ -301,8 +301,6 @@
|
||||||
<script type="text/javascript" src="../js/damage_calc.js"></script>
|
<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_constants.js"></script>
|
||||||
<script type="text/javascript" src="../js/display.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_ing.js"></script>
|
||||||
<script type="text/javascript" src="../js/load.js"></script>
|
<script type="text/javascript" src="../js/load.js"></script>
|
||||||
<script type="text/javascript" src="../js/craft.js"></script>
|
<script type="text/javascript" src="../js/craft.js"></script>
|
||||||
|
|
20
credits.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
Theme, formatting, and overall inspiration: Wynndata (Dukio)
|
||||||
|
- https://wynndata.tk
|
||||||
|
|
||||||
|
The game, of course
|
||||||
|
- wynncraft.com
|
||||||
|
|
||||||
|
Additional Contributors, in no particular order:
|
||||||
|
- Kiocifer (Icons!)
|
||||||
|
- IncinerateMe (helping transition to 1.20.3 / CI helper)
|
||||||
|
- puppy (wynn2 ability tree help)
|
||||||
|
- SockMower (ability tree encode/decode optimization)
|
||||||
|
- ITechnically (coding emotional support / misc)
|
||||||
|
- touhoku (best IM)
|
||||||
|
- HeyZeer0 (huge help in getting our damage formulas right)
|
||||||
|
- Lennon (Skill point formula reversing)
|
||||||
|
- Phanta (WynnAtlas custom expression parser / item search)
|
||||||
|
- nbcss (Crafted Item mechanics reverse engineering)
|
||||||
|
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
|
||||||
|
- Atlas Inc discord (feedback, ideas, damage calc, etc)
|
|
@ -474,6 +474,11 @@ a:hover {
|
||||||
transform: rotate(270deg);
|
transform: rotate(270deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rotate-flip {
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.hide-scroll {
|
.hide-scroll {
|
||||||
|
@ -483,3 +488,13 @@ a:hover {
|
||||||
.hide-scroll::-webkit-scrollbar {
|
.hide-scroll::-webkit-scrollbar {
|
||||||
display: none; /* Safari and Chrome */
|
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%;
|
||||||
|
}
|
|
@ -62,8 +62,6 @@
|
||||||
<script type="text/javascript" src="/js/load_ing.js"></script>
|
<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_constants.js"></script>
|
||||||
<script type="text/javascript" src="/js/display.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>
|
<script type="text/javascript" src="/js/item.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -79,8 +79,6 @@
|
||||||
<script type="text/javascript" src="/js/damage_calc.js"></script>
|
<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_constants.js"></script>
|
||||||
<script type="text/javascript" src="/js/display.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/query_2.js"></script>
|
||||||
<script type="text/javascript" src="/js/expr_parser.js"></script>
|
<script type="text/javascript" src="/js/expr_parser.js"></script>
|
||||||
<script type="text/javascript" src="/js/load.js"></script>
|
<script type="text/javascript" src="/js/load.js"></script>
|
||||||
|
|
1026
js/atree.js
Normal file
|
@ -1,171 +0,0 @@
|
||||||
const atrees_old = {
|
|
||||||
"Assassin": [
|
|
||||||
{"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4},
|
|
||||||
{"title": "Dagger Proficiency I", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 2, "col": 3},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 3, "col": 4},
|
|
||||||
{"title": "Double Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 5, "col": 4},
|
|
||||||
{"title": "Dash", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 3},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 2},
|
|
||||||
{"title": "Smoke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 6},
|
|
||||||
{"title": "Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 3},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 8, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 0},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 2},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 6},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 8, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 8},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 1},
|
|
||||||
{"title": "Backstab", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 90, "row": 10, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 10, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 7},
|
|
||||||
{"title": "Fatality", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 11, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 0},
|
|
||||||
{"title": "Violent Vortex", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 11, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 2},
|
|
||||||
{"title": "Vanish", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 13, "col": 3},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 2},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 13, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 7},
|
|
||||||
{"title": "Lacerate", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 15, "col": 1},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 8},
|
|
||||||
{"title": "Wall of Smoke", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 16, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 0},
|
|
||||||
{"title": "Silent Killer", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 16, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 2},
|
|
||||||
{"title": "Shadow Travel", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 5},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 8},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 4},
|
|
||||||
{"title": "Exploding Clones", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 19, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 3},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 0},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 3},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 3},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 18, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 7},
|
|
||||||
{"title": "Weightless", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 20, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 20, "col": 2},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 20, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 21, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 8},
|
|
||||||
{"title": "Dancing Blade", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 21, "col": 0},
|
|
||||||
{"title": "Spin Attack Damage", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 21, "col": 3},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 3},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 22, "col": 1},
|
|
||||||
{"title": "Marked", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 5},
|
|
||||||
{"title": "Shurikens", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 8},
|
|
||||||
{"title": "Far Reach", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8},
|
|
||||||
{"title": "Stronger Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 5},
|
|
||||||
{"title": "Psithurism", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 1},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 1},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 3},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 5},
|
|
||||||
{"title": "Choke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 7},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 5},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 25, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 0},
|
|
||||||
{"title": "Death Magnet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 25, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 2},
|
|
||||||
{"title": "Cheaper Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 7},
|
|
||||||
{"title": "Parry", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 1},
|
|
||||||
{"title": "Fatal Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 3},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 3},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 27, "col": 6},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 6},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 27, "col": 8},
|
|
||||||
{"title": "Wall Jump", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 28, "col": 0},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 1},
|
|
||||||
{"title": "Harvester", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 4},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 7},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7 },
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 30, "col": 2},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 2 },
|
|
||||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 30, "col": 5},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 30, "col": 6},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 5},
|
|
||||||
{"title": "Ricochet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 1},
|
|
||||||
{"title": "Satsujin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 1},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 4},
|
|
||||||
{"title": "Forbidden Art", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 4},
|
|
||||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 7},
|
|
||||||
{"title": "Jasmine Bloom", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 7},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 32, "col": 0},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 0},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 2},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2},
|
|
||||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 5},
|
|
||||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8},
|
|
||||||
]
|
|
||||||
}
|
|
146
js/atree_ids.json
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
{
|
||||||
|
"Archer": {
|
||||||
|
"Arrow Shield": 0,
|
||||||
|
"Escape": 1,
|
||||||
|
"Arrow Bomb": 2,
|
||||||
|
"Heart Shatter": 3,
|
||||||
|
"Fire Creep": 4,
|
||||||
|
"Bryophyte Roots": 5,
|
||||||
|
"Nimble String": 6,
|
||||||
|
"Arrow Storm": 7,
|
||||||
|
"Guardian Angels": 8,
|
||||||
|
"Windy Feet": 9,
|
||||||
|
"Basaltic Trap": 10,
|
||||||
|
"Windstorm": 11,
|
||||||
|
"Grappling Hook": 12,
|
||||||
|
"Implosion": 13,
|
||||||
|
"Twain's Arc": 14,
|
||||||
|
"Fierce Stomp": 15,
|
||||||
|
"Scorched Earth": 16,
|
||||||
|
"Leap": 17,
|
||||||
|
"Shocking Bomb": 18,
|
||||||
|
"Mana Trap": 19,
|
||||||
|
"Escape Artist": 20,
|
||||||
|
"Initiator": 21,
|
||||||
|
"Call of the Hound": 22,
|
||||||
|
"Arrow Hurricane": 23,
|
||||||
|
"Geyser Stomp": 24,
|
||||||
|
"Crepuscular Ray": 25,
|
||||||
|
"Grape Bomb": 26,
|
||||||
|
"Tangled Traps": 27,
|
||||||
|
"Snow Storm": 28,
|
||||||
|
"All-Seeing Panoptes": 29,
|
||||||
|
"Minefield": 30,
|
||||||
|
"Bow Proficiency I": 31,
|
||||||
|
"Cheaper Arrow Bomb": 32,
|
||||||
|
"Cheaper Arrow Storm": 33,
|
||||||
|
"Cheaper Escape": 34,
|
||||||
|
"Earth Mastery": 35,
|
||||||
|
"Thunder Mastery": 36,
|
||||||
|
"Water Mastery": 37,
|
||||||
|
"Air Mastery": 38,
|
||||||
|
"Fire Mastery": 39,
|
||||||
|
"More Shields": 40,
|
||||||
|
"Stormy Feet": 41,
|
||||||
|
"Refined Gunpowder": 42,
|
||||||
|
"More Traps": 43,
|
||||||
|
"Better Arrow Shield": 44,
|
||||||
|
"Better Leap": 45,
|
||||||
|
"Better Guardian Angels": 46,
|
||||||
|
"Cheaper Arrow Storm (2)": 47,
|
||||||
|
"Precise Shot": 48,
|
||||||
|
"Cheaper Arrow Shield": 49,
|
||||||
|
"Rocket Jump": 50,
|
||||||
|
"Cheaper Escape (2)": 51,
|
||||||
|
"Stronger Hook": 52,
|
||||||
|
"Cheaper Arrow Bomb (2)": 53,
|
||||||
|
"Bouncing Bomb": 54,
|
||||||
|
"Homing Shots": 55,
|
||||||
|
"Shrapnel Bomb": 56,
|
||||||
|
"Elusive": 57,
|
||||||
|
"Double Shots": 58,
|
||||||
|
"Triple Shots": 59,
|
||||||
|
"Power Shots": 60,
|
||||||
|
"Focus": 61,
|
||||||
|
"More Focus": 62,
|
||||||
|
"More Focus (2)": 63,
|
||||||
|
"Traveler": 64,
|
||||||
|
"Patient Hunter": 65,
|
||||||
|
"Stronger Patient Hunter": 66,
|
||||||
|
"Frenzy": 67,
|
||||||
|
"Phantom Ray": 68,
|
||||||
|
"Arrow Rain": 69,
|
||||||
|
"Decimator": 70
|
||||||
|
},
|
||||||
|
"Warrior": {
|
||||||
|
"Bash": 0,
|
||||||
|
"Spear Proficiency 1": 1,
|
||||||
|
"Cheaper Bash": 2,
|
||||||
|
"Double Bash": 3,
|
||||||
|
"Charge": 4,
|
||||||
|
"Heavy Impact": 5,
|
||||||
|
"Vehement": 6,
|
||||||
|
"Tougher Skin": 7,
|
||||||
|
"Uppercut": 8,
|
||||||
|
"Cheaper Charge": 9,
|
||||||
|
"War Scream": 10,
|
||||||
|
"Earth Mastery": 11,
|
||||||
|
"Thunder Mastery": 12,
|
||||||
|
"Water Mastery": 13,
|
||||||
|
"Air Mastery": 14,
|
||||||
|
"Fire Mastery": 15,
|
||||||
|
"Quadruple Bash": 16,
|
||||||
|
"Fireworks": 17,
|
||||||
|
"Half-Moon Swipe": 18,
|
||||||
|
"Flyby Jab": 19,
|
||||||
|
"Flaming Uppercut": 20,
|
||||||
|
"Iron Lungs": 21,
|
||||||
|
"Generalist": 22,
|
||||||
|
"Counter": 23,
|
||||||
|
"Mantle of the Bovemists": 24,
|
||||||
|
"Bak'al's Grasp": 25,
|
||||||
|
"Spear Proficiency 2": 26,
|
||||||
|
"Cheaper Uppercut": 27,
|
||||||
|
"Aerodynamics": 28,
|
||||||
|
"Provoke": 29,
|
||||||
|
"Precise Strikes": 30,
|
||||||
|
"Air Shout": 31,
|
||||||
|
"Enraged Blow": 32,
|
||||||
|
"Flying Kick": 33,
|
||||||
|
"Stronger Mantle": 34,
|
||||||
|
"Manachism": 35,
|
||||||
|
"Boiling Blood": 36,
|
||||||
|
"Ragnarokkr": 37,
|
||||||
|
"Ambidextrous": 38,
|
||||||
|
"Burning Heart": 39,
|
||||||
|
"Stronger Bash": 40,
|
||||||
|
"Intoxicating Blood": 41,
|
||||||
|
"Comet": 42,
|
||||||
|
"Collide": 43,
|
||||||
|
"Rejuvenating Skin": 44,
|
||||||
|
"Uncontainable Corruption": 45,
|
||||||
|
"Radiant Devotee": 46,
|
||||||
|
"Whirlwind Strike": 47,
|
||||||
|
"Mythril Skin": 48,
|
||||||
|
"Armour Breaker": 49,
|
||||||
|
"Shield Strike": 50,
|
||||||
|
"Sparkling Hope": 51,
|
||||||
|
"Massive Bash": 52,
|
||||||
|
"Tempest": 53,
|
||||||
|
"Spirit of the Rabbit": 54,
|
||||||
|
"Massacre": 55,
|
||||||
|
"Axe Kick": 56,
|
||||||
|
"Radiance": 57,
|
||||||
|
"Cheaper Bash 2": 58,
|
||||||
|
"Cheaper War Scream": 59,
|
||||||
|
"Discombobulate": 60,
|
||||||
|
"Thunderclap": 61,
|
||||||
|
"Cyclone": 62,
|
||||||
|
"Second Chance": 63,
|
||||||
|
"Blood Pact": 64,
|
||||||
|
"Haemorrhage": 65,
|
||||||
|
"Brink of Madness": 66,
|
||||||
|
"Cheaper Uppercut 2": 67,
|
||||||
|
"Martyr": 68
|
||||||
|
}
|
||||||
|
}
|
35
js/build.js
|
@ -153,16 +153,29 @@ class Build{
|
||||||
*/
|
*/
|
||||||
initBuildStats(){
|
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
|
//Create a map of this build's stats
|
||||||
let statMap = new Map();
|
let statMap = new Map();
|
||||||
statMap.set("damageMultiplier", 1);
|
|
||||||
statMap.set("defMultiplier", 1);
|
statMap.set("defMultiplier", 1);
|
||||||
|
|
||||||
for (const staticID of staticIDs) {
|
for (const staticID of staticIDs) {
|
||||||
statMap.set(staticID, 0);
|
statMap.set(staticID, 0);
|
||||||
}
|
}
|
||||||
|
for (const staticID of must_ids) {
|
||||||
|
statMap.set(staticID, 0);
|
||||||
|
}
|
||||||
statMap.set("hp", levelToHPBase(this.level));
|
statMap.set("hp", levelToHPBase(this.level));
|
||||||
|
|
||||||
let major_ids = new Set();
|
let major_ids = new Set();
|
||||||
|
@ -176,23 +189,17 @@ class Build{
|
||||||
}
|
}
|
||||||
for (const staticID of staticIDs) {
|
for (const staticID of staticIDs) {
|
||||||
if (item_stats.get(staticID)) {
|
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));
|
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (item_stats.get("majorIds")) {
|
if (item_stats.get("majorIds")) {
|
||||||
for (const major_id of item_stats.get("majorIds")) {
|
for (const major_id of item_stats.get("majorIds")) {
|
||||||
major_ids.add(major_id);
|
major_ids.add(major_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
|
||||||
|
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
|
||||||
statMap.set("activeMajorIDs", major_ids);
|
statMap.set("activeMajorIDs", major_ids);
|
||||||
for (const [setName, count] of this.activeSetCounts) {
|
for (const [setName, count] of this.activeSetCounts) {
|
||||||
const bonus = sets.get(setName).bonuses[count-1];
|
const bonus = sets.get(setName).bonuses[count-1];
|
||||||
|
@ -211,13 +218,5 @@ class Build{
|
||||||
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
|
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
|
||||||
|
|
||||||
this.statMap = statMap;
|
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")]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ function parsePowdering(powder_info) {
|
||||||
return [powdering, powder_info];
|
return [powdering, powder_info];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let atree_data = null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Populate fields based on url, and calculate build.
|
* Populate fields based on url, and calculate build.
|
||||||
*/
|
*/
|
||||||
|
@ -62,7 +64,7 @@ function decodeBuild(url_tag) {
|
||||||
}
|
}
|
||||||
info[1] = info_str.slice(start_idx);
|
info[1] = info_str.slice(start_idx);
|
||||||
}
|
}
|
||||||
else if (version_number <= 6) {
|
else if (version_number <= 7) {
|
||||||
let info_str = info[1];
|
let info_str = info[1];
|
||||||
let start_idx = 0;
|
let start_idx = 0;
|
||||||
for (let i = 0; i < 9; ++i ) {
|
for (let i = 0; i < 9; ++i ) {
|
||||||
|
@ -101,7 +103,7 @@ function decodeBuild(url_tag) {
|
||||||
let powder_info = info[1].slice(10);
|
let powder_info = info[1].slice(10);
|
||||||
let res = parsePowdering(powder_info);
|
let res = parsePowdering(powder_info);
|
||||||
powdering = res[0];
|
powdering = res[0];
|
||||||
} else if (version_number <= 6){
|
} else if (version_number <= 7){
|
||||||
level = Base64.toInt(info[1].slice(10,12));
|
level = Base64.toInt(info[1].slice(10,12));
|
||||||
setValue("level-choice",level);
|
setValue("level-choice",level);
|
||||||
save_skp = true;
|
save_skp = true;
|
||||||
|
@ -117,7 +119,7 @@ function decodeBuild(url_tag) {
|
||||||
info[1] = res[1];
|
info[1] = res[1];
|
||||||
}
|
}
|
||||||
// Tomes.
|
// Tomes.
|
||||||
if (version == 6) {
|
if (version >= 6) {
|
||||||
//tome values do not appear in anything before v6.
|
//tome values do not appear in anything before v6.
|
||||||
for (let i in tomes) {
|
for (let i in tomes) {
|
||||||
let tome_str = info[1].charAt(i);
|
let tome_str = info[1].charAt(i);
|
||||||
|
@ -128,6 +130,14 @@ function decodeBuild(url_tag) {
|
||||||
info[1] = info[1].slice(7);
|
info[1] = info[1].slice(7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (version >= 7) {
|
||||||
|
// ugly af. only works since its the last thing. will be fixed with binary decode
|
||||||
|
atree_data = new BitVector(info[1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
atree_data = null;
|
||||||
|
}
|
||||||
|
|
||||||
for (let i in powder_inputs) {
|
for (let i in powder_inputs) {
|
||||||
setValue(powder_inputs[i], powdering[i]);
|
setValue(powder_inputs[i], powdering[i]);
|
||||||
}
|
}
|
||||||
|
@ -139,12 +149,13 @@ function decodeBuild(url_tag) {
|
||||||
|
|
||||||
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
||||||
*/
|
*/
|
||||||
function encodeBuild(build, powders, skillpoints) {
|
function encodeBuild(build, powders, skillpoints, atree, atree_state) {
|
||||||
|
|
||||||
if (build) {
|
if (build) {
|
||||||
let build_string;
|
let build_string;
|
||||||
|
|
||||||
//V6 encoding - Tomes
|
//V6 encoding - Tomes
|
||||||
|
//V7 encoding - ATree
|
||||||
build_version = 5;
|
build_version = 5;
|
||||||
build_string = "";
|
build_string = "";
|
||||||
tome_string = "";
|
tome_string = "";
|
||||||
|
@ -189,6 +200,12 @@ function encodeBuild(build, powders, skillpoints) {
|
||||||
}
|
}
|
||||||
build_string += tome_string;
|
build_string += tome_string;
|
||||||
|
|
||||||
|
if (atree_state.get(atree[0].ability.id).active) {
|
||||||
|
build_version = Math.max(build_version, 7);
|
||||||
|
const bitvec = encode_atree(atree, atree_state);
|
||||||
|
build_string += bitvec.toB64();
|
||||||
|
}
|
||||||
|
|
||||||
return build_version.toString() + "_" + build_string;
|
return build_version.toString() + "_" + build_string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,3 +233,58 @@ function shareBuild(build) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ability tree encode and decode functions
|
||||||
|
*
|
||||||
|
* Based on a traversal, basically only uses bits to represent the nodes that are on (and "dark" outgoing edges).
|
||||||
|
* credit: SockMower
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return: BitVector
|
||||||
|
*/
|
||||||
|
function encode_atree(atree, atree_state) {
|
||||||
|
let ret_vec = new BitVector(0, 0);
|
||||||
|
|
||||||
|
function traverse(head, atree_state, visited, ret) {
|
||||||
|
for (const child of head.children) {
|
||||||
|
if (visited.has(child.ability.id)) { continue; }
|
||||||
|
visited.set(child.ability.id, true);
|
||||||
|
if (atree_state.get(child.ability.id).active) {
|
||||||
|
ret.append(1, 1);
|
||||||
|
traverse(child, atree_state, visited, ret);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ret.append(0, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(atree[0], atree_state, new Map(), ret_vec);
|
||||||
|
return ret_vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return: List of active nodes
|
||||||
|
*/
|
||||||
|
function decode_atree(atree, bits) {
|
||||||
|
let i = 0;
|
||||||
|
let ret = [];
|
||||||
|
ret.push(atree[0]);
|
||||||
|
function traverse(head, visited, ret) {
|
||||||
|
for (const child of head.children) {
|
||||||
|
if (visited.has(child.ability.id)) { continue; }
|
||||||
|
visited.set(child.ability.id, true);
|
||||||
|
if (bits.read_bit(i)) {
|
||||||
|
i += 1;
|
||||||
|
ret.push(child);
|
||||||
|
traverse(child, visited, ret);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
traverse(atree[0], new Map(), ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
|
@ -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 classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"];
|
||||||
const wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "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 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");
|
//weaponTypes.push("sword");
|
||||||
//console.log(types)
|
//console.log(types)
|
||||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
||||||
|
@ -66,13 +66,26 @@ let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tom
|
||||||
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
||||||
let skpReqs = skp_order.map(x => x + "Req");
|
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
|
||||||
|
"critDamPct"
|
||||||
|
];
|
||||||
|
// 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" ]
|
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
|
//File reading for ID translations for JSON purposes
|
||||||
let reversetranslations = new Map();
|
let reversetranslations = new Map();
|
||||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||||
//does not include dmgMobs (wep tomes) and defMobs (armor tomes)
|
//does not include damMobs (wep tomes) and defMobs (armor tomes)
|
||||||
for (const [k, v] of translations) {
|
for (const [k, v] of translations) {
|
||||||
reversetranslations.set(v, k);
|
reversetranslations.set(v, k);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +116,19 @@ let nonRolledIDs = [
|
||||||
"skillpoints",
|
"skillpoints",
|
||||||
"reqs",
|
"reqs",
|
||||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
"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 = [
|
let rolledIDs = [
|
||||||
"hprPct",
|
"hprPct",
|
||||||
"mr",
|
"mr",
|
||||||
|
@ -131,13 +156,22 @@ let rolledIDs = [
|
||||||
"spPct2", "spRaw2",
|
"spPct2", "spRaw2",
|
||||||
"spPct3", "spRaw3",
|
"spPct3", "spRaw3",
|
||||||
"spPct4", "spRaw4",
|
"spPct4", "spRaw4",
|
||||||
"rainbowRaw",
|
"pDamRaw",
|
||||||
"sprint",
|
"sprint",
|
||||||
"sprintReg",
|
"sprintReg",
|
||||||
"jh",
|
"jh",
|
||||||
"lq",
|
"lq",
|
||||||
"gXp",
|
"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" ];
|
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||||
|
|
||||||
|
@ -188,7 +222,6 @@ function expandItem(item) {
|
||||||
}
|
}
|
||||||
expandedItem.set("minRolls",minRolls);
|
expandedItem.set("minRolls",minRolls);
|
||||||
expandedItem.set("maxRolls",maxRolls);
|
expandedItem.set("maxRolls",maxRolls);
|
||||||
expandedItem.set("powders", []);
|
|
||||||
return expandedItem;
|
return expandedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,20 +147,8 @@ function toggle_tab(tab) {
|
||||||
} else {
|
} else {
|
||||||
document.querySelector("#"+tab).style.display = "none";
|
document.querySelector("#"+tab).style.display = "none";
|
||||||
}
|
}
|
||||||
console.log(document.querySelector("#"+tab).style.display);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// toggle spell arrow
|
|
||||||
function toggle_spell_tab(tab) {
|
|
||||||
let arrow_img = document.querySelector("#" + "arrow_" + tab + "Avg");
|
|
||||||
if (document.querySelector("#"+tab).style.display == "none") {
|
|
||||||
document.querySelector("#"+tab).style.display = "";
|
|
||||||
arrow_img.src = arrow_img.src.replace("down", "up");
|
|
||||||
} else {
|
|
||||||
document.querySelector("#"+tab).style.display = "none";
|
|
||||||
arrow_img.src = arrow_img.src.replace("up", "down");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_boost_tab(tab) {
|
function toggle_boost_tab(tab) {
|
||||||
for (const i of skp_order) {
|
for (const i of skp_order) {
|
||||||
|
@ -397,16 +385,12 @@ function collapse_element(elmnt) {
|
||||||
document.querySelector(elmnt).style.removeProperty('display');
|
document.querySelector(elmnt).style.removeProperty('display');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Learn and use await
|
|
||||||
function init() {
|
function init() {
|
||||||
console.log("builder.js init");
|
console.log("builder.js init");
|
||||||
init_autocomplete();
|
init_autocomplete();
|
||||||
|
|
||||||
// Other "main" stuff
|
// Other "main" stuff
|
||||||
// Spell dropdowns
|
// Spell dropdowns
|
||||||
for (const i of spell_disp) {
|
|
||||||
document.querySelector("#"+i+"Avg").addEventListener("click", () => toggle_spell_tab(i));
|
|
||||||
}
|
|
||||||
for (const eq of equipment_keys) {
|
for (const eq of equipment_keys) {
|
||||||
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ function update_armor_powder_specials(elem_id) {
|
||||||
//update the label associated w/ the slider
|
//update the label associated w/ the slider
|
||||||
let elem = document.getElementById(elem_id);
|
let elem = document.getElementById(elem_id);
|
||||||
let label = document.getElementById(elem_id + "_label");
|
let label = document.getElementById(elem_id + "_label");
|
||||||
|
|
||||||
let value = elem.value;
|
let value = elem.value;
|
||||||
|
|
||||||
label.textContent = label.textContent.split(":")[0] + ": " + value
|
label.textContent = label.textContent.split(":")[0] + ": " + value
|
||||||
|
@ -89,23 +88,18 @@ let powder_special_input = new (class extends ComputeNode {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function updatePowderSpecials(buttonId) {
|
function updatePowderSpecials(buttonId) {
|
||||||
let name = (buttonId).split("-")[0];
|
let prefix = (buttonId).split("-")[0].replace(' ', '_') + '-';
|
||||||
let power = (buttonId).split("-")[1]; // [1, 5]
|
|
||||||
|
|
||||||
let elem = document.getElementById(buttonId);
|
let elem = document.getElementById(buttonId);
|
||||||
if (elem.classList.contains("toggleOn")) { //toggle the pressed button off
|
if (elem.classList.contains("toggleOn")) { elem.classList.remove("toggleOn"); }
|
||||||
elem.classList.remove("toggleOn");
|
else {
|
||||||
} else {
|
|
||||||
for (let i = 1;i < 6; i++) { //toggle all pressed buttons of the same powder special off
|
for (let i = 1;i < 6; i++) { //toggle all pressed buttons of the same powder special off
|
||||||
//name is same, power is i
|
//name is same, power is i
|
||||||
if(document.getElementById(name.replace(" ", "_") + "-" + i).classList.contains("toggleOn")) {
|
const elem2 = document.getElementById(prefix + i);
|
||||||
document.getElementById(name.replace(" ", "_") + "-" + i).classList.remove("toggleOn");
|
if(elem2.classList.contains("toggleOn")) { elem2.classList.remove("toggleOn"); }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//toggle the pressed button on
|
//toggle the pressed button on
|
||||||
elem.classList.add("toggleOn");
|
elem.classList.add("toggleOn");
|
||||||
}
|
}
|
||||||
|
|
||||||
powder_special_input.mark_dirty().update();
|
powder_special_input.mark_dirty().update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +126,7 @@ class PowderSpecialCalcNode extends ComputeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PowderSpecialDisplayNode extends ComputeNode {
|
class PowderSpecialDisplayNode extends ComputeNode {
|
||||||
|
// TODO: Refactor this entirely to be adding more spells to the spell list
|
||||||
constructor() {
|
constructor() {
|
||||||
super('builder-powder-special-display');
|
super('builder-powder-special-display');
|
||||||
this.fail_cb = true;
|
this.fail_cb = true;
|
||||||
|
@ -140,27 +135,11 @@ class PowderSpecialDisplayNode extends ComputeNode {
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
const powder_specials = input_map.get('powder-specials');
|
const powder_specials = input_map.get('powder-specials');
|
||||||
const stats = input_map.get('stats');
|
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);
|
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.
|
* Node for getting an item's stats from an item input field.
|
||||||
*
|
*
|
||||||
|
@ -177,6 +156,11 @@ class ItemInputNode extends InputNode {
|
||||||
constructor(name, item_input_field, none_item) {
|
constructor(name, item_input_field, none_item) {
|
||||||
super(name, item_input_field);
|
super(name, item_input_field);
|
||||||
this.none_item = new Item(none_item);
|
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);
|
this.none_item.statMap.set('NONE', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,14 +184,17 @@ class ItemInputNode extends InputNode {
|
||||||
item.statMap.set('powders', powdering);
|
item.statMap.set('powders', powdering);
|
||||||
}
|
}
|
||||||
let type_match;
|
let type_match;
|
||||||
if (this.none_item.statMap.get('category') === 'weapon') {
|
if (this.category == 'weapon') {
|
||||||
type_match = item.statMap.get('category') === 'weapon';
|
type_match = item.statMap.get('category') == 'weapon';
|
||||||
} else {
|
} 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 (type_match) {
|
||||||
if (item.statMap.get('category') === 'armor' && powdering !== undefined) {
|
if (item.statMap.get('category') == 'armor') {
|
||||||
applyArmorPowders(item.statMap, powdering);
|
applyArmorPowders(item.statMap);
|
||||||
|
}
|
||||||
|
else if (item.statMap.get('category') == 'weapon') {
|
||||||
|
apply_weapon_powders(item.statMap);
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
@ -245,6 +232,7 @@ class ItemInputDisplayNode extends ComputeNode {
|
||||||
this.input_field = document.getElementById(eq+"-choice");
|
this.input_field = document.getElementById(eq+"-choice");
|
||||||
this.health_field = document.getElementById(eq+"-health");
|
this.health_field = document.getElementById(eq+"-health");
|
||||||
this.level_field = document.getElementById(eq+"-lv");
|
this.level_field = document.getElementById(eq+"-lv");
|
||||||
|
this.powder_field = document.getElementById(eq+"-powder"); // possibly None
|
||||||
this.image = item_image;
|
this.image = item_image;
|
||||||
this.fail_cb = true;
|
this.fail_cb = true;
|
||||||
}
|
}
|
||||||
|
@ -269,10 +257,18 @@ class ItemInputDisplayNode extends ComputeNode {
|
||||||
this.input_field.classList.add("is-invalid");
|
this.input_field.classList.add("is-invalid");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (item.statMap.has('powders')) {
|
||||||
|
this.powder_field.placeholder = "powders";
|
||||||
|
}
|
||||||
|
|
||||||
if (item.statMap.has('NONE')) {
|
if (item.statMap.has('NONE')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.statMap.has('powders')) {
|
||||||
|
this.powder_field.placeholder = item.statMap.get('slots') + ' slots';
|
||||||
|
}
|
||||||
|
|
||||||
const tier = item.statMap.get('tier');
|
const tier = item.statMap.get('tier');
|
||||||
this.input_field.classList.add(tier);
|
this.input_field.classList.add(tier);
|
||||||
if (this.health_field) {
|
if (this.health_field) {
|
||||||
|
@ -315,9 +311,10 @@ class ItemDisplayNode extends ComputeNode {
|
||||||
*/
|
*/
|
||||||
class WeaponInputDisplayNode extends ComputeNode {
|
class WeaponInputDisplayNode extends ComputeNode {
|
||||||
|
|
||||||
constructor(name, image_field) {
|
constructor(name, image_field, dps_field) {
|
||||||
super(name);
|
super(name);
|
||||||
this.image = image_field;
|
this.image = image_field;
|
||||||
|
this.dps_field = dps_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
|
@ -326,20 +323,12 @@ class WeaponInputDisplayNode extends ComputeNode {
|
||||||
|
|
||||||
const type = item.statMap.get('type');
|
const type = item.statMap.get('type');
|
||||||
this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png');
|
this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png');
|
||||||
|
let dps = get_base_dps(item.statMap);
|
||||||
//as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff.
|
if (isNaN(dps)) {
|
||||||
if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) {
|
dps = dps[1];
|
||||||
toggle_tab('atree-dropdown');
|
if (isNaN(dps)) dps = 0;
|
||||||
toggleButton('toggle-atree');
|
|
||||||
}
|
|
||||||
|
|
||||||
//for some reason we have to cast to string
|
|
||||||
construct_AT(document.getElementById("atree-ui"), atrees[wep_to_class.get(type)]);
|
|
||||||
|
|
||||||
if (document.getElementById("toggle-atree").classList.contains("toggleOn")) {
|
|
||||||
toggle_tab('atree-dropdown');
|
|
||||||
toggleButton('toggle-atree');
|
|
||||||
}
|
}
|
||||||
|
this.dps_field.textContent = Math.round(dps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,6 +347,8 @@ class BuildEncodeNode extends ComputeNode {
|
||||||
|
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
const build = input_map.get('build');
|
const build = input_map.get('build');
|
||||||
|
const atree = input_map.get('atree');
|
||||||
|
const atree_state = input_map.get('atree-state');
|
||||||
let powders = [
|
let powders = [
|
||||||
input_map.get('helmet-powder'),
|
input_map.get('helmet-powder'),
|
||||||
input_map.get('chestplate-powder'),
|
input_map.get('chestplate-powder'),
|
||||||
|
@ -375,7 +366,7 @@ class BuildEncodeNode extends ComputeNode {
|
||||||
// TODO: grr global state for copy button..
|
// TODO: grr global state for copy button..
|
||||||
player_build = build;
|
player_build = build;
|
||||||
build_powders = powders;
|
build_powders = powders;
|
||||||
return encodeBuild(build, powders, skillpoints);
|
return encodeBuild(build, powders, skillpoints, atree, atree_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,32 +473,17 @@ class PowderInputNode extends InputNode {
|
||||||
* Signature: SpellSelectNode<int>(build: Build) => [Spell, SpellParts]
|
* Signature: SpellSelectNode<int>(build: Build) => [Spell, SpellParts]
|
||||||
*/
|
*/
|
||||||
class SpellSelectNode extends ComputeNode {
|
class SpellSelectNode extends ComputeNode {
|
||||||
constructor(spell_num) {
|
constructor(spell) {
|
||||||
super("builder-spell"+spell_num+"-select");
|
super("builder-spell"+spell.base_spell+"-select");
|
||||||
this.spell_idx = spell_num;
|
this.spell = spell;
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
const build = input_map.get('build');
|
const build = input_map.get('build');
|
||||||
|
|
||||||
const i = this.spell_idx;
|
|
||||||
let spell = spell_table[build.weapon.statMap.get("type")][i];
|
|
||||||
let stats = build.statMap;
|
let stats = build.statMap;
|
||||||
|
// TODO: apply major ids... DOOM.....
|
||||||
|
|
||||||
let spell_parts;
|
return [this.spell, this.spell.parts];
|
||||||
if (spell.parts) {
|
|
||||||
spell_parts = spell.parts;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
spell_parts = spell.variants.DEFAULT;
|
|
||||||
for (const majorID of stats.get("activeMajorIDs")) {
|
|
||||||
if (majorID in spell.variants) {
|
|
||||||
spell_parts = spell.variants[majorID];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [spell, spell_parts];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,7 +500,7 @@ function getDefenseStats(stats) {
|
||||||
defenseStats.push(totalHp);
|
defenseStats.push(totalHp);
|
||||||
//EHP
|
//EHP
|
||||||
let ehp = [totalHp, totalHp];
|
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[0] /= (1-def_pct)*(1-agi_pct)*defMult;
|
||||||
ehp[1] /= (1-def_pct)*defMult;
|
ehp[1] /= (1-def_pct)*defMult;
|
||||||
defenseStats.push(ehp);
|
defenseStats.push(ehp);
|
||||||
|
@ -563,8 +539,9 @@ class SpellDamageCalcNode extends ComputeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_func(input_map) {
|
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_info = input_map.get('spell-info');
|
||||||
|
const spell = spell_info[0];
|
||||||
const spell_parts = spell_info[1];
|
const spell_parts = spell_info[1];
|
||||||
const stats = input_map.get('stats');
|
const stats = input_map.get('stats');
|
||||||
const damage_mult = stats.get('damageMultiplier');
|
const damage_mult = stats.get('damageMultiplier');
|
||||||
|
@ -576,21 +553,67 @@ class SpellDamageCalcNode extends ComputeNode {
|
||||||
stats.get('agi')
|
stats.get('agi')
|
||||||
];
|
];
|
||||||
let spell_results = []
|
let spell_results = []
|
||||||
|
let spell_result_map = new Map();
|
||||||
|
const use_speed = (('use_atkspd' in spell) ? spell.use_atkspd : true);
|
||||||
|
const use_spell = (('scaling' in spell) ? spell.scaling === 'spell' : true);
|
||||||
|
|
||||||
|
// TODO: move preprocessing to separate node/node chain
|
||||||
for (const part of spell_parts) {
|
for (const part of spell_parts) {
|
||||||
if (part.type === "damage") {
|
let spell_result;
|
||||||
let results = calculateSpellDamage(stats, part.conversion,
|
if ('multipliers' in part) { // damage type spell
|
||||||
stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct"),
|
let results = calculateSpellDamage(stats, weapon, part.multipliers, use_spell, !use_speed);
|
||||||
part.multiplier / 100, weapon, skillpoints, damage_mult);
|
spell_result = {
|
||||||
spell_results.push(results);
|
type: "damage",
|
||||||
} else if (part.type === "heal") {
|
normal_min: results[2].map(x => x[0]),
|
||||||
// TODO: wynn2 formula
|
normal_max: results[2].map(x => x[1]),
|
||||||
let heal_amount = (part.strength * getDefenseStats(stats)[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2);
|
normal_total: results[0],
|
||||||
spell_results.push(heal_amount);
|
crit_min: results[2].map(x => x[2]),
|
||||||
} else if (part.type === "total") {
|
crit_max: results[2].map(x => x[3]),
|
||||||
// TODO: remove "total" type
|
crit_total: results[1],
|
||||||
spell_results.push(null);
|
|
||||||
}
|
}
|
||||||
|
} else if ('power' in part) {
|
||||||
|
// TODO: wynn2 formula
|
||||||
|
let _heal_amount = (part.power * getDefenseStats(stats)[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100)));
|
||||||
|
spell_result = {
|
||||||
|
type: "heal",
|
||||||
|
heal_amount: _heal_amount
|
||||||
|
}
|
||||||
|
} else if ('hits' in part) {
|
||||||
|
spell_result = {
|
||||||
|
normal_min: [0, 0, 0, 0, 0, 0],
|
||||||
|
normal_max: [0, 0, 0, 0, 0, 0],
|
||||||
|
normal_total: [0, 0],
|
||||||
|
crit_min: [0, 0, 0, 0, 0, 0],
|
||||||
|
crit_max: [0, 0, 0, 0, 0, 0],
|
||||||
|
crit_total: [0, 0],
|
||||||
|
heal_amount: 0
|
||||||
|
}
|
||||||
|
const dam_res_keys = ['normal_min', 'normal_max', 'normal_total', 'crit_min', 'crit_max', 'crit_total'];
|
||||||
|
for (const [subpart_name, hits] of Object.entries(part.hits)) {
|
||||||
|
const subpart = spell_result_map.get(subpart_name);
|
||||||
|
if (spell_result.type) {
|
||||||
|
if (subpart.type !== spell_result.type) {
|
||||||
|
throw "SpellCalc total subpart type mismatch";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
spell_result.type = subpart.type;
|
||||||
|
}
|
||||||
|
if (spell_result.type === 'damage') {
|
||||||
|
for (const key of dam_res_keys) {
|
||||||
|
for (let i in spell_result.normal_min) {
|
||||||
|
spell_result[key][i] += subpart[key][i] * hits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
spell_result.heal_amount += subpart.heal_amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spell_result.name = part.name;
|
||||||
|
spell_results.push(spell_result);
|
||||||
|
spell_result_map.set(part.name, spell_result);
|
||||||
}
|
}
|
||||||
return spell_results;
|
return spell_results;
|
||||||
}
|
}
|
||||||
|
@ -616,12 +639,11 @@ class SpellDisplayNode extends ComputeNode {
|
||||||
const spell_info = input_map.get('spell-info');
|
const spell_info = input_map.get('spell-info');
|
||||||
const damages = input_map.get('spell-damage');
|
const damages = input_map.get('spell-damage');
|
||||||
const spell = spell_info[0];
|
const spell = spell_info[0];
|
||||||
const spell_parts = spell_info[1];
|
|
||||||
|
|
||||||
const i = this.spell_idx;
|
const i = this.spell_idx;
|
||||||
let parent_elem = document.getElementById("spell"+i+"-info");
|
let parent_elem = document.getElementById("spell"+i+"-info");
|
||||||
let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
|
let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
|
||||||
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, spell_parts, damages);
|
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, damages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -629,6 +651,7 @@ class SpellDisplayNode extends ComputeNode {
|
||||||
Returns an array in the order:
|
Returns an array in the order:
|
||||||
*/
|
*/
|
||||||
function getMeleeStats(stats, weapon) {
|
function getMeleeStats(stats, weapon) {
|
||||||
|
stats = new Map(stats); // Shallow copy
|
||||||
const weapon_stats = weapon.statMap;
|
const weapon_stats = weapon.statMap;
|
||||||
const skillpoints = [
|
const skillpoints = [
|
||||||
stats.get('str'),
|
stats.get('str'),
|
||||||
|
@ -647,14 +670,12 @@ function getMeleeStats(stats, weapon) {
|
||||||
adjAtkSpd = 0;
|
adjAtkSpd = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let damage_mult = stats.get("damageMultiplier");
|
|
||||||
if (weapon_stats.get("type") === "relik") {
|
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.
|
//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.
|
//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, weapon_stats, [100, 0, 0, 0, 0, 0], false, true);
|
||||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct"), 0, weapon_stats, skillpoints, damage_mult);
|
|
||||||
|
|
||||||
let dex = skillpoints[1];
|
let dex = skillpoints[1];
|
||||||
|
|
||||||
|
@ -733,7 +754,7 @@ class DisplayBuildWarningsNode extends ComputeNode {
|
||||||
document.getElementById(skp_order[i]+"-warnings").textContent = ''
|
document.getElementById(skp_order[i]+"-warnings").textContent = ''
|
||||||
if (assigned > 100) {
|
if (assigned > 100) {
|
||||||
let skp_warning = document.createElement("p");
|
let skp_warning = document.createElement("p");
|
||||||
skp_warning.classList.add("warning"); skp_warning.classList.add("small-text");
|
skp_warning.classList.add("warning", "small-text");
|
||||||
skp_warning.textContent += "Cannot assign " + assigned + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually.";
|
skp_warning.textContent += "Cannot assign " + assigned + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually.";
|
||||||
document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning);
|
document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning);
|
||||||
}
|
}
|
||||||
|
@ -846,14 +867,13 @@ class AggregateEditableIDNode extends ComputeNode {
|
||||||
|
|
||||||
compute_func(input_map) {
|
compute_func(input_map) {
|
||||||
const build = input_map.get('build'); input_map.delete('build');
|
const build = input_map.get('build'); input_map.delete('build');
|
||||||
const weapon = input_map.get('weapon'); input_map.delete('weapon');
|
|
||||||
|
|
||||||
const output_stats = new Map(build.statMap);
|
const output_stats = new Map(build.statMap);
|
||||||
for (const [k, v] of input_map.entries()) {
|
for (const [k, v] of input_map.entries()) {
|
||||||
output_stats.set(k, v);
|
output_stats.set(k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type")));
|
output_stats.set('classDef', classDefenseMultipliers.get(build.weapon.statMap.get("type")));
|
||||||
return output_stats;
|
return output_stats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -956,12 +976,15 @@ class SumNumberInputNode extends InputNode {
|
||||||
|
|
||||||
let item_nodes = [];
|
let item_nodes = [];
|
||||||
let powder_nodes = [];
|
let powder_nodes = [];
|
||||||
let spelldmg_nodes = [];
|
|
||||||
let edit_input_nodes = [];
|
let edit_input_nodes = [];
|
||||||
let skp_inputs = [];
|
let skp_inputs = [];
|
||||||
|
let build_node;
|
||||||
|
let stat_agg_node;
|
||||||
|
let edit_agg_node;
|
||||||
|
let atree_graph_creator;
|
||||||
|
|
||||||
function builder_graph_init() {
|
function builder_graph_init() {
|
||||||
// Phase 1/2: Set up item input, propagate updates, etc.
|
// Phase 1/3: Set up item input, propagate updates, etc.
|
||||||
|
|
||||||
// Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff).
|
// Bind item input fields to input nodes, and some display stuff (for auto colorizing stuff).
|
||||||
for (const [eq, display_elem, none_item] of zip3(equipment_fields, build_fields, none_items)) {
|
for (const [eq, display_elem, none_item] of zip3(equipment_fields, build_fields, none_items)) {
|
||||||
|
@ -975,7 +998,6 @@ function builder_graph_init() {
|
||||||
//new PrintNode(eq+'-debug').link_to(item_input);
|
//new PrintNode(eq+'-debug').link_to(item_input);
|
||||||
//document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm');
|
//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]])) {
|
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 input_field = document.getElementById(eq+"-choice");
|
||||||
let item_image = document.getElementById(eq+"-img");
|
let item_image = document.getElementById(eq+"-img");
|
||||||
|
@ -987,13 +1009,14 @@ function builder_graph_init() {
|
||||||
|
|
||||||
// weapon image changer node.
|
// weapon image changer node.
|
||||||
let weapon_image = document.getElementById("weapon-img");
|
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.
|
// Level input node.
|
||||||
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
|
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
|
||||||
|
|
||||||
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
|
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
|
||||||
let build_node = new BuildAssembleNode();
|
build_node = new BuildAssembleNode();
|
||||||
for (const input of item_nodes) {
|
for (const input of item_nodes) {
|
||||||
build_node.link_to(input);
|
build_node.link_to(input);
|
||||||
}
|
}
|
||||||
|
@ -1018,15 +1041,15 @@ function builder_graph_init() {
|
||||||
item_nodes[3].link_to(powder_nodes[3], 'powdering');
|
item_nodes[3].link_to(powder_nodes[3], 'powdering');
|
||||||
item_nodes[8].link_to(powder_nodes[4], 'powdering');
|
item_nodes[8].link_to(powder_nodes[4], 'powdering');
|
||||||
|
|
||||||
// Phase 2/2: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage
|
// Phase 2/3: Set up editable IDs, skill points; use decodeBuild() skill points, calculate damage
|
||||||
|
|
||||||
let build_disp_node = new BuildDisplayNode()
|
let build_disp_node = new BuildDisplayNode()
|
||||||
build_disp_node.link_to(build_node, 'build');
|
build_disp_node.link_to(build_node, 'build');
|
||||||
|
|
||||||
// Create one node that will be the "aggregator node" (listen to all the editable id nodes, as well as the build_node (for non editable stats) and collect them into one statmap)
|
// Create one node that will be the "aggregator node" (listen to all the editable id nodes, as well as the build_node (for non editable stats) and collect them into one statmap)
|
||||||
let stat_agg_node = new AggregateStatsNode();
|
stat_agg_node = new AggregateStatsNode();
|
||||||
let edit_agg_node = new AggregateEditableIDNode();
|
edit_agg_node = new AggregateEditableIDNode();
|
||||||
edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon');
|
edit_agg_node.link_to(build_node, 'build');
|
||||||
for (const field of editable_item_fields) {
|
for (const field of editable_item_fields) {
|
||||||
// Create nodes that listens to each editable id input, the node name should match the "id"
|
// Create nodes that listens to each editable id input, the node name should match the "id"
|
||||||
const elem = document.getElementById(field);
|
const elem = document.getElementById(field);
|
||||||
|
@ -1051,15 +1074,40 @@ function builder_graph_init() {
|
||||||
stat_agg_node.link_to(edit_agg_node);
|
stat_agg_node.link_to(edit_agg_node);
|
||||||
build_disp_node.link_to(stat_agg_node, 'stats');
|
build_disp_node.link_to(stat_agg_node, 'stats');
|
||||||
|
|
||||||
|
// Phase 3/3: Set up atree stuff.
|
||||||
|
|
||||||
|
// These two are defined in `atree.js`
|
||||||
|
atree_node.link_to(build_node, 'build');
|
||||||
|
atree_merge.link_to(build_node, 'build');
|
||||||
|
atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node)
|
||||||
|
.link_to(atree_collect_spells, 'spells');
|
||||||
|
|
||||||
|
build_encode_node.link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// Trigger the update cascade for build!
|
||||||
|
// ---------------------------------------------------------------
|
||||||
for (const input_node of item_nodes.concat(powder_nodes)) {
|
for (const input_node of item_nodes.concat(powder_nodes)) {
|
||||||
input_node.update();
|
input_node.update();
|
||||||
}
|
}
|
||||||
level_input.update();
|
level_input.update();
|
||||||
|
|
||||||
|
// kinda janky, manually set atree and update. Some wasted compute here
|
||||||
|
if (atree_data !== null && atree_node.value !== null) { // janky check if atree is valid
|
||||||
|
const atree_state = atree_state_node.value;
|
||||||
|
if (atree_data.length > 0) {
|
||||||
|
const active_nodes = decode_atree(atree_node.value, atree_data);
|
||||||
|
for (const node of active_nodes) {
|
||||||
|
atree_set_state(atree_state.get(node.ability.id), true);
|
||||||
|
}
|
||||||
|
atree_state_node.mark_dirty().update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Powder specials.
|
// Powder specials.
|
||||||
let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials');
|
let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials');
|
||||||
new PowderSpecialDisplayNode().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(powder_special_calc, 'powder-boost');
|
||||||
stat_agg_node.link_to(armor_powder_node, 'armor-powder');
|
stat_agg_node.link_to(armor_powder_node, 'armor-powder');
|
||||||
powder_special_input.update();
|
powder_special_input.update();
|
||||||
|
@ -1069,22 +1117,6 @@ function builder_graph_init() {
|
||||||
|
|
||||||
// Also do something similar for skill points
|
// Also do something similar for skill points
|
||||||
|
|
||||||
for (let i = 0; i < 4; ++i) {
|
|
||||||
let spell_node = new SpellSelectNode(i);
|
|
||||||
spell_node.link_to(build_node, 'build');
|
|
||||||
// TODO: link and rewrite spell_node to the stat agg node
|
|
||||||
spell_node.link_to(stat_agg_node, 'stats')
|
|
||||||
|
|
||||||
let calc_node = new SpellDamageCalcNode(i);
|
|
||||||
calc_node.link_to(item_nodes[8], 'weapon-input').link_to(stat_agg_node, 'stats')
|
|
||||||
.link_to(spell_node, 'spell-info');
|
|
||||||
spelldmg_nodes.push(calc_node);
|
|
||||||
|
|
||||||
let display_node = new SpellDisplayNode(i);
|
|
||||||
display_node.link_to(stat_agg_node, 'stats'); // TODO: same here..
|
|
||||||
display_node.link_to(spell_node, 'spell-info');
|
|
||||||
display_node.link_to(calc_node, 'spell-damage');
|
|
||||||
}
|
|
||||||
for (const node of edit_input_nodes) {
|
for (const node of edit_input_nodes) {
|
||||||
node.update();
|
node.update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,9 @@ class ComputeNode {
|
||||||
throw "no compute func specified";
|
throw "no compute func specified";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add link to a parent compute node, optionally with an alias.
|
||||||
|
*/
|
||||||
link_to(parent_node, link_name) {
|
link_to(parent_node, link_name) {
|
||||||
this.inputs.push(parent_node)
|
this.inputs.push(parent_node)
|
||||||
link_name = (link_name !== undefined) ? link_name : parent_node.name;
|
link_name = (link_name !== undefined) ? link_name : parent_node.name;
|
||||||
|
@ -100,6 +103,26 @@ class ComputeNode {
|
||||||
parent_node.children.push(this);
|
parent_node.children.push(this);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a link to a parent node.
|
||||||
|
* TODO: time complexity of list deletion (not super relevant but it hurts my soul)
|
||||||
|
*/
|
||||||
|
remove_link(parent_node) {
|
||||||
|
const idx = this.inputs.indexOf(parent_node); // Get idx
|
||||||
|
this.inputs.splice(idx, 1); // remove element
|
||||||
|
|
||||||
|
this.input_translation.delete(parent_node.name);
|
||||||
|
const was_dirty = this.inputs_dirty.get(parent_node.name);
|
||||||
|
this.inputs_dirty.delete(parent_node.name);
|
||||||
|
if (was_dirty) {
|
||||||
|
this.inputs_dirty_count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx2 = parent_node.children.indexOf(this);
|
||||||
|
parent_node.children.splice(idx2, 1);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,3 +173,42 @@ class InputNode extends ComputeNode {
|
||||||
return this.input_field.value;
|
return this.input_field.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passthrough node for simple aggregation.
|
||||||
|
* Unfortunately if you use this too much you get layers and layers of maps...
|
||||||
|
*
|
||||||
|
* Signature: PassThroughNode(**kwargs) => Map[...]
|
||||||
|
*/
|
||||||
|
class PassThroughNode extends ComputeNode {
|
||||||
|
constructor(name) {
|
||||||
|
super(name);
|
||||||
|
this.breakout_nodes = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_func(input_map) {
|
||||||
|
return input_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a ComputeNode that will "break out" one part of this aggregation input.
|
||||||
|
* There is some overhead to this operation because ComputeNode is not exactly a free abstraction... oof
|
||||||
|
* Also you will recv updates whenever any input that is part of the aggregation changes even
|
||||||
|
* if the specific sub-input didn't change.
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* sub-input: The key to listen to
|
||||||
|
*/
|
||||||
|
get_node(sub_input) {
|
||||||
|
if (this.breakout_nodes.has(sub_input)) {
|
||||||
|
return this.breakout_nodes.get(sub_input);
|
||||||
|
}
|
||||||
|
const _name = this.name;
|
||||||
|
const ret = new (class extends ComputeNode {
|
||||||
|
constructor() { super('passthrough-'+_name+'-'+sub_input); }
|
||||||
|
compute_func(input_map) { return input_map.get(_name).get(sub_input); }
|
||||||
|
})().link_to(this);
|
||||||
|
this.breakout_nodes.set(sub_input, ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -172,16 +172,17 @@ function calculateCraft() {
|
||||||
document.getElementById("mat-2").textContent = recipe.get("materials")[1].get("item").split(" ").slice(1).join(" ") + " Tier:";
|
document.getElementById("mat-2").textContent = recipe.get("materials")[1].get("item").split(" ").slice(1).join(" ") + " Tier:";
|
||||||
|
|
||||||
//Display Recipe Stats
|
//Display Recipe Stats
|
||||||
displaysq2RecipeStats(player_craft, "recipe-stats");
|
displayRecipeStats(player_craft, "recipe-stats");
|
||||||
|
|
||||||
//Display Craft Stats
|
//Display Craft Stats
|
||||||
// displayCraftStats(player_craft, "craft-stats");
|
// displayCraftStats(player_craft, "craft-stats");
|
||||||
let mock_item = player_craft.statMap;
|
let mock_item = player_craft.statMap;
|
||||||
displaysq2ExpandedItem(mock_item, "craft-stats");
|
apply_weapon_powders(mock_item);
|
||||||
|
displayExpandedItem(mock_item, "craft-stats");
|
||||||
|
|
||||||
//Display Ingredients' Stats
|
//Display Ingredients' Stats
|
||||||
for (let i = 1; i < 7; i++) {
|
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
|
//Display Warnings - only ingred type warnings for now
|
||||||
let warning_elem = document.getElementById("craft-warnings");
|
let warning_elem = document.getElementById("craft-warnings");
|
||||||
|
@ -341,7 +342,7 @@ function toggleMaterial(buttonId) {
|
||||||
*/
|
*/
|
||||||
function updateCraftedImage() {
|
function updateCraftedImage() {
|
||||||
let input = document.getElementById("recipe-choice");
|
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";
|
document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,4 +365,8 @@ function resetFields() {
|
||||||
calculateCraft();
|
calculateCraft();
|
||||||
}
|
}
|
||||||
|
|
||||||
load_ing_init(init_crafter);
|
(async function() {
|
||||||
|
let load_promises = [ load_ing_init() ];
|
||||||
|
await Promise.all(load_promises);
|
||||||
|
init_crafter();
|
||||||
|
})();
|
||||||
|
|
30
js/d3_export.js
vendored
|
@ -1,30 +0,0 @@
|
||||||
// http://bl.ocks.org/rokotyan/0556f8facbaf344507cdc45dc3622177
|
|
||||||
|
|
||||||
// Set-up the export button
|
|
||||||
function set_export_button(svg, button_id, output_id) {
|
|
||||||
d3.select('#'+button_id).on('click', function(){
|
|
||||||
//get svg source.
|
|
||||||
var serializer = new XMLSerializer();
|
|
||||||
var source = serializer.serializeToString(svg.node());
|
|
||||||
console.log(source);
|
|
||||||
|
|
||||||
source = source.replace(/^<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;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,143 +1,304 @@
|
||||||
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]);
|
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();
|
function get_base_dps(item) {
|
||||||
|
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
//SUPER JANK @HPP PLS FIX
|
||||||
let damages = [];
|
if (item.get("tier") !== "Crafted") {
|
||||||
const rawDamages = buildStats.get("damageRaw");
|
let total_damage = 0;
|
||||||
for (let i = 0; i < rawDamages.length; i++) {
|
for (const damage_k of damage_keys) {
|
||||||
const damage_vals = rawDamages[i].split("-").map(Number);
|
damages = item.get(damage_k);
|
||||||
damages.push(damage_vals);
|
total_damage += damages[0] + damages[1];
|
||||||
}
|
}
|
||||||
|
return total_damage * attack_speed_mult / 2;
|
||||||
// Applying spell conversions
|
|
||||||
let neutralBase = damages[0].slice();
|
|
||||||
let neutralRemainingRaw = damages[0].slice();
|
|
||||||
|
|
||||||
|
|
||||||
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
|
|
||||||
|
|
||||||
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
|
|
||||||
//1st round - apply each as ingred, 2nd round - apply as normal
|
|
||||||
if (weapon.get("tier") === "Crafted") {
|
|
||||||
let damageBases = buildStats.get("damageBases").slice();
|
|
||||||
for (const p of powders.concat(weapon.get("ingredPowders"))) {
|
|
||||||
let powder = powderStats[p]; //use min, max, and convert
|
|
||||||
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
|
|
||||||
let diff = Math.floor(damageBases[0] * powder.convert/100);
|
|
||||||
damageBases[0] -= diff;
|
|
||||||
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
|
|
||||||
}
|
|
||||||
//update all damages
|
|
||||||
if(!weapon.get("custom")) {
|
|
||||||
for (let i = 0; i < damages.length; i++) {
|
|
||||||
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
neutralRemainingRaw = damages[0].slice();
|
|
||||||
neutralBase = damages[0].slice();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 5; ++i) {
|
|
||||||
let conversionRatio = spellConversions[i+1]/100;
|
|
||||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
|
||||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
|
||||||
damages[i+1][0] = Math.floor(round_near(damages[i+1][0] + min_diff));
|
|
||||||
damages[i+1][1] = Math.floor(round_near(damages[i+1][1] + max_diff));
|
|
||||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
|
||||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
|
||||||
}
|
|
||||||
|
|
||||||
//apply powders to weapon
|
|
||||||
for (const powderID of powders) {
|
|
||||||
const powder = powderStats[powderID];
|
|
||||||
// Bitwise to force conversion to integer (integer division).
|
|
||||||
const element = (powderID/6) | 0;
|
|
||||||
let conversionRatio = powder.convert/100;
|
|
||||||
if (neutralRemainingRaw[1] > 0) {
|
|
||||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
|
||||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
|
||||||
damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
|
|
||||||
damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
|
|
||||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
|
||||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
|
||||||
}
|
|
||||||
damages[element+1][0] += powder.min;
|
|
||||||
damages[element+1][1] += powder.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
damages[0] = neutralRemainingRaw;
|
|
||||||
|
|
||||||
let damageMult = damageMultiplier;
|
|
||||||
let melee = false;
|
|
||||||
// If we are doing melee calculations:
|
|
||||||
if (spellMultiplier == 0) {
|
|
||||||
spellMultiplier = 1;
|
|
||||||
melee = true;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
let total_damage_min = 0;
|
||||||
|
let total_damage_max = 0;
|
||||||
|
for (const damage_k of damage_keys) {
|
||||||
|
damages = item.get(damage_k);
|
||||||
|
total_damage_min += damages[0][0] + damages[0][1];
|
||||||
|
total_damage_max += damages[1][0] + damages[1][1];
|
||||||
}
|
}
|
||||||
//console.log(damages);
|
total_damage_min = attack_speed_mult * total_damage_min / 2;
|
||||||
//console.log(damageMult);
|
total_damage_max = attack_speed_mult * total_damage_max / 2;
|
||||||
rawModifier *= spellMultiplier * damageMultiplier;
|
return [total_damage_min, total_damage_max];
|
||||||
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.)
|
||||||
|
let total_convert = 0; //TODO get confirmation that this is how raw works.
|
||||||
|
for (let i = 1; i <= 5; ++i) {
|
||||||
|
if (conversions[i] > 0) {
|
||||||
|
const conv_frac = conversions[i]/100;
|
||||||
|
damages[i][0] += conv_frac * weapon_min;
|
||||||
|
damages[i][1] += conv_frac * weapon_max;
|
||||||
|
present[i] = true;
|
||||||
|
total_convert += conv_frac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 min_boost = raw_boost;
|
||||||
|
let max_boost = raw_boost;
|
||||||
|
if (total_max > 0) { // TODO: what about total negative all raw?
|
||||||
|
if (total_elem_min > 0) {
|
||||||
|
min_boost += (damages_obj[0] / total_min) * prop_raw;
|
||||||
|
}
|
||||||
|
max_boost += (damages_obj[1] / total_max) * prop_raw;
|
||||||
|
}
|
||||||
|
if (i != 0 && total_elem_max > 0) { // rainraw TODO above
|
||||||
|
if (total_elem_min > 0) {
|
||||||
|
min_boost += (damages_obj[0] / total_elem_min) * rainbow_raw;
|
||||||
|
}
|
||||||
|
max_boost += (damages_obj[1] / total_elem_max) * rainbow_raw;
|
||||||
|
}
|
||||||
|
damages_obj[0] += min_boost * total_convert;
|
||||||
|
damages_obj[1] += max_boost * total_convert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Spell schema:
|
||||||
|
|
||||||
|
spell: {
|
||||||
|
name: str internal string name for the spell. Unique identifier
|
||||||
|
cost: Optional[int] ignored for spells that are not id 1-4
|
||||||
|
display_text: str short description of the spell, ex. Bash, Meteor, Arrow Shield
|
||||||
|
base_spell: int spell index. 0-4 are reserved (0 is melee, 1-4 is common 4 spells)
|
||||||
|
spell_type: str [TODO: DEPRECATED/REMOVE] "healing" or "damage"
|
||||||
|
scaling: Optional[str] [DEFAULT: "spell"] "melee" or "spell"
|
||||||
|
use_atkspd: Optional[bool] [DEFAULT: true] true to factor attack speed, false otherwise.
|
||||||
|
display: Optional[str] [DEFAULT: "total"] "total" to sum all parts. Or, the name of a spell part
|
||||||
|
parts: List[part] Parts of this spell (different stuff the spell does basically)
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTE: when using `replace_spell` on an existing spell, all fields become optional.
|
||||||
|
Specified fields overwrite existing fields; unspecified fields are left unchanged.
|
||||||
|
|
||||||
|
|
||||||
|
There are three possible spell "part" types: damage, heal, and total.
|
||||||
|
|
||||||
|
part: spell_damage | spell_heal | spell_total
|
||||||
|
|
||||||
|
spell_damage: {
|
||||||
|
name: str != "total" Name of the part.
|
||||||
|
type: "damage" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||||
|
multipliers: array[num, 6] floating point spellmults (though supposedly wynn only supports integer mults)
|
||||||
|
}
|
||||||
|
spell_heal: {
|
||||||
|
name: str != "total" Name of the part.
|
||||||
|
type: "heal" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||||
|
power: num floating point healing power (1 is 100% of max hp).
|
||||||
|
}
|
||||||
|
spell_total: {
|
||||||
|
name: str != "total" Name of the part.
|
||||||
|
type: "total" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||||
|
hits: Map[str, num] Keys are other part names, numbers are the multipliers. Undefined behavior if subparts
|
||||||
|
are not the same type of spell. Can only pull from spells defined before it.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Before passing to display, use the following structs.
|
||||||
|
NOTE: total is collapsed into damage or healing.
|
||||||
|
|
||||||
|
spell_damage: {
|
||||||
|
type: "damage" Internal use
|
||||||
|
name: str Display name of part. Should be human readable
|
||||||
|
normal_min: array[num, 6] floating point damages (no crit, min), can be less than zero. Order: NETWFA
|
||||||
|
normal_max: array[num, 6] floating point damages (no crit, max)
|
||||||
|
normal_total: array[num, 2] (min, max) noncrit total damage (not negative)
|
||||||
|
crit_min: array[num, 6] floating point damages (crit, min), can be less than zero. Order: NETWFA
|
||||||
|
crit_max: array[num, 6] floating point damages (crit, max)
|
||||||
|
crit_total: array[num, 2] (min, max) crit total damage (not negative)
|
||||||
|
}
|
||||||
|
spell_heal: {
|
||||||
|
type: "heal" Internal use
|
||||||
|
name: str Display name of part. Should be human readable
|
||||||
|
heal_amount: num floating point HP healed (self)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const default_spells = {
|
||||||
|
wand: [{
|
||||||
|
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||||
|
name: "Wand Melee", // TODO: name for melee attacks?
|
||||||
|
display_text: "Mage basic attack",
|
||||||
|
base_spell: 0,
|
||||||
|
scaling: "melee", use_atkspd: false,
|
||||||
|
display: "Melee",
|
||||||
|
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||||
|
}, {
|
||||||
|
name: "Heal", // TODO: name for melee attacks? // JUST FOR TESTING...
|
||||||
|
display_text: "Heal spell!",
|
||||||
|
base_spell: 1,
|
||||||
|
display: "Total Heal",
|
||||||
|
parts: [
|
||||||
|
{ name: "First Pulse", power: 0.12 },
|
||||||
|
{ name: "Second and Third Pulses", power: 0.06 },
|
||||||
|
{ name: "Total Heal", hits: { "First Pulse": 1, "Second and Third Pulses": 2 } }
|
||||||
|
]
|
||||||
|
}],
|
||||||
|
spear: [{
|
||||||
|
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||||
|
name: "Melee", // TODO: name for melee attacks?
|
||||||
|
display_text: "Warrior basic attack",
|
||||||
|
base_spell: 0,
|
||||||
|
scaling: "melee", use_atkspd: false,
|
||||||
|
display: "Melee",
|
||||||
|
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||||
|
}],
|
||||||
|
bow: [{
|
||||||
|
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||||
|
name: "Bow Shot", // TODO: name for melee attacks?
|
||||||
|
display_text: "Archer basic attack",
|
||||||
|
base_spell: 0,
|
||||||
|
scaling: "melee", use_atkspd: false,
|
||||||
|
display: "Melee",
|
||||||
|
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||||
|
}],
|
||||||
|
dagger: [{
|
||||||
|
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||||
|
name: "Melee", // TODO: name for melee attacks?
|
||||||
|
display_text: "Assassin basic attack",
|
||||||
|
base_spell: 0,
|
||||||
|
scaling: "melee", use_atkspd: false,
|
||||||
|
display: "Melee",
|
||||||
|
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||||
|
}],
|
||||||
|
relik: [{
|
||||||
|
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||||
|
name: "Relik Melee", // TODO: name for melee attacks?
|
||||||
|
display_text: "Shaman basic attack",
|
||||||
|
base_spell: 0,
|
||||||
|
spell_type: "damage",
|
||||||
|
scaling: "melee", use_atkspd: false,
|
||||||
|
display: "Total",
|
||||||
|
parts: [
|
||||||
|
{ name: "Single Beam", multipliers: [33, 0, 0, 0, 0, 0] },
|
||||||
|
{ name: "Total", hits: { "Single Beam": 3 } }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
const spell_table = {
|
const spell_table = {
|
||||||
"wand": [
|
"wand": [
|
||||||
|
|
237
js/display.js
|
@ -33,9 +33,8 @@ function displaySetBonuses(parent_id,build) {
|
||||||
set_summary_elem.append(set_elem);
|
set_summary_elem.append(set_elem);
|
||||||
|
|
||||||
const bonus = active_set.bonuses[count-1];
|
const bonus = active_set.bonuses[count-1];
|
||||||
let mock_item = new Map();
|
let mock_item = new Map([["fixID", true],
|
||||||
mock_item.set("fixID", true);
|
["displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length]]);
|
||||||
mock_item.set("displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length);
|
|
||||||
let mock_minRolls = new Map();
|
let mock_minRolls = new Map();
|
||||||
let mock_maxRolls = new Map();
|
let mock_maxRolls = new Map();
|
||||||
mock_item.set("minRolls", mock_minRolls);
|
mock_item.set("minRolls", mock_minRolls);
|
||||||
|
@ -173,51 +172,9 @@ function displayExpandedItem(item, parent_id){
|
||||||
// #commands create a new element.
|
// #commands create a new element.
|
||||||
// !elemental is some janky hack for elemental damage.
|
// !elemental is some janky hack for elemental damage.
|
||||||
// normals just display a thing.
|
// normals just display a thing.
|
||||||
|
item = new Map(item); // shallow copy
|
||||||
if (item.get("category") === "weapon") {
|
if (item.get("category") === "weapon") {
|
||||||
let stats = new Map();
|
item.set('basedps', get_base_dps(item));
|
||||||
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]);
|
|
||||||
}
|
|
||||||
} else if (item.get("category") === "armor") {
|
} else if (item.get("category") === "armor") {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,7 +341,21 @@ function displayExpandedItem(item, parent_id){
|
||||||
bckgrd.appendChild(img);
|
bckgrd.appendChild(img);
|
||||||
}
|
}
|
||||||
} else {
|
} 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;
|
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
|
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);
|
p_elem = displayFixedID(parent_div, id, item.get(id), elemental_format);
|
||||||
} else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") {
|
} else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") {
|
||||||
|
@ -447,7 +418,7 @@ function displayExpandedItem(item, parent_id){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Show powder specials ;-;
|
//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"))) {
|
if(nonConsumables.includes(item.get("type"))) {
|
||||||
let powder_special = document.createElement("div");
|
let powder_special = document.createElement("div");
|
||||||
powder_special.classList.add("col");
|
powder_special.classList.add("col");
|
||||||
|
@ -555,19 +526,18 @@ function displayExpandedItem(item, parent_id){
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.get("category") === "weapon") {
|
if (item.get("category") === "weapon") {
|
||||||
let damage_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
|
||||||
let total_damages = item.get("basedps");
|
let total_damages = item.get("basedps");
|
||||||
let base_dps_elem = document.createElement("p");
|
let base_dps_elem = document.createElement("p");
|
||||||
base_dps_elem.classList.add("left");
|
base_dps_elem.classList.add("left");
|
||||||
base_dps_elem.classList.add("itemp");
|
base_dps_elem.classList.add("itemp");
|
||||||
if (item.get("tier") === "Crafted") {
|
if (item.get("tier") === "Crafted") {
|
||||||
let base_dps_min = total_damages[0] * damage_mult;
|
let base_dps_min = total_damages[0];
|
||||||
let base_dps_max = total_damages[1] * damage_mult;
|
let base_dps_max = total_damages[1];
|
||||||
|
|
||||||
base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3);
|
base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3);
|
||||||
}
|
}
|
||||||
else {
|
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(document.createElement("p"));
|
||||||
parent_div.appendChild(base_dps_elem);
|
parent_div.appendChild(base_dps_elem);
|
||||||
|
@ -1245,7 +1215,7 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) {
|
||||||
critStats.append(critChance);
|
critStats.append(critChance);
|
||||||
|
|
||||||
parent_elem.append(critStats);
|
parent_elem.append(critStats);
|
||||||
addClickableArrow(overallparent_elem);
|
addClickableArrow(overallparent_elem, parent_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayDefenseStats(parent_elem, statMap, insertSummary){
|
function displayDefenseStats(parent_elem, statMap, insertSummary){
|
||||||
|
@ -1502,9 +1472,13 @@ 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
|
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 spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
|
||||||
let part = spell["parts"][0];
|
let part = spell["parts"][0];
|
||||||
let _results = calculateSpellDamage(stats, part.conversion,
|
|
||||||
stats.get("mdRaw"), stats.get("mdPct"),
|
let tmp_conv = [];
|
||||||
0, weapon, skillpoints, stats.get('damageMultiplier') * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100
|
for (let i in part.conversion) {
|
||||||
|
tmp_conv.push(part.conversion[i] * part.multiplier[power-1] / 100);
|
||||||
|
}
|
||||||
|
console.log(tmp_conv);
|
||||||
|
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false, true);
|
||||||
|
|
||||||
let critChance = skillPointsToPercentage(skillpoints[1]);
|
let critChance = skillPointsToPercentage(skillpoints[1]);
|
||||||
let save_damages = [];
|
let save_damages = [];
|
||||||
|
@ -1592,19 +1566,19 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSpellCost(stats, spellIdx, cost) {
|
function getSpellCost(stats, spell) {
|
||||||
return Math.max(1, getBaseSpellCost(stats, spellIdx, cost));
|
return Math.max(1, getBaseSpellCost(stats, spell));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBaseSpellCost(stats, spellIdx, cost) {
|
function getBaseSpellCost(stats, spell) {
|
||||||
// old intelligence:
|
// old intelligence:
|
||||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(stats.get('int'))));
|
let cost = spell.cost; //Math.ceil(spell.cost * (1 - skillPointsToPercentage(stats.get('int'))));
|
||||||
cost += stats.get("spRaw"+spellIdx);
|
cost += stats.get("spRaw"+spell.base_spell);
|
||||||
return Math.floor(cost * (1 + stats.get("spPct"+spellIdx) / 100));
|
return Math.floor(cost * (1 + stats.get("spPct"+spell.base_spell) / 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_parts, damages) {
|
function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spellIdx, spell_results) {
|
||||||
// TODO: remove spellIdx (just used to flag melee and cost)
|
// TODO: remove spellIdx (just used to flag melee and cost)
|
||||||
// TODO: move cost calc out
|
// TODO: move cost calc out
|
||||||
parent_elem.textContent = "";
|
parent_elem.textContent = "";
|
||||||
|
@ -1614,14 +1588,14 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
||||||
overallparent_elem.textContent = "";
|
overallparent_elem.textContent = "";
|
||||||
let title_elemavg = document.createElement("b");
|
let title_elemavg = document.createElement("b");
|
||||||
|
|
||||||
if (spellIdx != 0) {
|
if ('cost' in spell) {
|
||||||
let first = document.createElement("span");
|
let first = document.createElement("span");
|
||||||
first.textContent = spell.title + " (";
|
first.textContent = spell.name + " (";
|
||||||
title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
|
title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
|
||||||
title_elemavg.appendChild(first);
|
title_elemavg.appendChild(first);
|
||||||
|
|
||||||
let second = document.createElement("span");
|
let second = document.createElement("span");
|
||||||
second.textContent = getSpellCost(stats, spellIdx, spell.cost);
|
second.textContent = getSpellCost(stats, spell);
|
||||||
second.classList.add("Mana");
|
second.classList.add("Mana");
|
||||||
|
|
||||||
title_elem.appendChild(second.cloneNode(true));
|
title_elem.appendChild(second.cloneNode(true));
|
||||||
|
@ -1629,51 +1603,56 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
||||||
|
|
||||||
|
|
||||||
let third = document.createElement("span");
|
let third = document.createElement("span");
|
||||||
third.textContent = ") [Base: " + getBaseSpellCost(stats, spellIdx, spell.cost) + " ]";
|
third.textContent = ")";// [Base: " + getBaseSpellCost(stats, spellIdx, spell.cost) + " ]";
|
||||||
title_elem.appendChild(third);
|
title_elem.appendChild(third);
|
||||||
let third_summary = document.createElement("span");
|
let third_summary = document.createElement("span");
|
||||||
third_summary.textContent = ")";
|
third_summary.textContent = ")";
|
||||||
title_elemavg.appendChild(third_summary);
|
title_elemavg.appendChild(third_summary);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
title_elem.textContent = spell.title;
|
title_elem.textContent = spell.name;
|
||||||
title_elemavg.textContent = spell.title;
|
title_elemavg.textContent = spell.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
parent_elem.append(title_elem);
|
parent_elem.append(title_elem);
|
||||||
overallparent_elem.append(title_elemavg);
|
overallparent_elem.append(title_elemavg);
|
||||||
|
|
||||||
overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
// if ('cost' in spell) {
|
||||||
|
// :( ...... ?
|
||||||
|
// overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
||||||
|
// }
|
||||||
|
|
||||||
let critChance = skillPointsToPercentage(stats.get('dex'));
|
let critChance = skillPointsToPercentage(stats.get('dex'));
|
||||||
|
|
||||||
let save_damages = [];
|
|
||||||
|
|
||||||
let part_divavg = document.createElement("p");
|
let part_divavg = document.createElement("p");
|
||||||
overallparent_elem.append(part_divavg);
|
overallparent_elem.append(part_divavg);
|
||||||
|
|
||||||
for (let i = 0; i < spell_parts.length; ++i) {
|
function _summary(text, val, fmt) {
|
||||||
const part = spell_parts[i];
|
let overallaverageLabel = document.createElement("p");
|
||||||
const damage = damages[i];
|
let first = document.createElement("span");
|
||||||
|
let second = document.createElement("span");
|
||||||
|
first.textContent = text;
|
||||||
|
second.textContent = val.toFixed(2);
|
||||||
|
overallaverageLabel.appendChild(first);
|
||||||
|
overallaverageLabel.appendChild(second);
|
||||||
|
second.classList.add(fmt);
|
||||||
|
part_divavg.append(overallaverageLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < spell_results.length; ++i) {
|
||||||
|
const spell_info = spell_results[i];
|
||||||
|
|
||||||
let part_div = document.createElement("p");
|
let part_div = document.createElement("p");
|
||||||
parent_elem.append(part_div);
|
parent_elem.append(part_div);
|
||||||
|
|
||||||
let subtitle_elem = document.createElement("p");
|
let subtitle_elem = document.createElement("p");
|
||||||
subtitle_elem.textContent = part.subtitle;
|
subtitle_elem.textContent = spell_info.name
|
||||||
part_div.append(subtitle_elem);
|
part_div.append(subtitle_elem);
|
||||||
|
|
||||||
if (part.type === "damage") {
|
if (spell_info.type === "damage") {
|
||||||
let _results = damage;
|
let totalDamNormal = spell_info.normal_total;
|
||||||
let totalDamNormal = _results[0];
|
let totalDamCrit = spell_info.crit_total;
|
||||||
let totalDamCrit = _results[1];
|
|
||||||
let results = _results[2];
|
|
||||||
|
|
||||||
for (let i = 0; i < 6; ++i) {
|
|
||||||
for (let j in results[i]) {
|
|
||||||
results[i][j] = results[i][j].toFixed(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
|
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
|
||||||
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
|
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
|
||||||
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
|
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
|
||||||
|
@ -1684,87 +1663,39 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
||||||
part_div.append(averageLabel);
|
part_div.append(averageLabel);
|
||||||
|
|
||||||
|
|
||||||
if (part.summary == true) {
|
if (spell_info.name === spell.display) {
|
||||||
let overallaverageLabel = document.createElement("p");
|
_summary(spell_info.name+ " Average: ", averageDamage, "Damage");
|
||||||
let first = document.createElement("span");
|
|
||||||
let second = document.createElement("span");
|
|
||||||
first.textContent = part.subtitle + " Average: ";
|
|
||||||
second.textContent = averageDamage.toFixed(2);
|
|
||||||
overallaverageLabel.appendChild(first);
|
|
||||||
overallaverageLabel.appendChild(second);
|
|
||||||
second.classList.add("Damage");
|
|
||||||
part_divavg.append(overallaverageLabel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _damage_display(label_text, average, result_idx) {
|
function _damage_display(label_text, average, dmg_min, dmg_max) {
|
||||||
let label = document.createElement("p");
|
let label = document.createElement("p");
|
||||||
label.textContent = label_text+average.toFixed(2);
|
label.textContent = label_text+average.toFixed(2);
|
||||||
part_div.append(label);
|
part_div.append(label);
|
||||||
|
|
||||||
let arrmin = [];
|
|
||||||
let arrmax = [];
|
|
||||||
for (let i = 0; i < 6; i++){
|
for (let i = 0; i < 6; i++){
|
||||||
if (results[i][1] != 0){
|
if (dmg_max[i] != 0){
|
||||||
let p = document.createElement("p");
|
let p = document.createElement("p");
|
||||||
p.classList.add(damageClasses[i]);
|
p.classList.add(damageClasses[i]);
|
||||||
p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1];
|
p.textContent = dmg_min[i].toFixed(2)+" \u2013 "+dmg_max[i].toFixed(2);
|
||||||
arrmin.push(results[i][result_idx]);
|
|
||||||
arrmax.push(results[i][result_idx + 1]);
|
|
||||||
part_div.append(p);
|
part_div.append(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_damage_display("Non-Crit Average: ", nonCritAverage, 0);
|
_damage_display("Non-Crit Average: ", nonCritAverage, spell_info.normal_min, spell_info.normal_max);
|
||||||
_damage_display("Crit Average: ", critAverage, 2);
|
_damage_display("Crit Average: ", critAverage, spell_info.crit_min, spell_info.crit_max);
|
||||||
|
} else if (spell_info.type === "heal") {
|
||||||
save_damages.push(averageDamage);
|
let heal_amount = spell_info.heal_amount;
|
||||||
} else if (part.type === "heal") {
|
|
||||||
let heal_amount = damage;
|
|
||||||
let healLabel = document.createElement("p");
|
let healLabel = document.createElement("p");
|
||||||
healLabel.textContent = heal_amount;
|
healLabel.textContent = heal_amount;
|
||||||
// healLabel.classList.add("damagep");
|
// healLabel.classList.add("damagep");
|
||||||
part_div.append(healLabel);
|
part_div.append(healLabel);
|
||||||
if (part.summary == true) {
|
if (spell_info.name === spell.display) {
|
||||||
let overallhealLabel = document.createElement("p");
|
_summary(spell_info.name+ ": ", heal_amount, "Set");
|
||||||
let first = document.createElement("span");
|
|
||||||
let second = document.createElement("span");
|
|
||||||
first.textContent = part.subtitle + ": ";
|
|
||||||
second.textContent = heal_amount;
|
|
||||||
overallhealLabel.appendChild(first);
|
|
||||||
second.classList.add("Set");
|
|
||||||
overallhealLabel.appendChild(second);
|
|
||||||
part_divavg.append(overallhealLabel);
|
|
||||||
}
|
}
|
||||||
} else if (part.type === "total") {
|
|
||||||
let total_damage = 0;
|
|
||||||
for (let i in part.factors) {
|
|
||||||
total_damage += save_damages[i] * part.factors[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
let dmgarr = part.factors.slice();
|
|
||||||
dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")");
|
|
||||||
|
|
||||||
|
|
||||||
let averageLabel = document.createElement("p");
|
|
||||||
averageLabel.textContent = "Average: "+total_damage.toFixed(2);
|
|
||||||
averageLabel.classList.add("damageSubtitle");
|
|
||||||
part_div.append(averageLabel);
|
|
||||||
|
|
||||||
let overallaverageLabel = document.createElement("p");
|
|
||||||
let overallaverageLabelFirst = document.createElement("span");
|
|
||||||
let overallaverageLabelSecond = document.createElement("span");
|
|
||||||
overallaverageLabelFirst.textContent = "Average: ";
|
|
||||||
overallaverageLabelSecond.textContent = total_damage.toFixed(2);
|
|
||||||
overallaverageLabelSecond.classList.add("Damage");
|
|
||||||
|
|
||||||
|
|
||||||
overallaverageLabel.appendChild(overallaverageLabelFirst);
|
|
||||||
overallaverageLabel.appendChild(overallaverageLabelSecond);
|
|
||||||
part_divavg.append(overallaverageLabel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addClickableArrow(overallparent_elem);
|
addClickableArrow(overallparent_elem, parent_elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Displays the ID costs of an item
|
/** Displays the ID costs of an item
|
||||||
|
@ -2177,11 +2108,23 @@ function stringCDF(id,val,base,amp) {
|
||||||
document.getElementById(id + "-cdf").appendChild(b3);
|
document.getElementById(id + "-cdf").appendChild(b3);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addClickableArrow(elem) {
|
function addClickableArrow(elem, target) {
|
||||||
//up and down arrow - done ugly
|
//up and down arrow - done ugly
|
||||||
let arrow = document.createElement("img");
|
let arrow = document.createElement("img");
|
||||||
arrow.id = "arrow_" + elem.id;
|
arrow.id = "arrow_" + elem.id;
|
||||||
arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem";
|
arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem";
|
||||||
arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png";
|
arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png";
|
||||||
elem.appendChild(arrow);
|
elem.appendChild(arrow);
|
||||||
|
arrow.addEventListener("click", () => toggle_spell_tab(arrow, target));
|
||||||
|
}
|
||||||
|
|
||||||
|
// toggle arrow thinger
|
||||||
|
function toggle_spell_tab(arrow_img, target) {
|
||||||
|
if (target.style.display == "none") {
|
||||||
|
target.style.display = "";
|
||||||
|
arrow_img.src = arrow_img.src.replace("down", "up");
|
||||||
|
} else {
|
||||||
|
target.style.display = "none";
|
||||||
|
arrow_img.src = arrow_img.src.replace("up", "down");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,224 +0,0 @@
|
||||||
let atree_map;
|
|
||||||
let atree_head;
|
|
||||||
let atree_connectors_map;
|
|
||||||
function construct_AT(elem, tree) {
|
|
||||||
console.log("constructing ability tree UI");
|
|
||||||
document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
|
||||||
elem.innerHTML = ""; //reset the atree in the DOM
|
|
||||||
|
|
||||||
if (tree === undefined) {return false;}
|
|
||||||
|
|
||||||
// add in the "Active" title to atree
|
|
||||||
let active_row = document.createElement("div");
|
|
||||||
active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center");
|
|
||||||
active_row.textContent = "Active:";
|
|
||||||
document.getElementById("atree-active").appendChild(active_row);
|
|
||||||
|
|
||||||
atree_map = new Map();
|
|
||||||
atree_connectors_map = new Map()
|
|
||||||
for (let i of tree) {
|
|
||||||
atree_map.set(i.display_name, {display: i.display, parents: i.parents, connectors: []});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < tree.length; i++) {
|
|
||||||
let node = tree[i];
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
for (let missing_row of missing_rows) {
|
|
||||||
if (document.getElementById("atree-row-" + missing_row) == null) {
|
|
||||||
for (let j = 0; j <= missing_row; j++) {
|
|
||||||
if (document.getElementById("atree-row-" + j) == null) {
|
|
||||||
let row = document.createElement('div');
|
|
||||||
row.classList.add("row");
|
|
||||||
row.id = "atree-row-" + j;
|
|
||||||
//was causing atree rows to be 0 height
|
|
||||||
row.style.minHeight = elem.scrollWidth / 9 + "px";
|
|
||||||
//row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px";
|
|
||||||
|
|
||||||
for (let k = 0; k < 9; k++) {
|
|
||||||
col = document.createElement('div');
|
|
||||||
col.classList.add('col', 'px-0');
|
|
||||||
col.style.minHeight = elem.scrollWidth / 9 + "px";
|
|
||||||
row.appendChild(col);
|
|
||||||
|
|
||||||
atree_connectors_map.set(j + "," + k, [])
|
|
||||||
};
|
|
||||||
elem.appendChild(row);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let connector_list = [];
|
|
||||||
// create connectors based on parent location
|
|
||||||
for (let parent of node.parents) {
|
|
||||||
let parent_node = atree_map.get(parent);
|
|
||||||
|
|
||||||
let connect_elem = document.createElement("div");
|
|
||||||
connect_elem.style = "background-size: cover; width: 100%; height: 100%;";
|
|
||||||
// connect up
|
|
||||||
for (let i = node.display.row - 1; i > parent_node.display.row; i--) {
|
|
||||||
let connector = connect_elem.cloneNode();
|
|
||||||
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"});
|
|
||||||
resolve_connector(i + "," + node.display.col, node);
|
|
||||||
}
|
|
||||||
// connect horizontally
|
|
||||||
let min = Math.min(parent_node.display.col, node.display.col);
|
|
||||||
let max = Math.max(parent_node.display.col, node.display.col);
|
|
||||||
for (let i = min + 1; i < max; i++) {
|
|
||||||
let connector = connect_elem.cloneNode();
|
|
||||||
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"});
|
|
||||||
resolve_connector(parent_node.display.row + "," + i, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect corners
|
|
||||||
|
|
||||||
if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) {
|
|
||||||
let connector = connect_elem.cloneNode();
|
|
||||||
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"});
|
|
||||||
if (parent_node.display.col > node.display.col) {
|
|
||||||
connector.classList.add("rotate-180");
|
|
||||||
}
|
|
||||||
else {// if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) {
|
|
||||||
connector.classList.add("rotate-270");
|
|
||||||
}
|
|
||||||
resolve_connector(parent_node.display.row + "," + node.display.col, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create node
|
|
||||||
let node_elem = document.createElement('div')
|
|
||||||
node_elem.style = "background-image: url('../media/atree/node.png'); background-size: cover; width: 100%; height: 100%;";
|
|
||||||
|
|
||||||
// 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');
|
|
||||||
active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto");
|
|
||||||
//was causing active element boxes to be 0 width
|
|
||||||
// active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "px";
|
|
||||||
active_tooltip.style.display = "none";
|
|
||||||
|
|
||||||
// tooltip text formatting
|
|
||||||
|
|
||||||
let active_tooltip_title = document.createElement('b');
|
|
||||||
active_tooltip_title.classList.add("scaled-font");
|
|
||||||
active_tooltip_title.innerHTML = node.display_name;
|
|
||||||
|
|
||||||
let active_tooltip_text = document.createElement('p');
|
|
||||||
active_tooltip_text.classList.add("scaled-font-sm");
|
|
||||||
active_tooltip_text.textContent = node.desc;
|
|
||||||
|
|
||||||
active_tooltip.appendChild(active_tooltip_title);
|
|
||||||
active_tooltip.appendChild(active_tooltip_text);
|
|
||||||
|
|
||||||
node_tooltip = active_tooltip.cloneNode(true);
|
|
||||||
|
|
||||||
active_tooltip.id = "atree-ab-" + node.display_name.replaceAll(" ", "");
|
|
||||||
|
|
||||||
node_tooltip.style.position = "absolute";
|
|
||||||
node_tooltip.style.zIndex = "100";
|
|
||||||
|
|
||||||
node_elem.appendChild(node_tooltip);
|
|
||||||
document.getElementById("atree-active").appendChild(active_tooltip);
|
|
||||||
|
|
||||||
node_elem.addEventListener('click', function(e) {
|
|
||||||
if (e.target !== this) {return;}
|
|
||||||
let tooltip = document.getElementById("atree-ab-" + node.display_name.replaceAll(" ", ""));
|
|
||||||
if (tooltip.style.display == "block") {
|
|
||||||
tooltip.style.display = "none";
|
|
||||||
this.classList.remove("atree-selected");
|
|
||||||
this.style.backgroundImage = 'url("../media/atree/node.png")';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tooltip.style.display = "block";
|
|
||||||
this.classList.add("atree-selected");
|
|
||||||
this.style.backgroundImage = 'url("../media/atree/node-selected.png")';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem);
|
|
||||||
};
|
|
||||||
|
|
||||||
atree_render_connection();
|
|
||||||
};
|
|
||||||
|
|
||||||
// resolve connector conflict
|
|
||||||
function resolve_connector(pos, node) {
|
|
||||||
if (atree_connectors_map.get(pos).length < 2) {return false;}
|
|
||||||
|
|
||||||
let line = false;
|
|
||||||
let angle = false;
|
|
||||||
let t = false;
|
|
||||||
for (let i of atree_connectors_map.get(pos)) {
|
|
||||||
if (i.type == "line") {
|
|
||||||
line += true;
|
|
||||||
} else if (i.type == "angle") {
|
|
||||||
angle += true;
|
|
||||||
} else if (i.type == "t") {
|
|
||||||
t += true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let connect_elem = document.createElement("div");
|
|
||||||
|
|
||||||
if ((line && 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"}])
|
|
||||||
}
|
|
||||||
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"}])
|
|
||||||
}
|
|
||||||
// override the conflict with the first children
|
|
||||||
atree_connectors_map.set(pos, [atree_connectors_map.get(pos)[0]])
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if a node doesn't have same row w/ its parents (used to solve conflict)
|
|
||||||
function atree_same_row(node) {
|
|
||||||
for (let i of node.parents) {
|
|
||||||
if (node.display.row == atree_map.get(i).display.row) { return false; }
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw the connector onto the screen
|
|
||||||
function atree_render_connection() {
|
|
||||||
for (let i of atree_connectors_map.keys()) {
|
|
||||||
if (atree_connectors_map.get(i).length != 0) {
|
|
||||||
document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||||
|
|
||||||
let tdb;
|
let tdb;
|
||||||
|
|
|
@ -48,9 +48,11 @@ function optimizeStrDex() {
|
||||||
let total_damage = 0;
|
let total_damage = 0;
|
||||||
for (const part of spell_parts) {
|
for (const part of spell_parts) {
|
||||||
if (part.type === "damage") {
|
if (part.type === "damage") {
|
||||||
let _results = calculateSpellDamage(stats, part.conversion,
|
let tmp_conv = [];
|
||||||
stats.get("sdRaw"), stats.get("sdPct"),
|
for (let i in part.conversion) {
|
||||||
part.multiplier / 100, player_build.weapon.statMap, total_skillpoints, 1);
|
tmp_conv.push(part.conversion[i] * part.multiplier);
|
||||||
|
}
|
||||||
|
let _results = calculateSpellDamage(stats, player_build.weapon.statMap, tmp_conv, true);
|
||||||
let totalDamNormal = _results[0];
|
let totalDamNormal = _results[0];
|
||||||
let totalDamCrit = _results[1];
|
let totalDamCrit = _results[1];
|
||||||
let results = _results[2];
|
let results = _results[2];
|
||||||
|
|
131
js/powders.js
|
@ -61,3 +61,134 @@ let powderSpecialStats = [
|
||||||
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
|
_ps("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
|
_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];
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,32 @@
|
||||||
|
// 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")
|
d3.select("#graph_body")
|
||||||
.append("div")
|
.append("div")
|
||||||
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
|
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
|
||||||
|
|
258
js/utils.js
|
@ -74,6 +74,8 @@ function log(b, n) {
|
||||||
// https://stackoverflow.com/a/27696695
|
// https://stackoverflow.com/a/27696695
|
||||||
// Modified for fixed precision
|
// Modified for fixed precision
|
||||||
|
|
||||||
|
// Base64.fromInt(-2147483648); // gives "200000"
|
||||||
|
// Base64.toInt("200000"); // gives -2147483648
|
||||||
Base64 = (function () {
|
Base64 = (function () {
|
||||||
var digitsStr =
|
var digitsStr =
|
||||||
// 0 8 16 24 32 40 48 56 63
|
// 0 8 16 24 32 40 48 56 63
|
||||||
|
@ -125,8 +127,246 @@ Base64 = (function () {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Base64.fromInt(-2147483648); // gives "200000"
|
|
||||||
// Base64.toInt("200000"); // gives -2147483648
|
/** A class used to represent an arbitrary length bit vector. Very useful for encoding and decoding.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class BitVector {
|
||||||
|
|
||||||
|
/** Constructs an arbitrary-length bit vector.
|
||||||
|
* @class
|
||||||
|
* @param {String | Number} data - The data to append.
|
||||||
|
* @param {Number} length - A set length for the data. Ignored if data is a string.
|
||||||
|
*
|
||||||
|
* The structure of the Uint32Array should be [[last, ..., first], ..., [last, ..., first], [empty space, last, ..., first]]
|
||||||
|
*/
|
||||||
|
constructor(data, length) {
|
||||||
|
let bit_vec = [];
|
||||||
|
|
||||||
|
if (typeof data === "string") {
|
||||||
|
let int = 0;
|
||||||
|
let bv_idx = 0;
|
||||||
|
length = data.length * 6;
|
||||||
|
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
let char = Base64.toInt(data[i]);
|
||||||
|
let pre_pos = bv_idx % 32;
|
||||||
|
int |= (char << bv_idx);
|
||||||
|
bv_idx += 6;
|
||||||
|
let post_pos = bv_idx % 32;
|
||||||
|
if (post_pos < pre_pos) { //we have to have filled up the integer
|
||||||
|
bit_vec.push(int);
|
||||||
|
int = (char >>> (6 - post_pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == data.length - 1 && post_pos != 0) {
|
||||||
|
bit_vec.push(int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof data === "number") {
|
||||||
|
if (typeof length === "undefined")
|
||||||
|
if (length < 0) {
|
||||||
|
throw new RangeError("BitVector must have nonnegative length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//convert to int just in case
|
||||||
|
data = Math.round(data);
|
||||||
|
|
||||||
|
//range of numbers that won't fit in a uint32
|
||||||
|
if (data > 2**32 - 1 || data < -(2 ** 32 - 1)) {
|
||||||
|
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
|
||||||
|
}
|
||||||
|
bit_vec.push(data);
|
||||||
|
} else {
|
||||||
|
throw new TypeError("BitVector must be instantiated with a Number or a B64 String");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.length = length;
|
||||||
|
this.bits = new Uint32Array(bit_vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return value of bit at index idx.
|
||||||
|
*
|
||||||
|
* @param {Number} idx - The index to read
|
||||||
|
*
|
||||||
|
* @returns The bit value at position idx
|
||||||
|
*/
|
||||||
|
read_bit(idx) {
|
||||||
|
if (idx < 0 || idx >= this.length) {
|
||||||
|
throw new RangeError("Cannot read bit outside the range of the BitVector. ("+idx+" > "+this.length+")");
|
||||||
|
}
|
||||||
|
return ((this.bits[Math.floor(idx / 32)] & (1 << idx)) == 0 ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
|
||||||
|
*
|
||||||
|
* @param {Number} start - The index to start slicing from. Inclusive.
|
||||||
|
* @param {Number} end - The index to end slicing at. Exclusive.
|
||||||
|
*
|
||||||
|
* @returns An integer representation of the sliced bits.
|
||||||
|
*/
|
||||||
|
slice(start, end) {
|
||||||
|
//TO NOTE: JS shifting is ALWAYS in mod 32. a << b will do a << (b mod 32) implicitly.
|
||||||
|
|
||||||
|
if (end < start) {
|
||||||
|
throw new RangeError("Cannot slice a range where the end is before the start.");
|
||||||
|
} else if (end == start) {
|
||||||
|
return 0;
|
||||||
|
} else if (end - start > 32) {
|
||||||
|
//requesting a slice of longer than 32 bits (safe integer "length")
|
||||||
|
throw new RangeError("Cannot slice a range of longer than 32 bits (unsafe to store in an integer).");
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = 0;
|
||||||
|
if (Math.floor((end - 1) / 32) == Math.floor(start / 32)) {
|
||||||
|
//the range is within 1 uint32 section - do some relatively fast bit twiddling
|
||||||
|
res = (this.bits[Math.floor(start / 32)] & ~((((~0) << ((end - 1))) << 1) | ~((~0) << (start)))) >>> (start % 32);
|
||||||
|
} else {
|
||||||
|
//the number of bits in the uint32s
|
||||||
|
let start_pos = (start % 32);
|
||||||
|
let int_idx = Math.floor(start/32);
|
||||||
|
res = (this.bits[int_idx] & ((~0) << (start))) >>> (start_pos);
|
||||||
|
res |= (this.bits[int_idx + 1] & ~((~0) << (end))) << (32 - start_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// General code - slow
|
||||||
|
// for (let i = start; i < end; i++) {
|
||||||
|
// res |= (get_bit(i) << (i - start));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assign bit at index idx to 1.
|
||||||
|
*
|
||||||
|
* @param {Number} idx - The index to set.
|
||||||
|
*/
|
||||||
|
set_bit(idx) {
|
||||||
|
if (idx < 0 || idx >= this.length) {
|
||||||
|
throw new RangeError("Cannot set bit outside the range of the BitVector.");
|
||||||
|
}
|
||||||
|
this.bits[Math.floor(idx / 32)] |= (1 << idx % 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Assign bit at index idx to 0.
|
||||||
|
*
|
||||||
|
* @param {Number} idx - The index to clear.
|
||||||
|
*/
|
||||||
|
clear_bit(idx) {
|
||||||
|
if (idx < 0 || idx >= this.length) {
|
||||||
|
throw new RangeError("Cannot clear bit outside the range of the BitVector.");
|
||||||
|
}
|
||||||
|
this.bits[Math.floor(idx / 32)] &= ~(1 << idx % 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
|
||||||
|
*
|
||||||
|
* @returns A b64 string representation of the BitVector.
|
||||||
|
*/
|
||||||
|
toB64() {
|
||||||
|
if (this.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let b64_str = "";
|
||||||
|
let i = 0;
|
||||||
|
while (i < this.length) {
|
||||||
|
b64_str += Base64.fromIntV(this.slice(i, i + 6), 1);
|
||||||
|
i += 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return b64_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
|
||||||
|
*
|
||||||
|
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
|
||||||
|
*/
|
||||||
|
toString() {
|
||||||
|
let ret_str = "";
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
ret_str = (this.read_bit(i) == 0 ? "0": "1") + ret_str;
|
||||||
|
}
|
||||||
|
return ret_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
|
||||||
|
*
|
||||||
|
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
|
||||||
|
*/
|
||||||
|
toStringR() {
|
||||||
|
let ret_str = "";
|
||||||
|
for (let i = 0; i < this.length; i++) {
|
||||||
|
ret_str += (this.read_bit(i) == 0 ? "0": "1");
|
||||||
|
}
|
||||||
|
return ret_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Appends data to the BitVector.
|
||||||
|
*
|
||||||
|
* @param {Number | String} data - The data to append.
|
||||||
|
* @param {Number} length - The length, in bits, of the new data. This is ignored if data is a string.
|
||||||
|
*/
|
||||||
|
append(data, length) {
|
||||||
|
if (length < 0) {
|
||||||
|
throw new RangeError("BitVector length must increase by a nonnegative number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let bit_vec = [];
|
||||||
|
for (const uint of this.bits) {
|
||||||
|
bit_vec.push(uint);
|
||||||
|
}
|
||||||
|
if (typeof data === "string") {
|
||||||
|
let int = bit_vec[bit_vec.length - 1];
|
||||||
|
let bv_idx = this.length;
|
||||||
|
length = data.length * 6;
|
||||||
|
let updated_curr = false;
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
let char = Base64.toInt(data[i]);
|
||||||
|
let pre_pos = bv_idx % 32;
|
||||||
|
int |= (char << bv_idx);
|
||||||
|
bv_idx += 6;
|
||||||
|
let post_pos = bv_idx % 32;
|
||||||
|
if (post_pos < pre_pos) { //we have to have filled up the integer
|
||||||
|
if (bit_vec.length == this.bits.length && !updated_curr) {
|
||||||
|
bit_vec[bit_vec.length - 1] = int;
|
||||||
|
updated_curr = true;
|
||||||
|
} else {
|
||||||
|
bit_vec.push(int);
|
||||||
|
}
|
||||||
|
int = (char >>> (6 - post_pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == data.length - 1) {
|
||||||
|
if (bit_vec.length == this.bits.length && !updated_curr) {
|
||||||
|
bit_vec[bit_vec.length - 1] = int;
|
||||||
|
} else if (post_pos != 0) {
|
||||||
|
bit_vec.push(int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof data === "number") {
|
||||||
|
//convert to int just in case
|
||||||
|
let int = Math.round(data);
|
||||||
|
|
||||||
|
//range of numbers that "could" fit in a uint32 -> [0, 2^32) U [-2^31, 2^31)
|
||||||
|
if (data > 2**32 - 1 || data < -(2 ** 31)) {
|
||||||
|
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
|
||||||
|
}
|
||||||
|
//could be split between multiple new ints
|
||||||
|
//reminder that shifts implicitly mod 32
|
||||||
|
bit_vec[bit_vec.length - 1] |= ((int & ~((~0) << length)) << (this.length));
|
||||||
|
if (((this.length - 1) % 32 + 1) + length > 32) {
|
||||||
|
bit_vec.push(int >>> (32 - this.length));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new TypeError("BitVector must be appended with a Number or a B64 String");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bits = new Uint32Array(bit_vec);
|
||||||
|
this.length += length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative.
|
Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative.
|
||||||
|
@ -499,3 +739,17 @@ function assert_error(func_binding, msg) {
|
||||||
}
|
}
|
||||||
throw new Error(msg ? msg : "Function didn't throw an error.");
|
throw new Error(msg ? msg : "Function didn't throw an error.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep copy object/array of basic types.
|
||||||
|
*/
|
||||||
|
function deepcopy(obj) {
|
||||||
|
if (typeof(obj) !== 'object' || obj === null) { // null or value type
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
let ret = Array.isArray(obj) ? [] : {};
|
||||||
|
for (let key in obj) {
|
||||||
|
ret[key] = deepcopy(obj[key]);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 962 B |
BIN
media/atree/highlight_angle.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
media/atree/highlight_c.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
media/atree/highlight_c_2_a.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_c_2_l.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_c_3.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_line.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
media/atree/highlight_t_2_a.png
Normal file
After Width: | Height: | Size: 708 B |
BIN
media/atree/highlight_t_2_l.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
media/atree/highlight_t_3.png
Normal file
After Width: | Height: | Size: 654 B |
BIN
media/atree/node_0.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
media/atree/node_0_blocked.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
media/atree/node_1.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
media/atree/node_1_blocked.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
media/atree/node_2.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
media/atree/node_2_blocked.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
media/atree/node_3.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
media/atree/node_3_blocked.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
media/atree/node_4.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
media/atree/node_4_blocked.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
media/atree/node_highlight.png
Normal file
After Width: | Height: | Size: 23 KiB |
64
py_script/atree-convertID.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
"""
|
||||||
|
Generate a JSON Ability Tree [atree_constants_idfied.json] with:
|
||||||
|
- All references replaced by numerical IDs
|
||||||
|
given a JSON Ability Tree with reference as string AND a JSON Ability Names to IDs.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Ability names to IDs data
|
||||||
|
with open("atree_ids.json") as f:
|
||||||
|
id_data = json.loads(f.read())
|
||||||
|
|
||||||
|
# Ability tree data with reference as string
|
||||||
|
with open("atree_constants.json") as f:
|
||||||
|
atree_data = json.loads(f.read())
|
||||||
|
|
||||||
|
for _class, info in atree_data.items():
|
||||||
|
def translate(path, ref):
|
||||||
|
ref_dict = info
|
||||||
|
for x in path:
|
||||||
|
ref_dict = ref_dict[x]
|
||||||
|
ref_dict[ref] = id_data[_class][ref_dict[ref]]
|
||||||
|
|
||||||
|
for abil in range(len(info)):
|
||||||
|
info[abil]["id"] = id_data[_class][info[abil]["display_name"]]
|
||||||
|
for ref in range(len(info[abil]["parents"])):
|
||||||
|
translate([abil, "parents"], ref)
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["dependencies"])):
|
||||||
|
translate([abil, "dependencies"], ref)
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["blockers"])):
|
||||||
|
translate([abil, "blockers"], ref)
|
||||||
|
|
||||||
|
if "base_abil" in info[abil]:
|
||||||
|
base_abil_name = info[abil]["base_abil"]
|
||||||
|
if base_abil_name in id_data[_class]:
|
||||||
|
translate([abil], "base_abil")
|
||||||
|
|
||||||
|
if "effects" not in info[abil]:
|
||||||
|
print("WARNING: abil missing 'effects' tag")
|
||||||
|
print(info[abil])
|
||||||
|
info[abil]["effects"] = []
|
||||||
|
for effect in info[abil]["effects"]:
|
||||||
|
if effect["type"] == "raw_stat":
|
||||||
|
for bonus in effect["bonuses"]:
|
||||||
|
if "abil" in bonus and bonus["abil"] in id_data[_class]:
|
||||||
|
bonus["abil"] = id_data[_class][bonus["abil"]]
|
||||||
|
|
||||||
|
elif effect["type"] == "stat_scaling":
|
||||||
|
if "inputs" in effect: # Might not exist for sliders
|
||||||
|
for _input in effect["inputs"]:
|
||||||
|
if "abil" in _input and _input["abil"] in id_data[_class]:
|
||||||
|
_input["abil"] = id_data[_class][_input["abil"]]
|
||||||
|
if isinstance(effect["output"], list):
|
||||||
|
for output in effect["output"]:
|
||||||
|
if "abil" in output and output["abil"] in id_data[_class]:
|
||||||
|
output["abil"] = id_data[_class][output["abil"]]
|
||||||
|
else:
|
||||||
|
if "abil" in effect["output"] and effect["output"]["abil"] in id_data[_class]:
|
||||||
|
effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]
|
||||||
|
|
||||||
|
|
||||||
|
with open('atree_constants_idfied.json', 'w', encoding='utf-8') as abil_dest:
|
||||||
|
json.dump(atree_data, abil_dest, ensure_ascii=False, indent=4)
|
42
py_script/atree-generateID.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
"""
|
||||||
|
Generate a minified JSON Ability Tree [atree_constants_min.json] AND a minified .js form [atree_constants_min.js] of the Ability Tree with:
|
||||||
|
- All references replaced by numerical IDs
|
||||||
|
- Extra JSON File with Class: [Original name as key and Assigned IDs as value].
|
||||||
|
given [atree_constants.js] .js form of the Ability Tree with reference as string.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
abilDict = {}
|
||||||
|
with open("atree_constants.js") as f:
|
||||||
|
data = f.read()
|
||||||
|
data = data.replace("const atrees = ", "")
|
||||||
|
data = json.loads(data)
|
||||||
|
for classType, info in data.items():
|
||||||
|
_id = 0
|
||||||
|
abilDict[classType] = {}
|
||||||
|
for abil in info:
|
||||||
|
abilDict[classType][abil["display_name"]] = _id
|
||||||
|
_id += 1
|
||||||
|
|
||||||
|
with open("atree_ids.json", "w", encoding='utf-8') as id_dest:
|
||||||
|
json.dump(abilDict, id_dest, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
for classType, info in data.items():
|
||||||
|
for abil in range(len(info)):
|
||||||
|
info[abil]["id"] = abilDict[classType][info[abil]["display_name"]]
|
||||||
|
for ref in range(len(info[abil]["parents"])):
|
||||||
|
info[abil]["parents"][ref] = abilDict[classType][info[abil]["parents"][ref]]
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["dependencies"])):
|
||||||
|
info[abil]["dependencies"][ref] = abilDict[classType][info[abil]["dependencies"][ref]]
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["blockers"])):
|
||||||
|
info[abil]["blockers"][ref] = abilDict[classType][info[abil]["blockers"][ref]]
|
||||||
|
|
||||||
|
data_str = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
|
||||||
|
data_str = "const atrees=" + data_str
|
||||||
|
with open('atree_constants_min.js', 'w', encoding='utf-8') as abil_dest:
|
||||||
|
abil_dest.write(data_str)
|
||||||
|
|
||||||
|
with open('atree_constants_min.json', 'w', encoding='utf-8') as json_dest:
|
||||||
|
json.dump(data, json_dest, ensure_ascii=False, separators=(',', ':'))
|
146
py_script/atree-ids.json
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
{
|
||||||
|
"Archer": {
|
||||||
|
"Arrow Shield": 1,
|
||||||
|
"Escape": 2,
|
||||||
|
"Arrow Bomb": 3,
|
||||||
|
"Heart Shatter": 4,
|
||||||
|
"Fire Creep": 5,
|
||||||
|
"Bryophyte Roots": 6,
|
||||||
|
"Nimble String": 7,
|
||||||
|
"Arrow Storm": 8,
|
||||||
|
"Guardian Angels": 9,
|
||||||
|
"Windy Feet": 10,
|
||||||
|
"Basaltic Trap": 11,
|
||||||
|
"Windstorm": 12,
|
||||||
|
"Grappling Hook": 13,
|
||||||
|
"Implosion": 14,
|
||||||
|
"Twain's Arc": 15,
|
||||||
|
"Fierce Stomp": 16,
|
||||||
|
"Scorched Earth": 17,
|
||||||
|
"Leap": 18,
|
||||||
|
"Shocking Bomb": 19,
|
||||||
|
"Mana Trap": 20,
|
||||||
|
"Escape Artist": 21,
|
||||||
|
"Initiator": 22,
|
||||||
|
"Call of the Hound": 23,
|
||||||
|
"Arrow Hurricane": 24,
|
||||||
|
"Geyser Stomp": 25,
|
||||||
|
"Crepuscular Ray": 26,
|
||||||
|
"Grape Bomb": 27,
|
||||||
|
"Tangled Traps": 28,
|
||||||
|
"Snow Storm": 29,
|
||||||
|
"All-Seeing Panoptes": 30,
|
||||||
|
"Minefield": 31,
|
||||||
|
"Bow Proficiency I": 32,
|
||||||
|
"Cheaper Arrow Bomb": 33,
|
||||||
|
"Cheaper Arrow Storm": 34,
|
||||||
|
"Cheaper Escape": 35,
|
||||||
|
"Earth Mastery": 36,
|
||||||
|
"Thunder Mastery": 37,
|
||||||
|
"Water Mastery": 38,
|
||||||
|
"Air Mastery": 39,
|
||||||
|
"Fire Mastery": 40,
|
||||||
|
"More Shields": 41,
|
||||||
|
"Stormy Feet": 42,
|
||||||
|
"Refined Gunpowder": 43,
|
||||||
|
"More Traps": 44,
|
||||||
|
"Better Arrow Shield": 45,
|
||||||
|
"Better Leap": 46,
|
||||||
|
"Better Guardian Angels": 47,
|
||||||
|
"Cheaper Arrow Storm (2)": 48,
|
||||||
|
"Precise Shot": 49,
|
||||||
|
"Cheaper Arrow Shield": 50,
|
||||||
|
"Rocket Jump": 51,
|
||||||
|
"Cheaper Escape (2)": 52,
|
||||||
|
"Stronger Hook": 53,
|
||||||
|
"Cheaper Arrow Bomb (2)": 54,
|
||||||
|
"Bouncing Bomb": 55,
|
||||||
|
"Homing Shots": 56,
|
||||||
|
"Shrapnel Bomb": 57,
|
||||||
|
"Elusive": 58,
|
||||||
|
"Double Shots": 59,
|
||||||
|
"Triple Shots": 60,
|
||||||
|
"Power Shots": 61,
|
||||||
|
"Focus": 62,
|
||||||
|
"More Focus": 63,
|
||||||
|
"More Focus (2)": 64,
|
||||||
|
"Traveler": 65,
|
||||||
|
"Patient Hunter": 66,
|
||||||
|
"Stronger Patient Hunter": 67,
|
||||||
|
"Frenzy": 68,
|
||||||
|
"Phantom Ray": 69,
|
||||||
|
"Arrow Rain": 70,
|
||||||
|
"Decimator": 71
|
||||||
|
},
|
||||||
|
"Warrior": {
|
||||||
|
"Bash": 1,
|
||||||
|
"Spear Proficiency 1": 2,
|
||||||
|
"Cheaper Bash": 3,
|
||||||
|
"Double Bash": 4,
|
||||||
|
"Charge": 5,
|
||||||
|
"Heavy Impact": 6,
|
||||||
|
"Vehement": 7,
|
||||||
|
"Tougher Skin": 8,
|
||||||
|
"Uppercut": 9,
|
||||||
|
"Cheaper Charge": 10,
|
||||||
|
"War Scream": 11,
|
||||||
|
"Earth Mastery": 12,
|
||||||
|
"Thunder Mastery": 13,
|
||||||
|
"Water Mastery": 14,
|
||||||
|
"Air Mastery": 15,
|
||||||
|
"Fire Mastery": 16,
|
||||||
|
"Quadruple Bash": 17,
|
||||||
|
"Fireworks": 18,
|
||||||
|
"Half-Moon Swipe": 19,
|
||||||
|
"Flyby Jab": 20,
|
||||||
|
"Flaming Uppercut": 21,
|
||||||
|
"Iron Lungs": 22,
|
||||||
|
"Generalist": 23,
|
||||||
|
"Counter": 24,
|
||||||
|
"Mantle of the Bovemists": 25,
|
||||||
|
"Bak'al's Grasp": 26,
|
||||||
|
"Spear Proficiency 2": 27,
|
||||||
|
"Cheaper Uppercut": 28,
|
||||||
|
"Aerodynamics": 29,
|
||||||
|
"Provoke": 30,
|
||||||
|
"Precise Strikes": 31,
|
||||||
|
"Air Shout": 32,
|
||||||
|
"Enraged Blow": 33,
|
||||||
|
"Flying Kick": 34,
|
||||||
|
"Stronger Mantle": 35,
|
||||||
|
"Manachism": 36,
|
||||||
|
"Boiling Blood": 37,
|
||||||
|
"Ragnarokkr": 38,
|
||||||
|
"Ambidextrous": 39,
|
||||||
|
"Burning Heart": 40,
|
||||||
|
"Stronger Bash": 41,
|
||||||
|
"Intoxicating Blood": 42,
|
||||||
|
"Comet": 43,
|
||||||
|
"Collide": 44,
|
||||||
|
"Rejuvenating Skin": 45,
|
||||||
|
"Uncontainable Corruption": 46,
|
||||||
|
"Radiant Devotee": 47,
|
||||||
|
"Whirlwind Strike": 48,
|
||||||
|
"Mythril Skin": 49,
|
||||||
|
"Armour Breaker": 50,
|
||||||
|
"Shield Strike": 51,
|
||||||
|
"Sparkling Hope": 52,
|
||||||
|
"Massive Bash": 53,
|
||||||
|
"Tempest": 54,
|
||||||
|
"Spirit of the Rabbit": 55,
|
||||||
|
"Massacre": 56,
|
||||||
|
"Axe Kick": 57,
|
||||||
|
"Radiance": 58,
|
||||||
|
"Cheaper Bash 2": 59,
|
||||||
|
"Cheaper War Scream": 60,
|
||||||
|
"Discombobulate": 61,
|
||||||
|
"Thunderclap": 62,
|
||||||
|
"Cyclone": 63,
|
||||||
|
"Second Chance": 64,
|
||||||
|
"Blood Pact": 65,
|
||||||
|
"Haemorrhage": 66,
|
||||||
|
"Brink of Madness": 67,
|
||||||
|
"Cheaper Uppercut 2": 68,
|
||||||
|
"Martyr": 69
|
||||||
|
}
|
||||||
|
}
|
4967
py_script/atree-parse.json
Normal file
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 482 B |
Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 463 B |
Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 463 B |
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 456 B |
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 634 B |
Before Width: | Height: | Size: 647 B After Width: | Height: | Size: 647 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 779 B |
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 872 B |
Before Width: | Height: | Size: 905 B After Width: | Height: | Size: 905 B |
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 917 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 635 B After Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 709 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 707 B |
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 797 B |
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 852 B |
Before Width: | Height: | Size: 554 B After Width: | Height: | Size: 554 B |
Before Width: | Height: | Size: 812 B After Width: | Height: | Size: 812 B |
Before Width: | Height: | Size: 513 B After Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 592 B After Width: | Height: | Size: 592 B |
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 510 B |
Before Width: | Height: | Size: 616 B After Width: | Height: | Size: 616 B |
Before Width: | Height: | Size: 700 B After Width: | Height: | Size: 700 B |
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 905 B After Width: | Height: | Size: 905 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1,007 B After Width: | Height: | Size: 1,007 B |
Before Width: | Height: | Size: 498 B After Width: | Height: | Size: 498 B |
Before Width: | Height: | Size: 720 B After Width: | Height: | Size: 720 B |
Before Width: | Height: | Size: 748 B After Width: | Height: | Size: 748 B |
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 615 B |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 722 B After Width: | Height: | Size: 722 B |
Before Width: | Height: | Size: 561 B After Width: | Height: | Size: 561 B |
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 572 B |