Merge branch 'master' into atree_hotfix
This commit is contained in:
commit
eb5db84b6f
33 changed files with 1476 additions and 1459 deletions
File diff suppressed because one or more lines are too long
|
@ -36,7 +36,50 @@
|
|||
<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-fluid overall-box">
|
||||
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
|
||||
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
|
||||
<div class="navbar-brand mx-auto scaled-font" style="height: 100%;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</div>
|
||||
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
|
||||
</div>
|
||||
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
|
||||
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</a>
|
||||
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</a>
|
||||
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</a>
|
||||
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</a>
|
||||
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</a>
|
||||
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</a>
|
||||
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
|
||||
<span>Swap Icon Style</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid overall-box mt-lg-2" style="margin-top: 6vh;">
|
||||
<!-- 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>
|
||||
|
@ -414,9 +457,6 @@
|
|||
Active boosts
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="vanish-boost" onclick="update_boosts('vanish-boost')">
|
||||
Vanish (+80%)
|
||||
</button>
|
||||
<button class="button-boost m-1 border-0 text-white dark-8u dark-shadow-sm" id="warscream-boost" onclick="update_boosts('warscream-boost')">
|
||||
War Scream
|
||||
</button>
|
||||
|
|
130
clean.json
130
clean.json
|
@ -40164,32 +40164,6 @@
|
|||
"mr": 5,
|
||||
"id": 1848
|
||||
},
|
||||
{
|
||||
"name": "Air Relic Dagger",
|
||||
"displayName": "Air Relic Daggers",
|
||||
"tier": "Rare",
|
||||
"type": "dagger",
|
||||
"category": "weapon",
|
||||
"slots": 3,
|
||||
"drop": "never",
|
||||
"nDam": "39-66",
|
||||
"fDam": "0-0",
|
||||
"wDam": "0-0",
|
||||
"aDam": "39-66",
|
||||
"tDam": "0-0",
|
||||
"eDam": "0-0",
|
||||
"atkSpd": "VERY_FAST",
|
||||
"lvl": 65,
|
||||
"agiReq": 30,
|
||||
"xpb": 15,
|
||||
"lb": 15,
|
||||
"agi": 7,
|
||||
"spd": 20,
|
||||
"sdRaw": 85,
|
||||
"aDamPct": 15,
|
||||
"aDefPct": 15,
|
||||
"id": 1846
|
||||
},
|
||||
{
|
||||
"name": "Air Relic Relik",
|
||||
"tier": "Rare",
|
||||
|
@ -40858,32 +40832,6 @@
|
|||
"tDamPct": 5,
|
||||
"id": 1877
|
||||
},
|
||||
{
|
||||
"name": "Earth Relic Dagger",
|
||||
"displayName": "Earth Relic Daggers",
|
||||
"tier": "Rare",
|
||||
"type": "dagger",
|
||||
"category": "weapon",
|
||||
"slots": 3,
|
||||
"drop": "never",
|
||||
"nDam": "85-110",
|
||||
"fDam": "0-0",
|
||||
"wDam": "0-0",
|
||||
"aDam": "0-0",
|
||||
"tDam": "0-0",
|
||||
"eDam": "85-110",
|
||||
"atkSpd": "NORMAL",
|
||||
"lvl": 65,
|
||||
"strReq": 30,
|
||||
"mdPct": 15,
|
||||
"xpb": 15,
|
||||
"lb": 15,
|
||||
"str": 7,
|
||||
"expd": 15,
|
||||
"eDamPct": 20,
|
||||
"eDefPct": 10,
|
||||
"id": 1880
|
||||
},
|
||||
{
|
||||
"name": "Dull Ancient Helmet",
|
||||
"tier": "Normal",
|
||||
|
@ -40978,32 +40926,6 @@
|
|||
"fDefPct": 20,
|
||||
"id": 1889
|
||||
},
|
||||
{
|
||||
"name": "Fire Relic Dagger",
|
||||
"displayName": "Fire Relic Daggers",
|
||||
"tier": "Rare",
|
||||
"type": "dagger",
|
||||
"category": "weapon",
|
||||
"slots": 3,
|
||||
"drop": "never",
|
||||
"nDam": "55-70",
|
||||
"fDam": "55-70",
|
||||
"wDam": "0-0",
|
||||
"aDam": "0-0",
|
||||
"tDam": "0-0",
|
||||
"eDam": "0-0",
|
||||
"atkSpd": "FAST",
|
||||
"lvl": 65,
|
||||
"defReq": 30,
|
||||
"xpb": 15,
|
||||
"lb": 15,
|
||||
"def": 7,
|
||||
"hpBonus": 920,
|
||||
"hprRaw": 80,
|
||||
"fDamPct": 10,
|
||||
"fDefPct": 20,
|
||||
"id": 1887
|
||||
},
|
||||
{
|
||||
"name": "Factory Helmet",
|
||||
"tier": "Unique",
|
||||
|
@ -42560,32 +42482,6 @@
|
|||
"tDamPct": 6,
|
||||
"id": 1971
|
||||
},
|
||||
{
|
||||
"name": "Thunder Relic Dagger",
|
||||
"displayName": "Thunder Relic Daggers",
|
||||
"tier": "Rare",
|
||||
"type": "dagger",
|
||||
"category": "weapon",
|
||||
"slots": 3,
|
||||
"drop": "never",
|
||||
"nDam": "22-99",
|
||||
"fDam": "0-0",
|
||||
"wDam": "0-0",
|
||||
"aDam": "0-0",
|
||||
"tDam": "22-99",
|
||||
"eDam": "0-0",
|
||||
"atkSpd": "VERY_FAST",
|
||||
"lvl": 65,
|
||||
"dexReq": 30,
|
||||
"ms": 5,
|
||||
"xpb": 15,
|
||||
"lb": 15,
|
||||
"dex": 7,
|
||||
"mdRaw": 70,
|
||||
"tDamPct": 20,
|
||||
"tDefPct": 10,
|
||||
"id": 1972
|
||||
},
|
||||
{
|
||||
"name": "Thunder Relic Relik",
|
||||
"tier": "Rare",
|
||||
|
@ -42740,32 +42636,6 @@
|
|||
"lvl": 75,
|
||||
"id": 1978
|
||||
},
|
||||
{
|
||||
"name": "Water Relic Dagger",
|
||||
"displayName": "Water Relic Daggers",
|
||||
"tier": "Rare",
|
||||
"type": "dagger",
|
||||
"category": "weapon",
|
||||
"slots": 3,
|
||||
"drop": "never",
|
||||
"nDam": "55-65",
|
||||
"fDam": "0-0",
|
||||
"wDam": "55-65",
|
||||
"aDam": "0-0",
|
||||
"tDam": "0-0",
|
||||
"eDam": "0-0",
|
||||
"atkSpd": "FAST",
|
||||
"lvl": 65,
|
||||
"intReq": 30,
|
||||
"mr": 5,
|
||||
"sdPct": 20,
|
||||
"xpb": 15,
|
||||
"lb": 15,
|
||||
"int": 7,
|
||||
"wDamPct": 10,
|
||||
"wDefPct": 20,
|
||||
"id": 1977
|
||||
},
|
||||
{
|
||||
"name": "Water Relic Relik",
|
||||
"tier": "Rare",
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -31,7 +31,50 @@
|
|||
<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 mt-5">
|
||||
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
|
||||
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
|
||||
<div class="navbar-brand mx-auto scaled-font" style="height: 100%;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</div>
|
||||
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
|
||||
</div>
|
||||
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
|
||||
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</a>
|
||||
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</a>
|
||||
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</a>
|
||||
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</a>
|
||||
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</a>
|
||||
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</a>
|
||||
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
|
||||
<span>Swap Icon Style</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container mt-lg-2" style="margin-top: 6vh;">
|
||||
<div class="row row-cols-1 row-cols-lg-3 gy-5">
|
||||
<div class="col col-lg-5">
|
||||
<!--crafter ui-->
|
||||
|
@ -39,7 +82,7 @@
|
|||
<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">
|
||||
<div id = "recipe-img" class = "img-fluid rounded Crafted-shadow" style = "background-image: url('../media/items/common.png'); image-rendering: pixelated; background-position: 25% 0; aspect-ratio: 1/1;"></div>
|
||||
<div id = "recipe-img" class = "img-fluid rounded Crafted-shadow" style = "background-image: url('../media/items/common.png'); background-size: 500% 100%; image-rendering: pixelated; background-position: 25% 0; aspect-ratio: 1/1;"></div>
|
||||
</div>
|
||||
<div class = "col ps-3">
|
||||
<div class = "row row-cols-2 align-items-center">
|
||||
|
@ -279,8 +322,6 @@
|
|||
</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>
|
||||
<script type="text/javascript" src="../js/build_utils.js"></script>
|
||||
<script type="text/javascript" src="../js/icons.js"></script>
|
||||
|
@ -293,6 +334,5 @@
|
|||
<script type="text/javascript" src="../js/load.js"></script>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -61,6 +61,7 @@ ul.search-box {
|
|||
position: absolute;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
li.search-item {
|
||||
|
@ -197,6 +198,60 @@ input.equipment-input:not(.is-invalid) {
|
|||
max-width: 95%;
|
||||
}
|
||||
|
||||
@media screen and (orientation: landscape) and (max-width: 1199px) {
|
||||
:root {
|
||||
--scaled-fontsize: 1rem;
|
||||
}
|
||||
.scaled-font {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.box-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.big-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.skp-tooltip {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.spellcost-tooltip b {
|
||||
font-size: .625rem !important;
|
||||
}
|
||||
|
||||
.scaled-item-icon {
|
||||
width: 3.2rem;
|
||||
}
|
||||
|
||||
.scaled-item-icon img {
|
||||
width: 2.8rem;
|
||||
}
|
||||
|
||||
.scaled-bckgrd {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.scaled-bckgrd img {
|
||||
width: 2.8rem;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.spell-display b {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) and (max-width: 1400px) {
|
||||
:root {
|
||||
--scaled-fontsize: 1rem;
|
||||
|
|
|
@ -33,7 +33,50 @@
|
|||
<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 row py-5 vh-100 mx-0 mx-lg-auto scaled-font">
|
||||
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
|
||||
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
|
||||
<div class="navbar-brand mx-auto scaled-font" style="height: 100%;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</div>
|
||||
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
|
||||
</div>
|
||||
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
|
||||
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</a>
|
||||
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</a>
|
||||
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</a>
|
||||
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</a>
|
||||
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</a>
|
||||
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</a>
|
||||
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
|
||||
<span>Swap Icon Style</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "container row py-5 vh-100 mx-0 mx-lg-auto scaled-font mt-lg-2" style="margin-top: 6vh;">
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col px-1">
|
||||
<div class = "row border border-dark border-3 mb-1 p-1 rounded dark-7">
|
||||
|
|
|
@ -31,7 +31,50 @@
|
|||
<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 scaled-font">
|
||||
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
|
||||
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
|
||||
<div class="navbar-brand mx-auto scaled-font" style="height: 100%;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</div>
|
||||
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
|
||||
</div>
|
||||
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
|
||||
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</a>
|
||||
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</a>
|
||||
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</a>
|
||||
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</a>
|
||||
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</a>
|
||||
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</a>
|
||||
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
|
||||
<span>Swap Icon Style</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "container py-5 vh-100 mx-0 mx-lg-auto scaled-font mt-lg-2" style="margin-top: 6vh;">
|
||||
<div class = "col">
|
||||
<div class = "row">
|
||||
<div class = "col text-end">
|
||||
|
@ -115,12 +158,12 @@
|
|||
</div>
|
||||
<div class = "row">
|
||||
<div class = "col-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "search-button" onclick = "doItemSearch()">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "search-button" onclick = "do_item_search()">
|
||||
Search!
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "reset-button" onclick = "resetItemSearch()">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "reset-button" onclick = "reset_item_search()">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
@ -141,10 +184,9 @@
|
|||
<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/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/sq2items.js"></script>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<script type="text/javascript" src="../js/powders.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<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/query_2.js"></script>
|
||||
<script type="text/javascript" src="/js/query.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_2.js"></script>
|
||||
|
|
293
js/atree.js
293
js/atree.js
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* This file defines computation graph nodes and display code relevant to the ability tree.
|
||||
* TODO: possibly split it up into compute and render... but its a bit complicated :/
|
||||
*/
|
||||
|
||||
/**
|
||||
ATreeNode spec:
|
||||
|
||||
|
@ -74,6 +79,7 @@ stat_bonus: {
|
|||
stat_scaling: {
|
||||
"type": "stat_scaling",
|
||||
"slider": bool,
|
||||
positive: bool // True to keep stat above 0. False to ignore floor. Default: True for normal, False for scaling
|
||||
"slider_name": Optional[str],
|
||||
"slider_step": Optional[float],
|
||||
round: Optional[bool] // Control floor behavior. True for stats and false for slider by default
|
||||
|
@ -463,24 +469,18 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
this.list_elem.innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
||||
// TODO: move to display?
|
||||
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");
|
||||
this.list_elem.appendChild(errorbox);
|
||||
const errorbox = make_elem('div', ['rounded-bottom', 'dark-4', 'border', 'p-0', 'mx-2', 'my-4', 'dark-shadow']);
|
||||
this.list_elem.append(errorbox);
|
||||
|
||||
let error_title = document.createElement('b');
|
||||
error_title.classList.add("warning", "scaled-font");
|
||||
error_title.innerHTML = "ATree Error!";
|
||||
errorbox.appendChild(error_title);
|
||||
const error_title = make_elem('b', ['warning', 'scaled-font'], { innerHTML: "ATree Error!" });
|
||||
errorbox.append(error_title);
|
||||
|
||||
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);
|
||||
errorbox.append(make_elem("p", ["warning", "small-text"], {textContent: errors[i]}));
|
||||
}
|
||||
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);
|
||||
errorbox.append(make_elem("p", ["warning", "small-text"], {textContent: error}));
|
||||
}
|
||||
}
|
||||
const ret_map = new Map();
|
||||
|
@ -494,23 +494,15 @@ const atree_render_active = new (class extends ComputeNode {
|
|||
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");
|
||||
|
||||
let active_tooltip_title = document.createElement('b');
|
||||
active_tooltip_title.classList.add("scaled-font");
|
||||
active_tooltip_title.innerHTML = abil.display_name;
|
||||
active_tooltip.appendChild(active_tooltip_title);
|
||||
const active_tooltip = make_elem('div', ['rounded-bottom', 'dark-4', 'border', 'p-0', 'mx-2', 'my-4', 'dark-shadow']);
|
||||
active_tooltip.append(make_elem('b', ['scaled-font'], { innerHTML: abil.display_name }));
|
||||
|
||||
for (const desc of abil.desc) {
|
||||
let active_tooltip_desc = document.createElement('p');
|
||||
active_tooltip_desc.classList.add("scaled-font-sm", "my-0", "mx-1", "text-wrap");
|
||||
active_tooltip_desc.textContent = desc;
|
||||
active_tooltip.appendChild(active_tooltip_desc);
|
||||
active_tooltip.append(make_elem('p', ['scaled-font-sm', 'my-0', 'mx-1', 'text-wrap'], { textContent: desc }));
|
||||
}
|
||||
ret_map.set(abil.id, active_tooltip);
|
||||
|
||||
this.list_elem.appendChild(active_tooltip);
|
||||
this.list_elem.append(active_tooltip);
|
||||
}
|
||||
return ret_map;
|
||||
}
|
||||
|
@ -567,27 +559,29 @@ const atree_collect_spells = new (class extends ComputeNode {
|
|||
|
||||
let found_part = false;
|
||||
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
|
||||
if (part.name === target_part) {
|
||||
if ('multipliers' in effect) {
|
||||
for (const [idx, v] of effect.multipliers.entries()) { // python: enumerate()
|
||||
part.multipliers[idx] += v;
|
||||
}
|
||||
}
|
||||
else if ('power' in effect) {
|
||||
part.power += effect.power;
|
||||
}
|
||||
else if ('hits' in effect) {
|
||||
for (const [idx, v] of Object.entries(effect.hits)) { // looks kinda similar to multipliers case... hmm... can we unify all of these three? (make healpower a list)
|
||||
if (idx in part.hits) { part.hits[idx] += v; }
|
||||
else { part.hits[idx] = v; }
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "uhh invalid spell add effect";
|
||||
}
|
||||
found_part = true;
|
||||
break;
|
||||
if (part.name !== target_part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('multipliers' in effect) {
|
||||
for (const [idx, v] of effect.multipliers.entries()) { // python: enumerate()
|
||||
part.multipliers[idx] += v;
|
||||
}
|
||||
}
|
||||
else if ('power' in effect) {
|
||||
part.power += effect.power;
|
||||
}
|
||||
else if ('hits' in effect) {
|
||||
for (const [idx, v] of Object.entries(effect.hits)) { // looks kinda similar to multipliers case... hmm... can we unify all of these three? (make healpower a list)
|
||||
if (idx in part.hits) { part.hits[idx] += v; }
|
||||
else { part.hits[idx] = v; }
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw "uhh invalid spell add effect";
|
||||
}
|
||||
found_part = true;
|
||||
break;
|
||||
}
|
||||
if (!found_part && behavior === 'merge') { // add part. if behavior is merge
|
||||
let spell_part = deepcopy(effect);
|
||||
|
@ -754,10 +748,13 @@ const atree_scaling = new (class extends ComputeNode {
|
|||
continue;
|
||||
case 'stat_scaling':
|
||||
let total = 0;
|
||||
const {round = true, slider = false, scaling = [0]} = effect;
|
||||
const {slider = false, scaling = [0]} = effect;
|
||||
let { positive = true, round = true } = effect;
|
||||
if (slider) {
|
||||
const slider_val = slider_map.get(effect.slider_name).slider.value;
|
||||
total = parseInt(slider_val) * scaling[0];
|
||||
round = false;
|
||||
positive = false;
|
||||
}
|
||||
else {
|
||||
// TODO: type: prop?
|
||||
|
@ -768,7 +765,7 @@ const atree_scaling = new (class extends ComputeNode {
|
|||
|
||||
if ('output' in effect) { // sometimes nodes will modify slider without having effect.
|
||||
if (round) { total = Math.floor(round_near(total)); }
|
||||
if (total < 0) { total = 0; } // Normal stat scaling will not go negative.
|
||||
if (positive && total < 0) { total = 0; } // Normal stat scaling will not go negative.
|
||||
if ('max' in effect && total > effect.max) { total = effect.max; }
|
||||
if (Array.isArray(effect.output)) {
|
||||
for (const output of effect.output) {
|
||||
|
@ -865,29 +862,28 @@ class AbilityTreeEnsureNodesNode extends ComputeNode {
|
|||
// TODO shortcut update path for sliders
|
||||
|
||||
for (const [spell_id, spell] of new Map([...spell_map].sort((a, b) => a[0] - b[0])).entries()) {
|
||||
let spell_node = new SpellSelectNode(spell);
|
||||
spell_node.link_to(build_node, 'build');
|
||||
let spell_node = new SpellSelectNode(spell)
|
||||
.link_to(build_node, 'build');
|
||||
|
||||
let calc_node = new SpellDamageCalcNode(spell.base_spell);
|
||||
calc_node.link_to(build_node, 'build').link_to(stat_agg_node, 'stats')
|
||||
let calc_node = new SpellDamageCalcNode(spell.base_spell)
|
||||
.link_to(build_node, 'build')
|
||||
.link_to(stat_agg_node, 'stats')
|
||||
.link_to(spell_node, 'spell-info');
|
||||
this.spelldmg_nodes.push(calc_node);
|
||||
|
||||
let display_elem = document.createElement('div');
|
||||
display_elem.classList.add("col", "pe-0");
|
||||
let display_elem = make_elem('div', ["col", "pe-0"]);
|
||||
// TODO: just pass these elements into the display node instead of juggling the raw IDs...
|
||||
let spell_summary = make_elem('div', ["col", "spell-display", "fake-button", "dark-5", "rounded", "dark-shadow", "pt-2", "border", "border-dark"],
|
||||
{ id: "spell"+spell.base_spell+"-infoAvg" });
|
||||
let spell_detail = make_elem('div', ["col", "spell-display", "dark-5", "rounded", "dark-shadow", "py-2"],
|
||||
{ id: "spell"+spell.base_spell+"-info" });
|
||||
spell_detail.style.display = "none";
|
||||
{ id: "spell"+spell.base_spell+"-info", style: { display: 'none' } });
|
||||
|
||||
display_elem.appendChild(spell_summary); display_elem.appendChild(spell_detail);
|
||||
display_elem.append(spell_summary, spell_detail);
|
||||
|
||||
let display_node = new SpellDisplayNode(spell.base_spell);
|
||||
display_node.link_to(stat_agg_node, 'stats');
|
||||
display_node.link_to(spell_node, 'spell-info');
|
||||
display_node.link_to(calc_node, 'spell-damage');
|
||||
let display_node = new SpellDisplayNode(spell.base_spell)
|
||||
.link_to(stat_agg_node, 'stats')
|
||||
.link_to(spell_node, 'spell-info')
|
||||
.link_to(calc_node, 'spell-damage');
|
||||
|
||||
this.spell_display_elem.appendChild(display_elem);
|
||||
}
|
||||
|
@ -910,25 +906,25 @@ 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 = make_elem("div", ["row", "item-title", "mx-auto", "justify-content-center"]);
|
||||
let active_word = make_elem("div", ["col-auto"], {textContent: "Active Abilities:"});
|
||||
const active_row = make_elem("div", ["row", "item-title", "mx-auto", "justify-content-center"]);
|
||||
const active_word = make_elem("div", ["col-auto"], {textContent: "Active Abilities:"});
|
||||
|
||||
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"});
|
||||
const active_AP_container = make_elem("div", ["col-auto"]);
|
||||
const active_AP_subcontainer = make_elem("div", ["row"]);
|
||||
const active_AP_cost = make_elem("div", ["col-auto", "mx-0", "px-0"], {id: "active_AP_cost", textContent: "0"});
|
||||
|
||||
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"});
|
||||
const active_AP_slash = make_elem("div", ["col-auto", "mx-0", "px-0"], {textContent: "/"});
|
||||
const active_AP_cap = make_elem("div", ["col-auto", "mx-0", "px-0"], {id: "active_AP_cap"});
|
||||
const active_AP_end = make_elem("div", ["col-auto", "mx-0", "px-0"], {textContent: " AP"});
|
||||
|
||||
active_AP_container.appendChild(active_AP_subcontainer);
|
||||
active_AP_container.append(active_AP_subcontainer);
|
||||
active_AP_subcontainer.append(active_AP_cost, active_AP_slash, active_AP_cap, active_AP_end);
|
||||
|
||||
active_row.append(active_word, active_AP_container);
|
||||
list_elem.appendChild(active_row);
|
||||
list_elem.append(active_row);
|
||||
|
||||
let atree_map = new Map();
|
||||
let atree_connectors_map = new Map()
|
||||
const atree_map = new Map();
|
||||
const atree_connectors_map = new Map()
|
||||
let max_row = 0;
|
||||
for (const i of tree) {
|
||||
atree_map.set(i.ability.id, {ability: i.ability, connectors: new Map(), active: false});
|
||||
|
@ -951,17 +947,11 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
|
||||
// Setup grid.
|
||||
for (let j = 0; j <= max_row; j++) {
|
||||
let row = document.createElement('div');
|
||||
row.classList.add("row");
|
||||
row.id = "atree-row-" + j;
|
||||
|
||||
const row = make_elem('div', ['row'], { id: "atree-row-"+j });
|
||||
for (let k = 0; k < 9; k++) {
|
||||
col = document.createElement('div');
|
||||
col.classList.add('col', 'px-0');
|
||||
col.style = "position: relative; aspect-ratio: 1/1;"
|
||||
row.appendChild(col);
|
||||
row.append(make_elem('div', ['col', 'px-0'], { style: "position: relative; aspect-ratio: 1/1;" }));
|
||||
}
|
||||
UI_elem.appendChild(row);
|
||||
UI_elem.append(row);
|
||||
}
|
||||
|
||||
for (const _node of tree) {
|
||||
|
@ -1017,52 +1007,100 @@ function render_AT(UI_elem, list_elem, tree) {
|
|||
|
||||
// create hitbox
|
||||
// this is necessary since images exceed the size of their square, but should only be interactible within that square
|
||||
let hitbox = document.createElement("div");
|
||||
hitbox.style = "position: absolute; cursor: pointer; left: 0; top: 0; width: 100%; height: 100%; z-index: 2;"
|
||||
let hitbox = make_elem('div', [], {
|
||||
style: 'position: absolute; cursor: pointer; left: 0; top: 0; width: 100%; height: 100%; z-index: 2;'
|
||||
});
|
||||
node_elem.appendChild(hitbox);
|
||||
|
||||
node_wrap.elem = node_elem;
|
||||
node_wrap.all_connectors_ref = atree_connectors_map;
|
||||
|
||||
hitbox.addEventListener('click', function(e) {
|
||||
if (e.target !== this && e.target!== this.children[0]) {return;}
|
||||
atree_set_state(node_wrap, !node_wrap.active);
|
||||
atree_state_node.mark_dirty().update();
|
||||
});
|
||||
// add listeners
|
||||
// listeners differ between mobile and desktop since hovering is a bit fucky on mobile
|
||||
if (!isMobile) { // desktop
|
||||
hitbox.addEventListener('click', function(e) {
|
||||
atree_set_state(node_wrap, !node_wrap.active);
|
||||
atree_state_node.mark_dirty().update();
|
||||
});
|
||||
|
||||
// add tooltip
|
||||
hitbox.addEventListener('mouseover', function(e) {
|
||||
if (e.target !== this) {
|
||||
return;
|
||||
}
|
||||
if (node_wrap.tooltip_elem) {
|
||||
node_wrap.tooltip_elem.remove();
|
||||
delete node_wrap.tooltip_elem;
|
||||
}
|
||||
node_wrap.tooltip_elem = generateTooltip(UI_elem, node_elem, ability, atree_map);
|
||||
});
|
||||
// add tooltip
|
||||
hitbox.addEventListener('mouseover', function(e) {
|
||||
if (node_wrap.tooltip_elem) {
|
||||
node_wrap.tooltip_elem.remove();
|
||||
delete node_wrap.tooltip_elem;
|
||||
}
|
||||
|
||||
hitbox.addEventListener('mouseout', function(e) {
|
||||
if (e.target !== this) {
|
||||
return;
|
||||
}
|
||||
if (node_wrap.tooltip_elem) {
|
||||
node_wrap.tooltip_elem.remove();
|
||||
delete node_wrap.tooltip_elem;
|
||||
}
|
||||
});
|
||||
node_wrap.tooltip_elem = make_elem("div", ["rounded-bottom", "dark-4", "border", "mx-2", "my-4", "dark-shadow", "text-start"], {
|
||||
style: {
|
||||
position: "absolute",
|
||||
zIndex: "100",
|
||||
top: (node_elem.getBoundingClientRect().top + window.pageYOffset + 50) + "px",
|
||||
left: UI_elem.getBoundingClientRect().left + "px",
|
||||
width: (UI_elem.getBoundingClientRect().width * 0.95) + "px"
|
||||
}
|
||||
});
|
||||
generateTooltip(node_wrap.tooltip_elem, node_elem, ability, atree_map);
|
||||
UI_elem.appendChild(node_wrap.tooltip_elem);
|
||||
});
|
||||
|
||||
hitbox.addEventListener('mouseout', function(e) {
|
||||
if (node_wrap.tooltip_elem) {
|
||||
node_wrap.tooltip_elem.remove();
|
||||
delete node_wrap.tooltip_elem;
|
||||
}
|
||||
});
|
||||
} else { // mobile
|
||||
// tap to toggle
|
||||
// long press to make a popup with the tooltip and a button to turn off/on
|
||||
let touchTimer = null;
|
||||
let didLongPress = false;
|
||||
|
||||
hitbox.addEventListener("touchstart", function(e) {
|
||||
clearTimeout(touchTimer);
|
||||
touchTimer = setTimeout(function() {
|
||||
let popup = make_elem("div", [], {style: "position: fixed; z-index: 10000; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, 0.6); padding-top: 10vh; padding-left: 2.5vw; user-select: none;"});
|
||||
popup.addEventListener("click", function(e) {
|
||||
// close popup if the background is clicked
|
||||
if (e.target !== this) {return;} // e.target is the lowest element that was the target of the event
|
||||
popup.remove();
|
||||
});
|
||||
|
||||
let tooltip = make_elem("div", ["rounded-bottom", "dark-4", "border", "dark-shadow", "text-start"], {"style": "width: 95vw; max-height: 80vh; overflow-y: scroll;"});
|
||||
generateTooltip(tooltip, node_elem, ability, atree_map);
|
||||
popup.appendChild(tooltip);
|
||||
|
||||
let toggleButton = make_elem("button", ["scaled-font", "disable-select"], {innerHTML: (node_wrap.active ? "Unselect Ability" : "Select Ability"), style: "width: 95vw; height: 8vh; margin-top: 2vh; text-align: center;"});
|
||||
toggleButton.addEventListener("click", function(e) {
|
||||
atree_set_state(node_wrap, !node_wrap.active);
|
||||
atree_state_node.mark_dirty().update();
|
||||
popup.remove();
|
||||
});
|
||||
popup.appendChild(toggleButton);
|
||||
|
||||
document.body.appendChild(popup);
|
||||
|
||||
didLongPress = true;
|
||||
touchTimer = null;
|
||||
}, 500);
|
||||
});
|
||||
hitbox.addEventListener("touchend", function(e) {
|
||||
if (!didLongPress) {
|
||||
clearTimeout(touchTimer);
|
||||
touchTimer = null;
|
||||
atree_set_state(node_wrap, !node_wrap.active);
|
||||
atree_state_node.mark_dirty().update();
|
||||
} else {
|
||||
didLongPress = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
atree_render_connection(atree_connectors_map);
|
||||
|
||||
return atree_map;
|
||||
};
|
||||
|
||||
function generateTooltip(UI_elem, node_elem, ability, atree_map) {
|
||||
let container = make_elem("div", ["rounded-bottom", "dark-4", "border", "mx-2", "my-4", "dark-shadow", "text-start"], {"style": "position: absolute; z-index: 100;"});
|
||||
container.style.top = (node_elem.getBoundingClientRect().top + window.pageYOffset + 50) + "px";
|
||||
container.style.left = UI_elem.getBoundingClientRect().left + "px";
|
||||
container.style.width = UI_elem.getBoundingClientRect().width * 0.95 + "px";
|
||||
|
||||
function generateTooltip(container, node_elem, ability, atree_map) {
|
||||
// title
|
||||
let title = make_elem("b", ["scaled-font", "mx-1"], {});
|
||||
title.innerHTML = ability.display_name;
|
||||
|
@ -1092,16 +1130,16 @@ function generateTooltip(UI_elem, node_elem, ability, atree_map) {
|
|||
container.innerHTML += "<br/><br/>";
|
||||
|
||||
// description
|
||||
let description = make_elem("p", ["scaled-font-sm", "my-0", "mx-1", "text-wrap", "mc-gray"], {});
|
||||
let description = make_elem("p", ["scaled-font", "my-0", "mx-1", "text-wrap", "mc-gray"], {});
|
||||
let numberRegex = /[+-]?\d+(\.\d+)?[%+s]?/g; // +/- (optional), 1 or more digits, period followed by 1 or more digits (optional), %/+/s (optional)
|
||||
description.innerHTML = ability.desc.replaceAll(numberRegex, (m) => { return "<span class = 'mc-white'>" + m + "</span>" });
|
||||
container.appendChild(description);
|
||||
|
||||
container.appendChild(document.createElement("br"));
|
||||
container.appendChild(make_elem('br'));
|
||||
|
||||
// archetype
|
||||
if ("archetype" in ability && ability.archetype !== "") {
|
||||
let archetype = make_elem("p", ["scaled-font-sm", "my-0", "mx-1"], {});
|
||||
let archetype = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
|
||||
archetype.innerHTML = ability.archetype + " Archetype";
|
||||
switch(ability.archetype) {
|
||||
case "Riftwalker":
|
||||
|
@ -1134,7 +1172,7 @@ function generateTooltip(UI_elem, node_elem, ability, atree_map) {
|
|||
break;
|
||||
}
|
||||
container.appendChild(archetype);
|
||||
container.appendChild(document.createElement("br"));
|
||||
container.appendChild(make_elem('br'));
|
||||
}
|
||||
|
||||
// calculate if requirements are satisfied
|
||||
|
@ -1163,48 +1201,45 @@ function generateTooltip(UI_elem, node_elem, ability, atree_map) {
|
|||
let reqNo = "<span class = 'mc-red'>✖</span>" // red x
|
||||
|
||||
// cost
|
||||
let cost = make_elem("p", ["scaled-font-sm", "my-0", "mx-1"], {});
|
||||
let cost = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
|
||||
if (apUsed + ability.cost > maxAP) {
|
||||
cost.innerHTML = reqNo;
|
||||
} else {
|
||||
cost.innerHTML = reqYes;
|
||||
}
|
||||
cost.innerHTML += " <span class = 'mc-gray'>Ability Points:</span> " + (maxAP - apUsed) + "<span class = 'mc-gray'>/" + ability.cost;
|
||||
cost.innerHTML += "<span class = 'mc-gray'>Ability Points:</span>" + (maxAP - apUsed) + "<span class = 'mc-gray'>/" + ability.cost;
|
||||
container.appendChild(cost);
|
||||
|
||||
// archetype req
|
||||
if (ability.archetype_req > 0 && ability.archetype != null) {
|
||||
let archReq = make_elem("p", ["scaled-font-sm", "my-0", "mx-1"], {});
|
||||
let archReq = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
|
||||
if (archChosen >= ability.archetype_req) {
|
||||
archReq.innerHTML = reqYes;
|
||||
} else {
|
||||
archReq.innerHTML = reqNo;
|
||||
}
|
||||
archReq.innerHTML += " <span class = 'mc-gray'>Min " + ability.archetype + " Archetype:</span> " + archChosen + "<span class = 'mc-gray'>/" + ability.archetype_req;
|
||||
archReq.innerHTML += "<span class = 'mc-gray'>Min" + ability.archetype + " Archetype:</span> " + archChosen + "<span class = 'mc-gray'>/" + ability.archetype_req;
|
||||
container.appendChild(archReq);
|
||||
}
|
||||
|
||||
// dependencies
|
||||
for (let i = 0; i < ability.dependencies.length; i++) {
|
||||
let dependency = make_elem("p", ["scaled-font-sm", "my-0", "mx-1"], {});
|
||||
let dependency = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
|
||||
if (satisfiedDependencies.includes(ability.dependencies[i])) {
|
||||
dependency.innerHTML = reqYes;
|
||||
} else {
|
||||
dependency.innerHTML = reqNo;
|
||||
}
|
||||
dependency.innerHTML += " <span class = 'mc-gray'>Required Ability:</span> " + atree_map.get(ability.dependencies[i]).ability.display_name;
|
||||
dependency.innerHTML += "<span class = 'mc-gray'>Required Ability:</span>" + atree_map.get(ability.dependencies[i]).ability.display_name;
|
||||
container.appendChild(dependency);
|
||||
}
|
||||
|
||||
// blockers
|
||||
for (let i = 0; i < blockedBy.length; i++) {
|
||||
let blocker = make_elem("p", ["scaled-font-sm", "my-0", "mx-1"], {});
|
||||
blocker.innerHTML = reqNo + " <span class = 'mc-gray'>Blocked By:</span> " + blockedBy[i];
|
||||
let blocker = make_elem("p", ["scaled-font", "my-0", "mx-1"], {});
|
||||
blocker.innerHTML = reqNo + "<span class = 'mc-gray'>Blocked By:</span>" + blockedBy[i];
|
||||
container.appendChild(blocker);
|
||||
}
|
||||
|
||||
UI_elem.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
// resolve connector conflict, when they occupy the same cell.
|
||||
|
@ -1303,7 +1338,7 @@ function atree_render_connection(atree_connectors_map) {
|
|||
drawAtlasImage(connector_elem, atreeConnectorAtlasImg, atreeConnectorAtlasPositions[connector_info.type]["0000"], atreeConnectorTileSize);
|
||||
let target_elem = document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]];
|
||||
if (target_elem.children.length != 0) {
|
||||
// janky special case...
|
||||
// janky special case... sometimes the ability tree tries to draw a link on top of a node...
|
||||
connector_elem.style.display = 'none';
|
||||
}
|
||||
target_elem.appendChild(connector_elem);
|
||||
|
|
|
@ -3739,7 +3739,7 @@ const atrees = {
|
|||
"type": "stat",
|
||||
"name": "damMult.Enraged"
|
||||
},
|
||||
"scaling": [3]
|
||||
"scaling": [2.2]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -5242,7 +5242,9 @@ const atrees = {
|
|||
"Teleport"
|
||||
],
|
||||
"dependencies": [],
|
||||
"blockers": [],
|
||||
"blockers": [
|
||||
"Wisdom"
|
||||
],
|
||||
"cost": 1,
|
||||
"display": {
|
||||
"row": 6,
|
||||
|
@ -5387,20 +5389,6 @@ const atrees = {
|
|||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "add_spell_prop",
|
||||
"base_spell": 3,
|
||||
"target_part": "Lightning Damage",
|
||||
"behavior": "modify",
|
||||
"multipliers": [
|
||||
30,
|
||||
90,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -5608,7 +5596,11 @@ const atrees = {
|
|||
"Burning Sigil"
|
||||
],
|
||||
"dependencies": [],
|
||||
"blockers": [],
|
||||
"blockers": [
|
||||
"Sunshower",
|
||||
"Larger Heal",
|
||||
"Orphion's Pulse"
|
||||
],
|
||||
"cost": 2,
|
||||
"display": {
|
||||
"row": 15,
|
||||
|
@ -6395,7 +6387,7 @@ const atrees = {
|
|||
},
|
||||
{
|
||||
"display_name": "More Winded",
|
||||
"desc": "Incrase your maximum Winded by +5.",
|
||||
"desc": "Increase your maximum Winded by +5.",
|
||||
"base_abil": "Windsweeper",
|
||||
"archetype": "Riftwalker",
|
||||
"archetype_req": 0,
|
||||
|
@ -6705,7 +6697,7 @@ const atrees = {
|
|||
},
|
||||
{
|
||||
"display_name": "More Winded II",
|
||||
"desc": "Incrase your maximum Winded by +5.",
|
||||
"desc": "Increase your maximum Winded by +5.",
|
||||
"base_abil": "Windsweeper",
|
||||
"archetype": "Riftwalker",
|
||||
"archetype_req": 0,
|
||||
|
@ -7675,6 +7667,17 @@ const atrees = {
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "raw_stat",
|
||||
"toggle": "Activate Backstab",
|
||||
"bonuses": [
|
||||
{
|
||||
"type": "stat",
|
||||
"name": "damMult.Backstab:3.Backstab Damage",
|
||||
"value": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
File diff suppressed because one or more lines are too long
28
js/build.js
28
js/build.js
|
@ -1,4 +1,9 @@
|
|||
|
||||
/**
|
||||
* This file defines a class representing the player Build.
|
||||
*
|
||||
* Keeps track of equipment list, equip order, skillpoint assignment (initial),
|
||||
* Aggregates item stats into a statMap to be used in damage calculation.
|
||||
*/
|
||||
|
||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.0], ["sword", 1.10]]);
|
||||
|
||||
|
@ -10,15 +15,9 @@ class Build{
|
|||
/**
|
||||
* @description Construct a build.
|
||||
* @param {Number} level : Level of the player.
|
||||
* @param {String[]} equipment : List of equipment names that make up the build.
|
||||
* In order: boots, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon.
|
||||
* @param {Number[]} powders : Powder application. List of lists of integers (powder IDs).
|
||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
||||
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
||||
*
|
||||
* @param {Object[]} tomes: List of tomes.
|
||||
* In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome.
|
||||
* 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building)
|
||||
* @param {String[]} items: List of equipment names that make up the build.
|
||||
* In order: Helmet, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Tomes [x7].
|
||||
* @param {Item} weapon: Weapon that this build is using.
|
||||
*/
|
||||
constructor(level, items, weapon){
|
||||
|
||||
|
@ -39,11 +38,15 @@ class Build{
|
|||
this.equipment = items;
|
||||
this.weapon = weapon;
|
||||
this.items = this.equipment.concat([this.weapon]);
|
||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||
|
||||
// calc skillpoints requires statmaps only
|
||||
let result = calculate_skillpoints(this.equipment.map((x) => x.statMap), this.weapon.statMap);
|
||||
this.equip_order = result[0];
|
||||
const _equip_order = result[0].slice();
|
||||
this.equip_order = [];
|
||||
for (const item of _equip_order) {
|
||||
if (item.get('category') === 'tome' || item.has('NONE')) { continue; }
|
||||
this.equip_order.push(item);
|
||||
}
|
||||
// How many skillpoints the player had to assign (5 number)
|
||||
this.base_skillpoints = result[1];
|
||||
// How many skillpoints the build ended up with (5 number)
|
||||
|
@ -61,7 +64,6 @@ class Build{
|
|||
return [this.equipment,this.weapon].flat();
|
||||
}
|
||||
|
||||
|
||||
/* Get all stats for this build. Stores in this.statMap.
|
||||
@pre The build itself should be valid. No checking of validity of pieces is done here.
|
||||
*/
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* File containing utility functions that are useful for the builder page.
|
||||
*/
|
||||
|
||||
/*Turns the input amount of skill points into a float precision percentage.
|
||||
* @param skp - the integer skillpoint count to be converted
|
||||
*/
|
||||
|
@ -66,7 +70,7 @@ const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set
|
|||
const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
//weaponTypes.push("sword");
|
||||
//console.log(types)
|
||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
||||
let item_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
||||
|
||||
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
||||
let skpReqs = skp_order.map(x => x + "Req");
|
||||
|
@ -231,8 +235,15 @@ function expandItem(item) {
|
|||
}
|
||||
|
||||
class Item {
|
||||
constructor(item_obj) {
|
||||
this.statMap = expandItem(item_obj);
|
||||
constructor(item_obj = null) {
|
||||
if (item_obj) { this.statMap = expandItem(item_obj); }
|
||||
else { this.statMap = new Map(); }
|
||||
}
|
||||
|
||||
copy() {
|
||||
const ret = new Item();
|
||||
ret.statMap = new Map(this.statMap);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* File containing utility functions relevant to the builder page, as well as the setup code (at the very bottom).
|
||||
*/
|
||||
|
||||
function populateBuildList() {
|
||||
const buildList = document.getElementById("build-choice");
|
||||
|
@ -60,6 +63,9 @@ function resetFields(){
|
|||
for (const i of equipment_inputs) {
|
||||
setValue(i, "");
|
||||
}
|
||||
for (const i of tomeInputs) {
|
||||
setValue(i, "");
|
||||
}
|
||||
setValue("str-skp", "0");
|
||||
setValue("dex-skp", "0");
|
||||
setValue("int-skp", "0");
|
||||
|
@ -124,26 +130,6 @@ function toggleButton(button_id) {
|
|||
}
|
||||
}
|
||||
|
||||
// Toggles display of a certain element, given the ID.
|
||||
function toggle_tab(tab) {
|
||||
if (document.querySelector("#"+tab).style.display == "none") {
|
||||
document.querySelector("#"+tab).style.display = "";
|
||||
} else {
|
||||
document.querySelector("#"+tab).style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle display of a certain tab, in a group of tabs, given the target tab ID, and a list of associated tabs.
|
||||
// Also sets visual display of an element with ID of target + "-btn" to selected.
|
||||
function show_tab(target, tabs) {
|
||||
//hide all tabs, then show the tab of the div clicked and highlight the correct button
|
||||
for (const i in tabs) {
|
||||
document.querySelector("#" + tabs[i]).style.display = "none";
|
||||
document.getElementById(tabs[i] + "-btn").classList.remove("selected-btn");
|
||||
}
|
||||
document.querySelector("#" + target).style.display = "";
|
||||
document.getElementById(target + "-btn").classList.add("selected-btn");
|
||||
}
|
||||
|
||||
// autocomplete initialize
|
||||
function init_autocomplete() {
|
||||
|
@ -231,8 +217,9 @@ function init_autocomplete() {
|
|||
for (const eq of tome_keys) {
|
||||
// build dropdown
|
||||
let tome_arr = [];
|
||||
for (const tome of tomeLists.get(eq.replace(/[0-9]/g, ''))) {
|
||||
let tome_obj = tomeMap.get(tome);
|
||||
let tome_aliases = new Map();
|
||||
for (const tome_name of tomeLists.get(eq.replace(/[0-9]/g, ''))) {
|
||||
let tome_obj = tomeMap.get(tome_name);
|
||||
if (tome_obj["restrict"] && tome_obj["restrict"] === "DEPRECATED") {
|
||||
continue;
|
||||
}
|
||||
|
@ -240,8 +227,10 @@ function init_autocomplete() {
|
|||
if (tome_obj["name"].includes('No ' + eq.charAt(0).toUpperCase())) {
|
||||
continue;
|
||||
}
|
||||
let tome_name = tome;
|
||||
let tome_alias = tome_obj['alias'];
|
||||
tome_arr.push(tome_name);
|
||||
tome_arr.push(tome_alias);
|
||||
tome_aliases.set(tome_alias, tome_name);
|
||||
}
|
||||
|
||||
// create dropdown
|
||||
|
@ -276,14 +265,18 @@ function init_autocomplete() {
|
|||
class: "scaled-font search-item",
|
||||
selected: "dark-5",
|
||||
element: (tome, data) => {
|
||||
tome.classList.add(tomeMap.get(data.value).tier);
|
||||
let val = data.value;
|
||||
if (tome_aliases.has(val)) { val = tome_aliases.get(val); }
|
||||
tome.classList.add(tomeMap.get(val).tier);
|
||||
},
|
||||
},
|
||||
events: {
|
||||
input: {
|
||||
selection: (event) => {
|
||||
if (event.detail.selection.value) {
|
||||
event.target.value = event.detail.selection.value;
|
||||
let val = event.detail.selection.value;
|
||||
if (tome_aliases.has(val)) { val = tome_aliases.get(val); }
|
||||
event.target.value = val;
|
||||
}
|
||||
event.target.dispatchEvent(new Event('change'));
|
||||
},
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/**
|
||||
* File containing compute graph structure of the builder page.
|
||||
*/
|
||||
|
||||
let armor_powder_node = new (class extends ComputeNode {
|
||||
constructor() { super('builder-armor-powder-input'); }
|
||||
|
||||
|
@ -203,8 +207,8 @@ class ItemPowderingNode extends ComputeNode {
|
|||
|
||||
compute_func(input_map) {
|
||||
const powdering = input_map.get('powdering');
|
||||
const item = {};
|
||||
item.statMap = new Map(input_map.get('item').statMap); // TODO: performance
|
||||
const input_item = input_map.get('item');
|
||||
const item = input_item.copy(); // TODO: performance
|
||||
|
||||
const max_slots = item.statMap.get('slots');
|
||||
item.statMap.set('powders', powdering.slice(0, max_slots));
|
||||
|
|
|
@ -397,4 +397,8 @@ class Craft{
|
|||
statMap.set("crafted", true);
|
||||
this.statMap = statMap;
|
||||
}
|
||||
|
||||
copy() {
|
||||
return new Craft(this.recipe, this.mat_tiers, this.ingreds, this.atkSpd, this.hash.slice(3));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,16 +31,6 @@ let player_craft;
|
|||
|
||||
|
||||
function init_crafter() {
|
||||
//no ing
|
||||
|
||||
console.log("all ingredients");
|
||||
console.log(ingMap);
|
||||
console.log("all recipes");
|
||||
console.log(recipeMap);
|
||||
/*console.log(ingList);
|
||||
console.log(recipeList);
|
||||
console.log(ingIDMap);
|
||||
console.log(recipeIDMap);*/
|
||||
try {
|
||||
document.getElementById("recipe-choice").addEventListener("change", (event) => {
|
||||
updateMaterials();
|
||||
|
@ -56,17 +46,16 @@ function init_crafter() {
|
|||
});
|
||||
|
||||
for (let i = 1; i < 4; ++i) {
|
||||
document.getElementById("mat-1-"+i).setAttribute("onclick", document.getElementById("mat-1-"+i).getAttribute("onclick") + "; calculateCraftSchedule();");
|
||||
document.getElementById("mat-2-"+i).setAttribute("onclick", document.getElementById("mat-2-"+i).getAttribute("onclick") + "; calculateCraftSchedule();");
|
||||
document.getElementById("mat-1-"+i).addEventListener("click", (e) => calculateCraftSchedule());
|
||||
document.getElementById("mat-2-"+i).addEventListener("click", (e) => calculateCraftSchedule());
|
||||
}
|
||||
for (let i = 1; i < 7; ++i) {
|
||||
document.getElementById("ing-choice-" + i ).setAttribute("oninput", "calculateCraftSchedule();");
|
||||
document.getElementById("ing-choice-" + i ).addEventListener("oninput", (e) => calculateCraftSchedule());
|
||||
}
|
||||
for (const str of ["slow", "normal", "fast"]) {
|
||||
document.getElementById(str + "-atk-button").setAttribute("onclick", document.getElementById(str + "-atk-button").getAttribute("onclick") + "; calculateCraftSchedule();");
|
||||
document.getElementById(str + "-atk-button").addEventListener("onclick", (e) => calculateCraftSchedule());
|
||||
}
|
||||
|
||||
|
||||
populateFields();
|
||||
decodeCraft(ing_url_tag);
|
||||
} catch (error) {
|
||||
|
@ -259,12 +248,10 @@ function populateFields() {
|
|||
ing_list.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/*
|
||||
Copies the CR Hash (CR-blahblahblah)
|
||||
*/
|
||||
* Copies the CR Hash (CR-blahblahblah)
|
||||
*/
|
||||
function copyRecipeHash() {
|
||||
if (player_craft) {
|
||||
copyTextToClipboard("CR-"+location.hash.slice(1));
|
||||
|
|
|
@ -336,4 +336,7 @@ class Custom {
|
|||
this.statMap.set("restrict", "Custom Item")
|
||||
}
|
||||
|
||||
copy() {
|
||||
return new Custom(new Map(this.statMap));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.00], ["ragnarokkr", 0.30], ["fortitude", 0.60] ]);
|
||||
/**
|
||||
* File implementing core damage calculation logic.
|
||||
*/
|
||||
|
||||
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["warscream", 0.00], ["ragnarokkr", 0.30], ["fortitude", 0.60] ]);
|
||||
|
||||
function get_base_dps(item) {
|
||||
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* File containing generic display code, ex. for displaying items and spell damage.
|
||||
* TODO: split this file into separate parts for each "component".
|
||||
*/
|
||||
|
||||
const itemBGPositions = {"bow": "0 0", "spear": "9.090909090909088% 0", "wand": "18.181818181818183% 0", "dagger": "27.27272727272727% 0", "relik": "36.36363636363637% 0",
|
||||
"helmet": "45.45454545454546% 0", "chestplate": "54.54545454545454% 0", "leggings": "63.63636363636363% 0", "boots": "72.72727272727272% 0",
|
||||
"ring": "81.81818181818181% 0", "bracelet": "90.90909090909092% 0", "necklace": "100% 0",
|
||||
|
@ -388,46 +393,45 @@ function displayExpandedItem(item, parent_id){
|
|||
}
|
||||
}
|
||||
//Show powder specials ;-;
|
||||
let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots"];//, "ring", "bracelet", "necklace"];
|
||||
if(nonConsumables.includes(item.get("type"))) {
|
||||
let powder_special = document.createElement("div");
|
||||
powder_special.classList.add("col");
|
||||
let powder_specials_check = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots"];
|
||||
if(powder_specials_check.includes(item.get("type"))) {
|
||||
let powder_special = make_elem("div", ['col']);
|
||||
let powders = item.get("powders");
|
||||
let element = "";
|
||||
let power = 0;
|
||||
let element;
|
||||
let power_index;
|
||||
for (let i = 0; i < powders.length; i++) {
|
||||
let firstPowderType = skp_elements[Math.floor(powders[i]/6)];
|
||||
if (element !== "") break;
|
||||
else if (powders[i]%6 > 2) { //t4+
|
||||
const firstPowderType = skp_elements[Math.floor(powders[i]/6)];
|
||||
const powder1_power = powders[i] % 6;
|
||||
if (powder1_power > 2) { //t4+
|
||||
for (let j = i+1; j < powders.length; j++) {
|
||||
let currentPowderType = skp_elements[Math.floor(powders[j]/6)]
|
||||
if (powders[j] % 6 > 2 && firstPowderType === currentPowderType) {
|
||||
const currentPowderType = skp_elements[Math.floor(powders[j]/6)]
|
||||
const powder2_power = powders[j] % 6;
|
||||
if (powder2_power > 2 && firstPowderType === currentPowderType) {
|
||||
element = currentPowderType;
|
||||
power = Math.round(((powders[i] % 6 + powders[j] % 6 + 2) / 2 - 4) * 2);
|
||||
power_index = powder1_power + powder2_power - 6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (element !== "") {//powder special is "[e,t,w,f,a]+[0,1,2,3,4]"
|
||||
let powderSpecial = powderSpecialStats[ skp_elements.indexOf(element)];
|
||||
let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
|
||||
let specialTitle = document.createElement("span");
|
||||
let specialEffects = document.createElement("span");
|
||||
addClasses(specialTitle, [damageClasses[skp_elements.indexOf(element) + 1]]);
|
||||
if (element) {//powder special is "[e,t,w,f,a]+[0,1,2,3,4]"
|
||||
const powderSpecial = powderSpecialStats[skp_elements.indexOf(element)];
|
||||
const specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
|
||||
const specialTitle = make_elem("span", [damageClasses[skp_elements.indexOf(element) + 1]]);
|
||||
const specialEffects = document.createElement("span");
|
||||
let effects;
|
||||
if (item.get("category") === "weapon") {//weapon
|
||||
effects = powderSpecial["weaponSpecialEffects"];
|
||||
specialTitle.textContent = powderSpecial["weaponSpecialName"];
|
||||
}else if (item.get("category") === "armor") {//armor
|
||||
} else if (item.get("category") === "armor") {//armor
|
||||
effects = powderSpecial["armorSpecialEffects"];
|
||||
specialTitle.textContent += powderSpecial["armorSpecialName"] + ": ";
|
||||
}
|
||||
for (const [key,value] of effects.entries()) {
|
||||
if (key !== "Description") {
|
||||
let effect = document.createElement("p");
|
||||
effect.classList.add("m-0");
|
||||
effect.textContent = key + ": " + value[power] + specialSuffixes.get(key);
|
||||
let effect = make_elem("p", ["m-0"], {
|
||||
textContent: key + ": " + value[power_index] + specialSuffixes.get(key)
|
||||
});
|
||||
if(key === "Damage"){
|
||||
effect.textContent += elementIcons[skp_elements.indexOf(element)];
|
||||
}
|
||||
|
@ -435,20 +439,19 @@ function displayExpandedItem(item, parent_id){
|
|||
effect.textContent += " / Mana Used";
|
||||
}
|
||||
specialEffects.appendChild(effect);
|
||||
}else{
|
||||
} else {
|
||||
specialTitle.textContent += "[ " + effects.get("Description") + " ]";
|
||||
}
|
||||
}
|
||||
powder_special.appendChild(specialTitle);
|
||||
powder_special.appendChild(specialEffects);
|
||||
powder_special.append(specialTitle, specialEffects);
|
||||
parent_div.appendChild(powder_special);
|
||||
}
|
||||
}
|
||||
|
||||
let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots", "ring", "bracelet", "necklace"];
|
||||
if(item.get("tier") && item.get("tier") === "Crafted") {
|
||||
let dura_elem = document.createElement("div");
|
||||
dura_elem.classList.add("col");
|
||||
let dura = [];
|
||||
let dura_elem = make_elem("div", ["col"]);
|
||||
let dura;
|
||||
let suffix = "";
|
||||
if(nonConsumables.includes(item.get("type"))) {
|
||||
dura = item.get("durability");
|
||||
|
@ -457,9 +460,9 @@ function displayExpandedItem(item, parent_id){
|
|||
dura = item.get("duration");
|
||||
dura_elem.textContent = "Duration: "
|
||||
suffix = " sec."
|
||||
let charges = document.createElement("b");
|
||||
charges.textContent = "Charges: " + item.get("charges");
|
||||
parent_div.appendChild(charges);
|
||||
parent_div.appendChild(make_elem('b', [], {
|
||||
textContent: "Charges: " + item.get("charges")
|
||||
}));
|
||||
}
|
||||
|
||||
if (typeof(dura) === "string") {
|
||||
|
@ -472,9 +475,7 @@ function displayExpandedItem(item, parent_id){
|
|||
}
|
||||
//Show item tier
|
||||
if (item.get("tier") && item.get("tier") !== " ") {
|
||||
let item_desc_elem = document.createElement("div");
|
||||
item_desc_elem.classList.add("col");
|
||||
item_desc_elem.classList.add(item.get("tier"));
|
||||
let item_desc_elem = make_elem("div", ["col", item.get("tier")]);
|
||||
if (tome_types.includes(item.get("type"))) {
|
||||
tome_type_map = new Map([["weaponTome", "Weapon Tome"],["armorTome", "Armor Tome"],["guildTome", "Guild Tome"]]);
|
||||
item_desc_elem.textContent = item.get("tier")+" "+tome_type_map.get(item.get("type"));
|
||||
|
@ -486,20 +487,19 @@ function displayExpandedItem(item, parent_id){
|
|||
|
||||
//Show item hash if applicable
|
||||
if (item.get("crafted") || item.get("custom")) {
|
||||
let item_desc_elem = document.createElement("p");
|
||||
item_desc_elem.classList.add('itemp');
|
||||
item_desc_elem.style.maxWidth = "100%";
|
||||
item_desc_elem.style.wordWrap = "break-word";
|
||||
item_desc_elem.style.wordBreak = "break-word";
|
||||
item_desc_elem.textContent = item.get("hash");
|
||||
parent_div.append(item_desc_elem);
|
||||
parent_div.append(make_elem('p', ['itemp'], {
|
||||
style: {
|
||||
maxWidth: '100%',
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'break-word'
|
||||
},
|
||||
textContent: item.get('hash')
|
||||
}));
|
||||
}
|
||||
|
||||
if (item.get("category") === "weapon") {
|
||||
let total_damages = item.get("basedps");
|
||||
let base_dps_elem = document.createElement("p");
|
||||
base_dps_elem.classList.add("left");
|
||||
base_dps_elem.classList.add("itemp");
|
||||
let base_dps_elem = make_elem("p", ["left", "itemp"]);
|
||||
if (item.get("tier") === "Crafted") {
|
||||
let base_dps_min = total_damages[0];
|
||||
let base_dps_max = total_damages[1];
|
||||
|
@ -509,8 +509,7 @@ function displayExpandedItem(item, parent_id){
|
|||
else {
|
||||
base_dps_elem.textContent = "Base DPS: "+(total_damages);
|
||||
}
|
||||
parent_div.appendChild(document.createElement("p"));
|
||||
parent_div.appendChild(base_dps_elem);
|
||||
parent_div.append(make_elem("p"), base_dps_elem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1268,8 +1267,8 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
|
|||
//iterate through the special and display its effects.
|
||||
let powder_special = make_elem("p", ["pt-3"]);
|
||||
let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
|
||||
let specialTitle = document.createElement("p");
|
||||
let specialEffects = document.createElement("p");
|
||||
let specialTitle = make_elem("p");
|
||||
let specialEffects = make_elem("p");
|
||||
specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]);
|
||||
let effects = special[0]["weaponSpecialEffects"];
|
||||
let power = special[1];
|
||||
|
@ -1510,9 +1509,7 @@ function displaySpellDamage(parent_elem, _overallparent_elem, stats, spell, spel
|
|||
_damage_display("Crit Average: ", critAverage, spell_info.crit_min, spell_info.crit_max);
|
||||
} else if (spell_info.type === "heal") {
|
||||
let heal_amount = spell_info.heal_amount;
|
||||
let healLabel = document.createElement("p");
|
||||
healLabel.textContent = heal_amount;
|
||||
// healLabel.classList.add("damagep");
|
||||
let healLabel = make_elem("p", ["Set"], {textContent: heal_amount.toFixed(2)});
|
||||
part_div.append(healLabel);
|
||||
if (spell_info.name === spell.display) {
|
||||
add_summary(spell_info.name+ ": ", heal_amount, "Set");
|
||||
|
|
158
js/items.js
158
js/items.js
|
@ -102,51 +102,53 @@ const special_mappings = {
|
|||
"No Defense Req": "f:defReq=0",
|
||||
};
|
||||
|
||||
let itemFilters = document.getElementById("filter-items");
|
||||
if (itemFilters) {
|
||||
for (let x in translate_mappings) {
|
||||
let el = document.createElement("option");
|
||||
el.value = x;
|
||||
itemFilters.appendChild(el);
|
||||
}
|
||||
for (let x in special_mappings) {
|
||||
let el = document.createElement("option");
|
||||
el.value = x;
|
||||
itemFilters.appendChild(el);
|
||||
}
|
||||
let item_filters = []
|
||||
for (let x in translate_mappings) {
|
||||
item_filters.push(x);
|
||||
}
|
||||
for (let x in special_mappings) {
|
||||
item_filters.push(x);
|
||||
}
|
||||
|
||||
let itemCategories = [ "armor", "accessory", "weapon" ];
|
||||
let item_categories = [ "armor", "accessory", "weapon" ];
|
||||
|
||||
function applyQuery(items, query) {
|
||||
return items.filter(query.filter, query).sort(query.compare);
|
||||
}
|
||||
|
||||
function displayItems(results) {
|
||||
let items_parent = document.getElementById("main");
|
||||
for (let i in results) {
|
||||
let item = results[i].itemExp;
|
||||
let box = document.createElement("div");
|
||||
box.classList.add("box");
|
||||
box.id = "item"+i;
|
||||
function displayItems(items_copy) {
|
||||
let items_parent = document.getElementById("search-results");
|
||||
for (let i in items_copy) {
|
||||
if (i > 200) {break;}
|
||||
let item = items_copy[i].itemExp;
|
||||
let box = make_elem('div', ['col-lg-3', 'col-sm-6', 'p-2'], {id: 'item'+i});
|
||||
//box.addEventListener("dblclick", function() {set_item(item);}); TODO: ??
|
||||
|
||||
let bckgrdbox = make_elem("div", ["dark-7", "rounded", "px-2", "col-auto"], {id: 'item'+i+'b'});
|
||||
box.append(bckgrdbox);
|
||||
items_parent.appendChild(box);
|
||||
displayExpandedItem(item, box.id);
|
||||
item.set("powders", []);
|
||||
if (item.get("category") == "weapon") {
|
||||
apply_weapon_powders(item);
|
||||
}
|
||||
displayExpandedItem(item, bckgrdbox.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
let searchDb;
|
||||
let search_db;
|
||||
let expr_parser;
|
||||
|
||||
function doItemSearch() {
|
||||
function do_item_search() {
|
||||
window.scrollTo(0, 0);
|
||||
let queries = [];
|
||||
queries.push('f:name?="'+document.getElementById("item-name-choice").value.trim()+'"');
|
||||
|
||||
let categoryOrType = document.getElementById("item-category-choice").value;
|
||||
if (itemTypes.includes(categoryOrType)) {
|
||||
queries.push('f:type="'+categoryOrType+'"');
|
||||
const cat_or_type = document.getElementById("item-category-choice").value;
|
||||
if (item_types.includes(cat_or_type)) {
|
||||
queries.push('f:type="'+cat_or_type+'"');
|
||||
}
|
||||
else if (itemCategories.includes(categoryOrType)) {
|
||||
queries.push('f:cat="'+categoryOrType+'"');
|
||||
else if (item_categories.includes(cat_or_type)) {
|
||||
queries.push('f:cat="'+cat_or_type+'"');
|
||||
}
|
||||
|
||||
let rarity = document.getElementById("item-rarity-choice").value;
|
||||
|
@ -162,7 +164,9 @@ function doItemSearch() {
|
|||
}
|
||||
}
|
||||
|
||||
let level_dat = document.getElementById("item-level-choice").value.split("-");
|
||||
let level_raw = document.getElementById("item-level-choice").value;
|
||||
if (!level_raw) { level_raw = '1-106'; };
|
||||
const level_dat = level_raw.split("-");
|
||||
queries.push('f:(lvl>='+parseInt(level_dat[0])+'&lvl<='+parseInt(level_dat[1])+')');
|
||||
|
||||
for (let i = 1; i <= 4; ++i) {
|
||||
|
@ -180,28 +184,27 @@ function doItemSearch() {
|
|||
}
|
||||
}
|
||||
|
||||
let filterQuery = "true";
|
||||
let sortQueries = [];
|
||||
let filter_query = "true";
|
||||
let sort_queries = [];
|
||||
console.log(queries);
|
||||
for (const query of queries) {
|
||||
if (query.startsWith("s:")) {
|
||||
sortQueries.push(query.slice(2));
|
||||
sort_queries.push(query.slice(2));
|
||||
}
|
||||
else if (query.startsWith("f:")) {
|
||||
filterQuery = filterQuery + "&" + query.slice(2);
|
||||
filter_query = filter_query + "&" + query.slice(2);
|
||||
}
|
||||
}
|
||||
console.log(filterQuery);
|
||||
console.log(sortQueries);
|
||||
document.getElementById("search-results").textContent = "";
|
||||
let results = [];
|
||||
try {
|
||||
const filterExpr = exprParser.parse(filterQuery);
|
||||
const sortExprs = sortQueries.map(q => exprParser.parse(q));
|
||||
for (let i = 0; i < searchDb.length; ++i) {
|
||||
const item = searchDb[i][0];
|
||||
const itemExp = searchDb[i][1];
|
||||
if (checkBool(filterExpr.resolve(item, itemExp))) {
|
||||
results.push({ item, itemExp, sortKeys: sortExprs.map(e => e.resolve(item, itemExp)) });
|
||||
const filter_expr = expr_parser.parse(filter_query);
|
||||
const sort_exprs = sort_queries.map(q => expr_parser.parse(q));
|
||||
for (let i = 0; i < search_db.length; ++i) {
|
||||
const item = search_db[i][0];
|
||||
const itemExp = search_db[i][1];
|
||||
if (checkBool(filter_expr.resolve(item, itemExp))) {
|
||||
results.push({ item, itemExp, sortKeys: sort_exprs.map(e => e.resolve(item, itemExp)) });
|
||||
}
|
||||
}
|
||||
results.sort((a, b) => {
|
||||
|
@ -211,13 +214,74 @@ function doItemSearch() {
|
|||
document.getElementById("summary").textContent = e.message;
|
||||
return;
|
||||
}
|
||||
document.getElementById("summary").textContent = results.length + " results."
|
||||
document.getElementById("summary").textContent = results.length + " results:"
|
||||
displayItems(results);
|
||||
}
|
||||
|
||||
function init_items() {
|
||||
searchDb = items.filter( i => ! i.remapID ).map( i => [i, expandItem(i, [])] );
|
||||
exprParser = new ExprParser(itemQueryProps, itemQueryFuncs);
|
||||
function reset_item_search() {
|
||||
const reset_fields = ["item-name-choice", "item-category-choice", "item-rarity-choice", "item-level-choice", "filter1-choice", "filter2-choice", "filter3-choice", "filter4-choice"]
|
||||
for (const field of reset_fields) {
|
||||
document.getElementById(field).value = "";
|
||||
}
|
||||
}
|
||||
|
||||
load_init(init_items);
|
||||
function init_items() {
|
||||
search_db = items.filter( i => ! i.remapID ).map( i => [i, expandItem(i, [])] );
|
||||
expr_parser = new ExprParser(itemQueryProps, itemQueryFuncs);
|
||||
//init dropdowns
|
||||
let filter_inputs = 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", item_filters],
|
||||
["filter2", item_filters],
|
||||
["filter3", item_filters],
|
||||
["filter4", item_filters]]);
|
||||
for (const [field, data] of filter_inputs) {
|
||||
let field_choice = document.getElementById(field+"-choice");
|
||||
// show dropdown on click
|
||||
field_choice.onclick = function() {field_choice.dispatchEvent(new Event('input', {bubbles:true}));};
|
||||
filter_inputs.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) {
|
||||
const message = make_elem('li', ['scaled-font'], {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;
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
(async function() {
|
||||
await Promise.resolve(load_init());
|
||||
init_items();
|
||||
})();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const DB_VERSION = 101;
|
||||
const DB_VERSION = 102;
|
||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||
|
||||
let db;
|
||||
|
@ -190,7 +190,7 @@ async function load_init() {
|
|||
}
|
||||
|
||||
// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon.
|
||||
for (const it of itemTypes) {
|
||||
for (const it of item_types) {
|
||||
itemLists.set(it, []);
|
||||
}
|
||||
|
||||
|
@ -254,5 +254,4 @@ function init_maps() {
|
|||
redirectMap.set(item.id, item.remapID);
|
||||
}
|
||||
}
|
||||
console.log(itemMap);
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ function init_ing_maps() {
|
|||
posMods: {"left": 0, "right": 0, "above": 0, "under": 0, "touching": 0, "notTouching": 0}
|
||||
};
|
||||
ing.id = 4001 + ing.pid;
|
||||
ing.diplayName = ing.name;
|
||||
ing.displayName = ing.name;
|
||||
switch(i) {
|
||||
case 0:
|
||||
ing.itemIDs["strReq"] = powderIng["skpReq"];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const TOME_DB_VERSION = 3;
|
||||
const TOME_DB_VERSION = 4;
|
||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||
|
||||
let tdb;
|
||||
|
|
654
js/query.js
654
js/query.js
|
@ -1,115 +1,549 @@
|
|||
let queryTypeMap = new Map();
|
||||
|
||||
class NameQuery {
|
||||
constructor(string) { this.queryString = string.toLowerCase(); }
|
||||
|
||||
filter(item) {
|
||||
if (item.get("restrict") && item.get("restrict") === "DEPRECATED") {
|
||||
return false;
|
||||
}
|
||||
return (item.get("displayName").toLowerCase().includes(this.queryString));
|
||||
}
|
||||
|
||||
compare(a, b) { return a < b; }
|
||||
}
|
||||
queryTypeMap.set("name", function(s) { return new NameQuery(s); } );
|
||||
|
||||
class LevelRangeQuery {
|
||||
constructor(min, max) { this.min = min; this.max = max; }
|
||||
|
||||
filter(item) {
|
||||
if (item.get("remapID") === undefined) {
|
||||
return (item.get("lvl") <= this.max && item.get("lvl") >= this.min);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
compare(a, b) { return a > b; }
|
||||
// dynamic type casts
|
||||
function checkBool(v) {
|
||||
if (typeof v !== 'boolean') throw new Error(`Expected boolean, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
class NegateQuery {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
this.compare = function(a, b) { return 0; };
|
||||
}
|
||||
|
||||
filter(item) {
|
||||
return (!item.get(this.id)) || (item.get(this.id) == 0);
|
||||
}
|
||||
}
|
||||
queryTypeMap.set("null", function(s) { return new IdQuery(s); } );
|
||||
|
||||
class IdQuery {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
if (nonRolledIDs.includes(id)) {
|
||||
this.compare = function(a, b) {
|
||||
return b.get(id) - a.get(id);
|
||||
};
|
||||
this.filter = function(a) {
|
||||
return a.get(this.id);
|
||||
}
|
||||
console.log("QUERY: ID, NONROLL");
|
||||
}
|
||||
else if (reversedIDs.includes(id)) {
|
||||
this.compare = function(a, b) {
|
||||
return a.get("maxRolls").get(id) - b.get("maxRolls").get(id);
|
||||
};
|
||||
this.filter = function(a) {
|
||||
return a.get("maxRolls").get(this.id);
|
||||
}
|
||||
console.log("QUERY: ID, REVERSE");
|
||||
}
|
||||
else {
|
||||
this.compare = function(a, b) {
|
||||
return b.get("maxRolls").get(id) - a.get("maxRolls").get(id);
|
||||
};
|
||||
this.filter = function(a) {
|
||||
return a.get("maxRolls").get(this.id);
|
||||
}
|
||||
console.log("QUERY: ID, ,,,");
|
||||
}
|
||||
}
|
||||
}
|
||||
queryTypeMap.set("stat", function(s) { return new IdQuery(s); } );
|
||||
|
||||
class IdMatchQuery {
|
||||
constructor(id, value) {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
this.compare = function(a, b) {
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
filter(item) {
|
||||
return item.get(this.id) && (item.get(this.id) == this.value);
|
||||
}
|
||||
function checkNum(v) {
|
||||
if (typeof v === 'boolean') {
|
||||
if (v) return 1;
|
||||
return 0;
|
||||
}
|
||||
if (typeof v !== 'number') throw new Error(`Expected number, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
class SumQuery {
|
||||
constructor(ids) {
|
||||
let getters = [];
|
||||
for (const id of ids) {
|
||||
if (nonRolledIDs.includes(id)) {
|
||||
getters.push(a => a.get(id));
|
||||
}
|
||||
else {
|
||||
getters.push(a => a.get("maxRolls").get(id));
|
||||
}
|
||||
|
||||
}
|
||||
this.compare = function(a, b) {
|
||||
let balance = 0;
|
||||
for (const getter of getters) {
|
||||
if (getter(a)) { balance -= getter(a); }
|
||||
if (getter(b)) { balance += getter(b); }
|
||||
}
|
||||
return balance;
|
||||
};
|
||||
}
|
||||
|
||||
filter(item) {
|
||||
return true;
|
||||
}
|
||||
function checkStr(v) {
|
||||
if (typeof v !== 'string') throw new Error(`Expected string, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function checkComparable(v) {
|
||||
if (typeof v === 'boolean') throw new Error('Boolean is not comparable');
|
||||
return v;
|
||||
}
|
||||
|
||||
// properties of items that can be looked up
|
||||
// each entry is a function `(item, extended item) -> value`
|
||||
const itemQueryProps = (function() {
|
||||
const props = {};
|
||||
|
||||
function prop(names, type, resolve) {
|
||||
if (Array.isArray(names)) {
|
||||
for (name of names) {
|
||||
props[name] = { type, resolve };
|
||||
}
|
||||
} else {
|
||||
props[names] = { type, resolve };
|
||||
}
|
||||
}
|
||||
|
||||
function maxId(names, idKey) {
|
||||
prop(names, 'number', (i, ie) => ie.get('maxRolls').get(idKey) || 0);
|
||||
}
|
||||
|
||||
function minId(names, idKey) {
|
||||
prop(names, 'number', (i, ie) => ie.get('minRolls').get(idKey) || 0);
|
||||
}
|
||||
|
||||
function rangeAvg(names, getProp) {
|
||||
prop(names, 'number', (i, ie) => {
|
||||
const range = getProp(i, ie);
|
||||
if (!range) return 0;
|
||||
const ndx = range.indexOf('-');
|
||||
return (parseInt(range.substring(0, ndx), 10) + parseInt(range.substring(ndx + 1), 10)) / 2;
|
||||
});
|
||||
}
|
||||
|
||||
function map(names, comps, outType, f) {
|
||||
return prop(names, outType, (i, ie) => {
|
||||
const args = [];
|
||||
for (let k = 0; k < comps.length; k++) args.push(comps[k].resolve(i, ie));
|
||||
return f.apply(null, args);
|
||||
});
|
||||
}
|
||||
|
||||
function sum(names, ...comps) {
|
||||
return map(names, comps, 'number', (...summands) => {
|
||||
let total = 0;
|
||||
for (let i = 0; i < summands.length; i++) total += summands[i];
|
||||
return total;
|
||||
});
|
||||
}
|
||||
|
||||
prop('name', 'string', (i, ie) => i.displayName || i.name);
|
||||
prop('type', 'string', (i, ie) => i.type);
|
||||
prop(['cat', 'category'], 'string', (i, ie) => i.category);
|
||||
const tierIndices = { Normal: 0, Unique: 1, Set: 2, Rare: 3, Legendary: 4, Fabled: 5, Mythic: 6 };
|
||||
prop(['rarityname', 'raritystr', 'tiername', 'tierstr'], 'string', (i, ie) => i.tier);
|
||||
prop(['rarity', 'tier'], 'number', (i, ie) => tierIndices[i.tier]);
|
||||
|
||||
prop(['level', 'lvl', 'combatlevel', 'combatlvl'], 'number', (i, ie) => i.lvl);
|
||||
prop(['strmin', 'strreq'], 'number', (i, ie) => i.strReq);
|
||||
prop(['dexmin', 'dexreq'], 'number', (i, ie) => i.dexReq);
|
||||
prop(['intmin', 'intreq'], 'number', (i, ie) => i.intReq);
|
||||
prop(['defmin', 'defreq'], 'number', (i, ie) => i.defReq);
|
||||
prop(['agimin', 'agireq'], 'number', (i, ie) => i.agiReq);
|
||||
sum(['summin', 'sumreq', 'totalmin', 'totalreq'], props.strmin, props.dexmin, props.intmin, props.defmin, props.agimin);
|
||||
|
||||
prop('str', 'number', (i, ie) => i.str);
|
||||
prop('dex', 'number', (i, ie) => i.dex);
|
||||
prop('int', 'number', (i, ie) => i.int);
|
||||
prop('def', 'number', (i, ie) => i.def);
|
||||
prop('agi', 'number', (i, ie) => i.agi);
|
||||
sum(['skillpoints', 'skillpts', 'attributes', 'attrs'], props.str, props.dex, props.int, props.def, props.agi);
|
||||
|
||||
rangeAvg(['neutraldmg', 'neutraldam', 'ndmg', 'ndam'], (i, ie) => i.nDam);
|
||||
rangeAvg(['earthdmg', 'earthdam', 'edmg', 'edam'], (i, ie) => i.eDam);
|
||||
rangeAvg(['thunderdmg', 'thunderdam', 'tdmg', 'tdam'], (i, ie) => i.tDam);
|
||||
rangeAvg(['waterdmg', 'waterdam', 'wdmg', 'wdam'], (i, ie) => i.wDam);
|
||||
rangeAvg(['firedmg', 'firedam', 'fdmg', 'fdam'], (i, ie) => i.fDam);
|
||||
rangeAvg(['airdmg', 'airdam', 'admg', 'adam'], (i, ie) => i.aDam);
|
||||
sum(['sumdmg', 'sumdam', 'totaldmg', 'totaldam'], props.ndam, props.edam, props.tdam, props.wdam, props.fdam, props.adam);
|
||||
|
||||
maxId(['earthdmg%', 'earthdam%', 'edmg%', 'edam%', 'edampct'], 'eDamPct');
|
||||
maxId(['thunderdmg%', 'thunderdam%', 'tdmg%', 'tdam%', 'tdampct'], 'tDamPct');
|
||||
maxId(['waterdmg%', 'waterdam%', 'wdmg%', 'wdam%', 'wdampct'], 'wDamPct');
|
||||
maxId(['firedmg%', 'firedam%', 'fdmg%', 'fdam%', 'fdampct'], 'fDamPct');
|
||||
maxId(['airdmg%', 'airdam%', 'admg%', 'adam%', 'adampct'], 'aDamPct');
|
||||
sum(['sumdmg%', 'sumdam%', 'totaldmg%', 'totaldam%', 'sumdampct', 'totaldampct'], props.edampct, props.tdampct, props.wdampct, props.fdampct, props.adampct);
|
||||
|
||||
maxId(['mainatkdmg', 'mainatkdam', 'mainatkdmg%', 'mainatkdam%', 'meleedmg', 'meleedam', 'meleedmg%', 'meleedam%', 'mdpct'], 'mdPct');
|
||||
maxId(['mainatkrawdmg', 'mainatkrawdam', 'mainatkneutraldmg', 'mainatkneutraldam', 'meleerawdmg', 'meleerawdam', 'meleeneutraldmg', 'meleeneutraldam', 'mdraw'], 'mdRaw');
|
||||
maxId(['spelldmg', 'spelldam', 'spelldmg%', 'spelldam%', 'sdpct'], 'sdPct');
|
||||
maxId(['spellrawdmg', 'spellrawdam', 'spellneutraldmg', 'spellneutraldam', 'sdraw'], 'sdRaw');
|
||||
|
||||
const atkSpdIndices = { SUPER_SLOW: -3, VERY_SLOW: -2, SLOW: -1, NORMAL: 0, FAST: 1, VERY_FAST: 2, SUPER_FAST: 3 };
|
||||
prop(['attackspeed', 'atkspd'], 'string', (i, ie) => i.atkSpd ? atkSpdIndices[i.atkSpd] : 0);
|
||||
maxId(['bonusattackspeed', 'bonusatkspd', 'attackspeedid', 'atkspdid', 'atktier'], 'atkTier');
|
||||
sum(['sumattackspeed', 'totalattackspeed', 'sumatkspd', 'totalatkspd', 'sumatktier', 'totalatktier'], props.atkspd, props.atktier);
|
||||
|
||||
prop(['earthdef', 'edef'], 'number', (i, ie) => i.eDef || 0);
|
||||
prop(['thunderdef', 'tdef'], 'number', (i, ie) => i.tDef || 0);
|
||||
prop(['waterdef', 'wdef'], 'number', (i, ie) => i.wDef || 0);
|
||||
prop(['firedef', 'fdef'], 'number', (i, ie) => i.fDef || 0);
|
||||
prop(['airdef', 'adef'], 'number', (i, ie) => i.aDef || 0);
|
||||
sum(['sumdef', 'totaldef'], props.edef, props.tdef, props.wdef, props.fdef, props.adef);
|
||||
|
||||
maxId(['earthdef%', 'edef%', 'edefpct'], 'eDefPct');
|
||||
maxId(['thunderdef%', 'tdef%', 'tdefpct'], 'tDefPct');
|
||||
maxId(['waterdef%', 'wdef%', 'wdefpct'], 'wDefPct');
|
||||
maxId(['firedef%', 'fdef%', 'fdefpct'], 'fDefPct');
|
||||
maxId(['airdef%', 'adef%', 'adefpct'], 'aDefPct');
|
||||
sum(['sumdef%', 'totaldef%', 'sumdefpct', 'totaldefpct'], props.edefpct, props.tdefpct, props.wdefpct, props.fdefpct, props.adefpct);
|
||||
|
||||
prop(['health', 'hp'], 'number', (i, ie) => i.hp || 0);
|
||||
maxId(['bonushealth', 'healthid', 'bonushp', 'hpid', 'hpbonus'], 'hpBonus');
|
||||
sum(['sumhealth', 'sumhp', 'totalhealth', 'totalhp'], props.hp, props.hpid);
|
||||
|
||||
maxId(['hpregen', 'hpr', 'hr', 'hprraw'], 'hprRaw');
|
||||
maxId(['hpregen%', 'hpr%', 'hr%', 'hprpct'], 'hprPct');
|
||||
maxId(['lifesteal', 'ls'], 'ls');
|
||||
maxId(['manaregen', 'mr'], 'mr');
|
||||
maxId(['manasteal', 'ms'], 'ms');
|
||||
|
||||
maxId(['walkspeed', 'movespeed', 'ws', 'spd'], 'spd');
|
||||
maxId('sprint', 'sprint');
|
||||
maxId(['sprintregen', 'sprintreg'], 'sprintReg');
|
||||
maxId(['jumpheight', 'jh'], 'jh');
|
||||
|
||||
maxId(['spellcost1', 'rawspellcost1', 'spcost1', 'spraw1'], 'spRaw1');
|
||||
maxId(['spellcost1%', 'spcost1%', 'sppct1'], 'spPct1');
|
||||
maxId(['spellcost2', 'rawspellcost2', 'spcost2', 'spraw2'], 'spRaw2');
|
||||
maxId(['spellcost2%', 'spcost2%', 'sppct2'], 'spPct2');
|
||||
maxId(['spellcost3', 'rawspellcost3', 'spcost3', 'spraw3'], 'spRaw3');
|
||||
maxId(['spellcost3%', 'spcost3%', 'sppct3'], 'spPct3');
|
||||
maxId(['spellcost4', 'rawspellcost4', 'spcost4', 'spraw4'], 'spRaw4');
|
||||
maxId(['spellcost4%', 'spcost4%', 'sppct4'], 'spPct4');
|
||||
sum(['sumspellcost', 'totalspellcost', 'sumrawspellcost', 'totalrawspellcost', 'sumspcost', 'totalspcost', 'sumspraw', 'totalspraw'], props.spraw1, props.spraw2, props.spraw3, props.spraw4);
|
||||
sum(['sumspellcost%', 'totalspellcost%', 'sumspcost%', 'totalspcost%', 'sumsppct', 'totalsppct'], props.sppct1, props.sppct2, props.sppct3, props.sppct4);
|
||||
|
||||
maxId(['exploding', 'expl', 'expd'], 'expd');
|
||||
maxId('poison', 'poison');
|
||||
maxId('thorns', 'thorns');
|
||||
maxId(['reflection', 'refl', 'ref'], 'ref');
|
||||
maxId(['soulpointregen', 'spr', 'spregen'], 'spRegen');
|
||||
maxId(['lootbonus', 'lb'], 'lb');
|
||||
maxId(['xpbonus', 'xpb', 'xb'], 'xpb');
|
||||
maxId(['stealing', 'esteal'], 'eSteal');
|
||||
prop(['powderslots', 'powders', 'slots', 'sockets'], 'number', (i, ie) => i.slots || 0);
|
||||
|
||||
return props;
|
||||
})();
|
||||
|
||||
// functions that can be called in query expressions
|
||||
const itemQueryFuncs = {
|
||||
max: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to max()');
|
||||
let runningMax = -Infinity;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (checkNum(args[i]) > runningMax) runningMax = args[i];
|
||||
}
|
||||
return runningMax;
|
||||
}
|
||||
},
|
||||
min: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to min()');
|
||||
let runningMin = Infinity;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (checkNum(args[i]) < runningMin) runningMin = args[i];
|
||||
}
|
||||
return runningMin;
|
||||
}
|
||||
},
|
||||
floor: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to floor()');
|
||||
return Math.floor(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
ceil: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to ceil()');
|
||||
return Math.ceil(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
round: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to round()');
|
||||
return Math.round(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
sqrt: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to sqrt()');
|
||||
return Math.sqrt(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
abs: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to abs()');
|
||||
return Math.abs(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
contains: {
|
||||
type: 'boolean',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 2) throw new Error('Not enough args to contains()');
|
||||
return checkStr(args[0]).toLowerCase().includes(checkStr(args[1]).toLowerCase());
|
||||
}
|
||||
},
|
||||
atkspdmod: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to atkSpdMod()');
|
||||
switch (checkNum(args[0])) {
|
||||
case 2:
|
||||
return 3.1;
|
||||
case 1:
|
||||
return 2.5;
|
||||
case 0:
|
||||
return 2.05;
|
||||
case -1:
|
||||
return 1.5;
|
||||
case -2:
|
||||
return 0.83;
|
||||
}
|
||||
if (args[0] <= -3) return 0.51;
|
||||
if (args[0] >= 3) return 4.3;
|
||||
throw new Error('Invalid argument to atkSpdMod()');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// static type check
|
||||
function staticCheck(expType, term) {
|
||||
if (expType === 'any' || expType === term.type) {
|
||||
return true;
|
||||
}
|
||||
if (expType === 'number' && term.type === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
throw new Error(`Expected ${expType}, but got ${term.type}`);
|
||||
}
|
||||
|
||||
// expression terms
|
||||
class Term {
|
||||
constructor(type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class LiteralTerm extends Term {
|
||||
constructor(type, value) {
|
||||
super(type);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
class BoolLitTerm extends LiteralTerm {
|
||||
constructor(value) {
|
||||
super('boolean', value);
|
||||
}
|
||||
}
|
||||
|
||||
class NumLitTerm extends LiteralTerm {
|
||||
constructor(value) {
|
||||
super('number', value);
|
||||
}
|
||||
}
|
||||
|
||||
class StrLitTerm extends LiteralTerm {
|
||||
constructor(value) {
|
||||
super('string', value);
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryOpTerm extends Term {
|
||||
constructor(type, leftType, left, rightType, right) {
|
||||
super(type);
|
||||
staticCheck(leftType, left);
|
||||
staticCheck(rightType, right);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.apply(this.left.resolve(item, itemExt), this.right.resolve(item, itemExt));
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class LogicalTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'boolean', left, 'boolean', right);
|
||||
}
|
||||
}
|
||||
|
||||
class ConjTerm extends LogicalTerm {
|
||||
apply(a, b) {
|
||||
return a && b;
|
||||
}
|
||||
}
|
||||
|
||||
class DisjTerm extends LogicalTerm {
|
||||
apply(a, b) {
|
||||
return a || b;
|
||||
}
|
||||
}
|
||||
|
||||
class EqualityTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'any', left, 'any', right);
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
return (typeof a === 'string' && typeof b === 'string')
|
||||
? this.compare(a.toLowerCase(), b.toLowerCase()) : this.compare(a, b);
|
||||
}
|
||||
|
||||
compare(a, b) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class EqTerm extends EqualityTerm {
|
||||
compare(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
|
||||
class NeqTerm extends EqualityTerm {
|
||||
compare(a, b) {
|
||||
return a !== b;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainsTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'string', left, 'string', right);
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
return a.toLowerCase().includes(b.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
class InequalityTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'any', left, 'any', right);
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
checkComparable(a);
|
||||
checkComparable(b);
|
||||
return (typeof a === 'string' && typeof b === 'string')
|
||||
? this.compare(a.toLowerCase(), b.toLowerCase()) : this.compare(a, b);
|
||||
}
|
||||
|
||||
compare(a, b) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class LeqTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a <= b;
|
||||
}
|
||||
}
|
||||
|
||||
class LtTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
|
||||
class GtTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
|
||||
class GeqTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a >= b;
|
||||
}
|
||||
}
|
||||
|
||||
class ArithmeticTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('number', 'number', left, 'number', right);
|
||||
}
|
||||
}
|
||||
|
||||
class AddTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
class SubTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
class MulTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
}
|
||||
|
||||
class DivTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a / b;
|
||||
}
|
||||
}
|
||||
|
||||
class ExpTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a ** b;
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryOpTerm extends Term {
|
||||
constructor(type, inType, inVal) {
|
||||
super(type);
|
||||
staticCheck(inType, inVal);
|
||||
this.inVal = inVal;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.apply(this.inVal.resolve(item, itemExt));
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class NegTerm extends UnaryOpTerm {
|
||||
constructor(inVal) {
|
||||
super('number', 'number', inVal);
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
return -x;
|
||||
}
|
||||
}
|
||||
|
||||
class InvTerm extends UnaryOpTerm {
|
||||
constructor(inVal) {
|
||||
super('boolean', 'boolean', inVal);
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
return !x;
|
||||
}
|
||||
}
|
||||
|
||||
class FnCallTerm extends Term {
|
||||
constructor(fn, argExprs) {
|
||||
super(fn.type);
|
||||
this.fn = fn;
|
||||
this.argExprs = argExprs;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
const argVals = [];
|
||||
for (const argExpr of this.argExprs) {
|
||||
argVals.push(argExpr.resolve(item, itemExt));
|
||||
}
|
||||
return this.fn.fn(item, itemExt, argVals);
|
||||
}
|
||||
}
|
||||
|
||||
class PropTerm extends Term {
|
||||
constructor(prop) {
|
||||
super(prop.type);
|
||||
this.prop = prop;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.prop.resolve(item, itemExt);
|
||||
}
|
||||
}
|
||||
|
||||
function compareLexico(ia, keysA, ib, keysB) {
|
||||
for (let i = 0; i < keysA.length; i++) { // assuming keysA and keysB are the same length
|
||||
let aKey = keysA[i], bKey = keysB[i];
|
||||
if (typeof aKey !== typeof bKey) throw new Error(`Incomparable types ${typeof aKey} and ${typeof bKey}`); // can this even happen?
|
||||
switch (typeof aKey) {
|
||||
case 'string':
|
||||
aKey = aKey.toLowerCase();
|
||||
bKey = bKey.toLowerCase();
|
||||
if (aKey < bKey) return -1;
|
||||
if (aKey > bKey) return 1;
|
||||
break;
|
||||
case 'number': // sort numeric stuff in reverse order
|
||||
aKey = isNaN(aKey) ? 0 : aKey;
|
||||
bKey = isNaN(bKey) ? 0 : bKey;
|
||||
if (aKey < bKey) return 1;
|
||||
if (aKey > bKey) return -1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Incomparable type ${typeof aKey}`);
|
||||
}
|
||||
}
|
||||
return ib.lvl - ia.lvl;
|
||||
}
|
||||
|
|
549
js/query_2.js
549
js/query_2.js
|
@ -1,549 +0,0 @@
|
|||
// dynamic type casts
|
||||
function checkBool(v) {
|
||||
if (typeof v !== 'boolean') throw new Error(`Expected boolean, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function checkNum(v) {
|
||||
if (typeof v === 'boolean') {
|
||||
if (v) return 1;
|
||||
return 0;
|
||||
}
|
||||
if (typeof v !== 'number') throw new Error(`Expected number, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function checkStr(v) {
|
||||
if (typeof v !== 'string') throw new Error(`Expected string, but got ${typeof v}`);
|
||||
return v;
|
||||
}
|
||||
|
||||
function checkComparable(v) {
|
||||
if (typeof v === 'boolean') throw new Error('Boolean is not comparable');
|
||||
return v;
|
||||
}
|
||||
|
||||
// properties of items that can be looked up
|
||||
// each entry is a function `(item, extended item) -> value`
|
||||
const itemQueryProps = (function() {
|
||||
const props = {};
|
||||
|
||||
function prop(names, type, resolve) {
|
||||
if (Array.isArray(names)) {
|
||||
for (name of names) {
|
||||
props[name] = { type, resolve };
|
||||
}
|
||||
} else {
|
||||
props[names] = { type, resolve };
|
||||
}
|
||||
}
|
||||
|
||||
function maxId(names, idKey) {
|
||||
prop(names, 'number', (i, ie) => ie.get('maxRolls').get(idKey) || 0);
|
||||
}
|
||||
|
||||
function minId(names, idKey) {
|
||||
prop(names, 'number', (i, ie) => ie.get('minRolls').get(idKey) || 0);
|
||||
}
|
||||
|
||||
function rangeAvg(names, getProp) {
|
||||
prop(names, 'number', (i, ie) => {
|
||||
const range = getProp(i, ie);
|
||||
if (!range) return 0;
|
||||
const ndx = range.indexOf('-');
|
||||
return (parseInt(range.substring(0, ndx), 10) + parseInt(range.substring(ndx + 1), 10)) / 2;
|
||||
});
|
||||
}
|
||||
|
||||
function map(names, comps, outType, f) {
|
||||
return prop(names, outType, (i, ie) => {
|
||||
const args = [];
|
||||
for (let k = 0; k < comps.length; k++) args.push(comps[k].resolve(i, ie));
|
||||
return f.apply(null, args);
|
||||
});
|
||||
}
|
||||
|
||||
function sum(names, ...comps) {
|
||||
return map(names, comps, 'number', (...summands) => {
|
||||
let total = 0;
|
||||
for (let i = 0; i < summands.length; i++) total += summands[i];
|
||||
return total;
|
||||
});
|
||||
}
|
||||
|
||||
prop('name', 'string', (i, ie) => i.displayName || i.name);
|
||||
prop('type', 'string', (i, ie) => i.type);
|
||||
prop(['cat', 'category'], 'string', (i, ie) => i.category);
|
||||
const tierIndices = { Normal: 0, Unique: 1, Set: 2, Rare: 3, Legendary: 4, Fabled: 5, Mythic: 6 };
|
||||
prop(['rarityname', 'raritystr', 'tiername', 'tierstr'], 'string', (i, ie) => i.tier);
|
||||
prop(['rarity', 'tier'], 'number', (i, ie) => tierIndices[i.tier]);
|
||||
|
||||
prop(['level', 'lvl', 'combatlevel', 'combatlvl'], 'number', (i, ie) => i.lvl);
|
||||
prop(['strmin', 'strreq'], 'number', (i, ie) => i.strReq);
|
||||
prop(['dexmin', 'dexreq'], 'number', (i, ie) => i.dexReq);
|
||||
prop(['intmin', 'intreq'], 'number', (i, ie) => i.intReq);
|
||||
prop(['defmin', 'defreq'], 'number', (i, ie) => i.defReq);
|
||||
prop(['agimin', 'agireq'], 'number', (i, ie) => i.agiReq);
|
||||
sum(['summin', 'sumreq', 'totalmin', 'totalreq'], props.strmin, props.dexmin, props.intmin, props.defmin, props.agimin);
|
||||
|
||||
prop('str', 'number', (i, ie) => i.str);
|
||||
prop('dex', 'number', (i, ie) => i.dex);
|
||||
prop('int', 'number', (i, ie) => i.int);
|
||||
prop('def', 'number', (i, ie) => i.def);
|
||||
prop('agi', 'number', (i, ie) => i.agi);
|
||||
sum(['skillpoints', 'skillpts', 'attributes', 'attrs'], props.str, props.dex, props.int, props.def, props.agi);
|
||||
|
||||
rangeAvg(['neutraldmg', 'neutraldam', 'ndmg', 'ndam'], (i, ie) => i.nDam);
|
||||
rangeAvg(['earthdmg', 'earthdam', 'edmg', 'edam'], (i, ie) => i.eDam);
|
||||
rangeAvg(['thunderdmg', 'thunderdam', 'tdmg', 'tdam'], (i, ie) => i.tDam);
|
||||
rangeAvg(['waterdmg', 'waterdam', 'wdmg', 'wdam'], (i, ie) => i.wDam);
|
||||
rangeAvg(['firedmg', 'firedam', 'fdmg', 'fdam'], (i, ie) => i.fDam);
|
||||
rangeAvg(['airdmg', 'airdam', 'admg', 'adam'], (i, ie) => i.aDam);
|
||||
sum(['sumdmg', 'sumdam', 'totaldmg', 'totaldam'], props.ndam, props.edam, props.tdam, props.wdam, props.fdam, props.adam);
|
||||
|
||||
maxId(['earthdmg%', 'earthdam%', 'edmg%', 'edam%', 'edampct'], 'eDamPct');
|
||||
maxId(['thunderdmg%', 'thunderdam%', 'tdmg%', 'tdam%', 'tdampct'], 'tDamPct');
|
||||
maxId(['waterdmg%', 'waterdam%', 'wdmg%', 'wdam%', 'wdampct'], 'wDamPct');
|
||||
maxId(['firedmg%', 'firedam%', 'fdmg%', 'fdam%', 'fdampct'], 'fDamPct');
|
||||
maxId(['airdmg%', 'airdam%', 'admg%', 'adam%', 'adampct'], 'aDamPct');
|
||||
sum(['sumdmg%', 'sumdam%', 'totaldmg%', 'totaldam%', 'sumdampct', 'totaldampct'], props.edampct, props.tdampct, props.wdampct, props.fdampct, props.adampct);
|
||||
|
||||
maxId(['mainatkdmg', 'mainatkdam', 'mainatkdmg%', 'mainatkdam%', 'meleedmg', 'meleedam', 'meleedmg%', 'meleedam%', 'mdpct'], 'mdPct');
|
||||
maxId(['mainatkrawdmg', 'mainatkrawdam', 'mainatkneutraldmg', 'mainatkneutraldam', 'meleerawdmg', 'meleerawdam', 'meleeneutraldmg', 'meleeneutraldam', 'mdraw'], 'mdRaw');
|
||||
maxId(['spelldmg', 'spelldam', 'spelldmg%', 'spelldam%', 'sdpct'], 'sdPct');
|
||||
maxId(['spellrawdmg', 'spellrawdam', 'spellneutraldmg', 'spellneutraldam', 'sdraw'], 'sdRaw');
|
||||
|
||||
const atkSpdIndices = { SUPER_SLOW: -3, VERY_SLOW: -2, SLOW: -1, NORMAL: 0, FAST: 1, VERY_FAST: 2, SUPER_FAST: 3 };
|
||||
prop(['attackspeed', 'atkspd'], 'string', (i, ie) => i.atkSpd ? atkSpdIndices[i.atkSpd] : 0);
|
||||
maxId(['bonusattackspeed', 'bonusatkspd', 'attackspeedid', 'atkspdid', 'atktier'], 'atkTier');
|
||||
sum(['sumattackspeed', 'totalattackspeed', 'sumatkspd', 'totalatkspd', 'sumatktier', 'totalatktier'], props.atkspd, props.atktier);
|
||||
|
||||
prop(['earthdef', 'edef'], 'number', (i, ie) => i.eDef || 0);
|
||||
prop(['thunderdef', 'tdef'], 'number', (i, ie) => i.tDef || 0);
|
||||
prop(['waterdef', 'wdef'], 'number', (i, ie) => i.wDef || 0);
|
||||
prop(['firedef', 'fdef'], 'number', (i, ie) => i.fDef || 0);
|
||||
prop(['airdef', 'adef'], 'number', (i, ie) => i.aDef || 0);
|
||||
sum(['sumdef', 'totaldef'], props.edef, props.tdef, props.wdef, props.fdef, props.adef);
|
||||
|
||||
maxId(['earthdef%', 'edef%', 'edefpct'], 'eDefPct');
|
||||
maxId(['thunderdef%', 'tdef%', 'tdefpct'], 'tDefPct');
|
||||
maxId(['waterdef%', 'wdef%', 'wdefpct'], 'wDefPct');
|
||||
maxId(['firedef%', 'fdef%', 'fdefpct'], 'fDefPct');
|
||||
maxId(['airdef%', 'adef%', 'adefpct'], 'aDefPct');
|
||||
sum(['sumdef%', 'totaldef%', 'sumdefpct', 'totaldefpct'], props.edefpct, props.tdefpct, props.wdefpct, props.fdefpct, props.adefpct);
|
||||
|
||||
prop(['health', 'hp'], 'number', (i, ie) => i.hp || 0);
|
||||
maxId(['bonushealth', 'healthid', 'bonushp', 'hpid', 'hpbonus'], 'hpBonus');
|
||||
sum(['sumhealth', 'sumhp', 'totalhealth', 'totalhp'], props.hp, props.hpid);
|
||||
|
||||
maxId(['hpregen', 'hpr', 'hr', 'hprraw'], 'hprRaw');
|
||||
maxId(['hpregen%', 'hpr%', 'hr%', 'hprpct'], 'hprPct');
|
||||
maxId(['lifesteal', 'ls'], 'ls');
|
||||
maxId(['manaregen', 'mr'], 'mr');
|
||||
maxId(['manasteal', 'ms'], 'ms');
|
||||
|
||||
maxId(['walkspeed', 'movespeed', 'ws', 'spd'], 'spd');
|
||||
maxId('sprint', 'sprint');
|
||||
maxId(['sprintregen', 'sprintreg'], 'sprintReg');
|
||||
maxId(['jumpheight', 'jh'], 'jh');
|
||||
|
||||
maxId(['spellcost1', 'rawspellcost1', 'spcost1', 'spraw1'], 'spRaw1');
|
||||
maxId(['spellcost1%', 'spcost1%', 'sppct1'], 'spPct1');
|
||||
maxId(['spellcost2', 'rawspellcost2', 'spcost2', 'spraw2'], 'spRaw2');
|
||||
maxId(['spellcost2%', 'spcost2%', 'sppct2'], 'spPct2');
|
||||
maxId(['spellcost3', 'rawspellcost3', 'spcost3', 'spraw3'], 'spRaw3');
|
||||
maxId(['spellcost3%', 'spcost3%', 'sppct3'], 'spPct3');
|
||||
maxId(['spellcost4', 'rawspellcost4', 'spcost4', 'spraw4'], 'spRaw4');
|
||||
maxId(['spellcost4%', 'spcost4%', 'sppct4'], 'spPct4');
|
||||
sum(['sumspellcost', 'totalspellcost', 'sumrawspellcost', 'totalrawspellcost', 'sumspcost', 'totalspcost', 'sumspraw', 'totalspraw'], props.spraw1, props.spraw2, props.spraw3, props.spraw4);
|
||||
sum(['sumspellcost%', 'totalspellcost%', 'sumspcost%', 'totalspcost%', 'sumsppct', 'totalsppct'], props.sppct1, props.sppct2, props.sppct3, props.sppct4);
|
||||
|
||||
maxId(['exploding', 'expl', 'expd'], 'expd');
|
||||
maxId('poison', 'poison');
|
||||
maxId('thorns', 'thorns');
|
||||
maxId(['reflection', 'refl', 'ref'], 'ref');
|
||||
maxId(['soulpointregen', 'spr', 'spregen'], 'spRegen');
|
||||
maxId(['lootbonus', 'lb'], 'lb');
|
||||
maxId(['xpbonus', 'xpb', 'xb'], 'xpb');
|
||||
maxId(['stealing', 'esteal'], 'eSteal');
|
||||
prop(['powderslots', 'powders', 'slots', 'sockets'], 'number', (i, ie) => i.slots || 0);
|
||||
|
||||
return props;
|
||||
})();
|
||||
|
||||
// functions that can be called in query expressions
|
||||
const itemQueryFuncs = {
|
||||
max: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to max()');
|
||||
let runningMax = -Infinity;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (checkNum(args[i]) > runningMax) runningMax = args[i];
|
||||
}
|
||||
return runningMax;
|
||||
}
|
||||
},
|
||||
min: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to min()');
|
||||
let runningMin = Infinity;
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (checkNum(args[i]) < runningMin) runningMin = args[i];
|
||||
}
|
||||
return runningMin;
|
||||
}
|
||||
},
|
||||
floor: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to floor()');
|
||||
return Math.floor(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
ceil: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to ceil()');
|
||||
return Math.ceil(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
round: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to round()');
|
||||
return Math.round(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
sqrt: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to sqrt()');
|
||||
return Math.sqrt(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
abs: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to abs()');
|
||||
return Math.abs(checkNum(args[0]));
|
||||
}
|
||||
},
|
||||
contains: {
|
||||
type: 'boolean',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 2) throw new Error('Not enough args to contains()');
|
||||
return checkStr(args[0]).toLowerCase().includes(checkStr(args[1]).toLowerCase());
|
||||
}
|
||||
},
|
||||
atkspdmod: {
|
||||
type: 'number',
|
||||
fn: function(item, itemExp, args) {
|
||||
if (args.length < 1) throw new Error('Not enough args to atkSpdMod()');
|
||||
switch (checkNum(args[0])) {
|
||||
case 2:
|
||||
return 3.1;
|
||||
case 1:
|
||||
return 2.5;
|
||||
case 0:
|
||||
return 2.05;
|
||||
case -1:
|
||||
return 1.5;
|
||||
case -2:
|
||||
return 0.83;
|
||||
}
|
||||
if (args[0] <= -3) return 0.51;
|
||||
if (args[0] >= 3) return 4.3;
|
||||
throw new Error('Invalid argument to atkSpdMod()');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// static type check
|
||||
function staticCheck(expType, term) {
|
||||
if (expType === 'any' || expType === term.type) {
|
||||
return true;
|
||||
}
|
||||
if (expType === 'number' && term.type === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
throw new Error(`Expected ${expType}, but got ${term.type}`);
|
||||
}
|
||||
|
||||
// expression terms
|
||||
class Term {
|
||||
constructor(type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class LiteralTerm extends Term {
|
||||
constructor(type, value) {
|
||||
super(type);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
class BoolLitTerm extends LiteralTerm {
|
||||
constructor(value) {
|
||||
super('boolean', value);
|
||||
}
|
||||
}
|
||||
|
||||
class NumLitTerm extends LiteralTerm {
|
||||
constructor(value) {
|
||||
super('number', value);
|
||||
}
|
||||
}
|
||||
|
||||
class StrLitTerm extends LiteralTerm {
|
||||
constructor(value) {
|
||||
super('string', value);
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryOpTerm extends Term {
|
||||
constructor(type, leftType, left, rightType, right) {
|
||||
super(type);
|
||||
staticCheck(leftType, left);
|
||||
staticCheck(rightType, right);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.apply(this.left.resolve(item, itemExt), this.right.resolve(item, itemExt));
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class LogicalTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'boolean', left, 'boolean', right);
|
||||
}
|
||||
}
|
||||
|
||||
class ConjTerm extends LogicalTerm {
|
||||
apply(a, b) {
|
||||
return a && b;
|
||||
}
|
||||
}
|
||||
|
||||
class DisjTerm extends LogicalTerm {
|
||||
apply(a, b) {
|
||||
return a || b;
|
||||
}
|
||||
}
|
||||
|
||||
class EqualityTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'any', left, 'any', right);
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
return (typeof a === 'string' && typeof b === 'string')
|
||||
? this.compare(a.toLowerCase(), b.toLowerCase()) : this.compare(a, b);
|
||||
}
|
||||
|
||||
compare(a, b) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class EqTerm extends EqualityTerm {
|
||||
compare(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
|
||||
class NeqTerm extends EqualityTerm {
|
||||
compare(a, b) {
|
||||
return a !== b;
|
||||
}
|
||||
}
|
||||
|
||||
class ContainsTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'string', left, 'string', right);
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
return a.toLowerCase().includes(b.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
class InequalityTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('boolean', 'any', left, 'any', right);
|
||||
}
|
||||
|
||||
apply(a, b) {
|
||||
checkComparable(a);
|
||||
checkComparable(b);
|
||||
return (typeof a === 'string' && typeof b === 'string')
|
||||
? this.compare(a.toLowerCase(), b.toLowerCase()) : this.compare(a, b);
|
||||
}
|
||||
|
||||
compare(a, b) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class LeqTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a <= b;
|
||||
}
|
||||
}
|
||||
|
||||
class LtTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
|
||||
class GtTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a > b;
|
||||
}
|
||||
}
|
||||
|
||||
class GeqTerm extends InequalityTerm {
|
||||
compare(a, b) {
|
||||
return a >= b;
|
||||
}
|
||||
}
|
||||
|
||||
class ArithmeticTerm extends BinaryOpTerm {
|
||||
constructor(left, right) {
|
||||
super('number', 'number', left, 'number', right);
|
||||
}
|
||||
}
|
||||
|
||||
class AddTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
}
|
||||
|
||||
class SubTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a - b;
|
||||
}
|
||||
}
|
||||
|
||||
class MulTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
}
|
||||
|
||||
class DivTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a / b;
|
||||
}
|
||||
}
|
||||
|
||||
class ExpTerm extends ArithmeticTerm {
|
||||
apply(a, b) {
|
||||
return a ** b;
|
||||
}
|
||||
}
|
||||
|
||||
class UnaryOpTerm extends Term {
|
||||
constructor(type, inType, inVal) {
|
||||
super(type);
|
||||
staticCheck(inType, inVal);
|
||||
this.inVal = inVal;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.apply(this.inVal.resolve(item, itemExt));
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
throw new Error('Abstract method!');
|
||||
}
|
||||
}
|
||||
|
||||
class NegTerm extends UnaryOpTerm {
|
||||
constructor(inVal) {
|
||||
super('number', 'number', inVal);
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
return -x;
|
||||
}
|
||||
}
|
||||
|
||||
class InvTerm extends UnaryOpTerm {
|
||||
constructor(inVal) {
|
||||
super('boolean', 'boolean', inVal);
|
||||
}
|
||||
|
||||
apply(x) {
|
||||
return !x;
|
||||
}
|
||||
}
|
||||
|
||||
class FnCallTerm extends Term {
|
||||
constructor(fn, argExprs) {
|
||||
super(fn.type);
|
||||
this.fn = fn;
|
||||
this.argExprs = argExprs;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
const argVals = [];
|
||||
for (const argExpr of this.argExprs) {
|
||||
argVals.push(argExpr.resolve(item, itemExt));
|
||||
}
|
||||
return this.fn.fn(item, itemExt, argVals);
|
||||
}
|
||||
}
|
||||
|
||||
class PropTerm extends Term {
|
||||
constructor(prop) {
|
||||
super(prop.type);
|
||||
this.prop = prop;
|
||||
}
|
||||
|
||||
resolve(item, itemExt) {
|
||||
return this.prop.resolve(item, itemExt);
|
||||
}
|
||||
}
|
||||
|
||||
function compareLexico(ia, keysA, ib, keysB) {
|
||||
for (let i = 0; i < keysA.length; i++) { // assuming keysA and keysB are the same length
|
||||
let aKey = keysA[i], bKey = keysB[i];
|
||||
if (typeof aKey !== typeof bKey) throw new Error(`Incomparable types ${typeof aKey} and ${typeof bKey}`); // can this even happen?
|
||||
switch (typeof aKey) {
|
||||
case 'string':
|
||||
aKey = aKey.toLowerCase();
|
||||
bKey = bKey.toLowerCase();
|
||||
if (aKey < bKey) return -1;
|
||||
if (aKey > bKey) return 1;
|
||||
break;
|
||||
case 'number': // sort numeric stuff in reverse order
|
||||
aKey = isNaN(aKey) ? 0 : aKey;
|
||||
bKey = isNaN(bKey) ? 0 : bKey;
|
||||
if (aKey < bKey) return 1;
|
||||
if (aKey > bKey) return -1;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Incomparable type ${typeof aKey}`);
|
||||
}
|
||||
}
|
||||
return ib.lvl - ia.lvl;
|
||||
}
|
|
@ -187,8 +187,17 @@ function calculate_skillpoints(equipment, weapon) {
|
|||
permute_check(idx+1, skillpoints_applied, skillpoints, activeSetCounts, has_skillpoint, total_applied, order.concat(permutation.map(x => x.item)));
|
||||
}
|
||||
}
|
||||
// skip root.
|
||||
permute_check(1, best_skillpoints, final_skillpoints, best_activeSetCounts, allFalse.slice(), 0, []);
|
||||
if (sccs.length === 1) {
|
||||
// Only crafteds. Just do end check (check req first, then apply sp after)
|
||||
const total = check_end(best_skillpoints, final_skillpoints, best_activeSetCounts, allFalse.slice());
|
||||
final_skillpoints = best_skillpoints;
|
||||
best_total = total;
|
||||
best_activeSetCounts = best_activeSetCounts;
|
||||
best = [];
|
||||
} else {
|
||||
// skip root.
|
||||
permute_check(1, best_skillpoints, final_skillpoints, best_activeSetCounts, allFalse.slice(), 0, []);
|
||||
}
|
||||
|
||||
// add extra sp bonus
|
||||
apply_skillpoints(final_skillpoints, weapon, best_activeSetCounts);
|
||||
|
|
264
js/sq2items.js
264
js/sq2items.js
|
@ -1,264 +0,0 @@
|
|||
let itemCategories = [ "armor", "accessory", "weapon" ];
|
||||
|
||||
const sq2_translate_mappings = {
|
||||
//"Name": "name",
|
||||
//"Display Name": "displayName",
|
||||
//"tier"Tier": ",
|
||||
//"Set": "set",
|
||||
"Powder Slots": "slots",
|
||||
//"Type": "type",
|
||||
//"armorType", (deleted)
|
||||
//"color", (deleted)
|
||||
//"lore", (deleted)
|
||||
//"material", (deleted)
|
||||
"Drop type": "drop",
|
||||
"Quest requirement": "quest",
|
||||
"Restriction": "restrict",
|
||||
//"Base Neutral Damage": "nDam",
|
||||
//"Base Fire Damage": "fDam",
|
||||
//"Base Water Damage": "wDam",
|
||||
//"Base Air Damage": "aDam",
|
||||
//"Base Thunder Damage": "tDam",
|
||||
//"Base Earth Damage": "eDam",
|
||||
//"Base Attack Speed": "atkSpd",
|
||||
"Health": "hp",
|
||||
"Raw Fire Defense": "fDef",
|
||||
"Raw Water Defense": "wDef",
|
||||
"Raw Air Defense": "aDef",
|
||||
"Raw Thunder Defense": "tDef",
|
||||
"Raw Earth Defense": "eDef",
|
||||
"Combat Level": "lvl",
|
||||
//"Class Requirement": "classReq",
|
||||
"Req Strength": "strReq",
|
||||
"Req Dexterity": "dexReq",
|
||||
"Req Intelligence": "intReq",
|
||||
"Req Agility": "agiReq",
|
||||
"Req Defense": "defReq",
|
||||
"% Health Regen": "hprPct",
|
||||
"Mana Regen": "mr",
|
||||
"% Spell Damage": "sdPct",
|
||||
"% Melee Damage": "mdPct",
|
||||
"Life Steal": "ls",
|
||||
"Mana Steal": "ms",
|
||||
"XP Bonus": "xpb",
|
||||
"Loot Bonus": "lb",
|
||||
"Reflection": "ref",
|
||||
"Strength": "str",
|
||||
"Dexterity": "dex",
|
||||
"Intelligence": "int",
|
||||
"Agility": "agi",
|
||||
"Defense": "def",
|
||||
"Thorns": "thorns",
|
||||
"Exploding": "expd",
|
||||
"Walk Speed": "spd",
|
||||
"Attack Speed Bonus": "atkTier",
|
||||
"Poison": "poison",
|
||||
"Health Bonus": "hpBonus",
|
||||
"Soul Point Regen": "spRegen",
|
||||
"Stealing": "eSteal",
|
||||
"Raw Health Regen": "hprRaw",
|
||||
"Raw Spell": "sdRaw",
|
||||
"Raw Melee": "mdRaw",
|
||||
"% Fire Damage": "fDamPct",
|
||||
"% Water Damage": "wDamPct",
|
||||
"% Air Damage": "aDamPct",
|
||||
"% Thunder Damage": "tDamPct",
|
||||
"% Earth Damage": "eDamPct",
|
||||
"% Fire Defense": "fDefPct",
|
||||
"% Water Defense": "wDefPct",
|
||||
"% Air Defense": "aDefPct",
|
||||
"% Thunder Defense": "tDefPct",
|
||||
"% Earth Defense": "eDefPct",
|
||||
"Fixed IDs": "fixID",
|
||||
"Custom Skin": "skin",
|
||||
//"Item Category": "category",
|
||||
|
||||
"1st Spell Cost %": "spPct1",
|
||||
"1st Spell Cost Raw": "spRaw1",
|
||||
"2nd Spell Cost %": "spPct2",
|
||||
"2nd Spell Cost Raw": "spRaw2",
|
||||
"3rd Spell Cost %": "spPct3",
|
||||
"3rd Spell Cost Raw": "spRaw3",
|
||||
"4th Spell Cost %": "spPct4",
|
||||
"4th Spell Cost Raw": "spRaw4",
|
||||
|
||||
"Rainbow Spell Damage": "rainbowRaw",
|
||||
"Sprint": "sprint",
|
||||
"Sprint Regen": "sprintReg",
|
||||
"Jump Height": "jh",
|
||||
"Loot Quality": "lq",
|
||||
|
||||
"Gather XP Bonus": "gXp",
|
||||
"Gather Speed Bonus": "gSpd",
|
||||
};
|
||||
|
||||
const sq2_special_mappings = {
|
||||
"Sum (skill points)": new SumQuery(["str", "dex", "int", "def", "agi"]),
|
||||
"Sum (Mana Sustain)": new SumQuery(["mr", "ms"]),
|
||||
"Sum (Life Sustain)": new SumQuery(["hpr", "ls"]),
|
||||
"Sum (Health + Health Bonus)": new SumQuery(["hp", "hpBonus"]),
|
||||
"No Strength Req": new NegateQuery("strReq"),
|
||||
"No Dexterity Req": new NegateQuery("dexReq"),
|
||||
"No Intelligence Req": new NegateQuery("intReq"),
|
||||
"No Agility Req": new NegateQuery("agiReq"),
|
||||
"No Defense Req": new NegateQuery("defReq"),
|
||||
};
|
||||
|
||||
let sq2ItemFilters = []
|
||||
for (let x in sq2_translate_mappings) {
|
||||
sq2ItemFilters.push(x);
|
||||
}
|
||||
for (let x in sq2_special_mappings) {
|
||||
sq2ItemFilters.push(x);
|
||||
}
|
||||
|
||||
function applyQuery(items, query) {
|
||||
return items.filter(query.filter, query).sort(query.compare);
|
||||
}
|
||||
|
||||
function displayItems(items_copy) {
|
||||
let items_parent = document.getElementById("search-results");
|
||||
for (let i in items_copy) {
|
||||
if (i > 200) {break;}
|
||||
let item = items_copy[i];
|
||||
let box = document.createElement("div");
|
||||
box.classList.add("col-lg-3", "col-sm-6", "p-2");
|
||||
box.id = "item"+i;
|
||||
box.addEventListener("dblclick", function() {set_item(item);});
|
||||
|
||||
let bckgrdbox = document.createElement("div");
|
||||
bckgrdbox.classList.add("dark-7", "rounded", "px-2", "col-auto");
|
||||
box.appendChild(bckgrdbox);
|
||||
bckgrdbox.id = "item"+i+"b";
|
||||
items_parent.appendChild(box);
|
||||
item.set("powders", []);
|
||||
if (item.get("category") == "weapon") {
|
||||
apply_weapon_powders(item);
|
||||
}
|
||||
displayExpandedItem(item, bckgrdbox.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
let items_expanded;
|
||||
|
||||
function doItemSearch() {
|
||||
// window.scrollTo(0, 0);
|
||||
let queries = [];
|
||||
queries.push(new NameQuery(document.getElementById("item-name-choice").value.trim()));
|
||||
|
||||
let categoryOrType = document.getElementById("item-category-choice").value;
|
||||
if (itemTypes.includes(categoryOrType)) {
|
||||
queries.push(new IdMatchQuery("type", categoryOrType));
|
||||
}
|
||||
else if (itemCategories.includes(categoryOrType)) {
|
||||
queries.push(new IdMatchQuery("category", categoryOrType));
|
||||
}
|
||||
|
||||
let rarity = document.getElementById("item-rarity-choice").value;
|
||||
if (rarity) {
|
||||
if (rarity === "ANY") {
|
||||
|
||||
}
|
||||
else {
|
||||
queries.push(new IdMatchQuery("tier", rarity));
|
||||
}
|
||||
}
|
||||
|
||||
let level_dat = document.getElementById("item-level-choice").value ? document.getElementById("item-level-choice").value.split("-") : [1, 106];
|
||||
queries.push(new LevelRangeQuery(parseInt(level_dat[0]), parseInt(level_dat[1])));
|
||||
|
||||
for (let i = 1; i <= 4; ++i) {
|
||||
let raw_dat = document.getElementById("filter"+i+"-choice").value;
|
||||
let filter_dat = sq2_translate_mappings[raw_dat];
|
||||
if (filter_dat !== undefined) {
|
||||
queries.push(new IdQuery(filter_dat));
|
||||
continue;
|
||||
}
|
||||
filter_dat = sq2_special_mappings[raw_dat];
|
||||
if (filter_dat !== undefined) {
|
||||
queries.push(filter_dat);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let items_copy = items_expanded.slice();
|
||||
document.getElementById("search-results").textContent = "";
|
||||
for (const query of queries) {
|
||||
console.log(items_copy.length);
|
||||
console.log(query, query.filter);
|
||||
items_copy = applyQuery(items_copy, query);
|
||||
console.log(items_copy.length);
|
||||
}
|
||||
document.getElementById("summary").textContent = items_copy.length + " results:"
|
||||
displayItems(items_copy);
|
||||
}
|
||||
|
||||
function resetItemSearch() {
|
||||
resetFields = ["item-name-choice", "item-category-choice", "item-rarity-choice", "item-level-choice", "filter1-choice", "filter2-choice", "filter3-choice", "filter4-choice"]
|
||||
for (const field of resetFields) {
|
||||
document.getElementById(field).value = "";
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
(async function() {
|
||||
await Promise.resolve(load_init());
|
||||
init_items();
|
||||
})();
|
66
js/utils.js
66
js/utils.js
|
@ -1,6 +1,14 @@
|
|||
let getUrl = window.location;
|
||||
const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
||||
|
||||
// huge regex :doom:
|
||||
// replace with navigator.userAgentData.mobile once it has wider support
|
||||
const isMobile = function() {
|
||||
let check = false;
|
||||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
|
||||
return check;
|
||||
}(); // runs immediately, so mobileCheck is a boolean not a function
|
||||
|
||||
const zip2 = (a, b) => a.map((k, i) => [k, b[i]]);
|
||||
const zip3 = (a, b, c) => a.map((k, i) => [k, b[i], c[i]]);
|
||||
|
||||
|
@ -604,16 +612,6 @@ function matchType(object, target) {
|
|||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add multiple classes to a html element
|
||||
*/
|
||||
function addClasses(elem, classes) {
|
||||
for (let _class of classes) {
|
||||
elem.classList.add(_class);
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
*/
|
||||
|
@ -884,6 +882,17 @@ function make_elem(type, classlist = [], args = {}) {
|
|||
const ret_elem = document.createElement(type);
|
||||
ret_elem.classList.add(...classlist);
|
||||
for (const i in args) {
|
||||
if (i === 'style') {
|
||||
const style_obj = args[i];
|
||||
if (typeof style_obj === 'string' || style_obj instanceof String) {
|
||||
ret_elem.style = style_obj;
|
||||
continue;
|
||||
}
|
||||
for (const k in style_obj) {
|
||||
ret_elem.style[k] = style_obj[k];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ret_elem[i] = args[i];
|
||||
}
|
||||
return ret_elem;
|
||||
|
@ -952,3 +961,40 @@ function make_SCC_graph(root_node, nodes) {
|
|||
}
|
||||
return sccs;
|
||||
}
|
||||
|
||||
|
||||
// Toggles display of a certain element, given the ID.
|
||||
function toggle_tab(tab) {
|
||||
let elem = document.getElementById(tab);
|
||||
if (elem.style.display == "none") {
|
||||
elem.style.display = "";
|
||||
} else {
|
||||
elem.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle display of a certain tab, in a group of tabs, given the target tab ID, and a list of associated tabs.
|
||||
// Also sets visual display of an element with ID of target + "-btn" to selected.
|
||||
function show_tab(target, tabs) {
|
||||
//hide all tabs, then show the tab of the div clicked and highlight the correct button
|
||||
for (const i in tabs) {
|
||||
document.getElementById(tabs[i]).style.display = "none";
|
||||
document.getElementById(tabs[i] + "-btn").classList.remove("selected-btn");
|
||||
}
|
||||
document.getElementById(target).style.display = "";
|
||||
document.getElementById(target + "-btn").classList.add("selected-btn");
|
||||
}
|
||||
|
||||
// mobile navbar appearance control
|
||||
let scrollPos = 0
|
||||
if (screen.width < 992) {
|
||||
document.addEventListener('scroll', (e) => {
|
||||
if (document.documentElement.scrollTop - scrollPos > 20) {
|
||||
document.getElementById("mobile-navbar").style.display = "none";
|
||||
document.getElementById("mobile-navbar-dropdown").style.display = "none";
|
||||
} else if (document.documentElement.scrollTop - scrollPos < -50 || scrollPos < 70) {
|
||||
document.getElementById("mobile-navbar").style.display = "";
|
||||
}
|
||||
scrollPos = document.documentElement.scrollTop;
|
||||
});
|
||||
}
|
|
@ -45,8 +45,51 @@
|
|||
<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 id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
|
||||
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
|
||||
<div class="navbar-brand mx-auto scaled-font" style="height: 100%;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</div>
|
||||
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
|
||||
</div>
|
||||
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
|
||||
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</a>
|
||||
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</a>
|
||||
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</a>
|
||||
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</a>
|
||||
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</a>
|
||||
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</a>
|
||||
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
|
||||
<span>Swap Icon Style</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class = "container row py-5 vh-100 mx-0 mx-lg-auto scaled-font">
|
||||
<div class = "container row py-5 vh-100 mx-0 mx-lg-auto scaled-font mt-lg-2" style="margin-top: 6vh;">
|
||||
<div id = "mapdiv" class = "col-lg-8 col-sm-12 rounded border border-light border-3">
|
||||
|
||||
</div>
|
||||
|
|
168
tomes.json
168
tomes.json
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"tomes":
|
||||
[
|
||||
"tomes": [
|
||||
{
|
||||
"name": "Retaliating Tome of Armour Mastery I",
|
||||
"tier": "Fabled",
|
||||
|
@ -14,7 +13,8 @@
|
|||
"ref": 6,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 0
|
||||
"id": 0,
|
||||
"alias": "Thorns I"
|
||||
},
|
||||
{
|
||||
"name": "Retaliating Tome of Armour Mastery II",
|
||||
|
@ -28,7 +28,8 @@
|
|||
"thorns": 8,
|
||||
"ref": 8,
|
||||
"fixID": false,
|
||||
"id": 1
|
||||
"id": 1,
|
||||
"alias": "Thorns II"
|
||||
},
|
||||
{
|
||||
"name": "Destructive Tome of Armour Mastery I",
|
||||
|
@ -43,7 +44,8 @@
|
|||
"mdPct": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 2
|
||||
"id": 2,
|
||||
"alias": "Melee I"
|
||||
},
|
||||
{
|
||||
"name": "Destructive Tome of Armour Mastery II",
|
||||
|
@ -57,7 +59,8 @@
|
|||
"thorns": 6,
|
||||
"reflection": 6,
|
||||
"fixID": false,
|
||||
"id": 3
|
||||
"id": 3,
|
||||
"alias": "Melee II"
|
||||
},
|
||||
{
|
||||
"name": "Sorcerer's Tome of Armour Mastery I",
|
||||
|
@ -71,7 +74,8 @@
|
|||
"sdPct": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 4
|
||||
"id": 4,
|
||||
"alias": "Spell Damage I"
|
||||
},
|
||||
{
|
||||
"name": "Sorcerer's Tome of Armour Mastery II",
|
||||
|
@ -84,7 +88,8 @@
|
|||
"defMobs": 5,
|
||||
"sdPct": 6,
|
||||
"fixID": false,
|
||||
"id": 5
|
||||
"id": 5,
|
||||
"alias": "Spell Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Everlasting Tome of Armour Mastery I",
|
||||
|
@ -98,7 +103,8 @@
|
|||
"hprRaw": 15,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 6
|
||||
"id": 6,
|
||||
"alias": "Health Regen I"
|
||||
},
|
||||
{
|
||||
"name": "Everlasting Tome of Armour Mastery II",
|
||||
|
@ -111,7 +117,8 @@
|
|||
"defMobs": 5,
|
||||
"hprRaw": 60,
|
||||
"fixID": false,
|
||||
"id": 7
|
||||
"id": 7,
|
||||
"alias": "Health Regen II"
|
||||
},
|
||||
{
|
||||
"name": "Vampiric Tome of Armour Mastery I",
|
||||
|
@ -125,7 +132,8 @@
|
|||
"ls": 25,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 8
|
||||
"id": 8,
|
||||
"alias": "Life Steal I"
|
||||
},
|
||||
{
|
||||
"name": "Vampiric Tome of Armour Mastery II",
|
||||
|
@ -138,7 +146,8 @@
|
|||
"defMobs": 5,
|
||||
"ls": 85,
|
||||
"fixID": false,
|
||||
"id": 9
|
||||
"id": 9,
|
||||
"alias": "Life Steal II"
|
||||
},
|
||||
{
|
||||
"name": "Greedy Tome of Armour Mastery I",
|
||||
|
@ -152,7 +161,8 @@
|
|||
"lb": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 10
|
||||
"id": 10,
|
||||
"alias": "Loot Bonus I"
|
||||
},
|
||||
{
|
||||
"name": "Greedy Tome of Armour Mastery II",
|
||||
|
@ -165,7 +175,8 @@
|
|||
"defMobs": 5,
|
||||
"lb": 6,
|
||||
"fixID": false,
|
||||
"id": 11
|
||||
"id": 11,
|
||||
"alias": "Loot Bonus II"
|
||||
},
|
||||
{
|
||||
"name": "Weightless Tome of Armour Mastery I",
|
||||
|
@ -179,7 +190,8 @@
|
|||
"spd": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
"id": 12
|
||||
"id": 12,
|
||||
"alias": "Walk Speed I"
|
||||
},
|
||||
{
|
||||
"name": "Weightless Tome of Armour Mastery II",
|
||||
|
@ -192,7 +204,8 @@
|
|||
"defMobs": 5,
|
||||
"spd": 6,
|
||||
"fixID": false,
|
||||
"id": 13
|
||||
"id": 13,
|
||||
"alias": "Walk Speed II"
|
||||
},
|
||||
{
|
||||
"name": "Blooming Tome of Armour Mastery II",
|
||||
|
@ -206,7 +219,8 @@
|
|||
"eDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
"id": 14
|
||||
"id": 14,
|
||||
"alias": "Earth Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Pulsing Tome of Armour Mastery II",
|
||||
|
@ -220,7 +234,8 @@
|
|||
"tDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
"id": 15
|
||||
"id": 15,
|
||||
"alias": "Thunder Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Oceanic Tome of Armour Mastery II",
|
||||
|
@ -234,7 +249,8 @@
|
|||
"wDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
"id": 16
|
||||
"id": 16,
|
||||
"alias": "Water Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Courageous Tome of Armour Mastery II",
|
||||
|
@ -248,7 +264,8 @@
|
|||
"fDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
"id": 17
|
||||
"id": 17,
|
||||
"alias": "Fire Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Clouded Tome of Armour Mastery II",
|
||||
|
@ -262,7 +279,8 @@
|
|||
"aDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
"id": 18
|
||||
"id": 18,
|
||||
"alias": "Air Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Radiant Tome of Armour Mastery II",
|
||||
|
@ -280,7 +298,8 @@
|
|||
"aDefPct": 6,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
"id": 19
|
||||
"id": 19,
|
||||
"alias": "Rainbow Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Tome of Weapon Mastery I",
|
||||
|
@ -292,7 +311,8 @@
|
|||
"lvl": 60,
|
||||
"damMobs": 6,
|
||||
"fixID": false,
|
||||
"id": 20
|
||||
"id": 20,
|
||||
"alias": "Weapon Mastery I"
|
||||
},
|
||||
{
|
||||
"name": "Earthbound Tome of Weapon Mastery I",
|
||||
|
@ -305,7 +325,8 @@
|
|||
"damMobs": 7,
|
||||
"str": 3,
|
||||
"fixID": false,
|
||||
"id": 21
|
||||
"id": 21,
|
||||
"alias": "Strength I"
|
||||
},
|
||||
{
|
||||
"name": "Earthbound Tome of Weapon Mastery II",
|
||||
|
@ -318,7 +339,8 @@
|
|||
"damMobs": 8,
|
||||
"str": 3,
|
||||
"fixID": false,
|
||||
"id": 22
|
||||
"id": 22,
|
||||
"alias": "Strength II"
|
||||
},
|
||||
{
|
||||
"name": "Nimble Tome of Weapon Mastery I",
|
||||
|
@ -331,7 +353,8 @@
|
|||
"damMobs": 7,
|
||||
"dex": 3,
|
||||
"fixID": false,
|
||||
"id": 23
|
||||
"id": 23,
|
||||
"alias": "Dexterity I"
|
||||
},
|
||||
{
|
||||
"name": "Nimble Tome of Weapon Mastery II",
|
||||
|
@ -344,7 +367,8 @@
|
|||
"damMobs": 8,
|
||||
"dex": 3,
|
||||
"fixID": false,
|
||||
"id": 24
|
||||
"id": 24,
|
||||
"alias": "Dexterity II"
|
||||
},
|
||||
{
|
||||
"name": "Mystical Tome of Weapon Mastery I",
|
||||
|
@ -357,7 +381,8 @@
|
|||
"damMobs": 7,
|
||||
"int": 3,
|
||||
"fixID": false,
|
||||
"id": 25
|
||||
"id": 25,
|
||||
"alias": "Intelligence I"
|
||||
},
|
||||
{
|
||||
"name": "Mystical Tome of Weapon Mastery II",
|
||||
|
@ -370,7 +395,8 @@
|
|||
"damMobs": 8,
|
||||
"int": 3,
|
||||
"fixID": false,
|
||||
"id": 26
|
||||
"id": 26,
|
||||
"alias": "Intelligence II"
|
||||
},
|
||||
{
|
||||
"name": "Warding Tome of Weapon Mastery I",
|
||||
|
@ -383,7 +409,8 @@
|
|||
"damMobs": 7,
|
||||
"def": 3,
|
||||
"fixID": false,
|
||||
"id": 27
|
||||
"id": 27,
|
||||
"alias": "Defense I"
|
||||
},
|
||||
{
|
||||
"name": "Warding Tome of Weapon Mastery II",
|
||||
|
@ -396,7 +423,8 @@
|
|||
"damMobs": 8,
|
||||
"def": 3,
|
||||
"fixID": false,
|
||||
"id": 28
|
||||
"id": 28,
|
||||
"alias": "Defense II"
|
||||
},
|
||||
{
|
||||
"name": "Athletic Tome of Weapon Mastery I",
|
||||
|
@ -409,7 +437,8 @@
|
|||
"damMobs": 7,
|
||||
"agi": 3,
|
||||
"fixID": false,
|
||||
"id": 29
|
||||
"id": 29,
|
||||
"alias": "Agility I"
|
||||
},
|
||||
{
|
||||
"name": "Athletic Tome of Weapon Mastery II",
|
||||
|
@ -422,7 +451,8 @@
|
|||
"damMobs": 8,
|
||||
"agi": 3,
|
||||
"fixID": false,
|
||||
"id": 30
|
||||
"id": 30,
|
||||
"alias": "Agility II"
|
||||
},
|
||||
{
|
||||
"name": "Cosmic Tome of Weapon Mastery I",
|
||||
|
@ -439,7 +469,8 @@
|
|||
"def": 1,
|
||||
"agi": 1,
|
||||
"fixID": false,
|
||||
"id": 31
|
||||
"id": 31,
|
||||
"alias": "Rainbow Skillpoint I"
|
||||
},
|
||||
{
|
||||
"name": "Cosmic Tome of Weapon Mastery II",
|
||||
|
@ -456,7 +487,8 @@
|
|||
"def": 1,
|
||||
"agi": 1,
|
||||
"fixID": false,
|
||||
"id": 32
|
||||
"id": 32,
|
||||
"alias": "Rainbow Skillpoint II"
|
||||
},
|
||||
{
|
||||
"name": "Seismic Tome of Weapon Mastery II",
|
||||
|
@ -469,7 +501,8 @@
|
|||
"damMobs": 12,
|
||||
"eDamPct": 7,
|
||||
"fixID": false,
|
||||
"id": 33
|
||||
"id": 33,
|
||||
"alias": "Earth Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Voltaic Tome of Weapon Mastery II",
|
||||
|
@ -482,7 +515,8 @@
|
|||
"damMobs": 12,
|
||||
"tDamPct": 7,
|
||||
"fixID": false,
|
||||
"id": 34
|
||||
"id": 34,
|
||||
"alias": "Thunder Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Abyssal Tome of Weapon Mastery II",
|
||||
|
@ -495,7 +529,8 @@
|
|||
"damMobs": 12,
|
||||
"wDamPct": 7,
|
||||
"fixID": false,
|
||||
"id": 35
|
||||
"id": 35,
|
||||
"alias": "Water Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Infernal Tome of Weapon Mastery II",
|
||||
|
@ -508,7 +543,8 @@
|
|||
"damMobs": 12,
|
||||
"fDamPct": 7,
|
||||
"fixID": false,
|
||||
"id": 36
|
||||
"id": 36,
|
||||
"alias": "Fire Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Cyclonic Tome of Weapon Mastery II",
|
||||
|
@ -521,7 +557,8 @@
|
|||
"damMobs": 12,
|
||||
"aDamPct": 7,
|
||||
"fixID": false,
|
||||
"id": 37
|
||||
"id": 37,
|
||||
"alias": "Air Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Astral Tome of Weapon Mastery II",
|
||||
|
@ -538,7 +575,8 @@
|
|||
"fDamPct": 6,
|
||||
"aDamPct": 6,
|
||||
"fixID": false,
|
||||
"id": 38
|
||||
"id": 38,
|
||||
"alias": "Rainbow Damage II"
|
||||
},
|
||||
{
|
||||
"name": "Brute's Tome of Allegiance",
|
||||
|
@ -551,7 +589,8 @@
|
|||
"str": 3,
|
||||
"eDamPct": 2,
|
||||
"fixID": false,
|
||||
"id": 39
|
||||
"id": 39,
|
||||
"alias": "Strength"
|
||||
},
|
||||
{
|
||||
"name": "Sadist's Tome of Allegiance",
|
||||
|
@ -564,7 +603,8 @@
|
|||
"dex": 3,
|
||||
"tDamPct": 2,
|
||||
"fixID": false,
|
||||
"id": 40
|
||||
"id": 40,
|
||||
"alias": "Dexterity"
|
||||
},
|
||||
{
|
||||
"name": "Mastermind's Tome of Allegiance",
|
||||
|
@ -577,7 +617,8 @@
|
|||
"int": 3,
|
||||
"wDamPct": 2,
|
||||
"fixID": false,
|
||||
"id": 41
|
||||
"id": 41,
|
||||
"alias": "Intelligence"
|
||||
},
|
||||
{
|
||||
"name": "Arsonist's Tome of Allegiance",
|
||||
|
@ -590,7 +631,8 @@
|
|||
"def": 3,
|
||||
"fDamPct": 2,
|
||||
"fixID": false,
|
||||
"id": 42
|
||||
"id": 42,
|
||||
"alias": "Defense"
|
||||
},
|
||||
{
|
||||
"name": "Ghost's Tome of Allegiance",
|
||||
|
@ -603,7 +645,8 @@
|
|||
"agi": 3,
|
||||
"aDamPct": 2,
|
||||
"fixID": false,
|
||||
"id": 43
|
||||
"id": 43,
|
||||
"alias": "Agility"
|
||||
},
|
||||
{
|
||||
"name": "Psychopath's Tome of Allegiance",
|
||||
|
@ -616,7 +659,8 @@
|
|||
"str": 2,
|
||||
"dex": 2,
|
||||
"fixID": false,
|
||||
"id": 44
|
||||
"id": 44,
|
||||
"alias": "ET"
|
||||
},
|
||||
{
|
||||
"name": "Loner's Tome of Allegiance",
|
||||
|
@ -629,7 +673,8 @@
|
|||
"str": 2,
|
||||
"int": 2,
|
||||
"fixID": false,
|
||||
"id": 45
|
||||
"id": 45,
|
||||
"alias": "EW"
|
||||
},
|
||||
{
|
||||
"name": "Warlock's Tome of Allegiance",
|
||||
|
@ -642,7 +687,8 @@
|
|||
"dex": 2,
|
||||
"int": 2,
|
||||
"fixID": false,
|
||||
"id": 46
|
||||
"id": 46,
|
||||
"alias": "TW"
|
||||
},
|
||||
{
|
||||
"name": "Destroyer's Tome of Allegiance",
|
||||
|
@ -655,7 +701,8 @@
|
|||
"str": 2,
|
||||
"def": 2,
|
||||
"fixID": false,
|
||||
"id": 47
|
||||
"id": 47,
|
||||
"alias": "EF"
|
||||
},
|
||||
{
|
||||
"name": "Devil's Tome of Allegiance",
|
||||
|
@ -668,7 +715,8 @@
|
|||
"dex": 2,
|
||||
"def": 2,
|
||||
"fixID": false,
|
||||
"id": 48
|
||||
"id": 48,
|
||||
"alias": "TF"
|
||||
},
|
||||
{
|
||||
"name": "Alchemist's Tome of Allegiance",
|
||||
|
@ -681,7 +729,8 @@
|
|||
"int": 2,
|
||||
"def": 2,
|
||||
"fixID": false,
|
||||
"id": 49
|
||||
"id": 49,
|
||||
"alias": "WF"
|
||||
},
|
||||
{
|
||||
"name": "Barbarian's Tome of Allegiance",
|
||||
|
@ -694,7 +743,8 @@
|
|||
"str": 2,
|
||||
"agi": 2,
|
||||
"fixID": false,
|
||||
"id": 50
|
||||
"id": 50,
|
||||
"alias": "EA"
|
||||
},
|
||||
{
|
||||
"name": "Freelancer's Tome of Allegiance",
|
||||
|
@ -707,7 +757,8 @@
|
|||
"dex": 2,
|
||||
"agi": 2,
|
||||
"fixID": false,
|
||||
"id": 51
|
||||
"id": 51,
|
||||
"alias": "TA"
|
||||
},
|
||||
{
|
||||
"name": "Sycophant's Tome of Allegiance",
|
||||
|
@ -720,7 +771,8 @@
|
|||
"int": 2,
|
||||
"agi": 2,
|
||||
"fixID": false,
|
||||
"id": 52
|
||||
"id": 52,
|
||||
"alias": "WA"
|
||||
},
|
||||
{
|
||||
"name": "Fanatic's Tome of Allegiance",
|
||||
|
@ -733,7 +785,8 @@
|
|||
"def": 2,
|
||||
"agi": 2,
|
||||
"fixID": false,
|
||||
"id": 53
|
||||
"id": 53,
|
||||
"alias": "FA"
|
||||
},
|
||||
{
|
||||
"name": "Assimilator's Tome of Allegiance",
|
||||
|
@ -749,7 +802,8 @@
|
|||
"def": 1,
|
||||
"agi": 1,
|
||||
"fixID": false,
|
||||
"id": 54
|
||||
"id": 54,
|
||||
"alias": "Rainbow"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -31,7 +31,50 @@
|
|||
<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 scaled-font" id = "main">
|
||||
<div id="mobile-navbar" class="navbar dark-5 dark-shadow fixed-top d-lg-none pb-0">
|
||||
<div class="container-fluid scaled-font justify-content-center" style="height: 5vh;">
|
||||
<div class="navbar-brand mx-auto scaled-font" style="height: 100%;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</div>
|
||||
<button class="btn dropdown-toggle dark-2 px-4 text-white scaled-font border-dark border-3" onclick="toggle_tab('mobile-navbar-dropdown');"></button>
|
||||
</div>
|
||||
<div class="container-fluid scaled-font dark-3 px-3 py-3" id="mobile-navbar-dropdown" style="display: none;">
|
||||
<a href="../builder/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/builder.png" alt="" style="height: 100%;">
|
||||
<span>WynnBuilder</span>
|
||||
</a>
|
||||
<a href="../crafter/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/crafter.png" alt="" style="height: 100%;">
|
||||
<span>WynnCrafter</span>
|
||||
</a>
|
||||
<a href="../items/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/searcher.png" alt="" style="height: 100%;">
|
||||
<span>WynnAtlas</span>
|
||||
</a>
|
||||
<a href="../custom/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/custom.png" alt="" style="height: 100%;">
|
||||
<span>WynnCustom</span>
|
||||
</a>
|
||||
<a href="../map/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/compass.png" alt="" style="height: 100%;">
|
||||
<span>WynnGPS</span>
|
||||
</a>
|
||||
<a href="../wynnfo/" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/book.png" alt="" style="height: 100%;">
|
||||
<span>WynnFo</span>
|
||||
</a>
|
||||
<a onclick = "toggleIcons()" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/new/reload.png" alt="" style="height: 100%;">
|
||||
<span>Swap Icon Style</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/CGavnAnerv" class="w-100 mb-3 text-white" style="height: 5vh; text-decoration: none;">
|
||||
<img src="../media/icons/discord.png" alt="" style="height: 100%;">
|
||||
<span>Discord</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "container py-5 vh-100 mx-0 mx-lg-auto scaled-font mt-lg-2" id = "main" style="margin-top: 6vh;">
|
||||
<div class = "row item-title">Welcome!</div>
|
||||
<p class = "row">This page is the main page for Wynnfo. Wynnfo is Wynnbuilder's page for all sorts of Wynncraft-related literature, including code documenation, game mechanic novels, and other literature! Browse at your leisure below.</p>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue