merge master into atlas-revamp

This commit is contained in:
fin444 2022-08-09 11:37:53 -07:00
commit e071140680
27 changed files with 5789 additions and 3102 deletions

File diff suppressed because one or more lines are too long

View file

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

3615
clean.json

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

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

View file

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

View file

@ -200,8 +200,8 @@
"lvl": 75,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdPct": {
"minimum": -22,
@ -9834,7 +9834,7 @@
"ids": {
"mr": {
"minimum": 0,
"maximum": 5
"maximum": 6
}
},
"itemIDs": {
@ -10533,7 +10533,7 @@
"ids": {
"ms": {
"minimum": 0,
"maximum": 5
"maximum": 4
}
},
"itemIDs": {
@ -10882,8 +10882,8 @@
"maximum": 15
},
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
}
},
"itemIDs": {
@ -11906,8 +11906,8 @@
"maximum": -5
},
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"gXp": {
"minimum": 4,
@ -14594,8 +14594,8 @@
"lvl": 76,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"spd": {
"minimum": -12,
@ -15404,8 +15404,8 @@
"lvl": 28,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"xpb": {
"minimum": -10,
@ -15569,8 +15569,8 @@
"lvl": 102,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"hpBonus": {
"minimum": -260,
@ -15684,8 +15684,8 @@
"lvl": 87,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdRaw": {
"minimum": 65,
@ -15762,8 +15762,8 @@
"lvl": 65,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
}
},
"itemIDs": {
@ -16037,8 +16037,8 @@
"lvl": 77,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"hpBonus": {
"minimum": -775,
@ -16270,8 +16270,8 @@
"lvl": 50,
"ids": {
"mr": {
"minimum": -5,
"maximum": -5
"minimum": -6,
"maximum": -6
},
"sdPct": {
"minimum": 8,
@ -16355,8 +16355,8 @@
"lvl": 90,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdPct": {
"minimum": 6,
@ -16396,8 +16396,8 @@
"lvl": 90,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"mdRaw": {
"minimum": 50,
@ -16734,8 +16734,8 @@
"maximum": 20
},
"ms": {
"minimum": -5,
"maximum": -5
"minimum": -4,
"maximum": -4
},
"fDamPct": {
"minimum": 10,
@ -16777,8 +16777,8 @@
"lvl": 99,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdPct": {
"minimum": 10,
@ -16944,8 +16944,8 @@
"maximum": 10
},
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
}
},
"itemIDs": {
@ -17488,12 +17488,12 @@
"lvl": 23,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"ms": {
"minimum": 0,
"maximum": 5
"maximum": 4
}
},
"itemIDs": {
@ -18623,8 +18623,8 @@
"maximum": -100
},
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"wDamPct": {
"minimum": 10,
@ -18908,8 +18908,8 @@
"lvl": 29,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdPct": {
"minimum": -8,
@ -18947,8 +18947,8 @@
"lvl": 105,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"agi": {
"minimum": 8,
@ -19535,8 +19535,8 @@
"lvl": 104,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"gXp": {
"minimum": 3,
@ -21060,8 +21060,8 @@
"lvl": 75,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"int": {
"minimum": 3,
@ -21387,12 +21387,12 @@
"lvl": 78,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"ms": {
"minimum": -5,
"maximum": -5
"minimum": -4,
"maximum": -4
},
"wDamPct": {
"minimum": 5,
@ -21550,8 +21550,8 @@
"lvl": 93,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"sdRaw": {
"minimum": 45,
@ -21595,8 +21595,8 @@
"lvl": 20,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"sdRaw": {
"minimum": 8,
@ -21958,8 +21958,8 @@
"lvl": 43,
"ids": {
"mr": {
"minimum": -5,
"maximum": -5
"minimum": -6,
"maximum": -6
},
"sdPct": {
"minimum": 11,
@ -22216,8 +22216,8 @@
"lvl": 75,
"ids": {
"mr": {
"minimum": -5,
"maximum": -5
"minimum": -6,
"maximum": -6
},
"sdPct": {
"minimum": 15,
@ -22714,8 +22714,8 @@
"lvl": 80,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdPct": {
"minimum": -20,
@ -22911,8 +22911,8 @@
"lvl": 8,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"ref": {
"minimum": 2,
@ -22988,8 +22988,8 @@
"lvl": 45,
"ids": {
"mr": {
"minimum": 10,
"maximum": 10
"minimum": 12,
"maximum": 12
}
},
"itemIDs": {
@ -23583,8 +23583,8 @@
"lvl": 42,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
}
},
"itemIDs": {
@ -23878,8 +23878,8 @@
"lvl": 50,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdPct": {
"minimum": -12,
@ -23957,8 +23957,8 @@
"lvl": 55,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"spd": {
"minimum": 8,
@ -24035,8 +24035,8 @@
"lvl": 43,
"ids": {
"mr": {
"minimum": -10,
"maximum": -10
"minimum": -12,
"maximum": -12
},
"sdPct": {
"minimum": 24,
@ -24120,8 +24120,8 @@
"maximum": 14
},
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"hprRaw": {
"minimum": 24,
@ -24160,8 +24160,8 @@
"lvl": 92,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"sdRaw": {
"minimum": 110,
@ -24373,8 +24373,8 @@
"lvl": 90,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"spd": {
"minimum": 16,
@ -25193,8 +25193,8 @@
"maximum": 20
},
"mr": {
"minimum": -5,
"maximum": -5
"minimum": -6,
"maximum": -6
}
},
"itemIDs": {
@ -25249,8 +25249,8 @@
"maximum": 20
},
"mr": {
"minimum": -5,
"maximum": -5
"minimum": -6,
"maximum": -6
}
},
"itemIDs": {
@ -25285,8 +25285,8 @@
"lvl": 83,
"ids": {
"mr": {
"minimum": 10,
"maximum": 10
"minimum": 12,
"maximum": 12
}
},
"itemIDs": {
@ -25544,8 +25544,8 @@
"lvl": 10,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
}
},
"itemIDs": {
@ -26028,8 +26028,8 @@
"lvl": 47,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
},
"tDamPct": {
"minimum": 6,
@ -26200,8 +26200,8 @@
"lvl": 87,
"ids": {
"mr": {
"minimum": 5,
"maximum": 5
"minimum": 6,
"maximum": 6
},
"eDamPct": {
"minimum": 10,
@ -26769,8 +26769,8 @@
"maximum": 12
},
"ms": {
"minimum": 10,
"maximum": 10
"minimum": 8,
"maximum": 8
},
"atkTier": {
"minimum": -1,
@ -26971,8 +26971,8 @@
"lvl": 68,
"ids": {
"ms": {
"minimum": 10,
"maximum": 10
"minimum": 8,
"maximum": 8
}
},
"itemIDs": {
@ -27344,8 +27344,8 @@
"lvl": 25,
"ids": {
"ms": {
"minimum": 5,
"maximum": 5
"minimum": 4,
"maximum": 4
}
},
"itemIDs": {

File diff suppressed because one or more lines are too long

View file

@ -126,10 +126,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>

View file

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

View file

@ -469,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();
@ -500,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;
}
@ -573,7 +559,10 @@ 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 (part.name !== target_part) {
continue;
}
if ('multipliers' in effect) {
for (const [idx, v] of effect.multipliers.entries()) { // python: enumerate()
part.multipliers[idx] += v;
@ -594,7 +583,6 @@ const atree_collect_spells = new (class extends ComputeNode {
found_part = true;
break;
}
}
if (!found_part && behavior === 'merge') { // add part. if behavior is merge
let spell_part = deepcopy(effect);
spell_part.name = target_part; // has some extra fields but whatever
@ -874,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);
}
@ -919,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});
@ -960,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) {
@ -1026,8 +1007,9 @@ 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;
@ -1153,7 +1135,7 @@ function generateTooltip(container, node_elem, ability, atree_map) {
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 !== "") {
@ -1190,7 +1172,7 @@ function generateTooltip(container, node_elem, ability, atree_map) {
break;
}
container.appendChild(archetype);
container.appendChild(document.createElement("br"));
container.appendChild(make_elem('br'));
}
// calculate if requirements are satisfied
@ -1356,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);

View file

@ -5896,7 +5896,7 @@ const atrees = {
"desc": "After healing 120% of your max health within 10s, apply a damage bonus to each player you've healed. (15s Cooldown)",
"base_abil": "Heal",
"archetype": "Light Bender",
"archetype_req": 0,
"archetype_req": 2,
"parents": [
"Cheaper Ice Snake",
"Cheaper Teleport II"
@ -7398,7 +7398,7 @@ const atrees = {
"name": "Per Hit",
"type": "damage",
"multipliers": [
25,
30,
0,
0,
10,
@ -7663,7 +7663,7 @@ const atrees = {
"name": "Backstab Damage",
"type": "damage",
"multipliers": [
200, 50, 0, 0, 0, 0
230, 70, 0, 0, 0, 0
]
}
]
@ -8228,7 +8228,7 @@ const atrees = {
},
{
"display_name": "Weightless",
"desc": "When you hit an enemy while airborne, gain +0.5 Mana (1.25+ blocks off the ground to be airborne)",
"desc": "When you hit an enemy while airborne, gain +0.7 Mana (1.25+ blocks off the ground to be airborne)",
"archetype": "Acrobat",
"archetype_req": 4,
"parents": [
@ -8911,7 +8911,7 @@ const atrees = {
},
{
"display_name": "Stronger Vortex",
"desc": "If you deal more damage than 3x of your max health in a single hit, deal 60% of the damage to other nearby enemies",
"desc": "If you deal more damage than 3.5x of your max health in a single hit, deal 60% of the damage to other nearby enemies",
"archetype": "Shadestepper",
"archetype_req": 4,
"parents": [
@ -9203,7 +9203,7 @@ const atrees = {
},
{
"display_name": "Jasmine Bloom",
"desc": "After spending 40 Mana, bloom an area under you that damages enemies below it every 0.4s After every bloom, reset the duration and increase the radius (Max 10 Blocks)",
"desc": "After spending 40 Mana, bloom an area under you that damages enemies below it every 0.3s After every bloom, reset the duration and increase the radius (Max 10 Blocks)",
"archetype": "Acrobat",
"archetype_req": 12,
"parents": [

File diff suppressed because one or more lines are too long

View file

@ -41,12 +41,11 @@ class Build{
// calc skillpoints requires statmaps only
let result = calculate_skillpoints(this.equipment.map((x) => x.statMap), this.weapon.statMap);
this.equip_order = result[0].slice();
for (let i = 0; i < this.equip_order.length; i++) {
if (this.equip_order[i].get("category") === "tome" || this.equip_order[i].get("fixID") === true) {
this.equip_order.splice(i, 1);
i--;
}
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];

View file

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

View file

@ -130,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() {

View file

@ -102,51 +102,53 @@ const special_mappings = {
"No Defense Req": "f:defReq=0",
};
let itemFilters = document.getElementById("filter-items");
if (itemFilters) {
let item_filters = []
for (let x in translate_mappings) {
let el = document.createElement("option");
el.value = x;
itemFilters.appendChild(el);
item_filters.push(x);
}
for (let x in special_mappings) {
let el = document.createElement("option");
el.value = x;
itemFilters.appendChild(el);
}
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();
})();

View file

@ -1,4 +1,4 @@
const DB_VERSION = 102;
const DB_VERSION = 104;
// @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, []);
}

View file

@ -1,4 +1,4 @@
const ING_DB_VERSION = 14;
const ING_DB_VERSION = 15;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js

View file

@ -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));
// dynamic type casts
function checkBool(v) {
if (typeof v !== 'boolean') throw new Error(`Expected boolean, but got ${typeof v}`);
return v;
}
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; }
}
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) {
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()');
}
}
};
}
filter(item) {
return item.get(this.id) && (item.get(this.id) == this.value);
}
}
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) {
// 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;
}

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,7 @@ for item in items:
# TEMP wynn2 migration
# note: 10x'd
mul_keys = {
mul_keys_x10 = {
"spPct1": 7,
"spPct2": 7,
"spPct3": 7,
@ -54,8 +54,8 @@ mul_keys = {
"spRaw2": 50,
"spRaw3": 50,
"spRaw4": 50,
"mr": 50,
"ms": 50
"mr": 60,
"ms": 40
}
def round_near(x, eps=1e-5):
@ -67,25 +67,27 @@ remap_items = []
#old_items_map = dict()
import math
for item in old_items:
for k, v in mul_keys.items():
if "remapID" in item:
remap_items.append(item)
elif item["name"] not in known_item_names:
for k, v in mul_keys_x10.items():
if k in item:
# SUPER JANKY ROUNDING
tentimes = round(item[k] * v)
tentimes = item[k] * v
rem = tentimes % 10
val = math.floor(round_near(tentimes / 10))
if rem >= 5:
val += 1
item[k] = val
if "remapID" in item:
remap_items.append(item)
elif item["name"] not in known_item_names:
if item['name'] == "Gale's Sight":
print(item)
items.append(item)
#print(f'Unknown old item: {item["name"]}!!!')
#old_items_map[item["name"]] = item
for set_name, set_info in old_data['sets'].items():
for bonus in set_info['bonuses']:
for k, v in mul_keys.items():
for k, v in mul_keys_x10.items():
if k in bonus:
# SUPER JANKY ROUNDING
tentimes = round(bonus[k] * v)

View file

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