merge everyone else's changes into fin444's branch
|
@ -18,7 +18,7 @@
|
|||
-->
|
||||
|
||||
<title>WynnBuilder</title>
|
||||
<link rel="icon" href="./media/memes/agony.png">
|
||||
<link rel="icon" href="../media/icons/new/builder.png" type="image/icon type">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
|
@ -51,6 +51,10 @@
|
|||
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
|
||||
</div>
|
||||
<div class="container-fluid me-4" style="max-width: 95%; display: none">
|
||||
<!-- REMOVE THIS DIV AT SOME POINT. -->
|
||||
<div class = "row scaled-font mx-auto" id = "discord-banner-dev">
|
||||
<div class = "col text-center item-title">Join the <a class = "link" href = "https://discord.gg/CGavnAnerv" target = "_blank">discord</a> today to suggest new features, submit bug reports, and hangout/talk to devs!</div>
|
||||
</div>
|
||||
<div class="row h-100 gx-lg-5 gy-3 mx-2 mx-lg-3 py-3">
|
||||
<div class="col-xl-6">
|
||||
<div class="row row-cols-1 mb-3 gy-4">
|
||||
|
@ -407,6 +411,8 @@
|
|||
</div>
|
||||
<div class="col text-center">
|
||||
<div id="summary-box"></div>
|
||||
<div id="err-box"></div>
|
||||
<div id="stack-box"></div>
|
||||
<div id="str-warnings"></div>
|
||||
<div id="dex-warnings"></div>
|
||||
<div id="int-warnings"></div>
|
||||
|
@ -428,7 +434,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">
|
||||
|
@ -617,10 +623,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>
|
||||
|
@ -1045,11 +1055,6 @@
|
|||
<div class="col eDam">
|
||||
Rage (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "e_slider" id = "str_boost_armor" name = "str-boost-armor" autocomplete = "off" min = '0' max = '400' value = '0' step = '1' onchange = "update_armor_powder_specials('str_boost_armor')">
|
||||
<input type="text" id="str_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "str_boost_armor_label" for="str-boost-armor">% Earth Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="dex-boost" style="display: none;">
|
||||
|
@ -1089,11 +1094,6 @@
|
|||
<div class="col tDam">
|
||||
Kill Streak (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "t_slider" id = "dex_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('dex_boost_armor')">
|
||||
<input type="text" id="dex_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "dex_boost_armor_label" for="dex-boost-armor">% Thunder Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="int-boost">
|
||||
|
@ -1133,11 +1133,6 @@
|
|||
<div class="col wDam">
|
||||
Concentration (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "w_slider" id = "int_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('int_boost_armor')">
|
||||
<input type="text" id="int_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "int_boost_armor_label" for="dex-boost-armor">% Water Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="def-boost" style="display: none;">
|
||||
|
@ -1177,11 +1172,6 @@
|
|||
<div class="col fDam">
|
||||
Endurance (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "f_slider" id = "def_boost_armor" name = "def-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('def_boost_armor')">
|
||||
<input type="text" id="def_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "def_boost_armor_label" for="def-boost-armor">% Fire Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="agi-boost" style="display: none;">
|
||||
|
@ -1221,11 +1211,6 @@
|
|||
<div class="col aDam">
|
||||
Dodge (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "a_slider" id = "agi_boost_armor" name = "agi-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('agi_boost_armor')">
|
||||
<input type="text" id="agi_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "agi_boost_armor_label" for="agi-boost-armor">% Air Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1263,21 +1248,10 @@
|
|||
<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 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">Input a weapon to see abilities!</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>
|
||||
<div class = "col">
|
||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
||||
|
@ -1329,11 +1303,11 @@
|
|||
<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">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
<a href="../credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -1425,7 +1399,6 @@
|
|||
<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>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2items.js"></script>
|
||||
<script type="text/javascript" src="../js/optimize.js"></script>
|
||||
|
||||
|
@ -1435,5 +1408,6 @@
|
|||
</div>
|
||||
<script src="https://d3js.org/d3.v7.js"></script>
|
||||
<script type="text/javascript" src="../js/render_compute_graph.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1055,11 +1055,6 @@
|
|||
<div class="col eDam">
|
||||
Rage (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "e_slider" id = "str_boost_armor" name = "str-boost-armor" autocomplete = "off" min = '0' max = '400' value = '0' step = '1' onchange = "update_armor_powder_specials('str_boost_armor')">
|
||||
<input type="text" id="str_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "str_boost_armor_label" for="str-boost-armor">% Earth Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="dex-boost" style="display: none;">
|
||||
|
@ -1099,11 +1094,6 @@
|
|||
<div class="col tDam">
|
||||
Kill Streak (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "t_slider" id = "dex_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('dex_boost_armor')">
|
||||
<input type="text" id="dex_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "dex_boost_armor_label" for="dex-boost-armor">% Thunder Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="int-boost">
|
||||
|
@ -1143,11 +1133,6 @@
|
|||
<div class="col wDam">
|
||||
Concentration (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "w_slider" id = "int_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('int_boost_armor')">
|
||||
<input type="text" id="int_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "int_boost_armor_label" for="dex-boost-armor">% Water Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="def-boost" style="display: none;">
|
||||
|
@ -1187,11 +1172,6 @@
|
|||
<div class="col fDam">
|
||||
Endurance (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "f_slider" id = "def_boost_armor" name = "def-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('def_boost_armor')">
|
||||
<input type="text" id="def_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "def_boost_armor_label" for="def-boost-armor">% Fire Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col" id="agi-boost" style="display: none;">
|
||||
|
@ -1231,11 +1211,6 @@
|
|||
<div class="col aDam">
|
||||
Dodge (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "a_slider" id = "agi_boost_armor" name = "agi-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('agi_boost_armor')">
|
||||
<input type="text" id="agi_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||
<label id = "agi_boost_armor_label" for="agi-boost-armor">% Air Dmg Boost: 0</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1332,7 +1307,7 @@
|
|||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div id="credits">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
<a href="../credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -1424,7 +1399,6 @@
|
|||
<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>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2items.js"></script>
|
||||
<script type="text/javascript" src="../js/optimize.js"></script>
|
||||
|
||||
|
|
3198
clean.json
|
@ -31,32 +31,30 @@
|
|||
<hr/>
|
||||
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
|
||||
</div>
|
||||
<div class = "container py-5 vh-100 mx-0 mx-lg-auto">
|
||||
<div class = "col">
|
||||
<div class = "row g-3">
|
||||
<div class = "col-lg-5 col-sm-12 text-center">
|
||||
<div class = "row gx-5 mb-2">
|
||||
<div class = "col-lg-6 col-sm-12">
|
||||
<div id = "recipe-dropdown" class = "row h-100 dark-shadow dark-6 rounded">
|
||||
<div class="container mt-5">
|
||||
<div class="row row-cols-1 row-cols-lg-3 gy-5">
|
||||
<div class="col col-lg-5">
|
||||
<!--crafter ui-->
|
||||
<div class="row row-cols-1 row-cols-lg-2 gx-5 gy-4 gy-lg-2">
|
||||
<div class="col" id="recipe-dropdown">
|
||||
<div class="row dark-shadow dark-5 rounded">
|
||||
<div id = "recipe-img-loc" class = "col-auto px-lg-1 g-0 dark-7 rounded-end my-auto text-center scaled-item-icon">
|
||||
<img id = "recipe-img" class = "img-fluid rounded Crafted-shadow" src = "../media/items/new/generic-potion.png">
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<div class = "row align-items-center">
|
||||
<div class = "col ps-3">
|
||||
<div class = "row row-cols-2 align-items-center">
|
||||
<div class = "col-4 px-0">
|
||||
<p class = "text-right mb-0 scaled-font fw-bold">Type:</p>
|
||||
<p class = "mb-0 scaled-font fw-bold">Type:</p>
|
||||
</div>
|
||||
<div class = "col-7 px-0">
|
||||
<div class = "col-8 px-0">
|
||||
<input class="recipeinput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="recipe-choices" id="recipe-choice" name="recipe-choice" placeholder="Potion"/>
|
||||
<datalist id="recipe-choices">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row align-items-center">
|
||||
<div class = "col-4 px-0">
|
||||
<p class = "text-right mb-0 scaled-font fw-bold">Lv:</p>
|
||||
<p class = "mb-0 scaled-font fw-bold">Lv:</p>
|
||||
</div>
|
||||
<div class = "col-7 px-0">
|
||||
<div class = "col-8 px-0">
|
||||
<input class="levelinput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="level-choices" id="level-choice" name="level-choice" placeholder="103-105" />
|
||||
<datalist id="level-choices">
|
||||
</datalist>
|
||||
|
@ -65,24 +63,24 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-6 col-sm-12">
|
||||
<div id = "atkSpdChoices" class = "row h-100 dark-shadow dark-6 rounded">
|
||||
<div class = "col py-2">
|
||||
<div class = "row h-50 align-items-center">
|
||||
<p class = "text-right mb-0 scaled-font fw-bold">Attack Speed</p>
|
||||
<div class="col">
|
||||
<div id = "atkSpdChoices" class = "row row-cols-1 dark-shadow dark-5 rounded">
|
||||
<div class="col pt-1">
|
||||
<p class = "text-center scaled-font fw-bold mb-1">Attack Speed</p>
|
||||
</div>
|
||||
<div class = "row h-50">
|
||||
<div class = "col-4 pl-1">
|
||||
<div class="col mb-2">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-auto px-2">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "slow-atk-button" onclick = "toggleAtkSpd('slow-atk-button')">
|
||||
Slow
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-4 px-0">
|
||||
<div class="col-auto px-2">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "normal-atk-button" onclick = "toggleAtkSpd('normal-atk-button')">
|
||||
Normal
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-4 pr-1">
|
||||
<div class="col-auto px-2">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "fast-atk-button" onclick = "toggleAtkSpd('fast-atk-button')">
|
||||
Fast
|
||||
</button>
|
||||
|
@ -91,12 +89,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-2">
|
||||
<div class = "col-lg-6 col-sm-12 justify-content-center">
|
||||
<div class="col">
|
||||
<div class = "row dark-shadow dark-6 rounded py-1 align-items-center">
|
||||
<div class = "col-6 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold" id = "mat-1">Mat 1 Tier:</p>
|
||||
<p class = "mb-0 scaled-font fw-bold text-center" id = "mat-1">Mat 1 Tier:</p>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-1-1" onclick = "toggleMaterial('mat-1-1')">1</button>
|
||||
|
@ -109,10 +105,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-6 col-sm-12 justify-content-center">
|
||||
<div class="col">
|
||||
<div class = "row dark-shadow dark-6 rounded py-1 align-items-center">
|
||||
<div class = "col-6 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold" id = "mat-2">Mat 2 Tier:</p>
|
||||
<p class = "mb-0 scaled-font fw-bold text-center" id = "mat-2">Mat 2 Tier:</p>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-2-1" onclick = "toggleMaterial('mat-2-1')">1</button>
|
||||
|
@ -125,11 +121,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-1">
|
||||
<div class="col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<div class = "row dark-shadow dark-6 rounded align-items-center just">
|
||||
<div class = "col-3 px-0 ps-2">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 1:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
|
@ -140,8 +134,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<div class = "row dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0 ps-2">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 2:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
|
@ -151,11 +145,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-1">
|
||||
<div class="col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<div class = "row dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0 ps-2">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 3:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
|
@ -166,8 +158,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<div class = "row dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0 ps-2">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 4:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
|
@ -177,11 +169,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-1">
|
||||
<div class="col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<div class = "row dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0 ps-2">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 5:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
|
@ -192,8 +182,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<div class = "row dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0 ps-2">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 6:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
|
@ -204,41 +194,39 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class = "row rounded dark-shadow dark-6 py-2 gy-3">
|
||||
<div class = "col-lg-2 col-sm-6">
|
||||
<div class = "row rounded dark-shadow dark-6 mt-3 p-2 align-items-center justify-content-center">
|
||||
<div class = "col-auto mx-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "reset-button" onclick = "resetFields()">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-6">
|
||||
<div class = "col-auto mx-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "copy-hash-button" onclick = "copyRecipeHash()">
|
||||
Copy Hash
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-lg-4 col-sm-6">
|
||||
<div class = "col-auto mx-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "copy-button" onclick = "copyRecipe()">
|
||||
Copy Short
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-6">
|
||||
<div class = "col-auto mx-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "share-button" onclick = "shareRecipe()">
|
||||
Copy Long
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-4">
|
||||
<div class="col col-lg-4">
|
||||
<div class = "recipe hide-container-block px-3 col rounded dark-6 text-light scaled-font p-3 border-dark dark-shadow g-0" style = "display:none">
|
||||
<div class = "row recipe-stats">
|
||||
<div class = "col" id = "recipe-stats"></div>
|
||||
<div class = "row row-cols-1 recipe-stats " id = "recipe-stats">
|
||||
</div>
|
||||
<div class = "row craft-warnings">
|
||||
<div class = "" id = "craft-warnings"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-3">
|
||||
<div class="col col-lg-3">
|
||||
<div class = "crafted row hide-container-block" style = "display:none">
|
||||
<div class = "craft-stats">
|
||||
<div class = "col rounded dark-6 text-light scaled-font p-3 border-dark dark-shadow g-0" id = "craft-stats"></div>
|
||||
|
@ -246,40 +234,38 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col ingredients-container hide-container-grid" id = "ingreds" style = "display:none">
|
||||
<div class = "col-lg-6 col-sm-12 hide-container-grid" id = "ingreds">
|
||||
<div class="row my-3">
|
||||
<div class="col col-lg-6 ingredients-container hide-container-grid" id = "ingreds" style = "display:none">
|
||||
<div class = "row mb-3">
|
||||
<div class="col">
|
||||
<p class="box-title hide-container-block">
|
||||
Ingredients
|
||||
</p>
|
||||
</div>
|
||||
<div class = "row mb-3">
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-1">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-1-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-2">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-2-stats"></div>
|
||||
<div class="row row-cols-1 row-cols-lg-2 g-3">
|
||||
<div class="ing-stats col-lg-6 col scaled-font" id = "ing-1">
|
||||
<div class = "rounded g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-1-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col scaled-font" id = "ing-2">
|
||||
<div class = "rounded g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-2-stats"></div>
|
||||
</div>
|
||||
<div class = "row mb-3">
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-3">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-3-stats"></div>
|
||||
<div class="ing-stats col-lg-6 col scaled-font" id = "ing-3">
|
||||
<div class = "rounded g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-3-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-4">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-4-stats"></div>
|
||||
<div class="ing-stats col-lg-6 col scaled-font" id = "ing-4">
|
||||
<div class = "rounded g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-4-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col scaled-font" id = "ing-5">
|
||||
<div class = "rounded g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-5-stats"></div>
|
||||
</div>
|
||||
<div class = "row mb-3">
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-5">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-5-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-6">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-6-stats"></div>
|
||||
<div class="ing-stats col-lg-6 col scaled-font" id = "ing-6">
|
||||
<div class = "rounded g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-6-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<div id="header2">
|
||||
|
@ -287,11 +273,12 @@
|
|||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div id="credits">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
<a href="../credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/query.js"></script>
|
||||
<script type="text/javascript" src="../js/query_2.js"></script>
|
||||
<script type="text/javascript" src="../js/utils.js"></script>
|
||||
|
@ -307,7 +294,5 @@
|
|||
<script type="text/javascript" src="../js/craft.js"></script>
|
||||
<script type="text/javascript" src="../js/crafter.js"></script>
|
||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<!-- <script type="text/javascript" src="../js/sq2items.js"></script> -->
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,7 +8,7 @@ The game, of course
|
|||
Additional Contributors, in no particular order:
|
||||
- Kiocifer (Icons!)
|
||||
- IncinerateMe (helping transition to 1.20.3 / CI helper)
|
||||
- puppy (wynn2 ability tree help)
|
||||
- puppy (dog)
|
||||
- SockMower (ability tree encode/decode optimization)
|
||||
- ITechnically (coding emotional support / misc)
|
||||
- touhoku (best IM)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
/* builder containers */
|
||||
|
||||
|
||||
.e_slider, .t_slider, .w_slider, .f_slider, .a_slider {
|
||||
.slider {
|
||||
-webkit-appearance: none;
|
||||
background: #AAAAAA;
|
||||
border-radius: 30px;
|
||||
|
@ -16,15 +16,14 @@
|
|||
}
|
||||
|
||||
/***** Chrome, Safari, Opera, and Edge Chromium *****/
|
||||
.e_slider::-webkit-slider-runnable-track, .t_slider::-webkit-slider-runnable-track, .w_slider::-webkit-slider-runnable-track, .f_slider::-webkit-slider-runnable-track, .a_slider::-webkit-slider-runnable-track {
|
||||
.slider::-webkit-slider-runnable-track{
|
||||
-webkit-appeareance: none;
|
||||
background:transparent;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/******** Firefox **** **/
|
||||
.e_slider::-moz-range-track, .t_slider::-moz-range-track, .w_slider::-moz-range-track, .f_slider::-moz-range-track, .a_slider::-moz-range-track {
|
||||
.slider::-moz-range-track {
|
||||
-webkit-appearance: none;
|
||||
background-color: transparent;
|
||||
border-radius: 30px;
|
||||
|
@ -32,7 +31,7 @@
|
|||
}
|
||||
|
||||
|
||||
.e_slider::-webkit-slider-thumb, .t_slider::-webkit-slider-thumb, .w_slider::-webkit-slider-thumb, .f_slider::-webkit-slider-thumb, .a_slider::-webkit-slider-thumb {
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 0.75rem;
|
||||
|
|
|
@ -1889,7 +1889,7 @@
|
|||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div id="credits">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
<a href="../credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -1906,8 +1906,6 @@
|
|||
<script type="text/javascript" src="/js/craft.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/custom.js"></script>
|
||||
<script type="text/javascript" src="/js/customizer.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -140,13 +140,11 @@
|
|||
<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.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>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2items.js"></script>
|
||||
<script type="text/javascript" src="../js/powders.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<div class = "col">
|
||||
<div class = "row">
|
||||
<div class = "col text-start" id = "credits">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
<a href="../credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
<div class = "col text-center" id = "help">
|
||||
<a href="items_2_help.html" class="link" target="_blank">Search Guide</a>
|
||||
|
|
506
js/atree.js
|
@ -76,6 +76,10 @@ stat_scaling: {
|
|||
"slider": bool,
|
||||
"slider_name": Optional[str],
|
||||
"slider_step": Optional[float],
|
||||
slider_behavior: Optional[str] // One of: "merge", "modify". default: merge
|
||||
// merge: add if exist, make new part if not exist
|
||||
// modify: change existing part. do nothing if not exist
|
||||
slider_max: Optional[float] // affected by slider_behavior
|
||||
"inputs": Optional[list[scaling_target]],
|
||||
"output": scaling_target | List[scaling_target],
|
||||
"scaling": list[float],
|
||||
|
@ -88,43 +92,46 @@ scaling_target: {
|
|||
}
|
||||
*/
|
||||
|
||||
|
||||
const elem_mastery_abil = { display_name: "Elemental Mastery", id: 998, properties: {}, effects: [] };
|
||||
|
||||
// TODO: Range numbers
|
||||
const default_abils = {
|
||||
wand: [{
|
||||
Mage: [{
|
||||
display_name: "Mage Melee",
|
||||
id: 999,
|
||||
desc: "Mage basic attack.",
|
||||
properties: {range: 5000},
|
||||
effects: [default_spells.wand[0]]
|
||||
}],
|
||||
spear: [{
|
||||
}, elem_mastery_abil ],
|
||||
Warrior: [{
|
||||
display_name: "Warrior Melee",
|
||||
id: 999,
|
||||
desc: "Warrior basic attack.",
|
||||
properties: {range: 2},
|
||||
effects: [default_spells.spear[0]]
|
||||
}],
|
||||
bow: [{
|
||||
}, elem_mastery_abil ],
|
||||
Archer: [{
|
||||
display_name: "Archer Melee",
|
||||
id: 999,
|
||||
desc: "Archer basic attack.",
|
||||
properties: {range: 20},
|
||||
effects: [default_spells.bow[0]]
|
||||
}],
|
||||
dagger: [{
|
||||
}, elem_mastery_abil ],
|
||||
Assassin: [{
|
||||
display_name: "Assassin Melee",
|
||||
id: 999,
|
||||
desc: "Assassin basic attack.",
|
||||
properties: {range: 2},
|
||||
effects: [default_spells.dagger[0]]
|
||||
}],
|
||||
relik: [{
|
||||
}, elem_mastery_abil ],
|
||||
Shaman: [{
|
||||
display_name: "Shaman Melee",
|
||||
id: 999,
|
||||
desc: "Shaman basic attack.",
|
||||
properties: {range: 15, speed: 0},
|
||||
effects: [default_spells.relik[0]]
|
||||
}],
|
||||
}, elem_mastery_abil ],
|
||||
};
|
||||
|
||||
|
||||
|
@ -162,6 +169,7 @@ const atree_node = new (class extends ComputeNode {
|
|||
}
|
||||
node.parents = parents;
|
||||
}
|
||||
console.log(atree_map);
|
||||
|
||||
let atree_topo_sort = [];
|
||||
topological_sort_tree(atree_head, atree_topo_sort, new Map());
|
||||
|
@ -170,6 +178,31 @@ const atree_node = new (class extends ComputeNode {
|
|||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* Create a reverse topological sort of the tree in the result list.
|
||||
* NOTE: our structure isn't a tree... it isn't even acyclic... but do it anyway i guess...
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Topological_sorting
|
||||
* @param tree: Root of tree to sort
|
||||
* @param res: Result list (reverse topological order)
|
||||
* @param mark_state: Bookkeeping. Call with empty Map()
|
||||
*/
|
||||
function topological_sort_tree(tree, res, mark_state) {
|
||||
const state = mark_state.get(tree);
|
||||
if (state === undefined) {
|
||||
// unmarked.
|
||||
mark_state.set(tree, false); // temporary mark
|
||||
for (const child of tree.children) {
|
||||
topological_sort_tree(child, res, mark_state);
|
||||
}
|
||||
mark_state.set(tree, true); // permanent mark
|
||||
res.push(tree);
|
||||
}
|
||||
// these cases are not needed. Case 1 does nothing, case 2 should never happen.
|
||||
// else if (state === true) { return; } // permanent mark.
|
||||
// else if (state === false) { throw "not a DAG"; } // temporary mark.
|
||||
}
|
||||
|
||||
/**
|
||||
* Display ability tree from topologically sorted list.
|
||||
*
|
||||
|
@ -212,50 +245,29 @@ const atree_state_node = new (class extends ComputeNode {
|
|||
}
|
||||
})().link_to(atree_render, 'atree-render');
|
||||
|
||||
/**
|
||||
* Create a reverse topological sort of the tree in the result list.
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Topological_sorting
|
||||
* @param tree: Root of tree to sort
|
||||
* @param res: Result list (reverse topological order)
|
||||
* @param mark_state: Bookkeeping. Call with empty Map()
|
||||
*/
|
||||
function topological_sort_tree(tree, res, mark_state) {
|
||||
const state = mark_state.get(tree);
|
||||
if (state === undefined) {
|
||||
// unmarked.
|
||||
mark_state.set(tree, false); // temporary mark
|
||||
for (const child of tree.children) {
|
||||
topological_sort_tree(child, res, mark_state);
|
||||
}
|
||||
mark_state.set(tree, true); // permanent mark
|
||||
res.push(tree);
|
||||
}
|
||||
// these cases are not needed. Case 1 does nothing, case 2 should never happen.
|
||||
// else if (state === true) { return; } // permanent mark.
|
||||
// else if (state === false) { throw "not a DAG"; } // temporary mark.
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect abilities and condense them into a list of "final abils".
|
||||
* This is just for rendering purposes, and for collecting things that modify spells into one chunk.
|
||||
* I stg if wynn makes abils that modify multiple spells
|
||||
* ... well we can extend this by making `base_abil` a list instead but annoy
|
||||
*
|
||||
* Signature: AbilityTreeMergeNode(build: Build, atree: ATree, atree-state: RenderedATree) => Map[id, Ability]
|
||||
* Signature: AbilityTreeMergeNode(player-class: WeaponType, atree: ATree, atree-state: RenderedATree) => Map[id, Ability]
|
||||
*/
|
||||
const atree_merge = new (class extends ComputeNode {
|
||||
constructor() { super('builder-atree-merge'); }
|
||||
|
||||
compute_func(input_map) {
|
||||
const build = input_map.get('build');
|
||||
const player_class = input_map.get('player-class');
|
||||
const atree_state = input_map.get('atree-state');
|
||||
const atree_order = input_map.get('atree');
|
||||
|
||||
let abils_merged = new Map();
|
||||
for (const abil of default_abils[build.weapon.statMap.get('type')]) {
|
||||
for (const abil of default_abils[player_class]) {
|
||||
let tmp_abil = deepcopy(abil);
|
||||
if (!Array.isArray(tmp_abil.desc)) {
|
||||
if (!('desc' in tmp_abil)) {
|
||||
tmp_abil.desc = [];
|
||||
}
|
||||
else if (!Array.isArray(tmp_abil.desc)) {
|
||||
tmp_abil.desc = [tmp_abil.desc];
|
||||
}
|
||||
tmp_abil.subparts = [abil.id];
|
||||
|
@ -269,6 +281,7 @@ const atree_merge = new (class extends ComputeNode {
|
|||
}
|
||||
const abil = node.ability;
|
||||
|
||||
if ('base_abil' in abil) {
|
||||
if (abils_merged.has(abil.base_abil)) {
|
||||
// Merge abilities.
|
||||
// TODO: What if there is more than one base abil?
|
||||
|
@ -282,6 +295,8 @@ const atree_merge = new (class extends ComputeNode {
|
|||
base_abil[propname] = abil[propname];
|
||||
}
|
||||
}
|
||||
// do nothing otherwise.
|
||||
}
|
||||
else {
|
||||
let tmp_abil = deepcopy(abil);
|
||||
if (!Array.isArray(tmp_abil.desc)) {
|
||||
|
@ -295,6 +310,56 @@ const atree_merge = new (class extends ComputeNode {
|
|||
}
|
||||
})().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
|
||||
|
||||
/**
|
||||
* Check if an atree node can be activated.
|
||||
*
|
||||
* Return: [yes/no, hard error, reason]
|
||||
*/
|
||||
function abil_can_activate(atree_node, atree_state, reachable, archetype_count, points_remain) {
|
||||
const {parents, ability} = atree_node;
|
||||
if (parents.length === 0) {
|
||||
return [true, false, ""];
|
||||
}
|
||||
let failed_deps = [];
|
||||
for (const dep_id of ability.dependencies) {
|
||||
if (!atree_state.get(dep_id).active) { failed_deps.push(dep_id) }
|
||||
}
|
||||
if (failed_deps.length > 0) {
|
||||
const dep_strings = failed_deps.map(i => '"' + atree_state.get(i).ability.display_name + '"');
|
||||
return [false, true, 'missing dep: ' + dep_strings.join(", ")];
|
||||
}
|
||||
let blocking_ids = [];
|
||||
for (const blocker_id of ability.blockers) {
|
||||
if (atree_state.get(blocker_id).active) { blocking_ids.push(blocker_id); }
|
||||
}
|
||||
if (blocking_ids.length > 0) {
|
||||
const blockers_strings = blocking_ids.map(i => '"' + atree_state.get(i).ability.display_name + '"');
|
||||
return [false, true, 'blocked by: '+blockers_strings.join(", ")];
|
||||
}
|
||||
let node_reachable = false;
|
||||
for (const parent of parents) {
|
||||
if (reachable.has(parent.ability.id)) {
|
||||
node_reachable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!node_reachable) {
|
||||
return [false, false, 'not reachable'];
|
||||
}
|
||||
if ('archetype' in ability && ability.archetype !== "") {
|
||||
if ('archetype_req' in ability && ability.archetype_req !== 0) {
|
||||
const others = (archetype_count.get(ability.archetype) || 0);
|
||||
if (others < ability.archetype_req) {
|
||||
return [false, false, ability.archetype+': '+others+' < '+ability.archetype_req];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ability.cost > points_remain) {
|
||||
return [false, false, "not enough ability points left"];
|
||||
}
|
||||
return [true, false, ""];
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate ability tree.
|
||||
* Return list of errors for rendering.
|
||||
|
@ -307,77 +372,94 @@ const atree_validate = new (class extends ComputeNode {
|
|||
compute_func(input_map) {
|
||||
const atree_state = input_map.get('atree-state');
|
||||
const atree_order = input_map.get('atree');
|
||||
const level = parseInt(input_map.get('level'));
|
||||
|
||||
if (atree_order.length == 0) { return [0, ['no atree data']]; }
|
||||
if (atree_order.length == 0) { return [0, false, ['no atree data']]; }
|
||||
|
||||
let errors = [];
|
||||
let reachable = new Map();
|
||||
atree_dfs_mark(atree_order[0], atree_state, reachable);
|
||||
let atree_to_add = [];
|
||||
let atree_not_present = [];
|
||||
// mark all selected nodes as bright, and mark all other nodes as dark.
|
||||
// also initialize the "to check" list, and the "not present" list.
|
||||
for (const node of atree_order) {
|
||||
const abil = node.ability;
|
||||
if (atree_state.get(abil.id).active) {
|
||||
atree_to_add.push([node, 'not reachable', false]);
|
||||
atree_state.get(abil.id).img.src = '../media/atree/'+abil.display.icon+'.png';
|
||||
}
|
||||
else {
|
||||
atree_not_present.push(abil.id);
|
||||
atree_state.get(abil.id).img.src = '../media/atree/'+abil.display.icon+'_blocked.png';
|
||||
}
|
||||
}
|
||||
|
||||
let reachable = new Set();
|
||||
let abil_points_total = 0;
|
||||
let archetype_count = new Map();
|
||||
for (const node of atree_order) {
|
||||
const abil = node.ability;
|
||||
if (!atree_state.get(abil.id).active) { continue; }
|
||||
abil_points_total += abil.cost;
|
||||
if (!reachable.get(abil.id)) { errors.push(abil.display_name + ' is not reachable!'); }
|
||||
|
||||
let failed_deps = [];
|
||||
for (const dep_id of abil.dependencies) {
|
||||
if (!atree_state.get(dep_id).active) { failed_deps.push(dep_id) }
|
||||
while (true) {
|
||||
let _add = [];
|
||||
for (const [node, fail_reason, fail_hardness] of atree_to_add) {
|
||||
const {ability} = node;
|
||||
const [success, hard_error, reason] = abil_can_activate(node, atree_state, reachable, archetype_count, 9999);
|
||||
if (!success) {
|
||||
_add.push([node, reason, hard_error]);
|
||||
continue;
|
||||
}
|
||||
if (failed_deps.length > 0) {
|
||||
const dep_string = failed_deps.map(i => '"' + atree_state.get(i).ability.display_name + '"');
|
||||
errors.push(abil.display_name + ' dependencies not satisfied: ' + dep_string.join(", "));
|
||||
}
|
||||
|
||||
let blocking_ids = [];
|
||||
for (const blocker_id of abil.blockers) {
|
||||
if (atree_state.get(blocker_id).active) { blocking_ids.push(blocker_id); }
|
||||
}
|
||||
if (blocking_ids.length > 0) {
|
||||
const blockers_string = blocking_ids.map(i => '"' + atree_state.get(i).ability.display_name + '"');
|
||||
errors.push(abil.display_name+' is blocked by: '+blockers_string.join(", "));
|
||||
}
|
||||
|
||||
if ('archetype' in abil && abil.archetype !== "") {
|
||||
if ('archetype' in ability && ability.archetype !== "") {
|
||||
let val = 1;
|
||||
if (archetype_count.has(abil.archetype)) {
|
||||
val = archetype_count.get(abil.archetype) + 1;
|
||||
if (archetype_count.has(ability.archetype)) {
|
||||
val = archetype_count.get(ability.archetype) + 1;
|
||||
}
|
||||
archetype_count.set(abil.archetype, val);
|
||||
archetype_count.set(ability.archetype, val);
|
||||
}
|
||||
abil_points_total += ability.cost;
|
||||
reachable.add(ability.id);
|
||||
}
|
||||
// TODO: FIX THIS! ARCHETYPE REQ IS A PAIN IN THE ASS
|
||||
// it doesn't follow topological order and theres some cases where "equip order" matters.
|
||||
for (const node of atree_order) {
|
||||
const abil = node.ability;
|
||||
if (!atree_state.get(abil.id).active) { continue; }
|
||||
if ('archetype_req' in abil && abil.archetype_req !== 0) {
|
||||
const others = archetype_count.get(abil.archetype) - 1;
|
||||
if (others < abil.archetype_req) {
|
||||
errors.push(abil.display_name+' fails archetype: '+abil.archetype+': '+others+' < '+abil.archetype_req)
|
||||
if (atree_to_add.length == _add.length) {
|
||||
break;
|
||||
}
|
||||
atree_to_add = _add;
|
||||
}
|
||||
const atree_level_table = ['lvl0wtf',1,2,2,3,3,4,4,5,5,6,6,7,8,8,9,9,10,11,11,12,12,13,14,14,15,16,16,17,17,18,18,19,19,20,20,20,21,21,22,22,23,23,23,24,24,25,25,26,26,27,27,28,28,29,29,30,30,31,31,32,32,33,33,34,34,34,35,35,35,36,36,36,37,37,37,38,38,38,38,39,39,39,39,40,40,40,40,41,41,41,41,42,42,42,42,43,43,43,43,44,44,44,44,45,45,45];
|
||||
let AP_cap;
|
||||
if (isNaN(level)) {
|
||||
AP_cap = 45;
|
||||
}
|
||||
else {
|
||||
AP_cap = atree_level_table[level];
|
||||
}
|
||||
document.getElementById('active_AP_cap').textContent = AP_cap;
|
||||
document.getElementById("active_AP_cost").textContent = abil_points_total;
|
||||
const ap_left = AP_cap - abil_points_total;
|
||||
|
||||
// using the "not present" list, highlight one-step reachable nodes.
|
||||
for (const node_id of atree_not_present) {
|
||||
const node = atree_state.get(node_id);
|
||||
const [success, hard_error, reason] = abil_can_activate(node, atree_state, reachable, archetype_count, ap_left);
|
||||
if (success) {
|
||||
node.img.src = '../media/atree/'+node.ability.display.icon+'.png';
|
||||
}
|
||||
}
|
||||
|
||||
if (abil_points_total > 45) {
|
||||
errors.push('too many ability points assigned! ('+abil_points_total+' > 45)');
|
||||
let hard_error = false;
|
||||
let errors = [];
|
||||
if (abil_points_total > AP_cap) {
|
||||
errors.push('too many ability points assigned! ('+abil_points_total+' > '+AP_cap+')');
|
||||
}
|
||||
for (const [node, fail_reason, fail_hardness] of atree_to_add) {
|
||||
if (fail_hardness) { hard_error = true; }
|
||||
errors.push(node.ability.display_name + ": " + fail_reason);
|
||||
}
|
||||
|
||||
return [abil_points_total, errors];
|
||||
return [hard_error, errors];
|
||||
}
|
||||
})().link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
|
||||
|
||||
function atree_dfs_mark(start, atree_state, mark) {
|
||||
if (mark.get(start.ability.id)) { return; }
|
||||
mark.set(start.ability.id, true);
|
||||
for (const child of start.children) {
|
||||
if (atree_state.get(child.ability.id).active) {
|
||||
atree_dfs_mark(child, atree_state, mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ability tree.
|
||||
* Return map of id -> corresponding html element.
|
||||
*
|
||||
* Signature: AbilityTreeRenderActiveNode(atree-merged: MergedATree, atree-order: ATree, atree-errors: List[str]) => Map[int, ATreeNode]
|
||||
*/
|
||||
const atree_render_active = new (class extends ComputeNode {
|
||||
constructor() {
|
||||
super('atree-render-active');
|
||||
|
@ -387,12 +469,11 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
compute_func(input_map) {
|
||||
const merged_abils = input_map.get('atree-merged');
|
||||
const atree_order = input_map.get('atree-order');
|
||||
const [abil_points_total, errors] = input_map.get('atree-errors');
|
||||
const [hard_error, _errors] = input_map.get('atree-errors');
|
||||
const errors = deepcopy(_errors);
|
||||
|
||||
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
||||
// TODO: move to display?
|
||||
document.getElementById("active_AP_cost").textContent = abil_points_total;
|
||||
|
||||
if (errors.length > 0) {
|
||||
let errorbox = document.createElement('div');
|
||||
errorbox.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
|
||||
|
@ -403,18 +484,27 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
error_title.innerHTML = "ATree Error!";
|
||||
errorbox.appendChild(error_title);
|
||||
|
||||
for (const error of errors) {
|
||||
let atree_warning = document.createElement("p");
|
||||
atree_warning.classList.add("warning", "small-text");
|
||||
atree_warning.textContent = error;
|
||||
for (let i = 0; i < 5 && i < errors.length; ++i) {
|
||||
const error = errors[i];
|
||||
const atree_warning = make_elem("p", ["warning", "small-text"], {textContent: error});
|
||||
errorbox.appendChild(atree_warning);
|
||||
}
|
||||
if (errors.length > 5) {
|
||||
const error = '... ' + (errors.length-5) + ' errors not shown';
|
||||
const atree_warning = make_elem("p", ["warning", "small-text"], {textContent: error});
|
||||
errorbox.appendChild(atree_warning);
|
||||
}
|
||||
}
|
||||
const ret_map = new Map();
|
||||
const to_render_id = [999, 998];
|
||||
for (const node of atree_order) {
|
||||
if (!merged_abils.has(node.ability.id)) {
|
||||
continue;
|
||||
}
|
||||
const abil = merged_abils.get(node.ability.id);
|
||||
to_render_id.push(node.ability.id);
|
||||
}
|
||||
for (const id of to_render_id) {
|
||||
const abil = merged_abils.get(id);
|
||||
|
||||
let active_tooltip = document.createElement('div');
|
||||
active_tooltip.classList.add("rounded-bottom", "dark-4", "border", "p-0", "mx-2", "my-4", "dark-shadow");
|
||||
|
@ -430,9 +520,11 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
active_tooltip_desc.textContent = desc;
|
||||
active_tooltip.appendChild(active_tooltip_desc);
|
||||
}
|
||||
ret_map.set(abil.id, active_tooltip);
|
||||
|
||||
this.list_elem.appendChild(active_tooltip);
|
||||
}
|
||||
return ret_map;
|
||||
}
|
||||
})().link_to(atree_node, 'atree-order').link_to(atree_merge, 'atree-merged').link_to(atree_validate, 'atree-errors');
|
||||
|
||||
|
@ -445,28 +537,31 @@ const atree_collect_spells = new (class extends ComputeNode {
|
|||
constructor() { super('atree-spell-collector'); }
|
||||
|
||||
compute_func(input_map) {
|
||||
if (input_map.size !== 1) { throw "AbilityTreeCollectSpellsNode accepts exactly one input (atree-merged)"; }
|
||||
const [atree_merged] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
||||
const atree_merged = input_map.get('atree-merged');
|
||||
const [hard_error, errors] = input_map.get('atree-errors');
|
||||
if (hard_error) { return []; }
|
||||
|
||||
let ret_spells = new Map();
|
||||
for (const [abil_id, abil] of atree_merged.entries()) {
|
||||
// TODO: Possibly, make a better way for detecting "spell abilities"?
|
||||
if (abil.effects.length == 0) { continue; }
|
||||
|
||||
let ret_spell = deepcopy(abil.effects[0]); // NOTE: do not mutate results of previous steps!
|
||||
let has_spell_def = false;
|
||||
for (const effect of abil.effects) {
|
||||
if (effect.type === 'replace_spell') {
|
||||
has_spell_def = true;
|
||||
// replace_spell just replaces all (defined) aspects.
|
||||
const ret_spell = ret_spells.get(effect.base_spell);
|
||||
if (ret_spell) {
|
||||
// NOTE: do not mutate results of previous steps!
|
||||
for (const key in effect) {
|
||||
ret_spell[key] = deepcopy(effect[key]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret_spells.set(effect.base_spell, deepcopy(effect));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_spell_def) { continue; }
|
||||
|
||||
const base_spell_id = ret_spell.base_spell;
|
||||
for (const [abil_id, abil] of atree_merged.entries()) {
|
||||
for (const effect of abil.effects) {
|
||||
switch (effect.type) {
|
||||
case 'replace_spell':
|
||||
|
@ -474,8 +569,9 @@ const atree_collect_spells = new (class extends ComputeNode {
|
|||
continue;
|
||||
case 'add_spell_prop': {
|
||||
const { base_spell, target_part = null, cost = 0, behavior = 'merge'} = effect;
|
||||
if (base_spell !== base_spell_id) { continue; } // TODO: redundant? if we assume abils only affect one spell
|
||||
// TODO: unjankify this... if ('cost' in ret_spell) { ret_spell.cost += cost; }
|
||||
const ret_spell = ret_spells.get(base_spell);
|
||||
// TODO: unjankify this...
|
||||
if ('cost' in ret_spell) { ret_spell.cost += cost; }
|
||||
|
||||
if (target_part === null) {
|
||||
continue;
|
||||
|
@ -517,7 +613,7 @@ const atree_collect_spells = new (class extends ComputeNode {
|
|||
}
|
||||
case 'convert_spell_conv':
|
||||
const { base_spell, target_part, conversion } = effect;
|
||||
if (base_spell !== base_spell_id) { continue; } // TODO: redundant? if we assume abils only affect one spell
|
||||
const ret_spell = ret_spells.get(base_spell);
|
||||
const elem_idx = damageClasses.indexOf(conversion);
|
||||
let filter = target_part === 'all';
|
||||
for (let part of ret_spell.parts) { // TODO: replace with Map? to avoid this linear search... idk prolly good since its not more verbose to type in json
|
||||
|
@ -536,18 +632,91 @@ const atree_collect_spells = new (class extends ComputeNode {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
ret_spells.set(base_spell_id, ret_spell);
|
||||
}
|
||||
return ret_spells;
|
||||
}
|
||||
})().link_to(atree_merge, 'atree-merged');
|
||||
})().link_to(atree_merge, 'atree-merged').link_to(atree_validate, 'atree-errors');
|
||||
|
||||
|
||||
/**
|
||||
* Make interactive elements (sliders, buttons)
|
||||
*
|
||||
* Signature: AbilityActiveUINode(atree-merged: MergedATree) => Map<str, slider_info>
|
||||
*
|
||||
* ElemState: {
|
||||
* value: int // value for sliders; 0-1 for toggles
|
||||
* }
|
||||
*/
|
||||
const atree_make_interactives = new (class extends ComputeNode {
|
||||
constructor() { super('atree-make-interactives'); }
|
||||
|
||||
compute_func(input_map) {
|
||||
const merged_abils = input_map.get('atree-merged');
|
||||
const atree_order = input_map.get('atree-order');
|
||||
const atree_html = input_map.get('atree-elements');
|
||||
|
||||
/**
|
||||
* slider_info
|
||||
* label_name: str,
|
||||
* max: int,
|
||||
* step: int,
|
||||
* id: str,
|
||||
* abil: atree_node
|
||||
* slider: html element
|
||||
* }
|
||||
*/
|
||||
// Map<str, slider_info>
|
||||
const slider_map = new Map();
|
||||
|
||||
// first, pull out all the sliders.
|
||||
for (const [abil_id, ability] of merged_abils.entries()) {
|
||||
for (const effect of ability.effects) {
|
||||
if (effect['type'] === "stat_scaling" && effect['slider'] === true) {
|
||||
const { slider_name, slider_behavior = 'merge', slider_max, slider_step } = effect;
|
||||
if (slider_map.has(slider_name)) {
|
||||
if (slider_max !== undefined) {
|
||||
const slider_info = slider_map.get(slider_name);
|
||||
slider_info.max += slider_max;
|
||||
}
|
||||
}
|
||||
else if (slider_behavior === 'merge') {
|
||||
slider_map.set(slider_name, {
|
||||
label_name: slider_name,
|
||||
max: slider_max,
|
||||
step: slider_step,
|
||||
id: "ability-slider"+ability.id,
|
||||
//color: effect['slider_color'] TODO: add colors to json
|
||||
abil: ability
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// next, render the sliders onto the abilities.
|
||||
for (const [slider_name, slider_info] of slider_map.entries()) {
|
||||
let slider_container = gen_slider_labeled(slider_info);
|
||||
atree_html.get(slider_info.abil.id).appendChild(slider_container);
|
||||
slider_info.slider = document.getElementById(slider_info.id);
|
||||
slider_info.slider.addEventListener("change", (e) => atree_stats.mark_dirty().update());
|
||||
}
|
||||
return slider_map;
|
||||
}
|
||||
})().link_to(atree_node, 'atree-order').link_to(atree_merge, 'atree-merged').link_to(atree_render_active, 'atree-elements');
|
||||
|
||||
|
||||
/**
|
||||
* Collect stats from ability tree.
|
||||
* Return StatMap of added stats (incl. cost modifications as raw cost)
|
||||
*
|
||||
* Signature: AbilityTreeStatsNode(atree-merged: MergedATree, build: Build, atree-interactive: Map<str, slider_info>) => StatMap
|
||||
*/
|
||||
const atree_stats = new (class extends ComputeNode {
|
||||
constructor() { super('atree-stats-collector'); }
|
||||
|
||||
compute_func(input_map) {
|
||||
if (input_map.size !== 1) { throw "AbilityTreeCollectStats accepts exactly one input (atree-merged)"; }
|
||||
const [atree_merged] = input_map.values(); // Extract values, pattern match it into size one list and bind to first element
|
||||
const atree_merged = input_map.get('atree-merged');
|
||||
const item_stats = input_map.get('build').statMap;
|
||||
const interactive_map = input_map.get('atree-interactive');
|
||||
|
||||
let ret_effects = new Map();
|
||||
for (const [abil_id, abil] of atree_merged.entries()) {
|
||||
|
@ -556,7 +725,46 @@ const atree_stats = new (class extends ComputeNode {
|
|||
for (const effect of abil.effects) {
|
||||
switch (effect.type) {
|
||||
case 'stat_scaling':
|
||||
// TODO: handle
|
||||
if (effect.slider) {
|
||||
if ('output' in effect) { // sometimes nodes will modify slider without having effect.
|
||||
const slider_val = interactive_map.get(effect.slider_name).slider.value;
|
||||
let total = parseInt(slider_val) * effect.scaling[0];
|
||||
if ('max' in effect && total > effect.max) { total = effect.max; }
|
||||
if (Array.isArray(effect.output)) {
|
||||
for (const output of effect.output) {
|
||||
if (output.type === 'stat') { // TODO: prop
|
||||
merge_stat(ret_effects, output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (effect.output.type === 'stat') {
|
||||
merge_stat(ret_effects, effect.output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: type: prop?
|
||||
let total = 0;
|
||||
for (const [scaling, input] of zip2(effect.scaling, effect.inputs)) {
|
||||
total += scaling * item_stats.get(input.name);
|
||||
}
|
||||
if ('max' in effect && total > effect.max) { total = effect.max; }
|
||||
// TODO: output (list...)
|
||||
if (Array.isArray(effect.output)) {
|
||||
for (const output of effect.output) {
|
||||
if (output.type === 'stat') {
|
||||
merge_stat(ret_effects, output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (effect.output.type === 'stat') {
|
||||
merge_stat(ret_effects, effect.output.name, total);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case 'raw_stat':
|
||||
// TODO: toggles...
|
||||
|
@ -564,28 +772,30 @@ const atree_stats = new (class extends ComputeNode {
|
|||
const { type, name, abil = "", value } = bonus;
|
||||
// TODO: prop
|
||||
if (type === "stat") {
|
||||
if (ret_effects.has(name)) { ret_effects.set(name, ret_effects.get(name) + value); }
|
||||
else { ret_effects.set(name, value); }
|
||||
merge_stat(ret_effects, name, value);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
case 'add_spell_prop':
|
||||
continue;
|
||||
// TODO unjankify....
|
||||
// costs are converted to raw cost ID
|
||||
const { base_spell, cost = 0} = effect;
|
||||
if (cost) {
|
||||
const key = "spRaw"+base_spell;
|
||||
if (ret_effects.has(key)) { ret_effects.set(key, ret_effects.get(key) + cost); }
|
||||
else { ret_effects.set(key, cost); }
|
||||
}
|
||||
continue;
|
||||
// const { base_spell, cost = 0} = effect;
|
||||
// if (cost) {
|
||||
// const key = "spRaw"+base_spell;
|
||||
// if (ret_effects.has(key)) { ret_effects.set(key, ret_effects.get(key) + cost); }
|
||||
// else { ret_effects.set(key, cost); }
|
||||
// }
|
||||
// continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(ret_effects);
|
||||
if (ret_effects.has('baseResist')) {
|
||||
merge_stat(ret_effects, "defMult", 1 - (ret_effects.get('baseResist') / 100));
|
||||
}
|
||||
return ret_effects;
|
||||
}
|
||||
})().link_to(atree_merge, 'atree-merged');
|
||||
})().link_to(atree_merge, 'atree-merged').link_to(atree_make_interactives, 'atree-interactive');
|
||||
|
||||
|
||||
/**
|
||||
|
@ -672,42 +882,21 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
UI_elem.style.paddingTop = "calc(var(--bs-gutter-x) * .5)";
|
||||
|
||||
// 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");
|
||||
let active_word = document.createElement("div");
|
||||
active_word.classList.add("col-auto");
|
||||
active_word.textContent = "Active Abilities:";
|
||||
let active_row = make_elem("div", ["row", "item-title", "mx-auto", "justify-content-center"]);
|
||||
let active_word = make_elem("div", ["col-auto"], {textContent: "Active Abilities:"});
|
||||
|
||||
let active_AP_container = document.createElement("div");
|
||||
active_AP_container.classList.add("col-auto");
|
||||
let active_AP_container = make_elem("div", ["col-auto"]);
|
||||
let active_AP_subcontainer = make_elem("div", ["row"]);
|
||||
let active_AP_cost = make_elem("div", ["col-auto", "mx-0", "px-0"], {id: "active_AP_cost", textContent: "0"});
|
||||
|
||||
let active_AP_subcontainer = document.createElement("div");
|
||||
active_AP_subcontainer.classList.add("row");
|
||||
let active_AP_slash = make_elem("div", ["col-auto", "mx-0", "px-0"], {textContent: "/"});
|
||||
let active_AP_cap = make_elem("div", ["col-auto", "mx-0", "px-0"], {id: "active_AP_cap"});
|
||||
let active_AP_end = make_elem("div", ["col-auto", "mx-0", "px-0"], {textContent: " AP"});
|
||||
|
||||
let active_AP_cost = document.createElement("div");
|
||||
active_AP_cost.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_cost.id = "active_AP_cost";
|
||||
active_AP_cost.textContent = "0";
|
||||
let active_AP_slash = document.createElement("div");
|
||||
active_AP_slash.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_slash.textContent = "/";
|
||||
let active_AP_cap = document.createElement("div");
|
||||
active_AP_cap.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_cap.id = "active_AP_cap";
|
||||
active_AP_cap.textContent = "45";
|
||||
let active_AP_end = document.createElement("div");
|
||||
active_AP_end.classList.add("col-auto", "mx-0", "px-0");
|
||||
active_AP_end.textContent = " AP";
|
||||
|
||||
//I can't believe we can't pass in multiple children at once
|
||||
active_AP_subcontainer.appendChild(active_AP_cost);
|
||||
active_AP_subcontainer.appendChild(active_AP_slash);
|
||||
active_AP_subcontainer.appendChild(active_AP_cap);
|
||||
active_AP_subcontainer.appendChild(active_AP_end);
|
||||
active_AP_container.appendChild(active_AP_subcontainer);
|
||||
active_AP_subcontainer.append(active_AP_cost, active_AP_slash, active_AP_cap, active_AP_end);
|
||||
|
||||
active_row.appendChild(active_word);
|
||||
active_row.appendChild(active_AP_container);
|
||||
active_row.append(active_word, active_AP_container);
|
||||
list_elem.appendChild(active_row);
|
||||
|
||||
let atree_map = new Map();
|
||||
|
@ -874,7 +1063,6 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
|
||||
document.getElementById("atree-row-" + ability.display.row).children[ability.display.col].appendChild(node_elem);
|
||||
};
|
||||
console.log(atree_connectors_map);
|
||||
atree_render_connection(atree_connectors_map);
|
||||
|
||||
return atree_map;
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
}
|
98
js/build.js
|
@ -1,94 +1,9 @@
|
|||
|
||||
|
||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20], ["sword", 1.10]]);
|
||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.0], ["sword", 1.10]]);
|
||||
|
||||
/**
|
||||
* @description Error to catch items that don't exist.
|
||||
* @module ItemNotFound
|
||||
*/
|
||||
class ItemNotFound {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} item the item name entered
|
||||
* @param {String} type the type of item
|
||||
* @param {Boolean} genElement whether to generate an element from inputs
|
||||
* @param {String} override override for item type
|
||||
*/
|
||||
constructor(item, type, genElement, override) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `Cannot find ${override||type} named ${item}`;
|
||||
if (genElement)
|
||||
/**
|
||||
* @public
|
||||
* @type {Element}
|
||||
*/
|
||||
this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0];
|
||||
else
|
||||
this.element = document.createElement("div");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error to catch incorrect input.
|
||||
* @module IncorrectInput
|
||||
*/
|
||||
class IncorrectInput {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} input the inputted text
|
||||
* @param {String} format the correct format
|
||||
* @param {String} sibling the id of the error node's sibling
|
||||
*/
|
||||
constructor(input, format, sibling) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `${input} is incorrect. Example: ${format}`;
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.id = sibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error that inputs an array of items to generate errors of.
|
||||
* @module ListError
|
||||
* @extends Error
|
||||
*/
|
||||
class ListError extends Error {
|
||||
/**
|
||||
* @class
|
||||
* @param {Array} errors array of errors
|
||||
*/
|
||||
constructor(errors) {
|
||||
let ret = [];
|
||||
if (typeof errors[0] == "string") {
|
||||
super(errors[0]);
|
||||
} else {
|
||||
super(errors[0].message);
|
||||
}
|
||||
for (let i of errors) {
|
||||
if (typeof i == "string") {
|
||||
ret.push(new Error(i));
|
||||
} else {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* @type {Object[]}
|
||||
*/
|
||||
this.errors = ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*Class that represents a wynn player's build.
|
||||
/*
|
||||
* Class that represents a wynn player's build.
|
||||
*/
|
||||
class Build{
|
||||
|
||||
|
@ -168,7 +83,6 @@ class Build{
|
|||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
|
@ -198,8 +112,10 @@ class Build{
|
|||
}
|
||||
}
|
||||
}
|
||||
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
|
||||
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
|
||||
statMap.set('damMult', new Map());
|
||||
statMap.set('defMult', new Map());
|
||||
statMap.get('damMult').set('tome', statMap.get('damMobs'))
|
||||
statMap.get('defMult').set('tome', statMap.get('defMobs'))
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets.get(setName).bonuses[count-1];
|
||||
|
|
|
@ -16,8 +16,8 @@ function skillPointsToPercentage(skp){
|
|||
}
|
||||
|
||||
// WYNN2: Skillpoint max scaling. Intel is cost reduction
|
||||
const skillpoint_final_mult = [1, 1, 0.5, 0.867, 0.951];
|
||||
// intel damage and water%
|
||||
const skillpoint_final_mult = [1, 1, 0.5/skillPointsToPercentage(150), 0.867, 0.951];
|
||||
// intel water%
|
||||
const skillpoint_damage_mult = [1, 1, 1, 0.867, 0.951];
|
||||
|
||||
/*Turns the input amount of levels into skillpoints available.
|
||||
|
@ -74,17 +74,17 @@ 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", "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",
|
||||
"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
|
||||
// Extra fake IDs (reserved for use in spell damage calculation) : damMult, defMult, 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
|
||||
|
@ -169,11 +169,11 @@ let rolledIDs = [
|
|||
"gXp",
|
||||
"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",
|
||||
"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
|
||||
|
@ -300,3 +300,28 @@ function idRound(id){
|
|||
return rounded;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stupid stupid multiplicative stats
|
||||
*/
|
||||
function merge_stat(stats, name, value) {
|
||||
const start = name.slice(0, 7);
|
||||
if (start === 'damMult' || start === 'defMult') {
|
||||
if (!stats.has(start)) {
|
||||
stats.set(start, new Map());
|
||||
}
|
||||
const map = stats.get(start);
|
||||
if (value instanceof Map) {
|
||||
for (const [k, v] of value.entries()) {
|
||||
merge_stat(map, k, v);
|
||||
}
|
||||
return;
|
||||
}
|
||||
merge_stat(map, name.slice(8), value);
|
||||
return;
|
||||
}
|
||||
if (stats.has(name)) {
|
||||
stats.set(name, stats.get(name) + value);
|
||||
}
|
||||
else { stats.set(name, value); }
|
||||
}
|
||||
|
|
|
@ -98,7 +98,6 @@ function resetFields(){
|
|||
for (const elem of skp_order) {
|
||||
console.log(document.getElementById(elem + "_boost_armor").value);
|
||||
document.getElementById(elem + "_boost_armor").value = 0;
|
||||
document.getElementById(elem + "_boost_armor_prev").value = 0;
|
||||
document.getElementById(elem + "_boost_armor").style.background = `linear-gradient(to right, #AAAAAA, #AAAAAA 0%, #AAAAAA 100%)`;
|
||||
document.getElementById(elem + "_boost_armor_label").textContent = `% ${damageClasses[skp_order.indexOf(elem)+1]} Damage Boost: 0`;
|
||||
}
|
||||
|
@ -394,6 +393,18 @@ function init() {
|
|||
for (const eq of equipment_keys) {
|
||||
document.querySelector("#"+eq+"-tooltip").addEventListener("click", () => collapse_element('#'+eq+'-tooltip'));
|
||||
}
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
const powder_special = powderSpecialStats[i];
|
||||
const elem_name = damageClasses[i+1]; // skip neutral
|
||||
const elem_char = skp_elements[i]; // TODO: merge?
|
||||
const skp_name = skp_order[i]; // TODO: merge?
|
||||
const boost_parent = document.getElementById(skp_name+'-boost');
|
||||
const slider_id = skp_name+'_boost_armor';
|
||||
const label_name = "% " + elem_name + " Dmg Boost";
|
||||
const slider_container = gen_slider_labeled({label_name: label_name, max: powder_special.cap, id: slider_id, color: elem_colors[i]});
|
||||
boost_parent.appendChild(slider_container);
|
||||
document.getElementById(slider_id).addEventListener("change", (_) => armor_powder_node.mark_dirty().update() );
|
||||
}
|
||||
|
||||
// Masonry setup
|
||||
let masonry = Macy({
|
||||
|
|
|
@ -11,29 +11,7 @@ let armor_powder_node = new (class extends ComputeNode {
|
|||
}
|
||||
return statMap;
|
||||
}
|
||||
})().update();
|
||||
|
||||
/* Updates PASSIVE powder special boosts (armors)
|
||||
*/
|
||||
function update_armor_powder_specials(elem_id) {
|
||||
//we only update the powder special + external stats if the player has a build
|
||||
let wynn_elem = elem_id.split("_")[0]; //str, dex, int, def, agi
|
||||
|
||||
//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
|
||||
|
||||
//update the slider's graphics
|
||||
let bg_color = elem_colors[skp_order.indexOf(wynn_elem)];
|
||||
let pct = Math.round(100 * value / powderSpecialStats[skp_order.indexOf(wynn_elem)].cap);
|
||||
elem.style.background = `linear-gradient(to right, ${bg_color}, ${bg_color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
|
||||
|
||||
armor_powder_node.mark_dirty().update();
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
let boosts_node = new (class extends ComputeNode {
|
||||
constructor() { super('builder-boost-input'); }
|
||||
|
@ -50,8 +28,8 @@ let boosts_node = new (class extends ComputeNode {
|
|||
}
|
||||
}
|
||||
let res = new Map();
|
||||
res.set('damageMultiplier', 1+damage_boost);
|
||||
res.set('defMultiplier', 1-def_boost);
|
||||
res.set('damMult.Potion', 100*damage_boost);
|
||||
res.set('defMult.Potion', 100*def_boost);
|
||||
return res;
|
||||
}
|
||||
})().update();
|
||||
|
@ -258,7 +236,7 @@ class ItemInputDisplayNode extends ComputeNode {
|
|||
this.input_field.classList.add("is-invalid");
|
||||
return null;
|
||||
}
|
||||
if (item.statMap.has('powders')) {
|
||||
if (this.powder_field && item.statMap.has('powders')) {
|
||||
this.powder_field.placeholder = "powders";
|
||||
}
|
||||
|
||||
|
@ -266,7 +244,7 @@ class ItemInputDisplayNode extends ComputeNode {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (item.statMap.has('powders')) {
|
||||
if (this.powder_field && item.statMap.has('powders')) {
|
||||
this.powder_field.placeholder = item.statMap.get('slots') + ' slots';
|
||||
}
|
||||
|
||||
|
@ -511,7 +489,10 @@ function getDefenseStats(stats) {
|
|||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = (2 - stats.get("classDef")) * stats.get("defMultiplier");
|
||||
let defMult = (2 - stats.get("classDef"));
|
||||
for (const [k, v] of stats.get("defMult").entries()) {
|
||||
defMult *= (1 - v/100);
|
||||
}
|
||||
// newehp = oldehp / [0.1 * A(x) + (1 - A(x)) * (1 - D(x))]
|
||||
ehp[0] = ehp[0] / (0.1*agi_pct + (1-agi_pct) * (1-def_pct));
|
||||
ehp[0] /= defMult;
|
||||
|
@ -558,7 +539,6 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
const spell = spell_info[0];
|
||||
const spell_parts = spell_info[1];
|
||||
const stats = input_map.get('stats');
|
||||
const damage_mult = stats.get('damageMultiplier');
|
||||
const skillpoints = [
|
||||
stats.get('str'),
|
||||
stats.get('dex'),
|
||||
|
@ -575,7 +555,7 @@ class SpellDamageCalcNode extends ComputeNode {
|
|||
for (const part of spell_parts) {
|
||||
let spell_result;
|
||||
if ('multipliers' in part) { // damage type spell
|
||||
let results = calculateSpellDamage(stats, weapon, part.multipliers, use_spell, !use_speed);
|
||||
let results = calculateSpellDamage(stats, weapon, part.multipliers, use_spell, !use_speed, spell.base_spell + '.' + part.name);
|
||||
spell_result = {
|
||||
type: "damage",
|
||||
normal_min: results[2].map(x => x[0]),
|
||||
|
@ -695,7 +675,7 @@ function getMeleeStats(stats, weapon) {
|
|||
}
|
||||
|
||||
if (weapon_stats.get("type") === "relik") {
|
||||
stats.set('damageMultiplier', 0.99); // CURSE YOU WYNNCRAFT
|
||||
merge_stat(stats, 'damMult.ShamanMelee', 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.
|
||||
}
|
||||
|
@ -863,18 +843,7 @@ class AggregateStatsNode extends ComputeNode {
|
|||
const output_stats = new Map();
|
||||
for (const [k, v] of input_map.entries()) {
|
||||
for (const [k2, v2] of v.entries()) {
|
||||
if (output_stats.has(k2)) {
|
||||
// TODO: ugly AF
|
||||
if (k2 === 'damageMultiplier' || k2 === 'defMultiplier') {
|
||||
output_stats.set(k2, v2 * output_stats.get(k2));
|
||||
}
|
||||
else {
|
||||
output_stats.set(k2, v2 + output_stats.get(k2));
|
||||
}
|
||||
}
|
||||
else {
|
||||
output_stats.set(k2, v2);
|
||||
}
|
||||
merge_stat(output_stats, k2, v2);
|
||||
}
|
||||
}
|
||||
return output_stats;
|
||||
|
@ -976,7 +945,7 @@ class SkillPointSetterNode extends ComputeNode {
|
|||
class SumNumberInputNode extends InputNode {
|
||||
compute_func(input_map) {
|
||||
let value = this.input_field.value;
|
||||
if (value === "") { value = 0; }
|
||||
if (value === "") { value = "0"; }
|
||||
|
||||
let input_num = 0;
|
||||
if (value.includes("+")) {
|
||||
|
@ -1039,6 +1008,9 @@ function builder_graph_init() {
|
|||
// Level input node.
|
||||
let level_input = new InputNode('level-input', document.getElementById('level-choice'));
|
||||
|
||||
// linking to atree verification
|
||||
atree_validate.link_to(level_input, 'level');
|
||||
|
||||
// "Build" now only refers to equipment and level (no powders). Powders are injected before damage calculation / stat display.
|
||||
build_node = new BuildAssembleNode();
|
||||
for (const input of item_nodes) {
|
||||
|
@ -1103,9 +1075,10 @@ function builder_graph_init() {
|
|||
let class_node = new PlayerClassNode('builder-class').link_to(build_node);
|
||||
// These two are defined in `atree.js`
|
||||
atree_node.link_to(class_node, 'player-class');
|
||||
atree_merge.link_to(build_node, 'build');
|
||||
atree_merge.link_to(class_node, 'player-class');
|
||||
atree_graph_creator = new AbilityTreeEnsureNodesNode(build_node, stat_agg_node)
|
||||
.link_to(atree_collect_spells, 'spells');
|
||||
atree_stats.link_to(build_node, 'build');
|
||||
stat_agg_node.link_to(atree_stats, 'atree-stats');
|
||||
|
||||
build_encode_node.link_to(atree_node, 'atree').link_to(atree_state_node, 'atree-state');
|
||||
|
@ -1116,6 +1089,7 @@ function builder_graph_init() {
|
|||
for (const input_node of item_nodes.concat(powder_nodes)) {
|
||||
input_node.update();
|
||||
}
|
||||
armor_powder_node.update();
|
||||
level_input.update();
|
||||
|
||||
// kinda janky, manually set atree and update. Some wasted compute here
|
||||
|
|
|
@ -26,7 +26,7 @@ function get_base_dps(item) {
|
|||
}
|
||||
|
||||
|
||||
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
|
||||
function calculateSpellDamage(stats, weapon, _conversions, use_spell_damage, ignore_speed=false, part_filter=undefined) {
|
||||
// TODO: Roll all the loops together maybe
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
|
@ -38,9 +38,30 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
else {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x));
|
||||
}
|
||||
let present = weapon.get(damage_present_key);
|
||||
let present = deepcopy(weapon.get(damage_present_key));
|
||||
|
||||
// Also theres prop and rainbow!!
|
||||
const damage_elements = ['n'].concat(skp_elements); // netwfa
|
||||
|
||||
// 2. Conversions.
|
||||
// 2.0: First, modify conversions.
|
||||
let conversions = deepcopy(_conversions);
|
||||
if (part_filter !== undefined) {
|
||||
const conv_postfix = ':'+part_filter;
|
||||
for (let i in damage_elements) {
|
||||
const stat_name = damage_elements[i]+'ConvBase'+conv_postfix;
|
||||
if (stats.has(stat_name)) {
|
||||
conversions[i] += stats.get(stat_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i in damage_elements) {
|
||||
const stat_name = damage_elements[i]+'ConvBase';
|
||||
if (stats.has(stat_name)) {
|
||||
conversions[i] += stats.get(stat_name);
|
||||
}
|
||||
}
|
||||
|
||||
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
|
||||
let damages = [];
|
||||
const neutral_convert = conversions[0] / 100;
|
||||
|
@ -68,9 +89,6 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
}
|
||||
total_convert += conversions[0]/100;
|
||||
|
||||
// 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"))];
|
||||
|
@ -81,7 +99,7 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
}
|
||||
|
||||
// 4. Add additive damage. TODO: Is there separate additive damage?
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
for (let i in damage_elements) {
|
||||
if (present[i]) {
|
||||
damages[i][0] += stats.get(damage_elements[i]+'DamAddMin');
|
||||
damages[i][1] += stats.get(damage_elements[i]+'DamAddMax');
|
||||
|
@ -104,10 +122,10 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
// 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;
|
||||
for (let i in damage_elements) {
|
||||
let damage_specific = damage_elements[i] + specific_boost_str + 'Pct';
|
||||
let damageBoost = 1 + skill_boost[i] + static_boost
|
||||
+ ((stats.get(damage_prefix+'Pct') + stats.get(damage_elements[i]+'DamPct')) /100);
|
||||
+ ((stats.get(damage_specific) + 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
|
||||
|
@ -154,7 +172,18 @@ function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, igno
|
|||
let total_dam_norm = [0, 0];
|
||||
let total_dam_crit = [0, 0];
|
||||
let damages_results = [];
|
||||
const damage_mult = stats.get("damageMultiplier");
|
||||
const mult_map = stats.get("damMult");
|
||||
let damage_mult = 1;
|
||||
for (const [k, v] of mult_map.entries()) {
|
||||
if (k.includes(':')) {
|
||||
// TODO: fragile... checking for specific part multipliers.
|
||||
const spell_match = k.split(':')[1];
|
||||
if (spell_match !== part_filter) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
damage_mult *= (1 + v/100);
|
||||
}
|
||||
|
||||
for (const damage of damages) {
|
||||
const res = [
|
||||
|
@ -247,15 +276,6 @@ const default_spells = {
|
|||
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...
|
||||
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"
|
||||
|
@ -296,130 +316,6 @@ const default_spells = {
|
|||
};
|
||||
|
||||
const spell_table = {
|
||||
"wand": [
|
||||
{ title: "Heal", cost: 6, parts: [
|
||||
{ subtitle: "First Pulse", type: "heal", strength: 0.12 },
|
||||
{ subtitle: "Second and Third Pulses", type: "heal", strength: 0.06 },
|
||||
{ subtitle: "Total Heal", type: "heal", strength: 0.24, summary: true },
|
||||
{ subtitle: "First Pulse (Ally)", type: "heal", strength: 0.20 },
|
||||
{ subtitle: "Second and Third Pulses (Ally)", type: "heal", strength: 0.1 },
|
||||
{ subtitle: "Total Heal (Ally)", type: "heal", strength: 0.4 }
|
||||
] },
|
||||
{ title: "Teleport", cost: 4, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 40, 0, 0, 0], summary: true },
|
||||
] },
|
||||
{ title: "Meteor", cost: 8, parts: [
|
||||
{ subtitle: "Blast Damage", type: "damage", multiplier: 500, conversion: [40, 30, 0, 0, 30, 0], summary: true },
|
||||
{ subtitle: "Burn Damage", type: "damage", multiplier: 125, conversion: [100, 0, 0, 0, 0, 0] },
|
||||
] },
|
||||
{ title: "Ice Snake", cost: 4, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 70, conversion: [50, 0, 0, 50, 0, 0], summary: true },
|
||||
] },
|
||||
],
|
||||
"spear": [
|
||||
{ title: "Bash", cost: 6, parts: [
|
||||
{ subtitle: "First Damage", type: "damage", multiplier: 130, conversion: [60, 40, 0, 0, 0, 0]},
|
||||
{ subtitle: "Explosion Damage", type: "damage", multiplier: 130, conversion: [100, 0, 0, 0, 0, 0]},
|
||||
{ subtitle: "Total Damage", type: "total", factors: [1, 1], summary: true },
|
||||
] },
|
||||
{ title: "Charge", cost: 4, variants: {
|
||||
DEFAULT: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [60, 0, 0, 0, 40, 0], summary: true }
|
||||
],
|
||||
RALLY: [
|
||||
{ subtitle: "Self Heal", type: "heal", strength: 0.07, summary: true },
|
||||
{ subtitle: "Ally Heal", type: "heal", strength: 0.15 }
|
||||
]
|
||||
} },
|
||||
{ title: "Uppercut", cost: 9, parts: [
|
||||
{ subtitle: "First Damage", type: "damage", multiplier: 300, conversion: [70, 20, 10, 0, 0, 0] },
|
||||
{ subtitle: "Fireworks Damage", type: "damage", multiplier: 50, conversion: [60, 0, 40, 0, 0, 0] },
|
||||
{ subtitle: "Crash Damage", type: "damage", multiplier: 50, conversion: [80, 0, 20, 0, 0, 0] },
|
||||
{ subtitle: "Total Damage", type: "total", factors: [1, 1, 1], summary: true },
|
||||
] },
|
||||
{ title: "War Scream", cost: 6, parts: [
|
||||
{ subtitle: "Area Damage", type: "damage", multiplier: 50, conversion: [0, 0, 0, 0, 75, 25], summary: true },
|
||||
{ subtitle: "Air Shout (Per Hit)", type: "damage", multiplier: 30, conversion: [0, 0, 0, 0, 75, 25] },
|
||||
] },
|
||||
],
|
||||
"bow": [
|
||||
{ title: "Arrow Storm", cost: 6, variants: {
|
||||
DEFAULT: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [60, 0, 25, 0, 15, 0], summary: true },
|
||||
{ subtitle: "Per Arrow (60)", type: "damage", multiplier: 10, conversion: [60, 0, 25, 0, 15, 0]}
|
||||
],
|
||||
HAWKEYE: [
|
||||
{ subtitle: "Total Damage (Hawkeye)", type: "damage", multiplier: 400, conversion: [60, 0, 25, 0, 15, 0], summary: true },
|
||||
{ subtitle: "Per Arrow (5)", type: "damage", multiplier: 80, conversion: [60, 0, 25, 0, 15, 0]}
|
||||
],
|
||||
} },
|
||||
{ title: "Escape", cost: 3, parts: [
|
||||
{ subtitle: "Landing Damage", type: "damage", multiplier: 100, conversion: [50, 0, 0, 0, 0, 50], summary: true },
|
||||
] },
|
||||
{ title: "Bomb Arrow", cost: 8, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 250, conversion: [60, 25, 0, 0, 15, 0], summary: true },
|
||||
] },
|
||||
{ title: "Arrow Shield", cost: 10, parts: [
|
||||
{ subtitle: "Shield Damage", type: "damage", multiplier: 100, conversion: [70, 0, 0, 0, 0, 30], summary: true },
|
||||
{ subtitle: "Arrow Rain Damage", type: "damage", multiplier: 200, conversion: [70, 0, 0, 0, 0, 30] },
|
||||
] },
|
||||
],
|
||||
"dagger": [
|
||||
{ title: "Spin Attack", cost: 6, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 150, conversion: [70, 0, 30, 0, 0, 0], summary: true},
|
||||
] },
|
||||
{ title: "Vanish", cost: 2, parts: [
|
||||
{ subtitle: "No Damage", type: "none", summary: true }
|
||||
] },
|
||||
{ title: "Multihit", cost: 8, parts: [
|
||||
{ subtitle: "1st to 10th Hit", type: "damage", multiplier: 27, conversion: [100, 0, 0, 0, 0, 0] },
|
||||
{ subtitle: "Fatality", type: "damage", multiplier: 120, conversion: [20, 0, 30, 50, 0, 0] },
|
||||
{ subtitle: "Total Damage", type: "total", factors: [10, 1], summary: true },
|
||||
] },
|
||||
{ title: "Smoke Bomb", cost: 8, variants: {
|
||||
DEFAULT: [
|
||||
{ subtitle: "Tick Damage (10 max)", type: "damage", multiplier: 60, conversion: [50, 25, 0, 0, 0, 25] },
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 600, conversion: [50, 25, 0, 0, 0, 25], summary: true },
|
||||
],
|
||||
CHERRY_BOMBS: [
|
||||
{ subtitle: "Total Damage (Cherry Bombs)", type: "damage", multiplier: 330, conversion: [50, 25, 0, 0, 0, 25], summary: true },
|
||||
{ subtitle: "Per Bomb", type: "damage", multiplier: 110, conversion: [50, 25, 0, 0, 0, 25] }
|
||||
]
|
||||
} },
|
||||
],
|
||||
"relik": [
|
||||
{ title: "Totem", cost: 4, parts: [
|
||||
{ subtitle: "Smash Damage", type: "damage", multiplier: 100, conversion: [80, 0, 0, 0, 20, 0]},
|
||||
{ subtitle: "Damage Tick", type: "damage", multiplier: 20, conversion: [80, 0, 0, 0, 0, 20]},
|
||||
{ subtitle: "Heal Tick", type: "heal", strength: 0.03, summary: true },
|
||||
] },
|
||||
{ title: "Haul", cost: 1, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [80, 0, 20, 0, 0, 0], summary: true },
|
||||
] },
|
||||
{ title: "Aura", cost: 8, parts: [
|
||||
{ subtitle: "One Wave", type: "damage", multiplier: 200, conversion: [70, 0, 0, 30, 0, 0], summary: true },
|
||||
] },
|
||||
{ title: "Uproot", cost: 6, parts: [
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: 100, conversion: [70, 30, 0, 0, 0, 0], summary: true },
|
||||
] },
|
||||
],
|
||||
"sword": [
|
||||
{ title: "Successive Strikes", cost: 5, parts: [
|
||||
{ subtitle: "Damage", type: "damage", multiplier: 65, conversion: [70, 0, 15, 0, 0, 15]},
|
||||
{ subtitle: "Final Strike", type: "damage", multiplier: 120, conversion: [70, 0, 15, 0, 0, 15]},
|
||||
{ subtitle: "Total Damage (Normal)", type: "total", factors: [2, 0], summary: true },
|
||||
] },
|
||||
{ title: "Dash", cost: 3, parts: [
|
||||
{ subtitle: "Damage", type: "damage", multiplier: 120, conversion: [60, 0, 0, 0, 0, 40], summary: true },
|
||||
] },
|
||||
{ title: "Execute", cost: 8, parts: [
|
||||
{ subtitle: "Minimum Damage", type: "damage", multiplier: 100, conversion: [60, 0, 20, 0, 20, 0]},
|
||||
{ subtitle: "Maximum Damage", type: "damage", multiplier: 1200, conversion: [60, 0, 20, 0, 20, 0], summary: true },
|
||||
] },
|
||||
{ title: "Blade Echo", cost: 4, parts: [
|
||||
{ subtitle: "Damage", type: "damage", multiplier: 125, conversion: [60, 0, 0, 20, 0, 20], summary: true },
|
||||
] },
|
||||
],
|
||||
"powder": [ //This is how instant-damage powder specials are implemented.
|
||||
{ title: "Quake", cost: 0, parts:[
|
||||
{ subtitle: "Total Damage", type: "damage", multiplier: [155, 220, 285, 350, 415], conversion: [0,100,0,0,0,0], summary: true},
|
||||
|
|
|
@ -550,9 +550,6 @@ function displayExpandedItem(item, parent_id){
|
|||
*/
|
||||
function displayRecipeStats(craft, parent_id) {
|
||||
let elem = document.getElementById(parent_id);
|
||||
if (!elem.classList.contains("col")) {
|
||||
elem.classList.add("col");
|
||||
}
|
||||
|
||||
//local vars
|
||||
elem.textContent = "";
|
||||
|
@ -565,91 +562,85 @@ function displayRecipeStats(craft, parent_id) {
|
|||
let effectiveness = craft["statMap"].get("ingredEffectiveness");
|
||||
|
||||
let title = document.createElement("div");
|
||||
title.classList.add("row", "box-title", "fw-bold", "justify-content-center");
|
||||
title.classList.add("col", "box-title", "fw-bold", "justify-content-center", "scaled-font");
|
||||
title.textContent = "Recipe Stats";
|
||||
elem.appendChild(title);
|
||||
|
||||
let mats = document.createElement("div");
|
||||
mats.classList.add("row");
|
||||
mats.classList.add("col");
|
||||
mats.textContent = "Crafting Materials: ";
|
||||
elem.appendChild(mats);
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
let tier = mat_tiers[i];
|
||||
let row = document.createElement("div");
|
||||
row.classList.add("row", "px-0", "mx-0");
|
||||
let b = document.createElement("div");
|
||||
let col = document.createElement("div");
|
||||
col.classList.add("col", "ps-4");
|
||||
let b = document.createElement("span");
|
||||
let mat = recipe.get("materials")[i];
|
||||
b.textContent = "- " + mat.get("amount") + "x " + mat.get("item").split(" ").slice(1).join(" ");
|
||||
b.classList.add("col");
|
||||
row.appendChild(b);
|
||||
col.appendChild(b);
|
||||
|
||||
let starsB = document.createElement("div");
|
||||
starsB.classList.add("T1-bracket", "col-auto", "px-0");
|
||||
let starsContainer = document.createElement("span");
|
||||
let starsB = document.createElement("span");
|
||||
starsB.classList.add("T1-bracket", "px-0");
|
||||
starsB.textContent = "[";
|
||||
row.appendChild(starsB);
|
||||
starsContainer.appendChild(starsB);
|
||||
for(let j = 0; j < 3; j ++) {
|
||||
let star = document.createElement("div");
|
||||
star.classList.add("col-auto", "px-0");
|
||||
let star = document.createElement("span");
|
||||
star.classList.add("px-0");
|
||||
star.textContent = "\u272B";
|
||||
if(j < tier) {
|
||||
star.classList.add("T1");
|
||||
} else {
|
||||
star.classList.add("T0");
|
||||
}
|
||||
row.append(star);
|
||||
starsContainer.append(star);
|
||||
}
|
||||
let starsE = document.createElement("div");
|
||||
starsE.classList.add("T1-bracket", "col-auto", "px-0");
|
||||
let starsE = document.createElement("span");
|
||||
starsE.classList.add("T1-bracket", "px-0");
|
||||
starsE.textContent = "]";
|
||||
row.appendChild(starsE);
|
||||
starsContainer.appendChild(starsE);
|
||||
|
||||
elem.appendChild(row);
|
||||
col.appendChild(starsContainer);
|
||||
|
||||
elem.appendChild(col);
|
||||
}
|
||||
|
||||
let ingredTable = document.createElement("div");
|
||||
ingredTable.classList.add("row");
|
||||
ingredTable.classList.add("col", "mt-2");
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let row = document.createElement("div");
|
||||
row.classList.add("row", "g-1", "justify-content-center");
|
||||
let ingredContainer = document.createElement("div");
|
||||
ingredContainer.classList.add("row", "row-cols-2", "g-3");
|
||||
for (let i = 0; i < 6; i++) {
|
||||
let ingredCell = document.createElement("div");
|
||||
ingredCell.classList.add("col");
|
||||
|
||||
let ingredTextContainer = document.createElement("div");
|
||||
ingredTextContainer.classList.add("border", "border-3", "rounded")
|
||||
|
||||
for (let j = 0; j < 2; j++) {
|
||||
if (j == 1) {
|
||||
let spacer = document.createElement("div");
|
||||
spacer.classList.add("col-1");
|
||||
row.appendChild(spacer);
|
||||
}
|
||||
let ingredName = ingreds[2 * i + j];
|
||||
let col = document.createElement("div");
|
||||
col.classList.add("col-5", "rounded", "dark-6", "border", "border-3", "dark-shadow");
|
||||
let ingredName = ingreds[i];
|
||||
let ingred_text = document.createElement("p");
|
||||
ingred_text.classList.add("mb-2", "ps-2");
|
||||
ingred_text.textContent = ingredName;
|
||||
ingredTextContainer.appendChild(ingred_text);
|
||||
|
||||
let temp_row = document.createElement("div");
|
||||
temp_row.classList.add("row");
|
||||
col.appendChild(temp_row);
|
||||
|
||||
let ingred_div = document.createElement("div");
|
||||
ingred_div.classList.add("col");
|
||||
ingred_div.textContent = ingredName;
|
||||
temp_row.appendChild(ingred_div);
|
||||
|
||||
let eff_div = document.createElement("div");
|
||||
eff_div.classList.add("col-auto");
|
||||
let e = effectiveness[2 * i + j];
|
||||
let eff_div = document.createElement("p");
|
||||
eff_div.classList.add("mb-2", "ps-2");
|
||||
let e = effectiveness[i];
|
||||
if (e > 0) {
|
||||
eff_div.classList.add("positive");
|
||||
} else if (e < 0) {
|
||||
eff_div.classList.add("negative");
|
||||
}
|
||||
eff_div.textContent = "[" + e + "%]";
|
||||
ingredTextContainer.appendChild(eff_div);
|
||||
|
||||
temp_row.appendChild(eff_div);
|
||||
ingredCell.appendChild(ingredTextContainer);
|
||||
|
||||
row.appendChild(col);
|
||||
}
|
||||
ingredTable.appendChild(row);
|
||||
ingredContainer.appendChild(ingredCell);
|
||||
}
|
||||
ingredTable.appendChild(ingredContainer);
|
||||
elem.appendChild(ingredTable);
|
||||
}
|
||||
|
||||
|
@ -877,7 +868,7 @@ function displayExpandedIngredient(ingred, parent_id) {
|
|||
row.appendChild(title);
|
||||
for(const skill of ingred.get("skills")) {
|
||||
let skill_div = document.createElement("div");
|
||||
skill_div.classList.add("row");
|
||||
skill_div.classList.add("row", "ps-4");
|
||||
skill_div.textContent = skill.charAt(0) + skill.substring(1).toLowerCase();
|
||||
row.appendChild(skill_div);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const DB_VERSION = 93;
|
||||
const DB_VERSION = 95;
|
||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||
|
||||
let db;
|
||||
|
|
124
js/sq2items.js
|
@ -1,57 +1,4 @@
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let filterInputs = new Map([["item-category", ["ALL", "armor", "helmet", "chestplate", "leggings", "boots", "accessory", "ring", "bracelet", "necklace", "weapon", "wand", "spear", "bow", "dagger", "relik"]],
|
||||
["item-rarity", ["ANY", "Normal", "Unique", "Set", "Rare", "Legendary", "Fabled", "Mythic", "Sane"]],
|
||||
["filter1", sq2ItemFilters],
|
||||
["filter2", sq2ItemFilters],
|
||||
["filter3", sq2ItemFilters],
|
||||
["filter4", sq2ItemFilters]]);
|
||||
for (const [field, data] of filterInputs) {
|
||||
let field_choice = document.getElementById(field+"-choice");
|
||||
// show dropdown on click
|
||||
field_choice.onclick = function() {field_choice.dispatchEvent(new Event('input', {bubbles:true}));};
|
||||
filterInputs.set(field, new autoComplete({
|
||||
data: {
|
||||
src: data,
|
||||
},
|
||||
threshold: 0,
|
||||
selector: "#"+ field +"-choice",
|
||||
wrapper: false,
|
||||
resultsList: {
|
||||
maxResults: 100,
|
||||
tabSelect: true,
|
||||
noResults: true,
|
||||
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
|
||||
element: (list, data) => {
|
||||
let position = document.getElementById(field+'-choice').getBoundingClientRect();
|
||||
list.style.top = position.bottom + window.scrollY +"px";
|
||||
list.style.left = position.x+"px";
|
||||
list.style.width = position.width+"px";
|
||||
list.style.maxHeight = position.height * 4 +"px";
|
||||
|
||||
if (!data.results.length) {
|
||||
message = document.createElement('li');
|
||||
message.classList.add('scaled-font');
|
||||
message.textContent = "No results found!";
|
||||
list.prepend(message);
|
||||
};
|
||||
},
|
||||
},
|
||||
resultItem: {
|
||||
class: "scaled-font search-item",
|
||||
selected: "dark-5",
|
||||
},
|
||||
events: {
|
||||
input: {
|
||||
selection: (event) => {
|
||||
if (event.detail.selection.value) {
|
||||
event.target.value = event.detail.selection.value;
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
};
|
||||
});
|
||||
let itemCategories = [ "armor", "accessory", "weapon" ];
|
||||
|
||||
const sq2_translate_mappings = {
|
||||
//"Name": "name",
|
||||
|
@ -184,7 +131,11 @@ function displayItems(items_copy) {
|
|||
box.appendChild(bckgrdbox);
|
||||
bckgrdbox.id = "item"+i+"b";
|
||||
items_parent.appendChild(box);
|
||||
displaysq2ExpandedItem(item, bckgrdbox.id);
|
||||
item.set("powders", []);
|
||||
if (item.get("category") == "weapon") {
|
||||
apply_weapon_powders(item);
|
||||
}
|
||||
displayExpandedItem(item, bckgrdbox.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,12 +169,12 @@ function doItemSearch() {
|
|||
|
||||
for (let i = 1; i <= 4; ++i) {
|
||||
let raw_dat = document.getElementById("filter"+i+"-choice").value;
|
||||
let filter_dat = translate_mappings[raw_dat];
|
||||
let filter_dat = sq2_translate_mappings[raw_dat];
|
||||
if (filter_dat !== undefined) {
|
||||
queries.push(new IdQuery(filter_dat));
|
||||
continue;
|
||||
}
|
||||
filter_dat = special_mappings[raw_dat];
|
||||
filter_dat = sq2_special_mappings[raw_dat];
|
||||
if (filter_dat !== undefined) {
|
||||
queries.push(filter_dat);
|
||||
continue;
|
||||
|
@ -251,6 +202,63 @@ function resetItemSearch() {
|
|||
|
||||
function init_items() {
|
||||
items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i) );
|
||||
|
||||
//init dropdowns
|
||||
let filterInputs = new Map([["item-category", ["ALL", "armor", "helmet", "chestplate", "leggings", "boots", "accessory", "ring", "bracelet", "necklace", "weapon", "wand", "spear", "bow", "dagger", "relik"]],
|
||||
["item-rarity", ["ANY", "Normal", "Unique", "Set", "Rare", "Legendary", "Fabled", "Mythic", "Sane"]],
|
||||
["filter1", sq2ItemFilters],
|
||||
["filter2", sq2ItemFilters],
|
||||
["filter3", sq2ItemFilters],
|
||||
["filter4", sq2ItemFilters]]);
|
||||
for (const [field, data] of filterInputs) {
|
||||
let field_choice = document.getElementById(field+"-choice");
|
||||
// show dropdown on click
|
||||
field_choice.onclick = function() {field_choice.dispatchEvent(new Event('input', {bubbles:true}));};
|
||||
filterInputs.set(field, new autoComplete({
|
||||
data: {
|
||||
src: data,
|
||||
},
|
||||
threshold: 0,
|
||||
selector: "#"+ field +"-choice",
|
||||
wrapper: false,
|
||||
resultsList: {
|
||||
maxResults: 100,
|
||||
tabSelect: true,
|
||||
noResults: true,
|
||||
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
|
||||
element: (list, data) => {
|
||||
let position = document.getElementById(field+'-choice').getBoundingClientRect();
|
||||
list.style.top = position.bottom + window.scrollY +"px";
|
||||
list.style.left = position.x+"px";
|
||||
list.style.width = position.width+"px";
|
||||
list.style.maxHeight = position.height * 4 +"px";
|
||||
|
||||
if (!data.results.length) {
|
||||
message = document.createElement('li');
|
||||
message.classList.add('scaled-font');
|
||||
message.textContent = "No results found!";
|
||||
list.prepend(message);
|
||||
};
|
||||
},
|
||||
},
|
||||
resultItem: {
|
||||
class: "scaled-font search-item",
|
||||
selected: "dark-5",
|
||||
},
|
||||
events: {
|
||||
input: {
|
||||
selection: (event) => {
|
||||
if (event.detail.selection.value) {
|
||||
event.target.value = event.detail.selection.value;
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
load_init(init_items);
|
||||
(async function() {
|
||||
await Promise.resolve(load_init());
|
||||
init_items();
|
||||
})();
|
||||
|
|
109
js/utils.js
|
@ -753,3 +753,112 @@ function deepcopy(obj) {
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function gen_slider_labeled({label_name, label_classlist = [], min = 0, max = 100, step = 1, default_val = min, id = undefined, color = "#FFFFFF", classlist = []}) {
|
||||
let slider_container = document.createElement("div");
|
||||
slider_container.classList.add("row");
|
||||
|
||||
let buf_col = document.createElement("div");
|
||||
buf_col.classList.add("col", "mx-1");
|
||||
|
||||
let label = document.createElement("div");
|
||||
label.classList.add("col");
|
||||
label.classList.add(...label_classlist);
|
||||
label.textContent = label_name + ": " + default_val;
|
||||
|
||||
let slider = gen_slider(min, max, step, default_val, id, color, classlist, label);
|
||||
|
||||
//we set IDs here because the slider's id is potentially only meaningful after gen_slider() is called
|
||||
label.id = slider.id + "_label";
|
||||
slider_container.id = slider.id + "-container";
|
||||
|
||||
buf_col.append(slider, label);
|
||||
slider_container.appendChild(buf_col);
|
||||
|
||||
return slider_container;
|
||||
}
|
||||
|
||||
/** Creates a slider input (input type = range) given styling parameters
|
||||
*
|
||||
* @param {Number | String} min - The minimum value for the slider. defaults to 0
|
||||
* @param {Number | String} max - The maximum value for the slider. defaults to 100
|
||||
* @param {Number | String} step - The granularity between possible values. defaults to 1
|
||||
* @param {Number | String} default_val - The default value to set the slider to.
|
||||
* @param {String} id - The element ID to use for the slider. defaults to the current date time
|
||||
* @param {String} color - The hex color to use for the slider. Needs the # character.
|
||||
* @param {Array<String>} classlist - A list of classes to add to the slider.
|
||||
* @returns
|
||||
*/
|
||||
function gen_slider(min = 0, max = 100, step = 1, default_val = min, id = undefined, color = "#FFFFFF", classlist = [], label = undefined) {
|
||||
//simple attribute vals
|
||||
let slider = document.createElement("input");
|
||||
slider.type = "range";
|
||||
slider.min = min;
|
||||
slider.max = max;
|
||||
slider.step = step;
|
||||
slider.value = default_val;
|
||||
slider.autocomplete = "off";
|
||||
if (id) {
|
||||
if (document.getElementById(id)) {
|
||||
throw new Error("ID " + id + " already exists within the DOM.")
|
||||
} else {
|
||||
slider.id = id;
|
||||
}
|
||||
} else {
|
||||
slider.id = new Date().toLocaleTimeString();
|
||||
}
|
||||
slider.color = color;
|
||||
slider.classList.add(...classlist); //special spread operator -
|
||||
//necessary for display purposes
|
||||
slider.style.webkitAppearance = "none";
|
||||
slider.style.borderRadius = "30px";
|
||||
slider.style.height = "0.5rem";
|
||||
slider.classList.add("px-0", "slider");
|
||||
|
||||
//set up recoloring
|
||||
slider.addEventListener("change", function(e) {
|
||||
recolor_slider(slider, label);
|
||||
});
|
||||
//do recoloring for the default val
|
||||
let pct = Math.round(100 * (parseInt(slider.value) - parseInt(slider.min)) / (parseInt(slider.max) - parseInt(slider.min)));
|
||||
slider.style.background = `rgba(0, 0, 0, 0) linear-gradient(to right, ${color}, ${color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
|
||||
|
||||
//return slider
|
||||
return slider;
|
||||
}
|
||||
|
||||
/** Recolors a slider. If the corresponding label exists, also update that.
|
||||
*
|
||||
* @param {slider} slider - the slider element
|
||||
* @param {label} label - the label element
|
||||
*/
|
||||
function recolor_slider(slider, label) {
|
||||
let color = slider.color;
|
||||
let pct = Math.round(100 * (parseInt(slider.value) - parseInt(slider.min)) / (parseInt(slider.max) - parseInt(slider.min)));
|
||||
slider.style.background = `rgba(0, 0, 0, 0) linear-gradient(to right, ${color}, ${color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
|
||||
|
||||
if (label) {
|
||||
//convention is that the number goes at the end... I parse by separating it at ':'
|
||||
label.textContent = label.textContent.split(":")[0] + ": " + slider.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for making an element in html.
|
||||
*
|
||||
* @param {String} type : type of element
|
||||
* @param {List[String]} classlist : css classes for element
|
||||
* @param {Map[String, String]} args : Properties for the element
|
||||
*/
|
||||
function make_elem(type, classlist = [], args = {}) {
|
||||
const ret_elem = document.createElement(type);
|
||||
ret_elem.classList.add(...classlist);
|
||||
for (const i in args) {
|
||||
ret_elem[i] = args[i];
|
||||
}
|
||||
return ret_elem;
|
||||
}
|
||||
|
|
BIN
media/atree/highlight_t_2_a_f.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.3 KiB |
|
@ -45,6 +45,7 @@ def translate_id(id_data, atree_data):
|
|||
for _input in effect["inputs"]:
|
||||
if "abil" in _input and _input["abil"] in id_data[_class]:
|
||||
_input["abil"] = id_data[_class][_input["abil"]]
|
||||
if "output" in effect:
|
||||
if isinstance(effect["output"], list):
|
||||
for output in effect["output"]:
|
||||
if "abil" in output and output["abil"] in id_data[_class]:
|
||||
|
|
|
@ -60,6 +60,7 @@ elif req.lower() == "maploc":
|
|||
else:
|
||||
response = requests.get(req)
|
||||
|
||||
response['version'] = CURR_WYNN_VERS
|
||||
data = response.json()
|
||||
data['version'] = CURR_WYNN_VERS
|
||||
|
||||
json.dump(response, open(outfile, "w+"))
|
||||
json.dump(data, open(outfile, "w+"))
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
{"items": [
|
||||
{"name":"Keratoconus","type":"helmet","level":101,"tier":"Legendary","sockets":3,"majorIds":[],"intelligence":65,"agility":65,"intelligencePoints":15,"defensePoints":-10,"health":3900,"thunderDefense":-150,"fireDefense":-150,"airDefense":250,"tDamPct-base":-40,"wDamPct-base":30,"aDamPct-base":35,"hprPct-base":-50,"mr-base":8,"spRaw4-base":-4,"category":"armor","displayName":"Keratoconus","bonusThunderDamage":-40,"bonusWaterDamage":30,"bonusAirDamage":35,"healthRegen":-50,"manaRegen":8,"spellCostRaw4":-4,"identified":false},
|
||||
{"name":"Wanderlust","type":"chestplate","level":103,"tier":"Legendary","sockets":2,"majorIds":[],"dexterity":45,"agility":55,"health":3500,"thunderDefense":75,"waterDefense":100,"fireDefense":-75,"airDefense":100,"wDamPct-base":20,"aDamPct-base":30,"sdRaw-base":208,"hprPct-base":-15,"ms-base":-11,"ls-base":230,"spd-base":25,"category":"armor","displayName":"Wanderlust","bonusWaterDamage":20,"bonusAirDamage":30,"spellDamageRaw":208,"healthRegen":-15,"manaSteal":-12,"lifeSteal":230,"speed":25,"identified":false},
|
||||
{"name":"Anaerobic","type":"leggings","level":104,"tier":"Legendary","majorIds":[],"strength":60,"agility":60,"strengthPoints":12,"health":3850,"earthDefense":100,"waterDefense":-150,"airDefense":100,"eDamPct-base":24,"aDamPct-base":32,"atkTier-base":-1,"mr-base":8,"ref-base":48,"spRaw1-base":-5,"category":"armor","displayName":"Anaerobic","bonusEarthDamage":24,"bonusAirDamage":32,"attackSpeedBonus":-1,"manaRegen":8,"reflection":48,"spellCostRaw1":-8,"identified":false},
|
||||
{"name":"Danse Macabre","type":"relik","level":102,"tier":"Rare","sockets":1,"majorIds":[],"classRequirement":"Shaman","dexterity":55,"defense":50,"damage":"0-0","earthDamage":"0-0","thunderDamage":"24-214","waterDamage":"0-0","fireDamage":"97-141","airDamage":"0-0","attackSpeed":"VERY_FAST","sdRaw-base":184,"atkTier-base":1,"hpBonus-base":-1150,"hprPct-base":-204,"ms-base":13,"spRaw1-base":-7,"category":"weapon","displayName":"Danse Macabre","basedps":238,"spellDamageRaw":184,"attackSpeedBonus":1,"healthBonus":-1150,"healthRegen":-204,"manaSteal":13,"spellCostRaw1":-7,"identified":false},
|
||||
{"name":"Anaerobic","type":"leggings","level":104,"tier":"Legendary","majorIds":[],"strength":60,"agility":60,"strengthPoints":12,"health":3850,"earthDefense":100,"waterDefense":-150,"airDefense":100,"eDamPct-base":24,"aDamPct-base":32,"atkTier-base":-1,"mr-base":8,"ref-base":48,"spRaw1-base":-5,"category":"armor","displayName":"Anaerobic","bonusEarthDamage":24,"bonusAirDamage":32,"attackSpeedBonus":-1,"manaRegen":8,"reflection":48,"spellCostRaw1":-8,"identified":false,"sockets":4},
|
||||
{"name":"Danse Macabre","type":"relik","level":102,"tier":"Rare","sockets":1,"majorIds":[],"classRequirement":"Shaman","dexterity":55,"defense":50,"damage":"0-0","earthDamage":"0-0","thunderDamage":"24-214","waterDamage":"0-0","fireDamage":"97-141","airDamage":"0-0","attackSpeed":"VERY_FAST","sdRaw-base":184,"atkTier-base":1,"hpBonus-base":-1150,"hprPct-base":-204,"ms-base":13,"spRaw1-base":-7,"category":"weapon","displayName":"Danse Macabre","basedps":238,"spellDamageRaw":184,"attackSpeedBonus":1,"healthBonus":-1150,"healthRegenRaw":-204,"manaSteal":13,"spellCostRaw1":-7,"identified":false},
|
||||
{"name":"Darkness's Dogma","type":"bow","level":103,"tier":"Rare","sockets":3,"majorIds":[],"classRequirement":"Archer","intelligence":60,"defense":50,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"600-650","fireDamage":"550-700","airDamage":"0-0","attackSpeed":"SUPER_SLOW","hpBonus-base":1500,"wDefPct-base":-50,"fDefPct-base":-50,"ms-base":13,"ls-base":-700,"ref-base":25,"category":"weapon","displayName":"Darkness's Dogma","basedps":1250,"healthBonus":1500,"bonusWaterDefense":-50,"bonusFireDefense":-50,"manaSteal":13,"lifeSteal":-700,"reflection":25,"identified":false},
|
||||
{"name":"Frameshift","type":"wand","level":104,"tier":"Rare","sockets":3,"majorIds":[],"classRequirement":"Mage","strength":40,"defense":60,"defensePoints":20,"damage":"0-0","earthDamage":"150-210","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"160-200","airDamage":"0-0","attackSpeed":"VERY_SLOW","wDamPct-base":-30,"mdPct-base":25,"mr-base":7,"ls-base":380,"spRaw1-base":-4,"category":"weapon","displayName":"Frameshift","basedps":360,"bonusWaterDamage":-30,"damageBonus":25,"manaRegen":7,"lifeSteal":380,"spellCostRaw1":-4,"identified":false},
|
||||
{"name":"Helminth","type":"spear","level":102,"tier":"Rare","sockets":2,"majorIds":[],"classRequirement":"Warrior","dexterity":65,"damage":"0-0","earthDamage":"0-0","thunderDamage":"1-199","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SUPER_FAST","tDamPct-base":20,"atkTier-base":1,"hpBonus-base":-1250,"wDefPct-base":-35,"aDefPct-base":-35,"ls-base":500,"category":"weapon","displayName":"Helminth","basedps":100,"bonusThunderDamage":20,"attackSpeedBonus":1,"healthBonus":-1250,"bonusWaterDefense":-35,"bonusAirDefense":-35,"lifeSteal":500,"identified":false},
|
||||
{"name":"Lanternfly Leg","type":"dagger","level":101,"tier":"Rare","sockets":3,"majorIds":[],"classRequirement":"Assassin","defense":50,"agility":50,"damage":"150-210","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"FAST","fDamPct-base":23,"aDamPct-base":23,"hpBonus-base":1000,"jh-base":1,"spd-base":30,"spRaw2-base":-2,"category":"weapon","displayName":"Lanternfly Leg","basedps":180,"bonusFireDamage":23,"bonusAirDamage":23,"healthBonus":1000,"jumpHeight":1,"speed":30,"spellCostRaw2":-4,"identified":false},
|
||||
{"name":"Dissonance","type":"helmet","level":103,"tier":"Unique","sockets":2,"majorIds":[],"dexterity":50,"intelligence":50,"health":3050,"thunderDefense":100,"waterDefense":100,"airDefense":-175,"tDamPct-base":12,"wDamPct-base":20,"sdPct-base":20,"ls-base":-275,"spd-base":-20,"sprint-base":20,"lb-base":20,"category":"armor","displayName":"Dissonance","bonusThunderDamage":12,"bonusWaterDamage":20,"spellDamage":20,"lifeSteal":-275,"speed":-20,"sprint":20,"lootBonus":20,"identified":false},
|
||||
{"name":"Roridula","type":"chestplate","level":104,"tier":"Unique","sockets":2,"majorIds":[],"strength":55,"intelligence":55,"strengthPoints":10,"health":3675,"earthDefense":150,"fireDefense":-150,"tDamPct-base":-30,"wDamPct-base":25,"ms-base":8,"spd-base":15,"spPct4-base":14,"xpb-base":25,"category":"armor","displayName":"Roridula","bonusThunderDamage":-30,"bonusWaterDamage":25,"manaSteal":8,"speed":15,"spellCostPct4":14,"xpBonus":25,"identified":false},
|
||||
{"name":"Atomizer","type":"leggings","level":102,"tier":"Unique","sockets":3,"majorIds":[],"agility":75,"agilityPoints":8,"health":3450,"waterDefense":120,"fireDefense":120,"airDefense":200,"poison-base":600,"wDefPct-base":31,"fDefPct-base":31,"hprPct-base":37,"thorns-base":25,"jh-base":1,"category":"armor","displayName":"Atomizer","poison":600,"bonusWaterDefense":31,"bonusFireDefense":31,"healthRegen":37,"thorns":25,"jumpHeight":1,"identified":false},
|
||||
{"name":"Atomizer","type":"leggings","level":102,"tier":"Unique","sockets":3,"majorIds":[],"agility":75,"agilityPoints":8,"health":4350,"waterDefense":120,"fireDefense":120,"airDefense":200,"poison-base":600,"wDefPct-base":31,"fDefPct-base":31,"hprPct-base":37,"thorns-base":25,"jh-base":1,"category":"armor","displayName":"Atomizer","poison":600,"bonusWaterDefense":31,"bonusFireDefense":31,"healthRegen":37,"thorns":25,"jumpHeight":1,"identified":false},
|
||||
{"name":"Wasteland Azalea","type":"boots","level":101,"tier":"Unique","sockets":3,"majorIds":[],"strength":45,"agility":50,"health":2750,"earthDefense":75,"fireDefense":-75,"eDamPct-base":25,"aDamPct-base":25,"sdRaw-base":140,"atkTier-base":-1,"poison-base":500,"sprint-base":-15,"spRaw3-base":-6,"category":"armor","displayName":"Wasteland Azalea","bonusEarthDamage":25,"bonusAirDamage":25,"spellDamageRaw":140,"attackSpeedBonus":-1,"poison":500,"sprint":-15,"spellCostRaw3":-6,"identified":false},
|
||||
{"name":"Tranquility","type":"ring","level":101,"tier":"Unique","majorIds":[],"dexterity":45,"intelligence":45,"strengthPoints":-3,"dexterityPoints":-3,"health":550,"waterDefense":50,"fireDefense":50,"sdPct-base":5,"hprPct-base":17,"spd-base":-7,"category":"accessory","displayName":"Tranquility","spellDamage":5,"healthRegen":17,"speed":-7,"identified":false},
|
||||
{"name":"Tranquility","type":"ring","level":101,"tier":"Unique","majorIds":[],"defense":45,"intelligence":45,"strengthPoints":-3,"dexterityPoints":-3,"health":550,"waterDefense":50,"fireDefense":50,"sdPct-base":5,"hprPct-base":17,"spd-base":-7,"category":"accessory","displayName":"Tranquility","spellDamage":5,"healthRegen":17,"speed":-7,"identified":false},
|
||||
{"name":"Misalignment","type":"bracelet","level":101,"tier":"Unique","majorIds":[],"strength":35,"dexterity":45,"dexterityPoints":3,"intelligencePoints":3,"eDamPct-base":6,"tDamPct-base":6,"wDamPct-base":10,"mr-base":3,"category":"accessory","displayName":"Misalignment","bonusEarthDamage":6,"bonusThunderDamage":6,"bonusWaterDamage":10,"manaRegen":3,"identified":false},
|
||||
{"name":"Grafted Eyestalk","type":"necklace","level":101,"tier":"Unique","majorIds":[],"defense":40,"agility":40,"defensePoints":5,"agilityPoints":5,"health":-600,"earthDefense":-40,"thunderDefense":-40,"sdPct-base":8,"hprPct-base":-11,"spRaw1-base":-1,"category":"accessory","displayName":"Grafted Eyestalk","spellDamage":8,"healthRegen":-12,"spellCostRaw1":-1,"identified":false},
|
||||
{"name":"Forbearance","type":"ring","level":105,"tier":"Fabled","majorIds":[],"dexterity":60,"intelligence":60,"dexterityPoints":6,"intelligencePoints":6,"tDamPct-base":-15,"wDamPct-base":-15,"mr-base":4,"ls-base":120,"category":"accessory","displayName":"Forbearance","bonusThunderDamage":-15,"bonusWaterDamage":-15,"manaRegen":4,"lifeSteal":120,"identified":false},
|
||||
|
@ -19,5 +19,216 @@
|
|||
{"name":"Breakthrough","type":"bracelet","level":105,"tier":"Fabled","majorIds":[],"intelligence":45,"agility":45,"strengthPoints":-4,"dexterityPoints":-4,"defensePoints":-6,"earthDefense":-45,"thunderDefense":-45,"fireDefense":-30,"sdPct-base":13,"sdRaw-base":75,"ms-base":6,"spRaw2-base":-4,"category":"accessory","displayName":"Breakthrough","spellDamage":13,"spellDamageRaw":75,"manaSteal":6,"spellCostRaw2":-4,"identified":false},
|
||||
{"name":"Detachment","type":"bracelet","level":105,"tier":"Fabled","majorIds":[],"dexterity":55,"agility":60,"thunderDefense":60,"airDefense":60,"mdRaw-base":-85,"sdRaw-base":-65,"spd-base":13,"spPct4-base":-13,"category":"accessory","displayName":"Detachment","damageBonusRaw":-85,"spellDamageRaw":-65,"speed":13,"spellCostPct4":-13,"identified":false},
|
||||
{"name":"Exhibition","type":"necklace","level":105,"tier":"Fabled","majorIds":[],"intelligence":80,"earthDefense":-30,"thunderDefense":-30,"waterDefense":60,"fireDefense":-30,"airDefense":-30,"mr-base":-4,"spRaw1-base":-2,"spRaw2-base":-2,"spRaw3-base":-2,"spRaw4-base":-2,"category":"accessory","displayName":"Exhibition","manaRegen":-4,"spellCostRaw1":-2,"spellCostRaw2":-2,"spellCostRaw3":-2,"spellCostRaw4":-2,"identified":false},
|
||||
{"name":"Simulacrum","type":"necklace","level":105,"tier":"Fabled","majorIds":[],"strength":55,"defense":45,"health":-800,"earthDefense":-70,"fireDefense":-90,"sdRaw-base":150,"hprRaw-base":-150,"mr-base":5,"spd-base":8,"category":"accessory","displayName":"Simulacrum","spellDamageRaw":150,"healthRegenRaw":-150,"manaRegen":5,"speed":8,"identified":false}
|
||||
{"name":"Simulacrum","type":"necklace","level":105,"tier":"Fabled","majorIds":[],"strength":55,"defense":45,"health":-800,"earthDefense":-70,"fireDefense":-90,"sdRaw-base":150,"hprRaw-base":-150,"mr-base":5,"spd-base":8,"category":"accessory","displayName":"Simulacrum","spellDamageRaw":150,"healthRegenRaw":-150,"manaRegen":5,"speed":8,"identified":false},
|
||||
{"name":"Medeis","type":"chestplate","level":100,"tier":"Unique","sockets":3,"majorIds":[],"dexterity":40,"intelligence":40,"defense":40,"dexterityPoints":10,"intelligencePoints":10,"defensePoints":10,"health":2950,"earthDefense":-100,"thunderDefense":75,"waterDefense":75,"fireDefense":75,"airDefense":-100,"tDamPct-base":8,"wDamPct-base":8,"fDamPct-base":8,"sdPct-base":8,"category":"armor","displayName":"Medeis","bonusEarthDamage":-75,"bonusThunderDamage":8,"bonusWaterDamage":8,"bonusFireDamage":8,"bonusAirDamage":-75,"spellDamage":8,"identified":false},
|
||||
{"name":"Roulette","type":"dagger","level":79,"tier":"Rare","majorIds":[],"dexterity":40,"dexterityPoints":8,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-58","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SUPER_FAST","category":"weapon","displayName":"Roulette","basedps":29,"bonusThunderDamage":888,"xpBonus":8,"lootBonus":8,"identified":false},
|
||||
{"name":"Prowess","type":"bracelet","level":100,"tier":"Legendary","majorIds":[],"quest":"The Qira Hive","strengthPoints":4,"dexterityPoints":4,"intelligencePoints":4,"defensePoints":4,"agilityPoints":4,"health":425,"category":"accessory","displayName":"Prowess","identified":true},
|
||||
{"name":"Caesura","type":"helmet","level":93,"tier":"Unique","sockets":2,"majorIds":[],"strength":45,"dexterity":60,"intelligence":30,"strengthPoints":10,"intelligencePoints":15,"health":2450,"earthDefense":100,"thunderDefense":100,"waterDefense":-250,"category":"armor","displayName":"Caesura","bonusEarthDamage":25,"bonusThunderDamage":25,"spellDamage":10,"spellDamageRaw":231,"manaRegen":-15,"manaSteal":-15,"identified":false},
|
||||
{"name":"Gigabyte","type":"necklace","level":93,"tier":"Legendary","strengthPoints":4,"dexterityPoints":4,"intelligencePoints":4,"defensePoints":4,"agilityPoints":4,"health":-512,"mr-base":-4,"identified":true,"category":"accessory","displayName":"Gigabyte","healthRegenRaw":48,"manaRegen":-4,"thorns":8,"speed":8},
|
||||
{"name":"Pro Tempore","type":"boots","level":88,"tier":"Unique","sockets":4,"majorIds":[],"dexterity":40,"intelligence":50,"intelligencePoints":10,"health":2350,"thunderDefense":-50,"waterDefense":-50,"sdRaw-base":104,"category":"armor","displayName":"Pro Tempore","spellDamage":20,"spellDamageRaw":104,"manaRegen":10,"manaSteal":-15,"lifeSteal":165,"identified":false},
|
||||
{"name":"Orange Lily","type":"bow","level":96,"tier":"Legendary","sockets":3,"strength":50,"intelligence":60,"damage":"75-140","earthDamage":"165-235","thunderDamage":"0-0","waterDamage":"165-235","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SLOW","identified":true,"category":"weapon","displayName":"Orange Lily","basedps":507.5,"bonusEarthDamage":20,"bonusWaterDamage":20,"bonusAirDamage":-150,"bonusAirDefense":-100,"manaRegen":10,"spellCostRaw3":-5,"spellCostRaw4":50},
|
||||
{"name":"Brainwash","type":"helmet","level":96,"tier":"Rare","majorIds":[],"strength":40,"dexterity":70,"strengthPoints":13,"intelligencePoints":-50,"health":2800,"earthDefense":70,"thunderDefense":100,"waterDefense":-220,"mr-base":10,"ms-base":15,"category":"armor","displayName":"Brainwash","bonusThunderDamage":15,"bonusWaterDamage":-30,"spellDamage":10,"spellDamageRaw":190,"healthRegen":-40,"manaRegen":10,"manaSteal":15,"identified":false},
|
||||
{"name":"Second Wind","type":"leggings","level":83,"tier":"Fabled","sockets":3,"majorIds":[],"defense":65,"agility":40,"agilityPoints":20,"health":6325,"earthDefense":-350,"thunderDefense":-350,"fireDefense":120,"airDefense":120,"ls-base":-475,"category":"armor","displayName":"Second Wind","attackSpeedBonus":1,"healthRegen":-30,"lifeSteal":-475,"speed":20,"identified":false},
|
||||
{"name":"Cumulonimbus","type":"helmet","level":94,"tier":"Rare","sockets":3,"majorIds":[],"dexterity":30,"intelligence":30,"agility":30,"dexterityPoints":10,"intelligencePoints":10,"agilityPoints":10,"health":1800,"thunderDefense":70,"waterDefense":70,"airDefense":70,"sdPct-base":15,"category":"armor","displayName":"Cumulonimbus","bonusThunderDamage":10,"bonusWaterDamage":10,"bonusAirDamage":10,"spellDamage":15,"speed":25,"identified":false},
|
||||
{"name":"Morrowind","type":"wand","level":96,"tier":"Legendary","sockets":3,"majorIds":[],"agility":45,"agilityPoints":14,"damage":"30-60","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"45-135","attackSpeed":"FAST","sdRaw-base":145,"category":"weapon","displayName":"Morrowind","basedps":135,"bonusEarthDamage":-15,"bonusAirDamage":23,"spellDamageRaw":145,"bonusAirDefense":23,"reflection":46,"speed":23,"identified":false},
|
||||
{"name": "Anima-Infused Helmet", "displayName":"Boreal-Patterned Crown","type":"helmet","level":100,"tier":"Legendary","sockets":2,"quest":"The Qira Hive","dexterity":40,"intelligence":40,"agility":40,"strengthPoints":-30,"defensePoints":-30,"health":3000,"thunderDefense":150,"waterDefense":150,"airDefense":150,"identified":true,"category":"armor","displayName":"Boreal-Patterned Crown","bonusThunderDamage":25,"bonusWaterDamage":25,"bonusAirDamage":25,"damageBonus":-40,"spellDamage":20,"spellDamageRaw":300,"bonusThunderDefense":10,"bonusWaterDefense":10,"bonusAirDefense":10,"manaRegen":8},
|
||||
{"name":"Corsair","type":"helmet","level":99,"tier":"Unique","sockets":2,"majorIds":[],"dexterity":55,"agility":35,"dexterityPoints":8,"health":2900,"earthDefense":-140,"thunderDefense":80,"airDefense":110,"ms-base":5,"spPct1-base":-10,"spPct4-base":-14,"category":"armor","displayName":"Corsair","bonusThunderDamage":10,"bonusAirDamage":12,"manaSteal":5,"speed":11,"spellCostPct1":-10,"spellCostPct4":-14,"emeraldStealing":4,"identified":false},
|
||||
{"name":"Scaldsteppers","type":"boots","level":90,"tier":"Unique","sockets":2,"majorIds":[],"intelligence":40,"defense":30,"intelligencePoints":7,"health":2325,"thunderDefense":-100,"waterDefense":110,"fireDefense":80,"airDefense":-90,"spRaw3-base":-4,"category":"armor","displayName":"Scaldsteppers","bonusWaterDamage":10,"bonusFireDamage":10,"bonusAirDamage":-12,"spellDamage":20,"exploding":10,"spellCostRaw3":-4,"identified":false},
|
||||
{"name":"Charging Flame","type":"spear","level":94,"tier":"Rare","sockets":2,"majorIds":[],"intelligence":40,"defense":40,"damage":"20-40","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"40-120","fireDamage":"20-140","airDamage":"0-0","attackSpeed":"NORMAL","mr-base":5,"spRaw2-base":-12,"category":"weapon","displayName":"Charging Flame","basedps":190,"bonusWaterDamage":12,"bonusFireDamage":12,"damageBonus":12,"spellDamage":12,"healthBonus":-1200,"bonusThunderDefense":-25,"manaRegen":5,"exploding":24,"spellCostRaw2":-12,"identified":false},
|
||||
{"name":"Aphotic","type":"helmet","level":98,"tier":"Legendary","sockets":2,"majorIds":[],"intelligence":100,"dexterityPoints":-80,"intelligencePoints":5,"health":3200,"thunderDefense":-150,"waterDefense":150,"spRaw3-base":-7,"category":"armor","displayName":"Aphotic","spellDamage":50,"attackSpeedBonus":-6,"spellCostRaw3":-7,"identified":false},
|
||||
{"name":"Silent Ballet","type":"relik","level":83,"tier":"Unique","majorIds":[],"strength":40,"agility":40,"damage":"0-0","earthDamage":"110-121","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"110-121","attackSpeed":"FAST","category":"weapon","displayName":"Silent Ballet","basedps":231,"damageBonus":40,"spellDamage":-40000,"spellDamageRaw":-40000,"spellCostPct1":-40,"spellCostPct2":-40,"spellCostPct3":-40,"spellCostPct4":-40,"spellCostRaw1":-400,"spellCostRaw2":-400,"spellCostRaw3":-400,"spellCostRaw4":-400,"identified":false},
|
||||
{"name":"Rhythm of the Seasons","type":"spear","level":100,"tier":"Fabled","sockets":2,"majorIds":["RALLY"],"strength":80,"defense":60,"defensePoints":12,"damage":"40-50","earthDamage":"100-140","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"VERY_FAST","mr-base":15,"category":"weapon","displayName":"Rhythm of the Seasons","basedps":165,"bonusWaterDamage":25,"healthBonus":1800,"healthRegenRaw":-660,"manaRegen":15,"identified":false},
|
||||
{"name":"Sorrow","type":"chestplate","level":72,"tier":"Rare","sockets":1,"intelligence":95,"health":1400,"thunderDefense":-150,"waterDefense":150,"identified":true,"category":"armor","displayName":"Sorrow","bonusWaterDamage":42,"spellDamage":8,"manaRegen":5,"manaSteal":-20,"spellCostRaw1":-8,"soulPoints":20},
|
||||
{"name":"Condensation","type":"boots","level":87,"tier":"Unique","majorIds":[],"intelligence":75,"intelligencePoints":10,"health":2000,"thunderDefense":-120,"waterDefense":100,"spRaw3-base":-6,"category":"armor","displayName":"Condensation","damageBonus":-30,"spellDamage":30,"bonusThunderDefense":-20,"spellCostRaw3":-6,"identified":false},
|
||||
{"name":"Augoeides","type":"chestplate","level":63,"tier":"Rare","majorIds":[],"intelligence":65,"health":1000,"earthDefense":20,"thunderDefense":20,"waterDefense":20,"fireDefense":20,"airDefense":20,"mr-base":5,"spRaw3-base":-5,"category":"armor","displayName":"Augoeides","damageBonusRaw":-52,"manaRegen":5,"reflection":30,"spellCostRaw3":-5,"xpBonus":5,"lootBonus":8,"soulPoints":10,"identified":false},
|
||||
{"name":"Matryoshka Shell","type":"chestplate","level":55,"tier":"Legendary","sockets":18,"majorIds":[],"strength":10,"dexterity":10,"intelligence":10,"defense":10,"agility":10,"health":550,"earthDefense":50,"thunderDefense":50,"waterDefense":50,"fireDefense":50,"airDefense":50,"spRaw1-base":-5,"category":"armor","displayName":"Matryoshka Shell","bonusEarthDefense":20,"bonusThunderDefense":20,"bonusWaterDefense":20,"bonusFireDefense":20,"bonusAirDefense":20,"speed":10,"spellCostRaw1":-5,"xpBonus":20,"lootBonus":20,"identified":false},
|
||||
{"name":"Pure","type":"wand","level":65,"tier":"Mythic","majorIds":["ENTROPY"],"intelligence":50,"agility":30,"damage":"0-5","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"20-45","fireDamage":"0-0","airDamage":"15-55","attackSpeed":"FAST","ms-base":30,"spRaw3-base":6,"category":"weapon","displayName":"Pure","basedps":70,"damageBonus":-100,"spellDamage":400,"manaSteal":30,"reflection":20,"spellCostRaw3":6,"xpBonus":30,"identified":false},
|
||||
{"name":"Resurgence","type":"boots","level":91,"tier":"Mythic","sockets":4,"majorIds":[],"intelligence":65,"defense":90,"intelligencePoints":25,"health":4550,"waterDefense":175,"fireDefense":125,"mr-base":30,"category":"armor","displayName":"Resurgence","damageBonus":-45,"spellDamage":-35,"healthRegenRaw":390,"manaRegen":30,"speed":-14,"soulPoints":20,"identified":false},
|
||||
{"name":"Galleon","type":"boots","level":92,"tier":"Mythic","sockets":3,"majorIds":[],"strength":65,"intelligence":60,"health":4500,"earthDefense":200,"waterDefense":250,"airDefense":-175,"ms-base":20,"category":"armor","displayName":"Galleon","bonusEarthDamage":36,"bonusWaterDamage":36,"damageBonus":45,"attackSpeedBonus":-1,"poison":4231,"manaSteal":20,"emeraldStealing":15,"lootBonus":20,"identified":false},
|
||||
{"name":"Boreal","type":"boots","level":93,"tier":"Mythic","sockets":3,"majorIds":[],"defense":75,"agility":65,"health":5000,"fireDefense":250,"airDefense":375,"mr-base":10,"category":"armor","displayName":"Boreal","bonusEarthDamage":-75,"bonusThunderDamage":-75,"bonusFireDefense":50,"bonusAirDefense":50,"healthRegen":100,"healthRegenRaw":269,"manaRegen":10,"reflection":25,"speed":25,"identified":false},
|
||||
{"name":"Freedom","type":"bow","level":93,"tier":"Mythic","sockets":4,"majorIds":[],"strength":40,"dexterity":40,"intelligence":40,"defense":40,"agility":40,"agilityPoints":30,"damage":"0-0","earthDamage":"85-109","thunderDamage":"45-149","waterDamage":"65-129","fireDamage":"75-119","airDamage":"55-139","attackSpeed":"NORMAL","mr-base":10,"category":"weapon","displayName":"Freedom","basedps":485,"damageBonusRaw":111,"spellDamageRaw":111,"healthBonus":1000,"manaRegen":10,"speed":15,"identified":false},
|
||||
{"name":"Olympic","type":"relik","level":93,"tier":"Mythic","sockets":3,"majorIds":[],"agility":105,"agilityPoints":25,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"345-375","attackSpeed":"FAST","spRaw1-base":-10,"spRaw2-base":-10,"category":"weapon","displayName":"Olympic","basedps":360,"bonusAirDamage":20,"bonusAirDefense":30,"jumpHeight":6,"speed":35,"spellCostRaw1":-10,"spellCostRaw2":-10,"identified":false},
|
||||
{"name":"Guardian","type":"spear","level":93,"tier":"Mythic","sockets":3,"majorIds":["GUARDIAN"],"defense":110,"defensePoints":20,"damage":"50-90","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"165-205","airDamage":"0-0","attackSpeed":"NORMAL","category":"weapon","displayName":"Guardian","basedps":255,"healthBonus":6000,"bonusEarthDefense":20,"bonusWaterDefense":20,"bonusFireDefense":20,"healthRegenRaw":585,"manaRegen":1,"thorns":25,"identified":false},
|
||||
{"name":"Hadal","type":"relik","level":94,"tier":"Mythic","sockets":3,"majorIds":[],"intelligence":130,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"1750-2150","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SUPER_SLOW","mr-base":30,"spPct3-base":112,"spPct4-base":112,"category":"weapon","displayName":"Hadal","basedps":1950,"spellDamage":75,"manaRegen":30,"spellCostPct3":112,"spellCostPct4":112,"identified":false},
|
||||
{"name":"Nullification","type":"dagger","level":95,"tier":"Mythic","sockets":3,"majorIds":[],"strength":30,"dexterity":30,"intelligence":30,"defense":30,"agility":30,"defensePoints":40,"damage":"0-0","earthDamage":"60-66","thunderDamage":"22-104","waterDamage":"46-80","fireDamage":"36-90","airDamage":"28-98","attackSpeed":"FAST","ms-base":16,"category":"weapon","displayName":"Nullification","basedps":315,"poison":-7000,"bonusEarthDefense":143,"bonusThunderDefense":143,"bonusWaterDefense":143,"bonusFireDefense":143,"bonusAirDefense":143,"manaSteal":16,"lifeSteal":495,"reflection":80,"identified":false},
|
||||
{"name":"Idol","type":"spear","level":95,"tier":"Mythic","sockets":3,"majorIds":[],"intelligence":120,"intelligencePoints":26,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"230-330","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"NORMAL","mr-base":10,"spRaw2-base":-50,"category":"weapon","displayName":"Idol","basedps":280,"spellDamageRaw":264,"bonusWaterDefense":15,"manaRegen":10,"reflection":30,"spellCostRaw2":-50,"soulPoints":25,"identified":false},
|
||||
{"name":"Dawnbreak","type":"boots","level":96,"tier":"Mythic","sockets":2,"majorIds":[],"dexterity":65,"defense":65,"health":4225,"thunderDefense":200,"waterDefense":-125,"fireDefense":200,"airDefense":-125,"ms-base":12,"category":"armor","displayName":"Dawnbreak","bonusThunderDamage":27,"bonusFireDamage":27,"damageBonusRaw":2700,"attackSpeedBonus":-20,"manaSteal":12,"lifeSteal":350,"exploding":23,"identified":false},
|
||||
{"name":"Cataclysm","type":"dagger","level":96,"tier":"Mythic","sockets":3,"majorIds":[],"dexterity":120,"dexterityPoints":20,"damage":"40-140","earthDamage":"0-0","thunderDamage":"45-305","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SUPER_FAST","category":"weapon","displayName":"Cataclysm","basedps":265,"bonusThunderDamage":17,"healthBonus":-6000,"thorns":21,"spellCostRaw1":-1,"emeraldStealing":5,"identified":false},
|
||||
{"name":"Grimtrap","type":"dagger","level":96,"tier":"Mythic","sockets":3,"majorIds":[],"strength":100,"strengthPoints":15,"damage":"175-235","earthDamage":"305-425","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SLOW","ms-base":-10,"spRaw4-base":-10,"category":"weapon","displayName":"Grimtrap","basedps":570,"poison":2000,"manaSteal":-10,"lifeSteal":650,"thorns":70,"spellCostRaw2":1,"spellCostRaw4":-10,"identified":false},
|
||||
{"name":"Weathered","type":"dagger","level":96,"tier":"Mythic","sockets":3,"majorIds":["ROVINGASSASSIN"],"agility":110,"agilityPoints":15,"damage":"40-80","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"140-230","attackSpeed":"VERY_FAST","ms-base":16,"category":"weapon","displayName":"Weathered","basedps":245,"bonusAirDamage":20,"attackSpeedBonus":1,"manaSteal":16,"reflection":25,"exploding":-50,"speed":25,"identified":false},
|
||||
{"name":"Thrundacrack","type":"spear","level":96,"tier":"Mythic","sockets":4,"majorIds":[],"dexterity":105,"dexterityPoints":35,"damage":"50-90","earthDamage":"0-0","thunderDamage":"50-220","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"VERY_FAST","spRaw3-base":-6,"category":"weapon","displayName":"Thrundacrack","basedps":205,"bonusThunderDamage":25,"bonusWaterDamage":60,"healthRegen":-60,"speed":9,"spellCostRaw3":-6,"identified":false},
|
||||
{"name":"Lament","type":"wand","level":96,"tier":"Mythic","sockets":3,"majorIds":[],"intelligence":110,"intelligencePoints":20,"damage":"70-90","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"180-190","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SLOW","ms-base":40,"spPct1-base":-35,"category":"weapon","displayName":"Lament","basedps":265,"bonusWaterDamage":80,"manaSteal":40,"lifeSteal":-500,"spellCostPct1":-35,"identified":false},
|
||||
{"name":"Toxoplasmosis","type":"relik","level":96,"tier":"Mythic","sockets":2,"majorIds":[],"strength":110,"strengthPoints":40,"damage":"0-0","earthDamage":"3-3","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"VERY_FAST","ms-base":18,"category":"weapon","displayName":"Toxoplasmosis","basedps":3,"poison":10000,"manaSteal":18,"lifeSteal":500,"speed":20,"lootBonus":20,"identified":false},
|
||||
{"name":"Stardew","type":"boots","level":97,"tier":"Mythic","sockets":2,"majorIds":[],"dexterity":65,"intelligence":75,"health":4075,"earthDefense":-100,"thunderDefense":150,"waterDefense":150,"fireDefense":-100,"airDefense":-100,"mr-base":-9,"ms-base":15,"category":"armor","displayName":"Stardew","bonusThunderDamage":35,"bonusWaterDamage":35,"spellDamageRaw":313,"manaRegen":-9,"manaSteal":15,"reflection":25,"identified":false},
|
||||
{"name":"Divzer","type":"bow","level":97,"tier":"Mythic","sockets":3,"majorIds":[],"dexterity":115,"dexterityPoints":37,"defensePoints":-39,"agilityPoints":-550,"damage":"48-50","earthDamage":"0-0","thunderDamage":"250-250","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SUPER_FAST","ms-base":30,"category":"weapon","displayName":"Divzer","basedps":299,"bonusWaterDamage":-550,"bonusFireDamage":-550,"damageBonusRaw":536,"spellDamageRaw":253,"attackSpeedBonus":1,"manaSteal":30,"lifeSteal":973,"identified":false},
|
||||
{"name":"Inferno","type":"dagger","level":97,"tier":"Mythic","sockets":3,"majorIds":[],"defense":105,"defensePoints":15,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"855-1045","airDamage":"0-0","attackSpeed":"VERY_SLOW","category":"weapon","displayName":"Inferno","basedps":950,"bonusFireDamage":35,"damageBonus":25,"damageBonusRaw":560,"healthBonus":1500,"bonusWaterDefense":-40,"healthRegen":-45,"manaRegen":-1,"speed":25,"spellCostRaw1":-1,"identified":false},
|
||||
{"name":"Nirvana","type":"dagger","level":97,"tier":"Mythic","sockets":3,"majorIds":["ARCANES"],"intelligence":110,"intelligencePoints":40,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"320-385","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"NORMAL","mr-base":10,"ms-base":-20,"category":"weapon","displayName":"Nirvana","basedps":352.5,"damageBonus":-80,"spellDamage":25,"healthBonus":-2000,"manaRegen":10,"manaSteal":-20,"reflection":15,"identified":false},
|
||||
{"name":"Collapse","type":"spear","level":97,"tier":"Mythic","sockets":3,"majorIds":["FISSION"],"strength":35,"dexterity":35,"intelligence":35,"defense":35,"agility":35,"strengthPoints":45,"damage":"40-65","earthDamage":"0-310","thunderDamage":"0-310","waterDamage":"0-310","fireDamage":"0-310","airDamage":"0-310","attackSpeed":"VERY_SLOW","ms-base":18,"category":"weapon","displayName":"Collapse","basedps":827.5,"damageBonus":50,"bonusEarthDefense":-65,"bonusThunderDefense":-65,"bonusWaterDefense":-65,"bonusFireDefense":-65,"bonusAirDefense":-65,"manaSteal":18,"exploding":250,"identified":false},
|
||||
{"name":"Gaia","type":"wand","level":97,"tier":"Mythic","sockets":3,"majorIds":[],"strength":105,"strengthPoints":25,"damage":"150-220","earthDamage":"380-490","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"VERY_SLOW","spRaw4-base":-9,"category":"weapon","displayName":"Gaia","basedps":620,"damageBonus":15,"damageBonusRaw":575,"spellDamageRaw":-275,"poison":2500,"thorns":15,"spellCostRaw4":-9,"identified":false},
|
||||
{"name":"Absolution","type":"relik","level":97,"tier":"Mythic","majorIds":[],"defense":115,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"195-205","airDamage":"0-0","attackSpeed":"SUPER_FAST","mr-base":16,"spRaw1-base":-20,"category":"weapon","displayName":"Absolution","basedps":200,"bonusWaterDamage":200,"bonusFireDamage":20,"healthBonus":3000,"bonusEarthDefense":45,"bonusThunderDefense":45,"manaRegen":16,"spellCostRaw1":-20,"soulPoints":23,"identified":false},
|
||||
{"name":"Spring","type":"bow","level":98,"tier":"Mythic","sockets":3,"majorIds":[],"intelligence":120,"strengthPoints":15,"dexterityPoints":-40,"intelligencePoints":15,"damage":"150-185","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"200-310","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"NORMAL","mr-base":30,"category":"weapon","displayName":"Spring","basedps":422.5,"bonusThunderDamage":-50,"bonusWaterDamage":20,"manaRegen":30,"identified":false},
|
||||
{"name":"Monster","type":"wand","level":98,"tier":"Mythic","sockets":3,"majorIds":[],"defense":110,"defensePoints":40,"damage":"110-140","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"160-220","airDamage":"0-0","attackSpeed":"SLOW","ms-base":10,"spRaw1-base":4,"category":"weapon","displayName":"Monster","basedps":315,"bonusFireDamage":25,"damageBonus":40,"healthBonus":3000,"manaSteal":10,"lifeSteal":500,"spellCostRaw1":4,"identified":false},
|
||||
{"name":"Revenant","type":"boots","level":99,"tier":"Mythic","sockets":3,"majorIds":[],"strength":70,"agility":70,"health":7000,"earthDefense":70,"airDefense":70,"ms-base":10,"spPct4-base":-28,"category":"armor","displayName":"Revenant","bonusEarthDamage":40,"bonusAirDamage":40,"damageBonus":-70,"damageBonusRaw":520,"healthBonus":-2500,"manaSteal":10,"reflection":120,"speed":40,"spellCostPct4":-28,"identified":false},
|
||||
{"name":"Fatal","type":"wand","level":99,"tier":"Mythic","sockets":3,"majorIds":[],"dexterity":110,"dexterityPoints":25,"damage":"0-0","earthDamage":"0-0","thunderDamage":"1-360","waterDamage":"0-0","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"VERY_FAST","spPct1-base":28,"spPct2-base":-49,"category":"weapon","displayName":"Fatal","basedps":180.5,"spellDamage":25,"manaSteal":1,"speed":15,"spellCostPct1":28,"spellCostPct2":-49,"identified":false},
|
||||
{"name":"Warp","type":"wand","level":99,"tier":"Mythic","sockets":3,"majorIds":[],"agility":130,"agilityPoints":20,"damage":"40-70","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"0-0","airDamage":"190-220","attackSpeed":"VERY_FAST","mr-base":-45,"spRaw1-base":4,"spRaw2-base":-299,"category":"weapon","displayName":"Warp","basedps":260,"bonusAirDamage":15,"healthRegen":-200,"healthRegenRaw":-600,"manaRegen":-45,"reflection":90,"exploding":50,"speed":180,"spellCostRaw1":4,"spellCostRaw2":-299,"identified":false},
|
||||
{"name":"Oblivion","type":"dagger","level":101,"tier":"Mythic","sockets":4,"majorIds":[],"dexterity":75,"intelligence":65,"dexterityPoints":15,"damage":"1-200","earthDamage":"0-0","thunderDamage":"600-999","waterDamage":"600-999","fireDamage":"0-0","airDamage":"0-0","attackSpeed":"SUPER_SLOW","mr-base":-30,"ms-base":15,"spRaw2-base":-20,"category":"weapon","displayName":"Oblivion","basedps":1699.5,"spellDamageRaw":265,"manaRegen":-30,"manaSteal":15,"exploding":40,"spellCostRaw2":-20,"soulPoints":40,"identified":false},
|
||||
{"name":"Epoch","type":"bow","level":102,"tier":"Mythic","sockets":3,"majorIds":[],"dexterity":70,"agility":70,"damage":"500-620","earthDamage":"0-0","thunderDamage":"480-640","waterDamage":"0-0","fireDamage":"0-0","airDamage":"520-600","attackSpeed":"SUPER_SLOW","sdPct-base":40,"category":"weapon","displayName":"Epoch","basedps":1680,"damageBonusRaw":769,"spellDamage":40,"manaSteal":-1,"lifeSteal":825,"speed":-20,"sprint":70,"spellCostRaw1":-1,"spellCostRaw4":-1,"identified":false},
|
||||
{"name":"Fantasia","type":"relik","level":96,"tier":"Mythic","sockets":3,"majorIds":[],"strength":45,"dexterity":45,"intelligence":45,"defense":45,"agility":45,"intelligencePoints":50,"damage":"0-0","earthDamage":"170-310","thunderDamage":"230-250","waterDamage":"200-280","fireDamage":"185-295","airDamage":"215-265","attackSpeed":"VERY_SLOW","spPct1-base":-27,"spPct2-base":-27,"spPct3-base":-27,"spPct4-base":-27,"category":"weapon","displayName":"Fantasia","basedps":1350,"spellDamage":30,"manaRegen":-20,"manaSteal":-20,"spellCostPct1":-27,"spellCostPct2":-27,"spellCostPct3":-27,"spellCostPct4":-27,"identified":false},
|
||||
{"name":"Slayer","type":"boots","level":94,"tier":"Mythic","sockets":2,"majorIds":[],"dexterity":75,"agility":60,"dexterityPoints":20,"health":3775,"waterDefense":-100,"spPct3-base":-30,"category":"armor","displayName":"Slayer","damageBonusRaw":285,"attackSpeedBonus":1,"healthRegenRaw":-270,"speed":27,"spellCostPct3":-30,"emeraldStealing":10,"identified":false},
|
||||
{"name":"Immolation","type":"relik","level":101,"tier":"Mythic","sockets":3,"majorIds":[],"defense":80,"agility":80,"defensePoints":50,"agilityPoints":50,"damage":"0-0","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"0-0","fireDamage":"200-440","airDamage":"310-330","attackSpeed":"SLOW","spPct3-base":-14,"category":"weapon","displayName":"Immolation","basedps":640,"bonusWaterDamage":-1000,"bonusFireDamage":45,"bonusAirDamage":45,"healthBonus":-2750,"healthRegen":-180,"spellCostPct3":-14,"identified":false},
|
||||
{"name":"Convergence","type":"spear","level":104,"tier":"Mythic","sockets":3,"majorIds":[],"intelligence":65,"defense":75,"damage":"70-90","earthDamage":"0-0","thunderDamage":"0-0","waterDamage":"100-120","fireDamage":"105-115","airDamage":"0-0","attackSpeed":"NORMAL","spPct3-base":-45,"category":"weapon","displayName":"Convergence","basedps":300,"bonusEarthDamage":55,"bonusThunderDamage":55,"bonusAirDefense":35,"healthRegen":43,"sprintRegen":43,"spellCostPct3":-45,"identified":false},
|
||||
{
|
||||
"name": "Panic Zealot",
|
||||
"tier": "Fabled",
|
||||
"type": "Relik",
|
||||
"restrictions": "Untradable",
|
||||
"material": "273:7",
|
||||
"dropType": "never",
|
||||
"addedLore": "They must know what you went through. They must suffer the same as you did.",
|
||||
"sockets": 3,
|
||||
"damage": "46-60",
|
||||
"earthDamage": "0-0",
|
||||
"thunderDamage": "0-0",
|
||||
"waterDamage": "0-0",
|
||||
"fireDamage": "0-0",
|
||||
"airDamage": "43-63",
|
||||
"attackSpeed": "SUPER_FAST",
|
||||
"level": 101,
|
||||
"strength": 0,
|
||||
"dexterity": 0,
|
||||
"intelligence": 0,
|
||||
"defense": 0,
|
||||
"agility": 85,
|
||||
"strengthPoints": 0,
|
||||
"dexterityPoints": 0,
|
||||
"intelligencePoints": 0,
|
||||
"defensePoints": 0,
|
||||
"agilityPoints": 0,
|
||||
"majorIds": [
|
||||
"FURIOUS_EFFIGY"
|
||||
],
|
||||
"damageBonus": 0,
|
||||
"damageBonusRaw": 0,
|
||||
"spellDamage": 0,
|
||||
"spellDamageRaw": 0,
|
||||
"rainbowSpellDamageRaw": 0,
|
||||
"healthRegen": 0,
|
||||
"healthRegenRaw": 0,
|
||||
"healthBonus": -5000,
|
||||
"poison": 0,
|
||||
"lifeSteal": 0,
|
||||
"manaRegen": 0,
|
||||
"manaSteal": 0,
|
||||
"spellCostPct1": -100,
|
||||
"spellCostRaw1": 0,
|
||||
"spellCostPct2": -100,
|
||||
"spellCostRaw2": 0,
|
||||
"spellCostPct3": -100,
|
||||
"spellCostRaw3": 0,
|
||||
"spellCostPct4": 0,
|
||||
"spellCostRaw4": 0,
|
||||
"thorns": 0,
|
||||
"reflection": 0,
|
||||
"attackSpeedBonus": 3,
|
||||
"speed": 30,
|
||||
"exploding": 0,
|
||||
"soulPoints": 0,
|
||||
"sprint": 0,
|
||||
"sprintRegen": 0,
|
||||
"jumpHeight": 0,
|
||||
"xpBonus": 0,
|
||||
"lootBonus": 0,
|
||||
"lootQuality": 0,
|
||||
"emeraldStealing": 0,
|
||||
"gatherXpBonus": 0,
|
||||
"gatherSpeed": 0,
|
||||
"bonusEarthDamage": 0,
|
||||
"bonusThunderDamage": -30,
|
||||
"bonusWaterDamage": 0,
|
||||
"bonusFireDamage": 0,
|
||||
"bonusAirDamage": 0,
|
||||
"bonusEarthDefense": 0,
|
||||
"bonusThunderDefense": 0,
|
||||
"bonusWaterDefense": 0,
|
||||
"bonusFireDefense": 0,
|
||||
"bonusAirDefense": 0,
|
||||
"category": "weapon"
|
||||
},
|
||||
{
|
||||
"name": "Ambivalence",
|
||||
"tier": "Legendary",
|
||||
"accessoryType": "Necklace",
|
||||
"set": null,
|
||||
"identified": true,
|
||||
"restrictions": "Untradable",
|
||||
"material": "259:29",
|
||||
"dropType": "never",
|
||||
"addedLore": null,
|
||||
"sockets": 0,
|
||||
"health": 0,
|
||||
"earthDefense": 0,
|
||||
"thunderDefense": 70,
|
||||
"waterDefense": 0,
|
||||
"fireDefense": 70,
|
||||
"airDefense": 70,
|
||||
"level": 100,
|
||||
"quest": null,
|
||||
"classRequirement": null,
|
||||
"strength": 0,
|
||||
"dexterity": 40,
|
||||
"intelligence": 0,
|
||||
"defense": 40,
|
||||
"agility": 40,
|
||||
"strengthPoints": 0,
|
||||
"dexterityPoints": 0,
|
||||
"intelligencePoints": -100,
|
||||
"defensePoints": 0,
|
||||
"agilityPoints": 0,
|
||||
"damageBonus": 0,
|
||||
"damageBonusRaw": 0,
|
||||
"spellDamage": 250,
|
||||
"spellDamageRaw": 0,
|
||||
"rainbowSpellDamageRaw": 0,
|
||||
"healthRegen": 0,
|
||||
"healthRegenRaw": 0,
|
||||
"healthBonus": 0,
|
||||
"poison": 0,
|
||||
"lifeSteal": 0,
|
||||
"manaRegen": 0,
|
||||
"manaSteal": 0,
|
||||
"spellCostPct1": 130,
|
||||
"spellCostRaw1": 0,
|
||||
"spellCostPct2": 85,
|
||||
"spellCostRaw2": 0,
|
||||
"spellCostPct3": 130,
|
||||
"spellCostRaw3": 0,
|
||||
"spellCostPct4": 100,
|
||||
"spellCostRaw4": 0,
|
||||
"thorns": 0,
|
||||
"reflection": 0,
|
||||
"attackSpeedBonus": 0,
|
||||
"speed": 0,
|
||||
"exploding": 0,
|
||||
"soulPoints": 0,
|
||||
"sprint": 0,
|
||||
"sprintRegen": 0,
|
||||
"jumpHeight": 0,
|
||||
"xpBonus": 0,
|
||||
"lootBonus": 0,
|
||||
"lootQuality": 0,
|
||||
"emeraldStealing": 0,
|
||||
"gatherXpBonus": 0,
|
||||
"gatherSpeed": 0,
|
||||
"bonusEarthDamage": 0,
|
||||
"bonusThunderDamage": 0,
|
||||
"bonusWaterDamage": 50,
|
||||
"bonusFireDamage": 0,
|
||||
"bonusAirDamage": 0,
|
||||
"bonusEarthDefense": 0,
|
||||
"bonusThunderDefense": 0,
|
||||
"bonusWaterDefense": 0,
|
||||
"bonusFireDefense": 0,
|
||||
"bonusAirDefense": 0,
|
||||
"category": "accessory"
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -58,6 +58,11 @@ mul_keys = {
|
|||
"ms": 50
|
||||
}
|
||||
|
||||
def round_near(x, eps=1e-5):
|
||||
if abs(x - round(x)) < eps:
|
||||
return round(x)
|
||||
return x
|
||||
|
||||
remap_items = []
|
||||
#old_items_map = dict()
|
||||
import math
|
||||
|
@ -67,7 +72,7 @@ for item in old_items:
|
|||
# SUPER JANKY ROUNDING
|
||||
tentimes = round(item[k] * v)
|
||||
rem = tentimes % 10
|
||||
val = math.floor(round(tentimes / 10))
|
||||
val = math.floor(round_near(tentimes / 10))
|
||||
if rem >= 5:
|
||||
val += 1
|
||||
item[k] = val
|
||||
|
|