Merge branch 'atree' into UI_test

This commit is contained in:
hppeng 2022-06-30 20:15:17 -07:00
commit 19134d3ab1
184 changed files with 8180 additions and 1567 deletions

View file

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

View file

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

View file

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

View file

@ -474,6 +474,11 @@ a:hover {
transform: rotate(270deg);
}
.rotate-flip {
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}
.hide-scroll {
@ -483,3 +488,13 @@ a:hover {
.hide-scroll::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
.atree-selected {
outline: 5px solid rgba(95, 214, 223, 0.8);
}
.atree-circle {
border-radius:50%;
-moz-border-radius:50%;
-webkit-border-radius:50%;
}

View file

@ -62,8 +62,6 @@
<script type="text/javascript" src="/js/load_ing.js"></script>
<script type="text/javascript" src="/js/display_constants.js"></script>
<script type="text/javascript" src="/js/display.js"></script>
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
<script type="text/javascript" src="/js/sq2display.js"></script>
<script type="text/javascript" src="/js/item.js"></script>
</body>
</html>

View file

@ -79,8 +79,6 @@
<script type="text/javascript" src="/js/damage_calc.js"></script>
<script type="text/javascript" src="/js/display_constants.js"></script>
<script type="text/javascript" src="/js/display.js"></script>
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
<script type="text/javascript" src="/js/sq2display.js"></script>
<script type="text/javascript" src="/js/query_2.js"></script>
<script type="text/javascript" src="/js/expr_parser.js"></script>
<script type="text/javascript" src="/js/load.js"></script>

1026
js/atree.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

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

View file

@ -153,16 +153,29 @@ class Build{
*/
initBuildStats(){
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "dmgMobs", "defMobs"];
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "damMobs", "defMobs"];
let must_ids = [
"eMdPct","eMdRaw","eSdPct","eSdRaw","eDamPct","eDamRaw","eDamAddMin","eDamAddMax",
"tMdPct","tMdRaw","tSdPct","tSdRaw","tDamPct","tDamRaw","tDamAddMin","tDamAddMax",
"wMdPct","wMdRaw","wSdPct","wSdRaw","wDamPct","wDamRaw","wDamAddMin","wDamAddMax",
"fMdPct","fMdRaw","fSdPct","fSdRaw","fDamPct","fDamRaw","fDamAddMin","fDamAddMax",
"aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax",
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
"mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
"rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
]
//Create a map of this build's stats
let statMap = new Map();
statMap.set("damageMultiplier", 1);
statMap.set("defMultiplier", 1);
for (const staticID of staticIDs) {
statMap.set(staticID, 0);
}
for (const staticID of must_ids) {
statMap.set(staticID, 0);
}
statMap.set("hp", levelToHPBase(this.level));
let major_ids = new Set();
@ -176,15 +189,7 @@ class Build{
}
for (const staticID of staticIDs) {
if (item_stats.get(staticID)) {
if (staticID === "dmgMobs") {
statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID));
}
else if (staticID === "defMobs") {
statMap.set('defMultiplier', statMap.get('defMultiplier') * item_stats.get(staticID));
}
else {
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
}
}
}
if (item_stats.get("majorIds")) {
@ -193,6 +198,8 @@ class Build{
}
}
}
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
statMap.set("activeMajorIDs", major_ids);
for (const [setName, count] of this.activeSetCounts) {
const bonus = sets.get(setName).bonuses[count-1];
@ -211,13 +218,5 @@ class Build{
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
this.statMap = statMap;
this.aggregateStats();
}
aggregateStats() {
let statMap = this.statMap;
let weapon_stats = this.weapon.statMap;
statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]);
}
}

View file

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

View file

@ -58,7 +58,7 @@ const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ];
const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"];
const wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "Shaman"]])
const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula
const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
//weaponTypes.push("sword");
//console.log(types)
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
@ -66,13 +66,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;
}

View file

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

View file

@ -22,7 +22,6 @@ function update_armor_powder_specials(elem_id) {
//update the label associated w/ the slider
let elem = document.getElementById(elem_id);
let label = document.getElementById(elem_id + "_label");
let value = elem.value;
label.textContent = label.textContent.split(":")[0] + ": " + value
@ -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();
}

View file

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

View file

@ -172,16 +172,17 @@ function calculateCraft() {
document.getElementById("mat-2").textContent = recipe.get("materials")[1].get("item").split(" ").slice(1).join(" ") + " Tier:";
//Display Recipe Stats
displaysq2RecipeStats(player_craft, "recipe-stats");
displayRecipeStats(player_craft, "recipe-stats");
//Display Craft Stats
// displayCraftStats(player_craft, "craft-stats");
let mock_item = player_craft.statMap;
displaysq2ExpandedItem(mock_item, "craft-stats");
apply_weapon_powders(mock_item);
displayExpandedItem(mock_item, "craft-stats");
//Display Ingredients' Stats
for (let i = 1; i < 7; i++) {
displaysq2ExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
displayExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
}
//Display Warnings - only ingred type warnings for now
let warning_elem = document.getElementById("craft-warnings");
@ -341,7 +342,7 @@ function toggleMaterial(buttonId) {
*/
function updateCraftedImage() {
let input = document.getElementById("recipe-choice");
if (item_types.includes(input.value)) {
if (all_types.includes(input.value)) {
document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png";
}
@ -364,4 +365,8 @@ function resetFields() {
calculateCraft();
}
load_ing_init(init_crafter);
(async function() {
let load_promises = [ load_ing_init() ];
await Promise.all(load_promises);
init_crafter();
})();

30
js/d3_export.js vendored
View file

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

View file

@ -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": [

View file

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

View file

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

View file

@ -1,4 +1,4 @@
const TOME_DB_VERSION = 2;
const TOME_DB_VERSION = 3;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let tdb;

View file

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

View file

@ -61,3 +61,134 @@ let powderSpecialStats = [
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
_ps("Wind Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a
];
/**
* Apply armor powders.
* Encoding shortcut assumes that all powders give +def to one element
* and -def to the element "behind" it in cycle ETWFA, which is true
* as of now and unlikely to change in the near future.
*/
function applyArmorPowders(expandedItem) {
const powders = expandedItem.get('powders');
for(const id of powders){
let powder = powderStats[id];
let name = powderNames.get(id).charAt(0);
let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5];
expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]);
expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]);
}
}
const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
const damage_present_key = 'damagePresent';
/**
* Apply weapon powders. MUTATES THE ITEM!
* Adds entries for `damage_keys` and `damage_present_key`
* For normal items, `damage_keys` is 6x2 list (elem: [min, max])
* For crafted items, `damage_keys` is 6x2x2 list (elem: [minroll: [min, max], maxroll: [min, max]])
*/
function apply_weapon_powders(item) {
let present;
if (item.get("tier") !== "Crafted") {
let weapon_result = calc_weapon_powder(item);
let damages = weapon_result[0];
present = weapon_result[1];
for (const i in damage_keys) {
item.set(damage_keys[i], damages[i]);
}
} else {
let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")];
let results_low = calc_weapon_powder(item, base_low);
let damage_low = results_low[0];
let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")];
let results_high = calc_weapon_powder(item, base_high);
let damage_high = results_high[0];
present = results_high[1];
for (const i in damage_keys) {
item.set(damage_keys[i], [damage_low[i], damage_high[i]]);
}
}
item.set(damage_present_key, present);
}
/**
* Calculate weapon damage from powder.
*
* Params:
* weapon: Weapon to apply powder to
* damageBases: used by crafted
*
* Return:
* [damages, damage_present]
*/
function calc_weapon_powder(weapon, damageBases) {
let powders = weapon.get("powders").slice();
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
let damages = [
weapon.get('nDam').split('-').map(Number),
weapon.get('eDam').split('-').map(Number),
weapon.get('tDam').split('-').map(Number),
weapon.get('wDam').split('-').map(Number),
weapon.get('fDam').split('-').map(Number),
weapon.get('aDam').split('-').map(Number)
];
// Applying spell conversions
let neutralBase = damages[0].slice();
let neutralRemainingRaw = damages[0].slice();
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
//1st round - apply each as ingred, 2nd round - apply as normal
if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) {
for (const p of powders.concat(weapon.get("ingredPowders"))) {
let powder = powderStats[p]; //use min, max, and convert
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
let diff = Math.floor(damageBases[0] * powder.convert/100);
damageBases[0] -= diff;
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
}
//update all damages
for (let i = 0; i < damages.length; i++) {
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
}
neutralRemainingRaw = damages[0].slice();
neutralBase = damages[0].slice();
}
//apply powders to weapon
for (const powderID of powders) {
const powder = powderStats[powderID];
// Bitwise to force conversion to integer (integer division).
const element = (powderID/6) | 0;
let conversionRatio = powder.convert/100;
if (neutralRemainingRaw[1] > 0) {
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
//damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
//damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
//neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
//neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
damages[element+1][0] += min_diff;
damages[element+1][1] += max_diff;
neutralRemainingRaw[0] -= min_diff;
neutralRemainingRaw[1] -= max_diff;
}
damages[element+1][0] += powder.min;
damages[element+1][1] += powder.max;
}
// The ordering of these two blocks decides whether neutral is present when converted away or not.
let present_elements = []
for (const damage of damages) {
present_elements.push(damage[1] > 0);
}
// The ordering of these two blocks decides whether neutral is present when converted away or not.
damages[0] = neutralRemainingRaw;
return [damages, present_elements];
}

View file

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

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

After

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

BIN
media/atree/highlight_c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 708 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

BIN
media/atree/node_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

BIN
media/atree/node_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
media/atree/node_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5 KiB

BIN
media/atree/node_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
media/atree/node_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

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

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

File diff suppressed because it is too large Load diff

View file

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View file

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 463 B

View file

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 463 B

View file

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 456 B

View file

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 466 B

View file

Before

Width:  |  Height:  |  Size: 580 B

After

Width:  |  Height:  |  Size: 580 B

View file

Before

Width:  |  Height:  |  Size: 634 B

After

Width:  |  Height:  |  Size: 634 B

View file

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 647 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 779 B

After

Width:  |  Height:  |  Size: 779 B

View file

Before

Width:  |  Height:  |  Size: 872 B

After

Width:  |  Height:  |  Size: 872 B

View file

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 905 B

View file

Before

Width:  |  Height:  |  Size: 917 B

After

Width:  |  Height:  |  Size: 917 B

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 635 B

After

Width:  |  Height:  |  Size: 635 B

View file

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

View file

Before

Width:  |  Height:  |  Size: 709 B

After

Width:  |  Height:  |  Size: 709 B

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 707 B

After

Width:  |  Height:  |  Size: 707 B

View file

Before

Width:  |  Height:  |  Size: 797 B

After

Width:  |  Height:  |  Size: 797 B

View file

Before

Width:  |  Height:  |  Size: 852 B

After

Width:  |  Height:  |  Size: 852 B

View file

Before

Width:  |  Height:  |  Size: 554 B

After

Width:  |  Height:  |  Size: 554 B

View file

Before

Width:  |  Height:  |  Size: 812 B

After

Width:  |  Height:  |  Size: 812 B

View file

Before

Width:  |  Height:  |  Size: 513 B

After

Width:  |  Height:  |  Size: 513 B

View file

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 599 B

View file

Before

Width:  |  Height:  |  Size: 599 B

After

Width:  |  Height:  |  Size: 599 B

View file

Before

Width:  |  Height:  |  Size: 592 B

After

Width:  |  Height:  |  Size: 592 B

View file

Before

Width:  |  Height:  |  Size: 510 B

After

Width:  |  Height:  |  Size: 510 B

View file

Before

Width:  |  Height:  |  Size: 616 B

After

Width:  |  Height:  |  Size: 616 B

View file

Before

Width:  |  Height:  |  Size: 700 B

After

Width:  |  Height:  |  Size: 700 B

View file

Before

Width:  |  Height:  |  Size: 540 B

After

Width:  |  Height:  |  Size: 540 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 905 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1,007 B

After

Width:  |  Height:  |  Size: 1,007 B

View file

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 498 B

View file

Before

Width:  |  Height:  |  Size: 720 B

After

Width:  |  Height:  |  Size: 720 B

View file

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 748 B

View file

Before

Width:  |  Height:  |  Size: 615 B

After

Width:  |  Height:  |  Size: 615 B

View file

Before

Width:  |  Height:  |  Size: 4 KiB

After

Width:  |  Height:  |  Size: 4 KiB

View file

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 722 B

View file

Before

Width:  |  Height:  |  Size: 561 B

After

Width:  |  Height:  |  Size: 561 B

View file

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 572 B

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