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_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/build_encode_decode.js"></script>
|
||||
<script type="text/javascript" src="../js/display_atree.js"></script>
|
||||
<script type="text/javascript" src="../js/atree.js"></script>
|
||||
<script type="text/javascript" src="../js/builder.js"></script>
|
||||
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||
|
@ -1433,7 +1433,6 @@
|
|||
<a id="saveLink">savelink</a>
|
||||
</div>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -429,7 +429,7 @@
|
|||
</div>
|
||||
<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')">
|
||||
Show Ability Tree
|
||||
Edit Abilities
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-3 py-2">
|
||||
|
@ -618,10 +618,14 @@
|
|||
</div>
|
||||
<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="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 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>
|
||||
|
@ -1264,21 +1268,23 @@
|
|||
<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>
|
||||
<div class = "col">
|
||||
<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>
|
||||
<div class = "col">
|
||||
<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>
|
||||
<div class = "col">
|
||||
<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>
|
||||
<div class = "col">
|
||||
<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 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 dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
||||
</div>
|
||||
<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 dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
||||
</div>
|
||||
<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 dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
||||
</div>
|
||||
<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 dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col">
|
||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
||||
|
@ -1330,7 +1336,7 @@
|
|||
<div class="col-12 dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<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>
|
||||
</div>
|
||||
<div id="credits">
|
||||
|
@ -1422,7 +1428,7 @@
|
|||
<script type="text/javascript" src="../js/build.js"></script>
|
||||
<script type="text/javascript" src="../js/build_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/build_encode_decode.js"></script>
|
||||
<script type="text/javascript" src="../js/display_atree.js"></script>
|
||||
<script type="text/javascript" src="../js/atree.js"></script>
|
||||
<script type="text/javascript" src="../js/builder.js"></script>
|
||||
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||
|
|
|
@ -282,7 +282,7 @@
|
|||
<div class="col dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<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>
|
||||
</div>
|
||||
<div id="credits">
|
||||
|
@ -301,8 +301,6 @@
|
|||
<script type="text/javascript" src="../js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="../js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/display.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="../js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="../js/load.js"></script>
|
||||
<script type="text/javascript" src="../js/craft.js"></script>
|
||||
|
|
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);
|
||||
}
|
||||
|
||||
.rotate-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.hide-scroll {
|
||||
|
@ -483,3 +488,13 @@ a:hover {
|
|||
.hide-scroll::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
||||
.atree-selected {
|
||||
outline: 5px solid rgba(95, 214, 223, 0.8);
|
||||
}
|
||||
|
||||
.atree-circle {
|
||||
border-radius:50%;
|
||||
-moz-border-radius:50%;
|
||||
-webkit-border-radius:50%;
|
||||
}
|
|
@ -62,8 +62,6 @@
|
|||
<script type="text/javascript" src="/js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="/js/item.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -79,8 +79,6 @@
|
|||
<script type="text/javascript" src="/js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="/js/query_2.js"></script>
|
||||
<script type="text/javascript" src="/js/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
|
|
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(){
|
||||
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"];
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "damMobs", "defMobs"];
|
||||
|
||||
let must_ids = [
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw","eDamPct","eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw","tDamPct","tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw","wDamPct","wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw","fDamPct","fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
"mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
]
|
||||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
statMap.set("damageMultiplier", 1);
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
for (const staticID of must_ids) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
statMap.set("hp", levelToHPBase(this.level));
|
||||
|
||||
let major_ids = new Set();
|
||||
|
@ -176,15 +189,7 @@ class Build{
|
|||
}
|
||||
for (const staticID of staticIDs) {
|
||||
if (item_stats.get(staticID)) {
|
||||
if (staticID === "dmgMobs") {
|
||||
statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID));
|
||||
}
|
||||
else if (staticID === "defMobs") {
|
||||
statMap.set('defMultiplier', statMap.get('defMultiplier') * item_stats.get(staticID));
|
||||
}
|
||||
else {
|
||||
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item_stats.get("majorIds")) {
|
||||
|
@ -193,6 +198,8 @@ class Build{
|
|||
}
|
||||
}
|
||||
}
|
||||
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
|
||||
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets.get(setName).bonuses[count-1];
|
||||
|
@ -211,13 +218,5 @@ class Build{
|
|||
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
|
||||
|
||||
this.statMap = statMap;
|
||||
|
||||
this.aggregateStats();
|
||||
}
|
||||
|
||||
aggregateStats() {
|
||||
let statMap = this.statMap;
|
||||
let weapon_stats = this.weapon.statMap;
|
||||
statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ function parsePowdering(powder_info) {
|
|||
return [powdering, powder_info];
|
||||
}
|
||||
|
||||
let atree_data = null;
|
||||
|
||||
/*
|
||||
* Populate fields based on url, and calculate build.
|
||||
*/
|
||||
|
@ -62,7 +64,7 @@ function decodeBuild(url_tag) {
|
|||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
else if (version_number <= 6) {
|
||||
else if (version_number <= 7) {
|
||||
let info_str = info[1];
|
||||
let start_idx = 0;
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
|
@ -101,7 +103,7 @@ function decodeBuild(url_tag) {
|
|||
let powder_info = info[1].slice(10);
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
} else if (version_number <= 6){
|
||||
} else if (version_number <= 7){
|
||||
level = Base64.toInt(info[1].slice(10,12));
|
||||
setValue("level-choice",level);
|
||||
save_skp = true;
|
||||
|
@ -117,7 +119,7 @@ function decodeBuild(url_tag) {
|
|||
info[1] = res[1];
|
||||
}
|
||||
// Tomes.
|
||||
if (version == 6) {
|
||||
if (version >= 6) {
|
||||
//tome values do not appear in anything before v6.
|
||||
for (let i in tomes) {
|
||||
let tome_str = info[1].charAt(i);
|
||||
|
@ -128,6 +130,14 @@ function decodeBuild(url_tag) {
|
|||
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) {
|
||||
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.
|
||||
*/
|
||||
function encodeBuild(build, powders, skillpoints) {
|
||||
function encodeBuild(build, powders, skillpoints, atree, atree_state) {
|
||||
|
||||
if (build) {
|
||||
let build_string;
|
||||
|
||||
//V6 encoding - Tomes
|
||||
//V7 encoding - ATree
|
||||
build_version = 5;
|
||||
build_string = "";
|
||||
tome_string = "";
|
||||
|
@ -189,6 +200,12 @@ function encodeBuild(build, powders, skillpoints) {
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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 wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "Shaman"]])
|
||||
const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula
|
||||
const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
//weaponTypes.push("sword");
|
||||
//console.log(types)
|
||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
||||
|
@ -66,13 +66,26 @@ let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tom
|
|||
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
||||
let skpReqs = skp_order.map(x => x + "Req");
|
||||
|
||||
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "dmgMobs", "defMobs"];
|
||||
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
|
||||
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
"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" ]
|
||||
|
||||
//File reading for ID translations for JSON purposes
|
||||
let reversetranslations = new Map();
|
||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include dmgMobs (wep tomes) and defMobs (armor tomes)
|
||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include damMobs (wep tomes) and defMobs (armor tomes)
|
||||
for (const [k, v] of translations) {
|
||||
reversetranslations.set(v, k);
|
||||
}
|
||||
|
@ -103,7 +116,19 @@ let nonRolledIDs = [
|
|||
"skillpoints",
|
||||
"reqs",
|
||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
||||
"majorIds"];
|
||||
"majorIds",
|
||||
"damMobs",
|
||||
"defMobs",
|
||||
// wynn2 damages.
|
||||
"eDamAddMin","eDamAddMax",
|
||||
"tDamAddMin","tDamAddMax",
|
||||
"wDamAddMin","wDamAddMax",
|
||||
"fDamAddMin","fDamAddMax",
|
||||
"aDamAddMin","aDamAddMax",
|
||||
"nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
"damAddMin","damAddMax", // all
|
||||
"rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral).
|
||||
];
|
||||
let rolledIDs = [
|
||||
"hprPct",
|
||||
"mr",
|
||||
|
@ -131,13 +156,22 @@ let rolledIDs = [
|
|||
"spPct2", "spRaw2",
|
||||
"spPct3", "spRaw3",
|
||||
"spPct4", "spRaw4",
|
||||
"rainbowRaw",
|
||||
"pDamRaw",
|
||||
"sprint",
|
||||
"sprintReg",
|
||||
"jh",
|
||||
"lq",
|
||||
"gXp",
|
||||
"gSpd"
|
||||
"gSpd",
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
];
|
||||
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
|
||||
|
@ -188,7 +222,6 @@ function expandItem(item) {
|
|||
}
|
||||
expandedItem.set("minRolls",minRolls);
|
||||
expandedItem.set("maxRolls",maxRolls);
|
||||
expandedItem.set("powders", []);
|
||||
return expandedItem;
|
||||
}
|
||||
|
||||
|
|
|
@ -147,20 +147,8 @@ function toggle_tab(tab) {
|
|||
} else {
|
||||
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) {
|
||||
for (const i of skp_order) {
|
||||
|
@ -397,16 +385,12 @@ function collapse_element(elmnt) {
|
|||
document.querySelector(elmnt).style.removeProperty('display');
|
||||
}
|
||||
|
||||
// TODO: Learn and use await
|
||||
function init() {
|
||||
console.log("builder.js init");
|
||||
init_autocomplete();
|
||||
|
||||
// Other "main" stuff
|
||||
// Spell dropdowns
|
||||
for (const i of spell_disp) {
|
||||
document.querySelector("#"+i+"Avg").addEventListener("click", () => toggle_spell_tab(i));
|
||||
}
|
||||
for (const eq of equipment_keys) {
|
||||
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ function update_armor_powder_specials(elem_id) {
|
|||
//update the label associated w/ the slider
|
||||
let elem = document.getElementById(elem_id);
|
||||
let label = document.getElementById(elem_id + "_label");
|
||||
|
||||
let value = elem.value;
|
||||
|
||||
label.textContent = label.textContent.split(":")[0] + ": " + value
|
||||
|
@ -89,23 +88,18 @@ let powder_special_input = new (class extends ComputeNode {
|
|||
})();
|
||||
|
||||
function updatePowderSpecials(buttonId) {
|
||||
let name = (buttonId).split("-")[0];
|
||||
let power = (buttonId).split("-")[1]; // [1, 5]
|
||||
|
||||
let prefix = (buttonId).split("-")[0].replace(' ', '_') + '-';
|
||||
let elem = document.getElementById(buttonId);
|
||||
if (elem.classList.contains("toggleOn")) { //toggle the pressed button off
|
||||
elem.classList.remove("toggleOn");
|
||||
} else {
|
||||
if (elem.classList.contains("toggleOn")) { elem.classList.remove("toggleOn"); }
|
||||
else {
|
||||
for (let i = 1;i < 6; i++) { //toggle all pressed buttons of the same powder special off
|
||||
//name is same, power is i
|
||||
if(document.getElementById(name.replace(" ", "_") + "-" + i).classList.contains("toggleOn")) {
|
||||
document.getElementById(name.replace(" ", "_") + "-" + i).classList.remove("toggleOn");
|
||||
}
|
||||
const elem2 = document.getElementById(prefix + i);
|
||||
if(elem2.classList.contains("toggleOn")) { elem2.classList.remove("toggleOn"); }
|
||||
}
|
||||
//toggle the pressed button on
|
||||
elem.classList.add("toggleOn");
|
||||
}
|
||||
|
||||
powder_special_input.mark_dirty().update();
|
||||
}
|
||||
|
||||
|
@ -132,6 +126,7 @@ class PowderSpecialCalcNode extends ComputeNode {
|
|||
}
|
||||
|
||||
class PowderSpecialDisplayNode extends ComputeNode {
|
||||
// TODO: Refactor this entirely to be adding more spells to the spell list
|
||||
constructor() {
|
||||
super('builder-powder-special-display');
|
||||
this.fail_cb = true;
|
||||
|
@ -140,27 +135,11 @@ class PowderSpecialDisplayNode extends ComputeNode {
|
|||
compute_func(input_map) {
|
||||
const powder_specials = input_map.get('powder-specials');
|
||||
const stats = input_map.get('stats');
|
||||
const weapon = input_map.get('weapon');
|
||||
const weapon = input_map.get('build').weapon;
|
||||
displayPowderSpecials(document.getElementById("powder-special-stats"), powder_specials, stats, weapon.statMap, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply armor powders.
|
||||
* Encoding shortcut assumes that all powders give +def to one element
|
||||
* and -def to the element "behind" it in cycle ETWFA, which is true
|
||||
* as of now and unlikely to change in the near future.
|
||||
*/
|
||||
function applyArmorPowders(expandedItem, powders) {
|
||||
for(const id of powders){
|
||||
let powder = powderStats[id];
|
||||
let name = powderNames.get(id).charAt(0);
|
||||
let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5];
|
||||
expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]);
|
||||
expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node for getting an item's stats from an item input field.
|
||||
*
|
||||
|
@ -177,6 +156,11 @@ class ItemInputNode extends InputNode {
|
|||
constructor(name, item_input_field, none_item) {
|
||||
super(name, item_input_field);
|
||||
this.none_item = new Item(none_item);
|
||||
this.category = this.none_item.statMap.get('category');
|
||||
if (this.category == 'armor' || this.category == 'weapon') {
|
||||
this.none_item.statMap.set('powders', []);
|
||||
apply_weapon_powders(this.none_item.statMap); // Needed to put in damagecalc zeros
|
||||
}
|
||||
this.none_item.statMap.set('NONE', true);
|
||||
}
|
||||
|
||||
|
@ -200,14 +184,17 @@ class ItemInputNode extends InputNode {
|
|||
item.statMap.set('powders', powdering);
|
||||
}
|
||||
let type_match;
|
||||
if (this.none_item.statMap.get('category') === 'weapon') {
|
||||
type_match = item.statMap.get('category') === 'weapon';
|
||||
if (this.category == 'weapon') {
|
||||
type_match = item.statMap.get('category') == 'weapon';
|
||||
} else {
|
||||
type_match = item.statMap.get('type') === this.none_item.statMap.get('type');
|
||||
type_match = item.statMap.get('type') == this.none_item.statMap.get('type');
|
||||
}
|
||||
if (type_match) {
|
||||
if (item.statMap.get('category') === 'armor' && powdering !== undefined) {
|
||||
applyArmorPowders(item.statMap, powdering);
|
||||
if (item.statMap.get('category') == 'armor') {
|
||||
applyArmorPowders(item.statMap);
|
||||
}
|
||||
else if (item.statMap.get('category') == 'weapon') {
|
||||
apply_weapon_powders(item.statMap);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
@ -245,6 +232,7 @@ class ItemInputDisplayNode extends ComputeNode {
|
|||
this.input_field = document.getElementById(eq+"-choice");
|
||||
this.health_field = document.getElementById(eq+"-health");
|
||||
this.level_field = document.getElementById(eq+"-lv");
|
||||
this.powder_field = document.getElementById(eq+"-powder"); // possibly None
|
||||
this.image = item_image;
|
||||
this.fail_cb = true;
|
||||
}
|
||||
|
@ -269,10 +257,18 @@ class ItemInputDisplayNode extends ComputeNode {
|
|||
this.input_field.classList.add("is-invalid");
|
||||
return null;
|
||||
}
|
||||
if (item.statMap.has('powders')) {
|
||||
this.powder_field.placeholder = "powders";
|
||||
}
|
||||
|
||||
if (item.statMap.has('NONE')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item.statMap.has('powders')) {
|
||||
this.powder_field.placeholder = item.statMap.get('slots') + ' slots';
|
||||
}
|
||||
|
||||
const tier = item.statMap.get('tier');
|
||||
this.input_field.classList.add(tier);
|
||||
if (this.health_field) {
|
||||
|
@ -315,9 +311,10 @@ class ItemDisplayNode extends ComputeNode {
|
|||
*/
|
||||
class WeaponInputDisplayNode extends ComputeNode {
|
||||
|
||||
constructor(name, image_field) {
|
||||
constructor(name, image_field, dps_field) {
|
||||
super(name);
|
||||
this.image = image_field;
|
||||
this.dps_field = dps_field;
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
|
@ -326,20 +323,12 @@ class WeaponInputDisplayNode extends ComputeNode {
|
|||
|
||||
const type = item.statMap.get('type');
|
||||
this.image.setAttribute('src', '../media/items/new/generic-'+type+'.png');
|
||||
|
||||
//as of now, we NEED to have the dropdown tab visible/not hidden in order to properly display atree stuff.
|
||||
if (!document.getElementById("toggle-atree").classList.contains("toggleOn")) {
|
||||
toggle_tab('atree-dropdown');
|
||||
toggleButton('toggle-atree');
|
||||
}
|
||||
|
||||
//for some reason we have to cast to string
|
||||
construct_AT(document.getElementById("atree-ui"), atrees[wep_to_class.get(type)]);
|
||||
|
||||
if (document.getElementById("toggle-atree").classList.contains("toggleOn")) {
|
||||
toggle_tab('atree-dropdown');
|
||||
toggleButton('toggle-atree');
|
||||
let dps = get_base_dps(item.statMap);
|
||||
if (isNaN(dps)) {
|
||||
dps = dps[1];
|
||||
if (isNaN(dps)) dps = 0;
|
||||
}
|
||||
this.dps_field.textContent = Math.round(dps);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -358,6 +347,8 @@ class BuildEncodeNode extends ComputeNode {
|
|||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build');
|
||||
const atree = input_map.get('atree');
|
||||
const atree_state = input_map.get('atree-state');
|
||||
let powders = [
|
||||
input_map.get('helmet-powder'),
|
||||
input_map.get('chestplate-powder'),
|
||||
|
@ -375,7 +366,7 @@ class BuildEncodeNode extends ComputeNode {
|
|||
// TODO: grr global state for copy button..
|
||||
player_build = build;
|
||||
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]
|
||||
*/
|
||||
class SpellSelectNode extends ComputeNode {
|
||||
constructor(spell_num) {
|
||||
super("builder-spell"+spell_num+"-select");
|
||||
this.spell_idx = spell_num;
|
||||
constructor(spell) {
|
||||
super("builder-spell"+spell.base_spell+"-select");
|
||||
this.spell = spell;
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build');
|
||||
|
||||
const i = this.spell_idx;
|
||||
let spell = spell_table[build.weapon.statMap.get("type")][i];
|
||||
let stats = build.statMap;
|
||||
// TODO: apply major ids... DOOM.....
|
||||
|
||||
let spell_parts;
|
||||
if (spell.parts) {
|
||||
spell_parts = spell.parts;
|
||||
}
|
||||
else {
|
||||
spell_parts = spell.variants.DEFAULT;
|
||||
for (const majorID of stats.get("activeMajorIDs")) {
|
||||
if (majorID in spell.variants) {
|
||||
spell_parts = spell.variants[majorID];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [spell, spell_parts];
|
||||
return [this.spell, this.spell.parts];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,7 +500,7 @@ function getDefenseStats(stats) {
|
|||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = (2 - stats.get("classDef")) * (2 - stats.get("defMultiplier"));
|
||||
let defMult = (2 - stats.get("classDef")) * stats.get("defMultiplier");
|
||||
ehp[0] /= (1-def_pct)*(1-agi_pct)*defMult;
|
||||
ehp[1] /= (1-def_pct)*defMult;
|
||||
defenseStats.push(ehp);
|
||||
|
@ -563,8 +539,9 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
const weapon = new Map(input_map.get('weapon-input').statMap);
|
||||
const weapon = input_map.get('build').weapon.statMap;
|
||||
const spell_info = input_map.get('spell-info');
|
||||
const spell = spell_info[0];
|
||||
const spell_parts = spell_info[1];
|
||||
const stats = input_map.get('stats');
|
||||
const damage_mult = stats.get('damageMultiplier');
|
||||
|
@ -576,21 +553,67 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
stats.get('agi')
|
||||
];
|
||||
let spell_results = []
|
||||
let spell_result_map = new Map();
|
||||
const use_speed = (('use_atkspd' in spell) ? spell.use_atkspd : true);
|
||||
const use_spell = (('scaling' in spell) ? spell.scaling === 'spell' : true);
|
||||
|
||||
// TODO: move preprocessing to separate node/node chain
|
||||
for (const part of spell_parts) {
|
||||
if (part.type === "damage") {
|
||||
let results = calculateSpellDamage(stats, part.conversion,
|
||||
stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct"),
|
||||
part.multiplier / 100, weapon, skillpoints, damage_mult);
|
||||
spell_results.push(results);
|
||||
} else if (part.type === "heal") {
|
||||
let spell_result;
|
||||
if ('multipliers' in part) { // damage type spell
|
||||
let results = calculateSpellDamage(stats, weapon, part.multipliers, use_spell, !use_speed);
|
||||
spell_result = {
|
||||
type: "damage",
|
||||
normal_min: results[2].map(x => x[0]),
|
||||
normal_max: results[2].map(x => x[1]),
|
||||
normal_total: results[0],
|
||||
crit_min: results[2].map(x => x[2]),
|
||||
crit_max: results[2].map(x => x[3]),
|
||||
crit_total: results[1],
|
||||
}
|
||||
} else if ('power' in part) {
|
||||
// TODO: wynn2 formula
|
||||
let heal_amount = (part.strength * getDefenseStats(stats)[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2);
|
||||
spell_results.push(heal_amount);
|
||||
} else if (part.type === "total") {
|
||||
// TODO: remove "total" type
|
||||
spell_results.push(null);
|
||||
let _heal_amount = (part.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;
|
||||
}
|
||||
|
@ -616,12 +639,11 @@ class SpellDisplayNode extends ComputeNode {
|
|||
const spell_info = input_map.get('spell-info');
|
||||
const damages = input_map.get('spell-damage');
|
||||
const spell = spell_info[0];
|
||||
const spell_parts = spell_info[1];
|
||||
|
||||
const i = this.spell_idx;
|
||||
let parent_elem = document.getElementById("spell"+i+"-info");
|
||||
let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
|
||||
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, spell_parts, damages);
|
||||
displaySpellDamage(parent_elem, overallparent_elem, stats, spell, i+1, damages);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,6 +651,7 @@ class SpellDisplayNode extends ComputeNode {
|
|||
Returns an array in the order:
|
||||
*/
|
||||
function getMeleeStats(stats, weapon) {
|
||||
stats = new Map(stats); // Shallow copy
|
||||
const weapon_stats = weapon.statMap;
|
||||
const skillpoints = [
|
||||
stats.get('str'),
|
||||
|
@ -647,14 +670,12 @@ function getMeleeStats(stats, weapon) {
|
|||
adjAtkSpd = 0;
|
||||
}
|
||||
|
||||
let damage_mult = stats.get("damageMultiplier");
|
||||
if (weapon_stats.get("type") === "relik") {
|
||||
damage_mult *= 0.99; // CURSE YOU WYNNCRAFT
|
||||
stats.set('damageMultiplier', 0.99); // CURSE YOU WYNNCRAFT
|
||||
//One day we will create WynnWynn and no longer have shaman 99% melee injustice.
|
||||
//In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams.
|
||||
}
|
||||
// 0spellmult for melee damage.
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct"), 0, weapon_stats, skillpoints, damage_mult);
|
||||
let results = calculateSpellDamage(stats, weapon_stats, [100, 0, 0, 0, 0, 0], false, true);
|
||||
|
||||
let dex = skillpoints[1];
|
||||
|
||||
|
@ -733,7 +754,7 @@ class DisplayBuildWarningsNode extends ComputeNode {
|
|||
document.getElementById(skp_order[i]+"-warnings").textContent = ''
|
||||
if (assigned > 100) {
|
||||
let skp_warning = document.createElement("p");
|
||||
skp_warning.classList.add("warning"); skp_warning.classList.add("small-text");
|
||||
skp_warning.classList.add("warning", "small-text");
|
||||
skp_warning.textContent += "Cannot assign " + assigned + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually.";
|
||||
document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning);
|
||||
}
|
||||
|
@ -846,14 +867,13 @@ class AggregateEditableIDNode extends ComputeNode {
|
|||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build'); input_map.delete('build');
|
||||
const weapon = input_map.get('weapon'); input_map.delete('weapon');
|
||||
|
||||
const output_stats = new Map(build.statMap);
|
||||
for (const [k, v] of input_map.entries()) {
|
||||
output_stats.set(k, v);
|
||||
}
|
||||
|
||||
output_stats.set('classDef', classDefenseMultipliers.get(weapon.statMap.get("type")));
|
||||
output_stats.set('classDef', classDefenseMultipliers.get(build.weapon.statMap.get("type")));
|
||||
return output_stats;
|
||||
}
|
||||
}
|
||||
|
@ -956,12 +976,15 @@ class SumNumberInputNode extends InputNode {
|
|||
|
||||
let item_nodes = [];
|
||||
let powder_nodes = [];
|
||||
let spelldmg_nodes = [];
|
||||
let edit_input_nodes = [];
|
||||
let skp_inputs = [];
|
||||
let build_node;
|
||||
let stat_agg_node;
|
||||
let edit_agg_node;
|
||||
let atree_graph_creator;
|
||||
|
||||
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).
|
||||
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);
|
||||
//document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm');
|
||||
}
|
||||
console.log(none_tomes);
|
||||
for (const [eq, none_item] of zip2(tome_fields, [none_tomes[0], none_tomes[0], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[1], none_tomes[2]])) {
|
||||
let input_field = document.getElementById(eq+"-choice");
|
||||
let item_image = document.getElementById(eq+"-img");
|
||||
|
@ -987,13 +1009,14 @@ function builder_graph_init() {
|
|||
|
||||
// weapon image changer node.
|
||||
let weapon_image = document.getElementById("weapon-img");
|
||||
new WeaponInputDisplayNode('weapon-type', weapon_image).link_to(item_nodes[8]);
|
||||
let weapon_dps = document.getElementById("weapon-dps");
|
||||
new WeaponInputDisplayNode('weapon-type', weapon_image, weapon_dps).link_to(item_nodes[8]);
|
||||
|
||||
// Level input node.
|
||||
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
|
||||
|
||||
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
|
||||
let build_node = new BuildAssembleNode();
|
||||
build_node = new BuildAssembleNode();
|
||||
for (const input of item_nodes) {
|
||||
build_node.link_to(input);
|
||||
}
|
||||
|
@ -1018,15 +1041,15 @@ function builder_graph_init() {
|
|||
item_nodes[3].link_to(powder_nodes[3], '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()
|
||||
build_disp_node.link_to(build_node, 'build');
|
||||
|
||||
// Create one node that will be the "aggregator node" (listen to all the editable id nodes, as well as the build_node (for non editable stats) and collect them into one statmap)
|
||||
let stat_agg_node = new AggregateStatsNode();
|
||||
let edit_agg_node = new AggregateEditableIDNode();
|
||||
edit_agg_node.link_to(build_node, 'build').link_to(item_nodes[8], 'weapon');
|
||||
stat_agg_node = new AggregateStatsNode();
|
||||
edit_agg_node = new AggregateEditableIDNode();
|
||||
edit_agg_node.link_to(build_node, 'build');
|
||||
for (const field of editable_item_fields) {
|
||||
// Create nodes that listens to each editable id input, the node name should match the "id"
|
||||
const elem = document.getElementById(field);
|
||||
|
@ -1051,15 +1074,40 @@ function builder_graph_init() {
|
|||
stat_agg_node.link_to(edit_agg_node);
|
||||
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)) {
|
||||
input_node.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.
|
||||
let powder_special_calc = new PowderSpecialCalcNode().link_to(powder_special_input, 'powder-specials');
|
||||
new PowderSpecialDisplayNode().link_to(powder_special_input, 'powder-specials')
|
||||
.link_to(stat_agg_node, 'stats').link_to(item_nodes[8], 'weapon');
|
||||
.link_to(stat_agg_node, 'stats').link_to(build_node, 'build');
|
||||
stat_agg_node.link_to(powder_special_calc, 'powder-boost');
|
||||
stat_agg_node.link_to(armor_powder_node, 'armor-powder');
|
||||
powder_special_input.update();
|
||||
|
@ -1069,22 +1117,6 @@ function builder_graph_init() {
|
|||
|
||||
// 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) {
|
||||
node.update();
|
||||
}
|
||||
|
|
|
@ -89,6 +89,9 @@ class ComputeNode {
|
|||
throw "no compute func specified";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add link to a parent compute node, optionally with an alias.
|
||||
*/
|
||||
link_to(parent_node, link_name) {
|
||||
this.inputs.push(parent_node)
|
||||
link_name = (link_name !== undefined) ? link_name : parent_node.name;
|
||||
|
@ -100,6 +103,26 @@ class ComputeNode {
|
|||
parent_node.children.push(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a link to a parent node.
|
||||
* TODO: time complexity of list deletion (not super relevant but it hurts my soul)
|
||||
*/
|
||||
remove_link(parent_node) {
|
||||
const idx = this.inputs.indexOf(parent_node); // Get idx
|
||||
this.inputs.splice(idx, 1); // remove element
|
||||
|
||||
this.input_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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:";
|
||||
|
||||
//Display Recipe Stats
|
||||
displaysq2RecipeStats(player_craft, "recipe-stats");
|
||||
displayRecipeStats(player_craft, "recipe-stats");
|
||||
|
||||
//Display Craft Stats
|
||||
// displayCraftStats(player_craft, "craft-stats");
|
||||
let mock_item = player_craft.statMap;
|
||||
displaysq2ExpandedItem(mock_item, "craft-stats");
|
||||
apply_weapon_powders(mock_item);
|
||||
displayExpandedItem(mock_item, "craft-stats");
|
||||
|
||||
//Display Ingredients' Stats
|
||||
for (let i = 1; i < 7; i++) {
|
||||
displaysq2ExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
|
||||
displayExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
|
||||
}
|
||||
//Display Warnings - only ingred type warnings for now
|
||||
let warning_elem = document.getElementById("craft-warnings");
|
||||
|
@ -341,7 +342,7 @@ function toggleMaterial(buttonId) {
|
|||
*/
|
||||
function updateCraftedImage() {
|
||||
let input = document.getElementById("recipe-choice");
|
||||
if (item_types.includes(input.value)) {
|
||||
if (all_types.includes(input.value)) {
|
||||
document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png";
|
||||
}
|
||||
|
||||
|
@ -364,4 +365,8 @@ function resetFields() {
|
|||
calculateCraft();
|
||||
}
|
||||
|
||||
load_ing_init(init_crafter);
|
||||
(async function() {
|
||||
let load_promises = [ load_ing_init() ];
|
||||
await Promise.all(load_promises);
|
||||
init_crafter();
|
||||
})();
|
||||
|
|
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] ]);
|
||||
// Calculate spell damage given a spell elemental conversion table, and a spell multiplier.
|
||||
// If spell mult is 0, its melee damage and we don't multiply by attack speed.
|
||||
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) {
|
||||
let buildStats = new Map(stats);
|
||||
//6x for damages, normal min normal max crit min crit max
|
||||
|
||||
let powders = weapon.get("powders").slice();
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
let damages = [];
|
||||
const rawDamages = buildStats.get("damageRaw");
|
||||
for (let i = 0; i < rawDamages.length; i++) {
|
||||
const damage_vals = rawDamages[i].split("-").map(Number);
|
||||
damages.push(damage_vals);
|
||||
}
|
||||
|
||||
// Applying spell conversions
|
||||
let neutralBase = damages[0].slice();
|
||||
let neutralRemainingRaw = damages[0].slice();
|
||||
|
||||
|
||||
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
|
||||
|
||||
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
|
||||
//1st round - apply each as ingred, 2nd round - apply as normal
|
||||
if (weapon.get("tier") === "Crafted") {
|
||||
let damageBases = buildStats.get("damageBases").slice();
|
||||
for (const p of powders.concat(weapon.get("ingredPowders"))) {
|
||||
let powder = powderStats[p]; //use min, max, and convert
|
||||
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
|
||||
let diff = Math.floor(damageBases[0] * powder.convert/100);
|
||||
damageBases[0] -= diff;
|
||||
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
|
||||
function get_base_dps(item) {
|
||||
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||
//SUPER JANK @HPP PLS FIX
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
let total_damage = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage += damages[0] + damages[1];
|
||||
}
|
||||
//update all damages
|
||||
if(!weapon.get("custom")) {
|
||||
for (let i = 0; i < damages.length; i++) {
|
||||
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
|
||||
}
|
||||
}
|
||||
|
||||
neutralRemainingRaw = damages[0].slice();
|
||||
neutralBase = damages[0].slice();
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let conversionRatio = spellConversions[i+1]/100;
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
damages[i+1][0] = Math.floor(round_near(damages[i+1][0] + min_diff));
|
||||
damages[i+1][1] = Math.floor(round_near(damages[i+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
}
|
||||
|
||||
//apply powders to weapon
|
||||
for (const powderID of powders) {
|
||||
const powder = powderStats[powderID];
|
||||
// Bitwise to force conversion to integer (integer division).
|
||||
const element = (powderID/6) | 0;
|
||||
let conversionRatio = powder.convert/100;
|
||||
if (neutralRemainingRaw[1] > 0) {
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
|
||||
damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
}
|
||||
damages[element+1][0] += powder.min;
|
||||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
damages[0] = neutralRemainingRaw;
|
||||
|
||||
let damageMult = damageMultiplier;
|
||||
let melee = false;
|
||||
// If we are doing melee calculations:
|
||||
if (spellMultiplier == 0) {
|
||||
spellMultiplier = 1;
|
||||
melee = true;
|
||||
return total_damage * attack_speed_mult / 2;
|
||||
}
|
||||
else {
|
||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
||||
let total_damage_min = 0;
|
||||
let total_damage_max = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage_min += damages[0][0] + damages[0][1];
|
||||
total_damage_max += damages[1][0] + damages[1][1];
|
||||
}
|
||||
total_damage_min = attack_speed_mult * total_damage_min / 2;
|
||||
total_damage_max = attack_speed_mult * total_damage_max / 2;
|
||||
return [total_damage_min, total_damage_max];
|
||||
}
|
||||
//console.log(damages);
|
||||
//console.log(damageMult);
|
||||
rawModifier *= spellMultiplier * damageMultiplier;
|
||||
let totalDamNorm = [0, 0];
|
||||
let totalDamCrit = [0, 0];
|
||||
let damages_results = [];
|
||||
// 0th skillpoint is strength, 1st is dex.
|
||||
let str = total_skillpoints[0];
|
||||
let strBoost = 1 + skillPointsToPercentage(str);
|
||||
if(!melee){
|
||||
let baseDam = rawModifier * strBoost;
|
||||
let baseDamCrit = rawModifier * (1 + strBoost);
|
||||
totalDamNorm = [baseDam, baseDam];
|
||||
totalDamCrit = [baseDamCrit, baseDamCrit];
|
||||
}
|
||||
let staticBoost = (pctModifier / 100.);
|
||||
let skillBoost = [0];
|
||||
for (let i in total_skillpoints) {
|
||||
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get(skp_elements[i]+"DamPct") / 100.);
|
||||
}
|
||||
|
||||
for (let i in damages) {
|
||||
let damageBoost = 1 + skillBoost[i] + staticBoost;
|
||||
damages_results.push([
|
||||
Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min
|
||||
Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max
|
||||
Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min
|
||||
Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max
|
||||
]);
|
||||
totalDamNorm[0] += damages_results[i][0];
|
||||
totalDamNorm[1] += damages_results[i][1];
|
||||
totalDamCrit[0] += damages_results[i][2];
|
||||
totalDamCrit[1] += damages_results[i][3];
|
||||
}
|
||||
if (melee) {
|
||||
totalDamNorm[0] += Math.max(strBoost*rawModifier, -damages_results[0][0]);
|
||||
totalDamNorm[1] += Math.max(strBoost*rawModifier, -damages_results[0][1]);
|
||||
totalDamCrit[0] += Math.max((strBoost+1)*rawModifier, -damages_results[0][2]);
|
||||
totalDamCrit[1] += Math.max((strBoost+1)*rawModifier, -damages_results[0][3]);
|
||||
}
|
||||
damages_results[0][0] += strBoost*rawModifier;
|
||||
damages_results[0][1] += strBoost*rawModifier;
|
||||
damages_results[0][2] += (strBoost + 1)*rawModifier;
|
||||
damages_results[0][3] += (strBoost + 1)*rawModifier;
|
||||
|
||||
if (totalDamNorm[0] < 0) totalDamNorm[0] = 0;
|
||||
if (totalDamNorm[1] < 0) totalDamNorm[1] = 0;
|
||||
if (totalDamCrit[0] < 0) totalDamCrit[0] = 0;
|
||||
if (totalDamCrit[1] < 0) totalDamCrit[1] = 0;
|
||||
|
||||
return [totalDamNorm, totalDamCrit, damages_results];
|
||||
}
|
||||
|
||||
|
||||
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
|
||||
// TODO: Roll all the loops together maybe
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
// 1. Get weapon damage (with powders).
|
||||
let weapon_damages;
|
||||
if (weapon.get('tier') === 'Crafted') {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x)[1]);
|
||||
}
|
||||
else {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x));
|
||||
}
|
||||
let present = weapon.get(damage_present_key);
|
||||
|
||||
// 2. Conversions.
|
||||
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
|
||||
let damages = [];
|
||||
const neutral_convert = conversions[0] / 100;
|
||||
let weapon_min = 0;
|
||||
let weapon_max = 0;
|
||||
for (const damage of weapon_damages) {
|
||||
let min_dmg = damage[0] * neutral_convert;
|
||||
let max_dmg = damage[1] * neutral_convert;
|
||||
damages.push([min_dmg, max_dmg]);
|
||||
weapon_min += damage[0];
|
||||
weapon_max += damage[1];
|
||||
}
|
||||
|
||||
// 2.2. Next, apply elemental conversions using damage computed in step 1.1.
|
||||
// Also, track which elements are present. (Add onto those present in the weapon itself.)
|
||||
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 = {
|
||||
"wand": [
|
||||
|
|
241
js/display.js
|
@ -33,9 +33,8 @@ function displaySetBonuses(parent_id,build) {
|
|||
set_summary_elem.append(set_elem);
|
||||
|
||||
const bonus = active_set.bonuses[count-1];
|
||||
let mock_item = new Map();
|
||||
mock_item.set("fixID", true);
|
||||
mock_item.set("displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length);
|
||||
let mock_item = new Map([["fixID", true],
|
||||
["displayName", setName+" Set: "+count+"/"+sets.get(setName).items.length]]);
|
||||
let mock_minRolls = new Map();
|
||||
let mock_maxRolls = new Map();
|
||||
mock_item.set("minRolls", mock_minRolls);
|
||||
|
@ -173,51 +172,9 @@ function displayExpandedItem(item, parent_id){
|
|||
// #commands create a new element.
|
||||
// !elemental is some janky hack for elemental damage.
|
||||
// normals just display a thing.
|
||||
item = new Map(item); // shallow copy
|
||||
if (item.get("category") === "weapon") {
|
||||
let stats = new Map();
|
||||
stats.set("atkSpd", item.get("atkSpd"));
|
||||
stats.set("eDamPct", 0);
|
||||
stats.set("tDamPct", 0);
|
||||
stats.set("wDamPct", 0);
|
||||
stats.set("fDamPct", 0);
|
||||
stats.set("aDamPct", 0);
|
||||
|
||||
//SUPER JANK @HPP PLS FIX
|
||||
let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
|
||||
let damages = results[2];
|
||||
let total_damage = 0;
|
||||
for (const i in damage_keys) {
|
||||
total_damage += damages[i][0] + damages[i][1];
|
||||
item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]);
|
||||
}
|
||||
total_damage = total_damage / 2;
|
||||
item.set("basedps", total_damage);
|
||||
|
||||
} else {
|
||||
stats.set("damageRaw", [item.get("nDamLow"), item.get("eDamLow"), item.get("tDamLow"), item.get("wDamLow"), item.get("fDamLow"), item.get("aDamLow")]);
|
||||
stats.set("damageBases", [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]);
|
||||
let resultsLow = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
|
||||
let damagesLow = resultsLow[2];
|
||||
stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
|
||||
stats.set("damageBases", [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]);
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
|
||||
let damages = results[2];
|
||||
console.log(damages);
|
||||
|
||||
let total_damage_min = 0;
|
||||
let total_damage_max = 0;
|
||||
for (const i in damage_keys) {
|
||||
total_damage_min += damagesLow[i][0] + damagesLow[i][1];
|
||||
total_damage_max += damages[i][0] + damages[i][1];
|
||||
item.set(damage_keys[i], damagesLow[i][0]+"-"+damagesLow[i][1]+"\u279c"+damages[i][0]+"-"+damages[i][1]);
|
||||
}
|
||||
total_damage_min = total_damage_min / 2;
|
||||
total_damage_max = total_damage_max / 2;
|
||||
item.set("basedps", [total_damage_min, total_damage_max]);
|
||||
}
|
||||
item.set('basedps', get_base_dps(item));
|
||||
} else if (item.get("category") === "armor") {
|
||||
}
|
||||
|
||||
|
@ -384,7 +341,21 @@ function displayExpandedItem(item, parent_id){
|
|||
bckgrd.appendChild(img);
|
||||
}
|
||||
} else {
|
||||
if (id.endsWith('Dam_')) {
|
||||
// TODO: kinda jank but replacing lists with txt at this step
|
||||
let damages = item.get(id);
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
damages = damages.map(x => Math.round(x));
|
||||
item.set(id, damages[0]+"-"+damages[1]);
|
||||
}
|
||||
else {
|
||||
damages = damages.map(x => x.map(y => Math.round(y)));
|
||||
item.set(id, damages[0][0]+"-"+damages[0][1]+"\u279c"+damages[1][0]+"-"+damages[1][1]);
|
||||
}
|
||||
}
|
||||
|
||||
let p_elem;
|
||||
// TODO: wtf is this if statement
|
||||
if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && parent_div.nodeName === "table") ) { //skp warp
|
||||
p_elem = displayFixedID(parent_div, id, item.get(id), elemental_format);
|
||||
} else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") {
|
||||
|
@ -447,7 +418,7 @@ function displayExpandedItem(item, parent_id){
|
|||
}
|
||||
}
|
||||
//Show powder specials ;-;
|
||||
let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots", "ring", "bracelet", "necklace"];
|
||||
let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots"];//, "ring", "bracelet", "necklace"];
|
||||
if(nonConsumables.includes(item.get("type"))) {
|
||||
let powder_special = document.createElement("div");
|
||||
powder_special.classList.add("col");
|
||||
|
@ -555,19 +526,18 @@ function displayExpandedItem(item, parent_id){
|
|||
}
|
||||
|
||||
if (item.get("category") === "weapon") {
|
||||
let damage_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||
let total_damages = item.get("basedps");
|
||||
let base_dps_elem = document.createElement("p");
|
||||
base_dps_elem.classList.add("left");
|
||||
base_dps_elem.classList.add("itemp");
|
||||
if (item.get("tier") === "Crafted") {
|
||||
let base_dps_min = total_damages[0] * damage_mult;
|
||||
let base_dps_max = total_damages[1] * damage_mult;
|
||||
let base_dps_min = total_damages[0];
|
||||
let base_dps_max = total_damages[1];
|
||||
|
||||
base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3);
|
||||
}
|
||||
else {
|
||||
base_dps_elem.textContent = "Base DPS: "+(total_damages * damage_mult);
|
||||
base_dps_elem.textContent = "Base DPS: "+(total_damages);
|
||||
}
|
||||
parent_div.appendChild(document.createElement("p"));
|
||||
parent_div.appendChild(base_dps_elem);
|
||||
|
@ -1245,7 +1215,7 @@ function displayMeleeDamage(parent_elem, overallparent_elem, meleeStats) {
|
|||
critStats.append(critChance);
|
||||
|
||||
parent_elem.append(critStats);
|
||||
addClickableArrow(overallparent_elem);
|
||||
addClickableArrow(overallparent_elem, parent_elem);
|
||||
}
|
||||
|
||||
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
|
||||
let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
|
||||
let part = spell["parts"][0];
|
||||
let _results = calculateSpellDamage(stats, part.conversion,
|
||||
stats.get("mdRaw"), stats.get("mdPct"),
|
||||
0, weapon, skillpoints, stats.get('damageMultiplier') * ((part.multiplier[power-1] / 100)));//part.multiplier[power] / 100
|
||||
|
||||
let tmp_conv = [];
|
||||
for (let i in part.conversion) {
|
||||
tmp_conv.push(part.conversion[i] * part.multiplier[power-1] / 100);
|
||||
}
|
||||
console.log(tmp_conv);
|
||||
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false, true);
|
||||
|
||||
let critChance = skillPointsToPercentage(skillpoints[1]);
|
||||
let save_damages = [];
|
||||
|
@ -1592,19 +1566,19 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
|
|||
}
|
||||
}
|
||||
|
||||
function getSpellCost(stats, spellIdx, cost) {
|
||||
return Math.max(1, getBaseSpellCost(stats, spellIdx, cost));
|
||||
function getSpellCost(stats, spell) {
|
||||
return Math.max(1, getBaseSpellCost(stats, spell));
|
||||
}
|
||||
|
||||
function getBaseSpellCost(stats, spellIdx, cost) {
|
||||
// old intelligence:
|
||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(stats.get('int'))));
|
||||
cost += stats.get("spRaw"+spellIdx);
|
||||
return Math.floor(cost * (1 + stats.get("spPct"+spellIdx) / 100));
|
||||
function getBaseSpellCost(stats, spell) {
|
||||
// old intelligence:
|
||||
let cost = spell.cost; //Math.ceil(spell.cost * (1 - skillPointsToPercentage(stats.get('int'))));
|
||||
cost += stats.get("spRaw"+spell.base_spell);
|
||||
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: move cost calc out
|
||||
parent_elem.textContent = "";
|
||||
|
@ -1614,14 +1588,14 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
|||
overallparent_elem.textContent = "";
|
||||
let title_elemavg = document.createElement("b");
|
||||
|
||||
if (spellIdx != 0) {
|
||||
if ('cost' in spell) {
|
||||
let first = document.createElement("span");
|
||||
first.textContent = spell.title + " (";
|
||||
first.textContent = spell.name + " (";
|
||||
title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
|
||||
title_elemavg.appendChild(first);
|
||||
|
||||
let second = document.createElement("span");
|
||||
second.textContent = getSpellCost(stats, spellIdx, spell.cost);
|
||||
second.textContent = getSpellCost(stats, spell);
|
||||
second.classList.add("Mana");
|
||||
|
||||
title_elem.appendChild(second.cloneNode(true));
|
||||
|
@ -1629,51 +1603,56 @@ function displaySpellDamage(parent_elem, overallparent_elem, stats, spell, spell
|
|||
|
||||
|
||||
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);
|
||||
let third_summary = document.createElement("span");
|
||||
third_summary.textContent = ")";
|
||||
title_elemavg.appendChild(third_summary);
|
||||
}
|
||||
else {
|
||||
title_elem.textContent = spell.title;
|
||||
title_elemavg.textContent = spell.title;
|
||||
title_elem.textContent = spell.name;
|
||||
title_elemavg.textContent = spell.name;
|
||||
}
|
||||
|
||||
parent_elem.append(title_elem);
|
||||
overallparent_elem.append(title_elemavg);
|
||||
|
||||
overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
||||
// if ('cost' in spell) {
|
||||
// :( ...... ?
|
||||
// overallparent_elem.append(displayNextCosts(stats, spell, spellIdx));
|
||||
// }
|
||||
|
||||
let critChance = skillPointsToPercentage(stats.get('dex'));
|
||||
|
||||
let save_damages = [];
|
||||
|
||||
let part_divavg = document.createElement("p");
|
||||
overallparent_elem.append(part_divavg);
|
||||
|
||||
for (let i = 0; i < spell_parts.length; ++i) {
|
||||
const part = spell_parts[i];
|
||||
const damage = damages[i];
|
||||
function _summary(text, val, fmt) {
|
||||
let overallaverageLabel = document.createElement("p");
|
||||
let first = document.createElement("span");
|
||||
let second = document.createElement("span");
|
||||
first.textContent = text;
|
||||
second.textContent = val.toFixed(2);
|
||||
overallaverageLabel.appendChild(first);
|
||||
overallaverageLabel.appendChild(second);
|
||||
second.classList.add(fmt);
|
||||
part_divavg.append(overallaverageLabel);
|
||||
}
|
||||
|
||||
for (let i = 0; i < spell_results.length; ++i) {
|
||||
const spell_info = spell_results[i];
|
||||
|
||||
let part_div = document.createElement("p");
|
||||
parent_elem.append(part_div);
|
||||
|
||||
let subtitle_elem = document.createElement("p");
|
||||
subtitle_elem.textContent = part.subtitle;
|
||||
subtitle_elem.textContent = spell_info.name
|
||||
part_div.append(subtitle_elem);
|
||||
|
||||
if (part.type === "damage") {
|
||||
let _results = damage;
|
||||
let totalDamNormal = _results[0];
|
||||
let totalDamCrit = _results[1];
|
||||
let results = _results[2];
|
||||
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
for (let j in results[i]) {
|
||||
results[i][j] = results[i][j].toFixed(2);
|
||||
}
|
||||
}
|
||||
if (spell_info.type === "damage") {
|
||||
let totalDamNormal = spell_info.normal_total;
|
||||
let totalDamCrit = spell_info.crit_total;
|
||||
|
||||
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
|
||||
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
|
||||
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);
|
||||
|
||||
|
||||
if (part.summary == true) {
|
||||
let overallaverageLabel = document.createElement("p");
|
||||
let first = document.createElement("span");
|
||||
let second = document.createElement("span");
|
||||
first.textContent = part.subtitle + " Average: ";
|
||||
second.textContent = averageDamage.toFixed(2);
|
||||
overallaverageLabel.appendChild(first);
|
||||
overallaverageLabel.appendChild(second);
|
||||
second.classList.add("Damage");
|
||||
part_divavg.append(overallaverageLabel);
|
||||
if (spell_info.name === spell.display) {
|
||||
_summary(spell_info.name+ " Average: ", averageDamage, "Damage");
|
||||
}
|
||||
|
||||
function _damage_display(label_text, average, result_idx) {
|
||||
function _damage_display(label_text, average, dmg_min, dmg_max) {
|
||||
let label = document.createElement("p");
|
||||
label.textContent = label_text+average.toFixed(2);
|
||||
part_div.append(label);
|
||||
|
||||
let arrmin = [];
|
||||
let arrmax = [];
|
||||
for (let i = 0; i < 6; i++){
|
||||
if (results[i][1] != 0){
|
||||
if (dmg_max[i] != 0){
|
||||
let p = document.createElement("p");
|
||||
p.classList.add(damageClasses[i]);
|
||||
p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1];
|
||||
arrmin.push(results[i][result_idx]);
|
||||
arrmax.push(results[i][result_idx + 1]);
|
||||
p.textContent = dmg_min[i].toFixed(2)+" \u2013 "+dmg_max[i].toFixed(2);
|
||||
part_div.append(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
_damage_display("Non-Crit Average: ", nonCritAverage, 0);
|
||||
_damage_display("Crit Average: ", critAverage, 2);
|
||||
|
||||
save_damages.push(averageDamage);
|
||||
} else if (part.type === "heal") {
|
||||
let heal_amount = damage;
|
||||
_damage_display("Non-Crit Average: ", nonCritAverage, spell_info.normal_min, spell_info.normal_max);
|
||||
_damage_display("Crit Average: ", critAverage, spell_info.crit_min, spell_info.crit_max);
|
||||
} else if (spell_info.type === "heal") {
|
||||
let heal_amount = spell_info.heal_amount;
|
||||
let healLabel = document.createElement("p");
|
||||
healLabel.textContent = heal_amount;
|
||||
// healLabel.classList.add("damagep");
|
||||
part_div.append(healLabel);
|
||||
if (part.summary == true) {
|
||||
let overallhealLabel = document.createElement("p");
|
||||
let first = document.createElement("span");
|
||||
let second = document.createElement("span");
|
||||
first.textContent = part.subtitle + ": ";
|
||||
second.textContent = heal_amount;
|
||||
overallhealLabel.appendChild(first);
|
||||
second.classList.add("Set");
|
||||
overallhealLabel.appendChild(second);
|
||||
part_divavg.append(overallhealLabel);
|
||||
if (spell_info.name === spell.display) {
|
||||
_summary(spell_info.name+ ": ", heal_amount, "Set");
|
||||
}
|
||||
} else if (part.type === "total") {
|
||||
let total_damage = 0;
|
||||
for (let i in part.factors) {
|
||||
total_damage += save_damages[i] * part.factors[i];
|
||||
}
|
||||
|
||||
let dmgarr = part.factors.slice();
|
||||
dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")");
|
||||
|
||||
|
||||
let averageLabel = document.createElement("p");
|
||||
averageLabel.textContent = "Average: "+total_damage.toFixed(2);
|
||||
averageLabel.classList.add("damageSubtitle");
|
||||
part_div.append(averageLabel);
|
||||
|
||||
let overallaverageLabel = document.createElement("p");
|
||||
let overallaverageLabelFirst = document.createElement("span");
|
||||
let overallaverageLabelSecond = document.createElement("span");
|
||||
overallaverageLabelFirst.textContent = "Average: ";
|
||||
overallaverageLabelSecond.textContent = total_damage.toFixed(2);
|
||||
overallaverageLabelSecond.classList.add("Damage");
|
||||
|
||||
|
||||
overallaverageLabel.appendChild(overallaverageLabelFirst);
|
||||
overallaverageLabel.appendChild(overallaverageLabelSecond);
|
||||
part_divavg.append(overallaverageLabel);
|
||||
}
|
||||
}
|
||||
|
||||
addClickableArrow(overallparent_elem);
|
||||
addClickableArrow(overallparent_elem, parent_elem);
|
||||
}
|
||||
|
||||
/** Displays the ID costs of an item
|
||||
|
@ -2177,11 +2108,23 @@ function stringCDF(id,val,base,amp) {
|
|||
document.getElementById(id + "-cdf").appendChild(b3);
|
||||
}
|
||||
|
||||
function addClickableArrow(elem) {
|
||||
function addClickableArrow(elem, target) {
|
||||
//up and down arrow - done ugly
|
||||
let arrow = document.createElement("img");
|
||||
arrow.id = "arrow_" + elem.id;
|
||||
arrow.style.maxWidth = document.body.clientWidth > 900 ? "3rem" : "10rem";
|
||||
arrow.src = "../media/icons/" + (newIcons ? "new" : "old") + "/toggle_down.png";
|
||||
elem.appendChild(arrow);
|
||||
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
|
||||
|
||||
let tdb;
|
||||
|
|
|
@ -48,9 +48,11 @@ function optimizeStrDex() {
|
|||
let total_damage = 0;
|
||||
for (const part of spell_parts) {
|
||||
if (part.type === "damage") {
|
||||
let _results = calculateSpellDamage(stats, part.conversion,
|
||||
stats.get("sdRaw"), stats.get("sdPct"),
|
||||
part.multiplier / 100, player_build.weapon.statMap, total_skillpoints, 1);
|
||||
let tmp_conv = [];
|
||||
for (let i in part.conversion) {
|
||||
tmp_conv.push(part.conversion[i] * part.multiplier);
|
||||
}
|
||||
let _results = calculateSpellDamage(stats, player_build.weapon.statMap, tmp_conv, true);
|
||||
let totalDamNormal = _results[0];
|
||||
let totalDamCrit = _results[1];
|
||||
let results = _results[2];
|
||||
|
|
131
js/powders.js
|
@ -61,3 +61,134 @@ let powderSpecialStats = [
|
|||
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
|
||||
_ps("Wind Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a
|
||||
];
|
||||
|
||||
/**
|
||||
* Apply armor powders.
|
||||
* Encoding shortcut assumes that all powders give +def to one element
|
||||
* and -def to the element "behind" it in cycle ETWFA, which is true
|
||||
* as of now and unlikely to change in the near future.
|
||||
*/
|
||||
function applyArmorPowders(expandedItem) {
|
||||
const powders = expandedItem.get('powders');
|
||||
for(const id of powders){
|
||||
let powder = powderStats[id];
|
||||
let name = powderNames.get(id).charAt(0);
|
||||
let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5];
|
||||
expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]);
|
||||
expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]);
|
||||
}
|
||||
}
|
||||
|
||||
const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
|
||||
const damage_present_key = 'damagePresent';
|
||||
/**
|
||||
* Apply weapon powders. MUTATES THE ITEM!
|
||||
* Adds entries for `damage_keys` and `damage_present_key`
|
||||
* For normal items, `damage_keys` is 6x2 list (elem: [min, max])
|
||||
* For crafted items, `damage_keys` is 6x2x2 list (elem: [minroll: [min, max], maxroll: [min, max]])
|
||||
*/
|
||||
function apply_weapon_powders(item) {
|
||||
let present;
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
let weapon_result = calc_weapon_powder(item);
|
||||
let damages = weapon_result[0];
|
||||
present = weapon_result[1];
|
||||
for (const i in damage_keys) {
|
||||
item.set(damage_keys[i], damages[i]);
|
||||
}
|
||||
} else {
|
||||
let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")];
|
||||
let results_low = calc_weapon_powder(item, base_low);
|
||||
let damage_low = results_low[0];
|
||||
let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")];
|
||||
let results_high = calc_weapon_powder(item, base_high);
|
||||
let damage_high = results_high[0];
|
||||
present = results_high[1];
|
||||
|
||||
for (const i in damage_keys) {
|
||||
item.set(damage_keys[i], [damage_low[i], damage_high[i]]);
|
||||
}
|
||||
}
|
||||
item.set(damage_present_key, present);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate weapon damage from powder.
|
||||
*
|
||||
* Params:
|
||||
* weapon: Weapon to apply powder to
|
||||
* damageBases: used by crafted
|
||||
*
|
||||
* Return:
|
||||
* [damages, damage_present]
|
||||
*/
|
||||
function calc_weapon_powder(weapon, damageBases) {
|
||||
let powders = weapon.get("powders").slice();
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
let damages = [
|
||||
weapon.get('nDam').split('-').map(Number),
|
||||
weapon.get('eDam').split('-').map(Number),
|
||||
weapon.get('tDam').split('-').map(Number),
|
||||
weapon.get('wDam').split('-').map(Number),
|
||||
weapon.get('fDam').split('-').map(Number),
|
||||
weapon.get('aDam').split('-').map(Number)
|
||||
];
|
||||
|
||||
// Applying spell conversions
|
||||
let neutralBase = damages[0].slice();
|
||||
let neutralRemainingRaw = damages[0].slice();
|
||||
|
||||
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
|
||||
|
||||
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
|
||||
//1st round - apply each as ingred, 2nd round - apply as normal
|
||||
if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) {
|
||||
for (const p of powders.concat(weapon.get("ingredPowders"))) {
|
||||
let powder = powderStats[p]; //use min, max, and convert
|
||||
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
|
||||
let diff = Math.floor(damageBases[0] * powder.convert/100);
|
||||
damageBases[0] -= diff;
|
||||
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
|
||||
}
|
||||
//update all damages
|
||||
for (let i = 0; i < damages.length; i++) {
|
||||
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
|
||||
}
|
||||
neutralRemainingRaw = damages[0].slice();
|
||||
neutralBase = damages[0].slice();
|
||||
}
|
||||
|
||||
//apply powders to weapon
|
||||
for (const powderID of powders) {
|
||||
const powder = powderStats[powderID];
|
||||
// Bitwise to force conversion to integer (integer division).
|
||||
const element = (powderID/6) | 0;
|
||||
let conversionRatio = powder.convert/100;
|
||||
if (neutralRemainingRaw[1] > 0) {
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
|
||||
//damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
|
||||
//damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
|
||||
//neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
//neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
damages[element+1][0] += min_diff;
|
||||
damages[element+1][1] += max_diff;
|
||||
neutralRemainingRaw[0] -= min_diff;
|
||||
neutralRemainingRaw[1] -= max_diff;
|
||||
}
|
||||
damages[element+1][0] += powder.min;
|
||||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
// The ordering of these two blocks decides whether neutral is present when converted away or not.
|
||||
let present_elements = []
|
||||
for (const damage of damages) {
|
||||
present_elements.push(damage[1] > 0);
|
||||
}
|
||||
|
||||
// The ordering of these two blocks decides whether neutral is present when converted away or not.
|
||||
damages[0] = neutralRemainingRaw;
|
||||
return [damages, present_elements];
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
.append("div")
|
||||
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
|
||||
|
|
300
js/utils.js
|
@ -74,8 +74,10 @@ function log(b, n) {
|
|||
// https://stackoverflow.com/a/27696695
|
||||
// Modified for fixed precision
|
||||
|
||||
// Base64.fromInt(-2147483648); // gives "200000"
|
||||
// Base64.toInt("200000"); // gives -2147483648
|
||||
Base64 = (function () {
|
||||
var digitsStr =
|
||||
var digitsStr =
|
||||
// 0 8 16 24 32 40 48 56 63
|
||||
// v v v v v v v v v
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-";
|
||||
|
@ -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.
|
||||
|
@ -238,7 +478,7 @@ function randomColor() {
|
|||
|
||||
/**
|
||||
* Generates a random color, but lightning must be relatively high (>0.5).
|
||||
*
|
||||
*
|
||||
* @returns a random color in RGB 6-bit form.
|
||||
*/
|
||||
function randomColorLight() {
|
||||
|
@ -246,7 +486,7 @@ function randomColorLight() {
|
|||
}
|
||||
|
||||
/** Generates a random color given HSL restrictions.
|
||||
*
|
||||
*
|
||||
* @returns a random color in RGB 6-bit form.
|
||||
*/
|
||||
function randomColorHSL(h,s,l) {
|
||||
|
@ -298,8 +538,8 @@ function randomColorHSL(h,s,l) {
|
|||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
}
|
||||
|
||||
/** Creates a tooltip.
|
||||
*
|
||||
/** Creates a tooltip.
|
||||
*
|
||||
* @param {DOM Element} elem - the element to make a tooltip
|
||||
* @param {String} element_type - the HTML element type that the tooltiptext should be.
|
||||
* @param {String} tooltiptext - the text to display in the tooltip.
|
||||
|
@ -330,7 +570,7 @@ function createTooltip(elem, element_type, tooltiptext, parent, classList) {
|
|||
}
|
||||
|
||||
/** A generic function that toggles the on and off state of a button.
|
||||
*
|
||||
*
|
||||
* @param {String} button_id - the id name of the button.
|
||||
*/
|
||||
function toggleButton(button_id) {
|
||||
|
@ -374,8 +614,8 @@ function addClasses(elem, classes) {
|
|||
return elem;
|
||||
}
|
||||
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
*/
|
||||
async function hardReload() {
|
||||
//https://gist.github.com/rmehner/b9a41d9f659c9b1c3340
|
||||
|
@ -413,13 +653,13 @@ const getScript = url => new Promise((resolve, reject) => {
|
|||
document.head.appendChild(script);
|
||||
})
|
||||
|
||||
/*
|
||||
/*
|
||||
GENERIC TEST FUNCTIONS
|
||||
*/
|
||||
/** The generic assert function. Fails on all "false-y" values. Useful for non-object equality checks, boolean value checks, and existence checks.
|
||||
*
|
||||
*
|
||||
* @param {*} arg - argument to assert.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert(arg, msg) {
|
||||
if (!arg) {
|
||||
|
@ -428,10 +668,10 @@ GENERIC TEST FUNCTIONS
|
|||
}
|
||||
|
||||
/** Asserts object equality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_equals(arg1, arg2, msg) {
|
||||
if (!Object.is(arg1, arg2)) {
|
||||
|
@ -440,10 +680,10 @@ function assert_equals(arg1, arg2, msg) {
|
|||
}
|
||||
|
||||
/** Asserts object inequality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_not_equals(arg1, arg2, msg) {
|
||||
if (Object.is(arg1, arg2)) {
|
||||
|
@ -452,11 +692,11 @@ function assert_equals(arg1, arg2, msg) {
|
|||
}
|
||||
|
||||
/** Asserts proximity between 2 arguments. Should be used for any floating point datatype.
|
||||
*
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {Number} epsilon - the margin of error (<= del difference is ok). Defaults to -1E5.
|
||||
* @param {String} msg - the error message to throw.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
|
||||
if (Math.abs(arg1 - arg2) > epsilon) {
|
||||
|
@ -465,7 +705,7 @@ function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
|
|||
}
|
||||
|
||||
/** Asserts that the input argument is null.
|
||||
*
|
||||
*
|
||||
* @param {*} arg - the argument to test for null.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
|
@ -476,7 +716,7 @@ function assert_null(arg, msg) {
|
|||
}
|
||||
|
||||
/** Asserts that the input argument is undefined.
|
||||
*
|
||||
*
|
||||
* @param {*} arg - the argument to test for undefined.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
|
@ -487,7 +727,7 @@ function assert_null(arg, msg) {
|
|||
}
|
||||
|
||||
/** Asserts that there is an error when a callback function is run.
|
||||
*
|
||||
*
|
||||
* @param {Function} func_binding - a function binding to run. Can be passed in with func.bind(null, arg1, ..., argn)
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
|
@ -496,6 +736,20 @@ function assert_error(func_binding, msg) {
|
|||
func_binding();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
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 |